@@ -2,7 +2,10 @@ import { Request, Response } from 'express'
22
33import { Invoice , InvoiceStatus } from '../../@types/invoice'
44import { createLogger } from '../../factories/logger-factory'
5+ import { createSettings } from '../../factories/settings-factory'
56import { fromOpenNodeInvoice } from '../../utils/transform'
7+ import { getRemoteAddress } from '../../utils/http'
8+ import { hmacSha256 } from '../../utils/secret'
69import { IController } from '../../@types/controllers'
710import { IPaymentsService } from '../../@types/services'
811
@@ -13,14 +16,62 @@ export class OpenNodeCallbackController implements IController {
1316 private readonly paymentsService : IPaymentsService ,
1417 ) { }
1518
16- // TODO: Validate
1719 public async handleRequest (
1820 request : Request ,
1921 response : Response ,
2022 ) {
2123 debug ( 'request headers: %o' , request . headers )
2224 debug ( 'request body: %O' , request . body )
2325
26+ const settings = createSettings ( )
27+ const remoteAddress = getRemoteAddress ( request , settings )
28+ const paymentProcessor = settings . payments ?. processor
29+
30+ if ( paymentProcessor !== 'opennode' ) {
31+ debug ( 'denied request from %s to /callbacks/opennode which is not the current payment processor' , remoteAddress )
32+ response
33+ . status ( 403 )
34+ . send ( 'Forbidden' )
35+ return
36+ }
37+
38+ const validStatuses = [ 'expired' , 'refunded' , 'unpaid' , 'processing' , 'underpaid' , 'paid' ]
39+
40+ if (
41+ ! request . body
42+ || typeof request . body . id !== 'string'
43+ || typeof request . body . hashed_order !== 'string'
44+ || typeof request . body . status !== 'string'
45+ || ! validStatuses . includes ( request . body . status )
46+ ) {
47+ response
48+ . status ( 400 )
49+ . setHeader ( 'content-type' , 'text/plain; charset=utf8' )
50+ . send ( 'Bad Request' )
51+ return
52+ }
53+
54+ const openNodeApiKey = process . env . OPENNODE_API_KEY
55+ if ( ! openNodeApiKey ) {
56+ debug ( 'OPENNODE_API_KEY is not configured; unable to verify OpenNode callback from %s' , remoteAddress )
57+ response
58+ . status ( 500 )
59+ . setHeader ( 'content-type' , 'text/plain; charset=utf8' )
60+ . send ( 'Internal Server Error' )
61+ return
62+ }
63+
64+ const expected = hmacSha256 ( openNodeApiKey , request . body . id ) . toString ( 'hex' )
65+ const actual = request . body . hashed_order
66+
67+ if ( expected !== actual ) {
68+ debug ( 'unauthorized request from %s to /callbacks/opennode: hashed_order mismatch' , remoteAddress )
69+ response
70+ . status ( 403 )
71+ . send ( 'Forbidden' )
72+ return
73+ }
74+
2475 const invoice = fromOpenNodeInvoice ( request . body )
2576
2677 debug ( 'invoice' , invoice )
0 commit comments