• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cameri / nostream / 24609919997

18 Apr 2026 05:23PM UTC coverage: 30.829% (-32.0%) from 62.807%
24609919997

Pull #454

github

web-flow
Merge c337d7af3 into 26bcdd51d
Pull Request #454: fix: OpenNode callback accepts unauthenticated requests

268 of 1385 branches covered (19.35%)

Branch coverage included in aggregate %.

29 of 41 new or added lines in 3 files covered. (70.73%)

976 existing lines in 41 files now uncovered.

1164 of 3260 relevant lines covered (35.71%)

5.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.67
/src/controllers/callbacks/nodeless-callback-controller.ts
1
import { always, applySpec, ifElse, is, path, prop, propEq, propSatisfies } from 'ramda'
1✔
2
import { Request, Response } from 'express'
3

4
import { Invoice, InvoiceStatus } from '../../@types/invoice'
1✔
5
import { createLogger } from '../../factories/logger-factory'
1✔
6
import { createSettings } from '../../factories/settings-factory'
1✔
7
import { fromNodelessInvoice } from '../../utils/transform'
1✔
8
import { hmacSha256 } from '../../utils/secret'
1✔
9
import { IController } from '../../@types/controllers'
10
import { IPaymentsService } from '../../@types/services'
11
import { nodelessCallbackBodySchema } from '../../schemas/nodeless-callback-schema'
1✔
12
import { validateSchema } from '../../utils/validation'
1✔
13

14
const debug = createLogger('nodeless-callback-controller')
1✔
15

16
export class NodelessCallbackController implements IController {
1✔
17
  public constructor(
UNCOV
18
    private readonly paymentsService: IPaymentsService,
×
19
  ) {}
20

21
  public async handleRequest(
22
    request: Request,
23
    response: Response,
24
  ) {
UNCOV
25
    debug('callback request headers: %o', request.headers)
×
UNCOV
26
    debug('callback request body: %O', request.body)
×
27

UNCOV
28
    const bodyValidation = validateSchema(nodelessCallbackBodySchema)(request.body)
×
UNCOV
29
    if (bodyValidation.error) {
×
UNCOV
30
      debug('nodeless callback request rejected: invalid body %o', bodyValidation.error)
×
UNCOV
31
      response
×
32
        .status(400)
33
        .setHeader('content-type', 'application/json; charset=utf8')
34
        .send('{"status":"error","message":"Malformed body"}')
UNCOV
35
      return
×
36
    }
37

UNCOV
38
    const settings = createSettings()
×
UNCOV
39
    const paymentProcessor = settings.payments?.processor
×
40

UNCOV
41
    const expected = hmacSha256(process.env.NODELESS_WEBHOOK_SECRET, (request as any).rawBody).toString('hex')
×
UNCOV
42
    const actual = request.headers['nodeless-signature']
×
43

UNCOV
44
    if (expected !== actual) {
×
UNCOV
45
      console.error('nodeless callback request rejected: signature mismatch:', { expected, actual })
×
UNCOV
46
      response
×
47
        .status(403)
48
        .send('Forbidden')
UNCOV
49
      return
×
50
    }
51

UNCOV
52
    if (paymentProcessor !== 'nodeless') {
×
UNCOV
53
      debug('denied request from %s to /callbacks/nodeless which is not the current payment processor')
×
UNCOV
54
      response
×
55
        .status(403)
56
        .send('Forbidden')
UNCOV
57
      return
×
58
    }
59

UNCOV
60
    const nodelessInvoice = applySpec({
×
61
      id: prop('uuid'),
62
      status: prop('status'),
63
      satsAmount: prop('amount'),
64
      metadata: prop('metadata'),
65
      paidAt: ifElse(
66
        propEq('status', 'paid'),
67
        always(new Date().toISOString()),
68
        always(null),
69
      ),
70
      createdAt: ifElse(
71
        propSatisfies(is(String), 'createdAt'),
72
        prop('createdAt'),
73
        path(['metadata', 'createdAt']),
74
      ),
75
    })(request.body)
76

UNCOV
77
    debug('nodeless invoice: %O', nodelessInvoice)
×
78

UNCOV
79
    const invoice = fromNodelessInvoice(nodelessInvoice)
×
80

UNCOV
81
    debug('invoice: %O', invoice)
×
82

83
    let updatedInvoice: Invoice
UNCOV
84
    try {
×
UNCOV
85
      updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice)
×
UNCOV
86
      debug('updated invoice: %O', updatedInvoice)
×
87
    } catch (error) {
UNCOV
88
      console.error(`Unable to persist invoice ${invoice.id}`, error)
×
89

UNCOV
90
      throw error
×
91
    }
92

UNCOV
93
    if (
×
94
      updatedInvoice.status !== InvoiceStatus.COMPLETED
×
95
      && !updatedInvoice.confirmedAt
96
    ) {
UNCOV
97
      response
×
98
        .status(200)
99
        .send()
100

UNCOV
101
      return
×
102
    }
103

UNCOV
104
    invoice.amountPaid = invoice.amountRequested
×
UNCOV
105
    updatedInvoice.amountPaid = invoice.amountRequested
×
106

UNCOV
107
    try {
×
UNCOV
108
      await this.paymentsService.confirmInvoice(invoice)
×
UNCOV
109
      await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice)
×
110
    } catch (error) {
UNCOV
111
      console.error(`Unable to confirm invoice ${invoice.id}`, error)
×
112

UNCOV
113
      throw error
×
114
    }
115

UNCOV
116
    response
×
117
      .status(200)
118
      .setHeader('content-type', 'application/json; charset=utf8')
119
      .send('{"status":"ok"}')
120
  }
121
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc