Skip to content

Commit 356f4fa

Browse files
fix: validate OpenNode webhook signature before processing
1 parent 341ddea commit 356f4fa

1 file changed

Lines changed: 38 additions & 1 deletion

File tree

src/controllers/callbacks/opennode-callback-controller.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { Request, Response } from 'express'
22

33
import { Invoice, InvoiceStatus } from '../../@types/invoice'
44
import { createLogger } from '../../factories/logger-factory'
5+
import { createSettings } from '../../factories/settings-factory'
56
import { fromOpenNodeInvoice } from '../../utils/transform'
7+
import { getRemoteAddress } from '../../utils/http'
8+
import { hmacSha256 } from '../../utils/secret'
69
import { IController } from '../../@types/controllers'
710
import { IPaymentsService } from '../../@types/services'
811

@@ -13,14 +16,48 @@ 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+
if (
39+
!request.body
40+
|| typeof request.body.id !== 'string'
41+
|| typeof request.body.hashed_order !== 'string'
42+
) {
43+
response
44+
.status(400)
45+
.setHeader('content-type', 'text/plain; charset=utf8')
46+
.send('Bad Request')
47+
return
48+
}
49+
50+
const expected = hmacSha256(process.env.OPENNODE_API_KEY, request.body.id).toString('hex')
51+
const actual = request.body.hashed_order
52+
53+
if (expected !== actual) {
54+
debug('unauthorized request from %s to /callbacks/opennode: hashed_order mismatch', remoteAddress)
55+
response
56+
.status(403)
57+
.send('Forbidden')
58+
return
59+
}
60+
2461
const invoice = fromOpenNodeInvoice(request.body)
2562

2663
debug('invoice', invoice)

0 commit comments

Comments
 (0)