Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/release-whitelist-exact-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostream": major
---

Use exact pubkey matching for fee-schedule whitelists and event pubkey whitelist/blacklist checks.
6 changes: 3 additions & 3 deletions src/app/static-mirroring-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export class StaticMirroringWorker implements IRunnable {
if (
typeof limits.pubkey?.whitelist !== 'undefined' &&
limits.pubkey.whitelist.length > 0 &&
!limits.pubkey.whitelist.some((prefix) => event.pubkey.startsWith(prefix))
!limits.pubkey.whitelist.includes(event.pubkey)
) {
debug(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`)
return false
Expand All @@ -248,7 +248,7 @@ export class StaticMirroringWorker implements IRunnable {
if (
typeof limits.pubkey?.blacklist !== 'undefined' &&
limits.pubkey.blacklist.length > 0 &&
limits.pubkey.blacklist.some((prefix) => event.pubkey.startsWith(prefix))
limits.pubkey.blacklist.includes(event.pubkey)
) {
debug(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`)
return false
Expand Down Expand Up @@ -288,7 +288,7 @@ export class StaticMirroringWorker implements IRunnable {

const isApplicableFee = (feeSchedule: FeeSchedule) =>
feeSchedule.enabled &&
!feeSchedule.whitelists?.pubkeys?.some((prefix) => event.pubkey.startsWith(prefix)) &&
!feeSchedule.whitelists?.pubkeys?.includes(event.pubkey) &&
!feeSchedule.whitelists?.event_kinds?.some(isEventKindOrRangeMatch(event))

const feeSchedules = currentSettings.payments?.feeSchedules?.admission?.filter(isApplicableFee)
Expand Down
8 changes: 3 additions & 5 deletions src/controllers/invoices/post-invoice-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export class PostInvoiceController implements IController {
}

const isApplicableFee = (feeSchedule: FeeSchedule) =>
feeSchedule.enabled && !feeSchedule.whitelists?.pubkeys?.some((prefix) => pubkey.startsWith(prefix))
const admissionFee = currentSettings.payments?.feeSchedules.admission.filter(isApplicableFee)
feeSchedule.enabled && !feeSchedule.whitelists?.pubkeys?.includes(pubkey)
const admissionFee = currentSettings.payments?.feeSchedules?.admission?.filter(isApplicableFee) ?? []

if (!Array.isArray(admissionFee) || !admissionFee.length) {
response.status(400).setHeader('content-type', 'text/plain; charset=utf8').send('No admission fee required')
Expand All @@ -106,9 +106,7 @@ export class PostInvoiceController implements IController {
}

let invoice: Invoice
const amount = admissionFee.reduce((sum, fee) => {
return fee.enabled && !fee.whitelists?.pubkeys?.includes(pubkey) ? BigInt(fee.amount) + sum : sum
}, 0n)
const amount = admissionFee.reduce((sum, fee) => BigInt(fee.amount) + sum, 0n)

try {
const description = `${relayName} Admission Fee for ${toBech32('npub')(pubkey)}`
Expand Down
6 changes: 3 additions & 3 deletions src/handlers/event-message-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,15 @@ export class EventMessageHandler implements IMessageHandler {
if (
typeof limits.pubkey?.whitelist !== 'undefined' &&
limits.pubkey.whitelist.length > 0 &&
!limits.pubkey.whitelist.some((prefix) => event.pubkey.startsWith(prefix))
!limits.pubkey.whitelist.includes(event.pubkey)
) {
return 'blocked: pubkey not allowed'
}

if (
typeof limits.pubkey?.blacklist !== 'undefined' &&
limits.pubkey.blacklist.length > 0 &&
limits.pubkey.blacklist.some((prefix) => event.pubkey.startsWith(prefix))
limits.pubkey.blacklist.includes(event.pubkey)
) {
return 'blocked: pubkey not allowed'
}
Expand Down Expand Up @@ -332,7 +332,7 @@ export class EventMessageHandler implements IMessageHandler {

const isApplicableFee = (feeSchedule: FeeSchedule) =>
feeSchedule.enabled &&
!feeSchedule.whitelists?.pubkeys?.some((prefix) => event.pubkey.startsWith(prefix)) &&
!feeSchedule.whitelists?.pubkeys?.includes(event.pubkey) &&
!feeSchedule.whitelists?.event_kinds?.some(isEventKindOrRangeMatch(event))

const feeSchedules = currentSettings.payments?.feeSchedules?.admission?.filter(isApplicableFee)
Expand Down
2 changes: 1 addition & 1 deletion src/services/payments-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export class PaymentsService implements IPaymentsService {
}

const isApplicableFee = (feeSchedule: FeeSchedule) =>
feeSchedule.enabled && !feeSchedule.whitelists?.pubkeys?.some((prefix) => invoice.pubkey.startsWith(prefix))
feeSchedule.enabled && !feeSchedule.whitelists?.pubkeys?.includes(invoice.pubkey)
const admissionFeeSchedules = currentSettings.payments?.feeSchedules?.admission ?? []
const admissionFeeAmount = admissionFeeSchedules.reduce((sum, feeSchedule) => {
return sum + (isApplicableFee(feeSchedule) ? BigInt(feeSchedule.amount) : 0n)
Expand Down
12 changes: 6 additions & 6 deletions test/unit/handlers/event-message-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ describe('EventMessageHandler', () => {
expect((handler as any).canAcceptEvent(event)).to.be.undefined
})

it('returns undefined if pubkey is not blacklisted by prefix', () => {
it('returns undefined if pubkey is not an exact match in the blacklist', () => {
eventLimits.pubkey.blacklist = ['aa55']
event.pubkey = 'aabbcc'
expect((handler as any).canAcceptEvent(event)).to.be.undefined
Expand All @@ -490,10 +490,10 @@ describe('EventMessageHandler', () => {
expect((handler as any).canAcceptEvent(event)).to.equal('blocked: pubkey not allowed')
})

it('returns reason if pubkey is blacklisted by prefix', () => {
it('returns undefined if pubkey extends a blacklist entry but is not an exact match', () => {
eventLimits.pubkey.blacklist = ['aa55']
event.pubkey = 'aa55ccddeeff'
expect((handler as any).canAcceptEvent(event)).to.equal('blocked: pubkey not allowed')
expect((handler as any).canAcceptEvent(event)).to.be.undefined
})
})

Expand All @@ -509,10 +509,10 @@ describe('EventMessageHandler', () => {
expect((handler as any).canAcceptEvent(event)).to.be.undefined
})

it('returns undefined if pubkey is whitelisted by prefix', () => {
it('returns reason if pubkey is not an exact match in the whitelist', () => {
eventLimits.pubkey.whitelist = ['aa55']
event.pubkey = 'aa55ccddeeff'
expect((handler as any).canAcceptEvent(event)).to.be.undefined
expect((handler as any).canAcceptEvent(event)).to.equal('blocked: pubkey not allowed')
})

it('returns reason if pubkey is not whitelisted', () => {
Expand All @@ -521,7 +521,7 @@ describe('EventMessageHandler', () => {
expect((handler as any).canAcceptEvent(event)).to.equal('blocked: pubkey not allowed')
})

it('returns reason if pubkey is not whitelisted by prefix', () => {
it('returns reason if pubkey is not whitelisted by exact match', () => {
eventLimits.pubkey.whitelist = ['aa55']
event.pubkey = 'aabbccddeeff'
expect((handler as any).canAcceptEvent(event)).to.equal('blocked: pubkey not allowed')
Expand Down
19 changes: 16 additions & 3 deletions test/unit/services/payments-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,9 @@ describe('PaymentsService', () => {
expect(userRepository.admitUser).not.to.have.been.called
})

it('skips the fee for whitelisted pubkeys', async () => {
it('skips the fee for whitelisted pubkeys (exact match)', async () => {
settings.returns(makeSettings([
{ enabled: true, amount: 1000n, whitelists: { pubkeys: ['whitelisted'] } },
{ enabled: true, amount: 1000n, whitelists: { pubkeys: ['whitelistedpubkey'] } },
]))

await service.confirmInvoice(makeCompletedInvoice({
Expand All @@ -406,10 +406,23 @@ describe('PaymentsService', () => {
amountPaid: 5000n,
}))

// pubkey starts with 'whitelisted' → isApplicableFee = false → admissionFeeAmount = 0 → not admitted
// pubkey does not exactly match whitelist entry -> fee applies -> user must pay to get admitted
expect(userRepository.admitUser).not.to.have.been.called
})

it('applies the fee when pubkey is not an exact whitelist match (prefix alone is insufficient)', async () => {
settings.returns(makeSettings([
{ enabled: true, amount: 1000n, whitelists: { pubkeys: ['whitelisted'] } },
]))
await service.confirmInvoice(makeCompletedInvoice({
pubkey: 'whitelistedpubkey',
unit: InvoiceUnit.MSATS,
amountPaid: 5000n,
}))
expect(userRepository.admitUser).to.have.been.calledOnce
})


it('rolls back the transaction and re-throws on error', async () => {
settings.returns(makeSettings([]))
invoiceRepository.confirmInvoice.rejects(new Error('db error'))
Expand Down
Loading