Skip to content

Commit 845dedc

Browse files
committed
feat: support pubkey/ip whitelisting for event rate limits
1 parent 3620026 commit 845dedc

File tree

2 files changed

+104
-9
lines changed

2 files changed

+104
-9
lines changed

src/handlers/event-message-handler.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,22 @@ export class EventMessageHandler implements IMessageHandler {
126126
}
127127

128128
protected async isRateLimited(event: Event): Promise<boolean> {
129-
const rateLimits = this.settings().limits?.event?.rateLimits
129+
const { whitelists, rateLimits } = this.settings().limits?.event ?? {}
130130
if (!rateLimits || !rateLimits.length) {
131-
return
131+
return false
132+
}
133+
134+
if (
135+
Array.isArray(whitelists?.pubkeys)
136+
&& whitelists.pubkeys.includes(event.pubkey)
137+
) {
138+
return false
139+
}
140+
141+
if (Array.isArray(whitelists?.ipAddresses)
142+
&& whitelists.ipAddresses.includes(this.webSocket.getClientAddress())
143+
) {
144+
return false
132145
}
133146

134147
const rateLimiter = this.slidingWindowRateLimiter()

test/unit/handlers/event-message-handler.spec.ts

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import { EventLimits, ISettings } from '../../../src/@types/settings'
1010
import { IncomingEventMessage, MessageType } from '../../../src/@types/messages'
1111
import { Event } from '../../../src/@types/event'
1212
import { EventMessageHandler } from '../../../src/handlers/event-message-handler'
13+
import { IWebSocketAdapter } from '../../../src/@types/adapters'
1314
import { WebSocketAdapterEvent } from '../../../src/constants/adapter'
1415

1516
const { expect } = chai
1617

1718
describe('EventMessageHandler', () => {
18-
let webSocket: EventEmitter
19+
let webSocket: IWebSocketAdapter
1920
let handler: EventMessageHandler
2021
let event: Event
2122
let message: IncomingEventMessage
@@ -59,7 +60,7 @@ describe('EventMessageHandler', () => {
5960
execute: strategyExecuteStub,
6061
})
6162
onMessageSpy = sandbox.fake.returns(undefined)
62-
webSocket = new EventEmitter()
63+
webSocket = new EventEmitter() as any
6364
webSocket.on(WebSocketAdapterEvent.Message, onMessageSpy)
6465
message = [MessageType.EVENT, event]
6566
isRateLimitedStub = sandbox.stub(EventMessageHandler.prototype, 'isRateLimited' as any)
@@ -548,6 +549,8 @@ describe('EventMessageHandler', () => {
548549
let eventLimits: EventLimits
549550
let settings: ISettings
550551
let rateLimiterHitStub: SinonStub
552+
let getClientAddressStub: Sinon.SinonStub
553+
let webSocket: IWebSocketAdapter
551554

552555
beforeEach(() => {
553556
eventLimits = {
@@ -559,22 +562,101 @@ describe('EventMessageHandler', () => {
559562
},
560563
} as any
561564
rateLimiterHitStub = sandbox.stub()
565+
getClientAddressStub = sandbox.stub()
566+
webSocket = {
567+
getClientAddress: getClientAddressStub,
568+
} as any
562569
handler = new EventMessageHandler(
563-
{} as any,
570+
webSocket,
564571
() => null,
565572
() => settings,
566573
() => ({ hit: rateLimiterHitStub })
567574
)
568575
})
569576

570-
it('returns undefined if rate limits setting is not set', async () => {
577+
it('fulfills with false if limits setting is not set', async () => {
578+
settings.limits = undefined
579+
return expect((handler as any).isRateLimited(event)).to.eventually.be.false
580+
})
581+
582+
583+
it('fulfills with false if event limits setting is not set', async () => {
584+
settings.limits.event = undefined
585+
return expect((handler as any).isRateLimited(event)).to.eventually.be.false
586+
})
587+
588+
it('fulfills with false if rate limits setting is not set', async () => {
571589
eventLimits.rateLimits = undefined
572-
return expect((handler as any).isRateLimited(event)).to.eventually.be.undefined
590+
return expect((handler as any).isRateLimited(event)).to.eventually.be.false
573591
})
574592

575-
it('returns undefined if rate limits setting is empty', async () => {
593+
it('fulfills with false if rate limits setting is empty', async () => {
576594
eventLimits.rateLimits = []
577-
return expect((handler as any).isRateLimited(event)).to.eventually.be.undefined
595+
return expect((handler as any).isRateLimited(event)).to.eventually.be.false
596+
})
597+
598+
it('skips rate limiter if IP is whitelisted', async () => {
599+
eventLimits.rateLimits = [
600+
{
601+
period: 60000,
602+
rate: 1,
603+
},
604+
]
605+
eventLimits.whitelists = {}
606+
eventLimits.whitelists.ipAddresses = ['2604:a880:cad:d0::e7e:7001']
607+
getClientAddressStub.returns('2604:a880:cad:d0::e7e:7001')
608+
609+
const actualResult = await (handler as any).isRateLimited(event)
610+
611+
expect(actualResult).to.be.false
612+
expect(rateLimiterHitStub).not.to.have.been.called
613+
})
614+
615+
it('calls rate limiter if IP is not whitelisted', async () => {
616+
eventLimits.rateLimits = [
617+
{
618+
period: 60000,
619+
rate: 1,
620+
},
621+
]
622+
eventLimits.whitelists = {}
623+
eventLimits.whitelists.ipAddresses = ['::1']
624+
getClientAddressStub.returns('2604:a880:cad:d0::e7e:7001')
625+
626+
await (handler as any).isRateLimited(event)
627+
628+
expect(rateLimiterHitStub).to.have.been.called
629+
})
630+
631+
it('skips rate limiter if pubkey is whitelisted', async () => {
632+
eventLimits.rateLimits = [
633+
{
634+
period: 60000,
635+
rate: 1,
636+
},
637+
]
638+
eventLimits.whitelists = {}
639+
eventLimits.whitelists.pubkeys = [event.pubkey]
640+
641+
const actualResult = await (handler as any).isRateLimited(event)
642+
643+
expect(actualResult).to.be.false
644+
expect(rateLimiterHitStub).not.to.have.been.called
645+
})
646+
647+
it('calls rate limiter if pubkey is not whitelisted', async () => {
648+
eventLimits.rateLimits = [
649+
{
650+
period: 60000,
651+
rate: 1,
652+
},
653+
]
654+
eventLimits.whitelists = {}
655+
eventLimits.whitelists.pubkeys = ['other']
656+
657+
await (handler as any).isRateLimited(event)
658+
659+
expect(rateLimiterHitStub).to.have.been.called
578660
})
579661

580662
it('calls hit with given rate limit settings', async () => {

0 commit comments

Comments
 (0)