@@ -50,7 +50,10 @@ export const zoomHandler: WebhookProviderHandler = {
5050 verifyAuth ( { request, rawBody, requestId, providerConfig } : AuthContext ) {
5151 const secretToken = providerConfig . secretToken as string | undefined
5252 if ( ! secretToken ) {
53- return null
53+ logger . warn (
54+ `[${ requestId } ] Zoom webhook missing secretToken in providerConfig — rejecting request`
55+ )
56+ return new NextResponse ( 'Unauthorized - Zoom secret token not configured' , { status : 401 } )
5457 }
5558
5659 const signature = request . headers . get ( 'x-zm-signature' )
@@ -98,7 +101,7 @@ export const zoomHandler: WebhookProviderHandler = {
98101 * Zoom sends an `endpoint.url_validation` event with a `plainToken` that must
99102 * be hashed with the app's secret token and returned alongside the original token.
100103 */
101- async handleChallenge ( body : unknown , _request : NextRequest , requestId : string , path : string ) {
104+ async handleChallenge ( body : unknown , request : NextRequest , requestId : string , path : string ) {
102105 const obj = body as Record < string , unknown > | null
103106 if ( obj ?. event !== 'endpoint.url_validation' ) {
104107 return null
@@ -118,7 +121,9 @@ export const zoomHandler: WebhookProviderHandler = {
118121 const webhooks = await db
119122 . select ( )
120123 . from ( webhook )
121- . where ( and ( eq ( webhook . path , path ) , eq ( webhook . isActive , true ) ) )
124+ . where (
125+ and ( eq ( webhook . path , path ) , eq ( webhook . provider , 'zoom' ) , eq ( webhook . isActive , true ) )
126+ )
122127 if ( webhooks . length > 0 ) {
123128 const config = webhooks [ 0 ] . providerConfig as Record < string , unknown > | null
124129 secretToken = ( config ?. secretToken as string ) || ''
@@ -135,6 +140,17 @@ export const zoomHandler: WebhookProviderHandler = {
135140 return null
136141 }
137142
143+ // Verify the challenge request's signature to prevent HMAC oracle attacks
144+ const signature = request . headers . get ( 'x-zm-signature' )
145+ const timestamp = request . headers . get ( 'x-zm-request-timestamp' )
146+ if ( signature && timestamp ) {
147+ const rawBody = JSON . stringify ( body )
148+ if ( ! validateZoomSignature ( secretToken , signature , timestamp , rawBody ) ) {
149+ logger . warn ( `[${ requestId } ] Zoom challenge request failed signature verification` )
150+ return null
151+ }
152+ }
153+
138154 const hashForValidate = crypto
139155 . createHmac ( 'sha256' , secretToken )
140156 . update ( plainToken )
0 commit comments