Skip to content

Commit b36b625

Browse files
committed
test: message/event rate limiting
1 parent 760cb38 commit b36b625

22 files changed

+502
-50
lines changed

settings.sample.json

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,53 @@
2323
"createdAt": {
2424
"maxPositiveDelta": 900,
2525
"maxNegativeDelta": 0
26-
}
26+
},
27+
"rateLimits": [
28+
{
29+
"kinds": [[0, 5], 7, [40, 49], [10000, 19999], [30000, 39999]],
30+
"period": 60000,
31+
"rate": 60
32+
},
33+
{
34+
"kinds": [[20000, 29999]],
35+
"period": 60000,
36+
"rate": 600
37+
},
38+
{
39+
"period": 3600000,
40+
"rate": 3600
41+
},
42+
{
43+
"period": 86400000,
44+
"rate": 86400
45+
}
46+
]
2747
},
2848
"client": {
2949
"subscription": {
3050
"maxSubscriptions": 10,
3151
"maxFilters": 10
3252
}
53+
},
54+
"message": {
55+
"rateLimits": [
56+
{
57+
"period": 60000,
58+
"rate": 600
59+
},
60+
{
61+
"period": 3600000,
62+
"rate": 3600
63+
},
64+
{
65+
"period": 86400000,
66+
"rate": 86400
67+
}
68+
],
69+
"ipWhitelist": [
70+
"::1",
71+
"::ffff:10.10.10.1"
72+
]
3373
}
3474
}
3575
}

src/@types/cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import {
55
RedisScripts,
66
} from 'redis'
77

8-
export type Cache = RedisClientType<RedisModules, RedisFunctions, RedisScripts>
8+
export type CacheClient = RedisClientType<RedisModules, RedisFunctions, RedisScripts>

src/adapters/redis-adapter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Cache } from '../@types/cache'
1+
import { CacheClient } from '../@types/cache'
22
import { createLogger } from '../factories/logger-factory'
33
import { ICacheAdapter } from '../@types/adapters'
44

