@@ -2,12 +2,14 @@ import { Request, Response } from 'express'
22
33import { deriveFromSecret , hmacSha256 } from '../../utils/secret'
44import { Invoice , InvoiceStatus } from '../../@types/invoice'
5+ import { lnbitsCallbackBodySchema , lnbitsCallbackQuerySchema } from '../../schemas/lnbits-callback-schema'
56import { createLogger } from '../../factories/logger-factory'
67import { createSettings } from '../../factories/settings-factory'
78import { getRemoteAddress } from '../../utils/http'
89import { IController } from '../../@types/controllers'
910import { IInvoiceRepository } from '../../@types/repositories'
1011import { IPaymentsService } from '../../@types/services'
12+ import { validateSchema } from '../../utils/validation'
1113
1214const debug = createLogger ( 'lnbits-callback-controller' )
1315
@@ -17,7 +19,6 @@ export class LNbitsCallbackController implements IController {
1719 private readonly invoiceRepository : IInvoiceRepository
1820 ) { }
1921
20- // TODO: Validate
2122 public async handleRequest (
2223 request : Request ,
2324 response : Response ,
@@ -37,34 +38,46 @@ export class LNbitsCallbackController implements IController {
3738 return
3839 }
3940
40- let validationPassed = false
41-
42- if ( typeof request . query . hmac === 'string' && request . query . hmac . match ( / ^ [ 0 - 9 ] { 1 , 20 } : [ 0 - 9 a - f ] { 64 } $ / ) ) {
43- const split = request . query . hmac . split ( ':' )
44- if ( hmacSha256 ( deriveFromSecret ( 'lnbits-callback-hmac-key' ) , split [ 0 ] ) . toString ( 'hex' ) === split [ 1 ] ) {
45- if ( parseInt ( split [ 0 ] ) > Date . now ( ) ) {
46- validationPassed = true
47- }
48- }
41+ const queryValidation = validateSchema ( lnbitsCallbackQuerySchema ) ( request . query )
42+ if ( queryValidation . error ) {
43+ debug ( 'unauthorized request from %s to /callbacks/lnbits: invalid query %o' , remoteAddress , queryValidation . error )
44+ response
45+ . status ( 403 )
46+ . send ( 'Forbidden' )
47+ return
4948 }
5049
51- if ( ! validationPassed ) {
52- debug ( 'unauthorized request from %s to /callbacks/lnbits' , remoteAddress )
50+ const hmac = request . query . hmac as string
51+ const split = hmac . split ( ':' )
52+ const expiryString = split [ 0 ]
53+ const expiry = Number ( expiryString )
54+ const hasValidSplit = split . length === 2
55+ const hasValidExpiry =
56+ / ^ \d + $ / . test ( expiryString ) &&
57+ Number . isSafeInteger ( expiry )
58+ if (
59+ ! hasValidSplit ||
60+ hmacSha256 ( deriveFromSecret ( 'lnbits-callback-hmac-key' ) , expiryString ) . toString ( 'hex' ) !== split [ 1 ] ||
61+ ! hasValidExpiry ||
62+ expiry <= Date . now ( )
63+ ) {
64+ debug ( 'unauthorized request from %s to /callbacks/lnbits: hmac signature mismatch or expired' , remoteAddress )
5365 response
5466 . status ( 403 )
5567 . send ( 'Forbidden' )
5668 return
5769 }
5870
59- const body = request . body
60- if ( ! body || typeof body !== 'object' || typeof body . payment_hash !== 'string' || body . payment_hash . length !== 64 ) {
71+ const bodyValidation = validateSchema ( lnbitsCallbackBodySchema ) ( request . body )
72+ if ( bodyValidation . error ) {
6173 response
6274 . status ( 400 )
6375 . setHeader ( 'content-type' , 'text/plain; charset=utf8' )
6476 . send ( 'Malformed body' )
6577 return
6678 }
6779
80+ const body = request . body
6881 const invoice = await this . paymentsService . getInvoiceFromPaymentsProcessor ( body . payment_hash )
6982 const storedInvoice = await this . invoiceRepository . findById ( body . payment_hash )
7083
@@ -119,4 +132,4 @@ export class LNbitsCallbackController implements IController {
119132 . setHeader ( 'content-type' , 'text/plain; charset=utf8' )
120133 . send ( 'OK' )
121134 }
122- }
135+ }
0 commit comments