1+ import { timingSafeEqual } from 'crypto'
2+
13import { always , applySpec , ifElse , is , path , prop , propEq , propSatisfies } from 'ramda'
24import { Request , Response } from 'express'
35
46import { Invoice , InvoiceStatus } from '../../@types/invoice'
57import { createLogger } from '../../factories/logger-factory'
6- import { createSettings } from '../../factories/settings-factory'
78import { fromNodelessInvoice } from '../../utils/transform'
89import { hmacSha256 } from '../../utils/secret'
910import { IController } from '../../@types/controllers'
1011import { IPaymentsService } from '../../@types/services'
11- import { nodelessCallbackBodySchema } from '../../schemas/nodeless-callback-schema'
12+ import { nodelessCallbackBodySchema , nodelessSignatureSchema } from '../../schemas/nodeless-callback-schema'
1213import { validateSchema } from '../../utils/validation'
1314
1415const logger = createLogger ( 'nodeless-callback-controller' )
@@ -30,20 +31,31 @@ export class NodelessCallbackController implements IController {
3031 return
3132 }
3233
33- const settings = createSettings ( )
34- const paymentProcessor = settings . payments ?. processor
35-
36- const expected = hmacSha256 ( process . env . NODELESS_WEBHOOK_SECRET , ( request as any ) . rawBody ) . toString ( 'hex' )
37- const actual = request . headers [ 'nodeless-signature' ]
34+ const webhookSecret = process . env . NODELESS_WEBHOOK_SECRET
35+ if ( ! webhookSecret ) {
36+ logger . error ( 'NODELESS_WEBHOOK_SECRET is not configured; unable to verify Nodeless callback' )
37+ response
38+ . status ( 500 )
39+ . setHeader ( 'content-type' , 'application/json; charset=utf8' )
40+ . send ( '{"status":"error","message":"Internal Server Error"}' )
41+ return
42+ }
3843
39- if ( expected !== actual ) {
40- logger . error ( 'nodeless callback request rejected: signature mismatch:' , { expected, actual } )
41- response . status ( 403 ) . send ( 'Forbidden' )
44+ const signatureValidation = validateSchema ( nodelessSignatureSchema ) ( request . headers [ 'nodeless-signature' ] )
45+ if ( signatureValidation . error ) {
46+ logger ( 'nodeless callback request rejected: invalid signature format' )
47+ response
48+ . status ( 400 )
49+ . setHeader ( 'content-type' , 'application/json; charset=utf8' )
50+ . send ( '{"status":"error","message":"Invalid signature"}' )
4251 return
4352 }
4453
45- if ( paymentProcessor !== 'nodeless' ) {
46- logger ( 'denied request from %s to /callbacks/nodeless which is not the current payment processor' )
54+ const expectedBuf = hmacSha256 ( webhookSecret , ( request as any ) . rawBody )
55+ const actualBuf = Buffer . from ( signatureValidation . value , 'hex' )
56+
57+ if ( ! timingSafeEqual ( expectedBuf , actualBuf ) ) {
58+ logger ( 'nodeless callback request rejected: signature mismatch' )
4759 response . status ( 403 ) . send ( 'Forbidden' )
4860 return
4961 }
0 commit comments