@@ -7,7 +7,7 @@ const debug = createLogger('redis-adapter')
77
export class RedisAdapter implements ICacheAdapter {
88
private connection: Promise<void>
99

10-
public constructor(private readonly client: Cache) {
10+
public constructor(private readonly client: CacheClient) {
1111
this.connection = client.connect()
1212

1313
this.connection.catch((error) => this.onClientError(error))

src/cache/client.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import { createClient, RedisClientOptions } from 'redis'
2-
import { Cache } from '../@types/cache'
2+
import { CacheClient } from '../@types/cache'
33

44
export const getCacheConfig = (): RedisClientOptions => ({
55
url: `redis://${process.env.REDIS_USER}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
66
password: process.env.REDIS_PASSWORD,
77
})
88

9-
export const getCacheClient = (): Cache => {
10-
const config = getCacheConfig()
9+
let instance: CacheClient = undefined
1110

12-
const client = createClient(config)
11+
export const getCacheClient = (): CacheClient => {
12+
if (!instance) {
13+
const config = getCacheConfig()
1314

14-
return client
15+
instance = createClient(config)
16+
}
17+
18+
return instance
1519
}

src/constants/base.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,21 @@ export enum EventKinds {
66
ENCRYPTED_DIRECT_MESSAGE = 4,
77
DELETE = 5,
88
REACTION = 7,
9+
// Channels
10+
CHANNEL_CREATION = 40,
11+
CHANNEL_METADATA = 41,
12+
CHANNEL_MESSAGE = 42,
13+
CHANNEL_HIDE_MESSAGE = 43,
14+
CHANNEL_MUTE_USER = 44,
15+
CHANNEL_RESERVED_FIRST = 45,
16+
CHANNEL_RESERVED_LAST = 49,
17+
// Replaceable events
918
REPLACEABLE_FIRST = 10000,
1019
REPLACEABLE_LAST = 19999,
20+
// Ephemeral events
1121
EPHEMERAL_FIRST = 20000,
1222
EPHEMERAL_LAST = 29999,
23+
// Parameterized replaceable events
1324
PARAMETERIZED_REPLACEABLE_FIRST = 30000,
1425
PARAMETERIZED_REPLACEABLE_LAST = 39999,
1526
}

src/handlers/event-message-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class EventMessageHandler implements IMessageHandler {
129129
}
130130

131131
protected async isRateLimited(event: Event): Promise<boolean> {
132-
const rateLimits = this.settings().limits.event?.rateLimits
132+
const rateLimits = this.settings().limits?.event?.rateLimits
133133
if (!rateLimits || !rateLimits.length) {
134134
return
135135
}

src/handlers/event-strategies/delete-event-strategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class DeleteEventStrategy implements IEventStrategy<Event, Promise<void>>
2121
this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, true, (count) ? '' : 'duplicate:'))
2222

2323
const ids = event.tags.reduce(
24-
(eventIds, tag) => (tag.length >= 2 && tag[0] === EventTags.Event && tag[1].length === 64)
24+
(eventIds, tag) => (tag.length >= 2 && tag[0] === EventTags.Event)
2525
? [...eventIds, tag[1]]
2626
: eventIds,
2727
[] as string[]

src/utils/messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ export const createEndOfStoredEventsNoticeMessage = (
2727
}
2828

2929
// NIP-20
30-
export const createCommandResult = (eventId: EventId, successful: boolean, message = '') => {
30+
export const createCommandResult = (eventId: EventId, successful: boolean, message: string) => {
3131
return [MessageType.OK, eventId, successful, message]
3232
}

src/utils/settings.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,20 @@ export class SettingsStatic {
5252
},
5353
rateLimits: [
5454
{
55-
kinds: [EventKinds.TEXT_NOTE],
55+
kinds: [
56+
[EventKinds.SET_METADATA, EventKinds.DELETE],
57+
EventKinds.REACTION,
58+
[EventKinds.CHANNEL_CREATION, EventKinds.CHANNEL_RESERVED_LAST],
59+
[EventKinds.REPLACEABLE_FIRST, EventKinds.REPLACEABLE_LAST],
60+
[EventKinds.PARAMETERIZED_REPLACEABLE_FIRST, EventKinds.PARAMETERIZED_REPLACEABLE_LAST],
61+
],
5662
period: 60000,
5763
rate: 60,
5864
},
5965
{
6066
kinds: [[EventKinds.EPHEMERAL_FIRST, EventKinds.EPHEMERAL_LAST]],
6167
period: 60000,
62-
rate: 240,
68+
rate: 600,
6369
},
6470
{
6571
period: 3600000,
@@ -80,15 +86,15 @@ export class SettingsStatic {
8086
message: {
8187
rateLimits: [
8288
{
83-
period: 60000, // minute
84-
rate: 240,
89+
period: 60000,
90+
rate: 600,
8591
},
8692
{
87-
period: 3600000, // hour
93+
period: 3600000,
8894
rate: 3600,
8995
},
9096
{
91-
period: 86400000, // day
97+
period: 86400000,
9298
rate: 86400,
9399
},
94100
],

test/integration/features/helpers.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,28 @@ export async function waitForNotice(ws: WebSocket): Promise<void> {
223223
ws.on('message', onMessage)
224224
})
225225
}
226+
227+
export async function waitForCommand(ws: WebSocket): Promise<any> {
228+
return new Promise<void>((resolve, reject) => {
229+
function cleanup() {
230+
ws.removeListener('message', onMessage)
231+
ws.removeListener('error', onError)
232+
}
233+
234+
function onError(error: Error) {
235+
reject(error)
236+
cleanup()
237+
}
238+
ws.once('error', onError)
239+
240+
function onMessage(raw: RawData) {
241+
const message = JSON.parse(raw.toString('utf8'))
242+
if (message[0] === MessageType.OK) {
243+
resolve(message)
244+
cleanup()
245+
}
246+
}
247+
248+
ws.on('message', onMessage)
249+
})
250+
}

0 commit comments

Comments
 (0)