Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
imksoo committed Jan 13, 2024
2 parents cc7ba4b + b6133a1 commit 246eb61
Show file tree
Hide file tree
Showing 34 changed files with 1,465 additions and 1,251 deletions.
3 changes: 3 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,6 @@ Running `nostream` for the first time creates the settings file in `<project_roo
| limits.message.rateLimits[].period | Rate limit period in milliseconds. |
| limits.message.rateLimits[].rate | Maximum number of messages during period. |
| limits.message.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
| limits.admissionCheck.rateLimits[].period | Rate limit period in milliseconds. |
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ NIPs with a relay-specific implementation are listed here.
- [x] NIP-16: Event Treatment
- [x] NIP-20: Command Results
- [x] NIP-22: Event `created_at` Limits
- [x] NIP-26: Delegated Event Signing
- [ ] NIP-26: Delegated Event Signing (REMOVED)
- [x] NIP-28: Public Chat
- [x] NIP-33: Parameterized Replaceable Events
- [x] NIP-40: Expiration Timestamp
Expand Down
18 changes: 18 additions & 0 deletions migrations/20240111204900_remove_delegator_from_events_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
exports.up = async function (knex) {
await knex.schema
.raw('DROP INDEX IF EXISTS pubkey_delegator_kind_idx;')
await knex.schema.alterTable('events', function (table) {
table.dropColumn('event_delegator')
})
}

exports.down = async function (knex) {
await knex.schema.alterTable('events', function (table) {
table.binary('event_delegator').nullable().index()
})
await knex.schema
.raw(
`CREATE UNIQUE INDEX pubkey_delegator_kind_idx
ON events ( event_pubkey, event_delegator, event_kind );`,
)
}
1,788 changes: 1,183 additions & 605 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nostream",
"version": "1.25.2",
"version": "2.0.0",
"description": "A Nostr relay written in Typescript.",
"supportedNips": [
1,
Expand All @@ -13,7 +13,6 @@
16,
20,
22,
26,
28,
33,
40
Expand Down Expand Up @@ -73,7 +72,7 @@
"devDependencies": {
"@commitlint/cli": "17.2.0",
"@commitlint/config-conventional": "17.2.0",
"@cucumber/cucumber": "8.7.0",
"@cucumber/cucumber": "10.2.1",
"@cucumber/pretty-formatter": "1.0.0",
"@semantic-release/commit-analyzer": "9.0.2",
"@semantic-release/git": "10.0.1",
Expand Down Expand Up @@ -116,7 +115,7 @@
},
"dependencies": {
"@noble/secp256k1": "1.7.1",
"axios": "1.2.6",
"axios": "1.6.5",
"bech32": "2.0.0",
"body-parser": "1.20.1",
"debug": "4.3.4",
Expand Down
9 changes: 9 additions & 0 deletions resources/default-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ limits:
- "::1"
- "10.10.10.1"
- "::ffff:10.10.10.1"
admissionCheck:
rateLimits:
- description: 30 admission checks/min or 1 check every 2 seconds
period: 60000
rate: 30
ipWhitelist:
- "::1"
- "10.10.10.1"
- "::ffff:10.10.10.1"
connection:
rateLimits:
- period: 1000
Expand Down
7 changes: 1 addition & 6 deletions src/@types/event.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ContextMetadata, EventId, Pubkey, Tag } from './base'
import { ContextMetadataKey, EventDeduplicationMetadataKey, EventDelegatorMetadataKey, EventExpirationTimeMetadataKey, EventKinds } from '../constants/base'
import { ContextMetadataKey, EventDeduplicationMetadataKey, EventExpirationTimeMetadataKey, EventKinds } from '../constants/base'

export interface BaseEvent {
id: EventId
Expand All @@ -21,10 +21,6 @@ export type UnsignedEvent = Omit<Event, 'sig'>

export type UnidentifiedEvent = Omit<UnsignedEvent, 'id'>

export interface DelegatedEvent extends Event {
[EventDelegatorMetadataKey]?: Pubkey
}

export interface ExpiringEvent extends Event {
[EventExpirationTimeMetadataKey]?: number
}
Expand All @@ -42,7 +38,6 @@ export interface DBEvent {
event_content: string
event_tags: Tag[]
event_signature: Buffer
event_delegator?: Buffer | null
event_deduplication?: string | null
first_seen: Date
deleted_at?: Date
Expand Down
1 change: 0 additions & 1 deletion src/@types/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export interface IEventRepository {
create(event: Event): Promise<number>
upsert(event: Event): Promise<number>
findByFilters(filters: SubscriptionFilter[]): IQueryResult<DBEvent[]>
insertStubs(pubkey: string, eventIdsToDelete: EventId[]): Promise<number>
deleteByPubkeyAndIds(pubkey: Pubkey, ids: EventId[]): Promise<number>
}

Expand Down
6 changes: 6 additions & 0 deletions src/@types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,14 @@ export interface InvoiceLimits {
ipWhitelist?: string[]
}

export interface AdmissionCheckLimits {
rateLimits: RateLimit[]
ipWhitelist?: string[]
}

export interface Limits {
invoice?: InvoiceLimits
admissionCheck?: AdmissionCheckLimits
connection?: ConnectionLimits
client?: ClientLimits
event?: EventLimits
Expand Down
2 changes: 1 addition & 1 deletion src/@types/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type SubscriptionId = string

export interface SubscriptionFilter {
ids?: EventId[]
kinds?: EventKinds[]
kinds?: (EventKinds | number)[]
since?: number
until?: number
authors?: Pubkey[]
Expand Down
2 changes: 0 additions & 2 deletions src/constants/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export enum EventTags {
Event = 'e',
Pubkey = 'p',
// Multicast = 'm',
Delegation = 'delegation',
Deduplication = 'd',
Expiration = 'expiration',
Invoice = 'bolt11',
Expand All @@ -49,7 +48,6 @@ export enum PaymentsProcessors {
LNBITS = 'lnbits',
}

export const EventDelegatorMetadataKey = Symbol('Delegator')
export const EventDeduplicationMetadataKey = Symbol('Deduplication')
export const ContextMetadataKey = Symbol('Context')
export const EventExpirationTimeMetadataKey = Symbol('Expiration')
70 changes: 70 additions & 0 deletions src/controllers/admission/get-admission-check-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Request, Response } from 'express'
import { createLogger } from '../../factories/logger-factory'
import { getRemoteAddress } from '../../utils/http'
import { IController } from '../../@types/controllers'
import { IRateLimiter } from '../../@types/utils'
import { IUserRepository } from '../../@types/repositories'
import { path } from 'ramda'
import { Settings } from '../../@types/settings'

const debug = createLogger('get-admission-check-controller')

export class GetSubmissionCheckController implements IController {
public constructor(
private readonly userRepository: IUserRepository,
private readonly settings: () => Settings,
private readonly rateLimiter: () => IRateLimiter,
){}

public async handleRequest(request: Request, response: Response): Promise<void> {
const currentSettings = this.settings()

const limited = await this.isRateLimited(request, currentSettings)
if (limited) {
response
.status(429)
.setHeader('content-type', 'text/plain; charset=utf8')
.send('Too many requests')
return
}

const pubkey = request.params.pubkey
const user = await this.userRepository.findByPubkey(pubkey)

let userAdmitted = false

const minBalance = currentSettings.limits?.event?.pubkey?.minBalance
if (user && user.isAdmitted && (!minBalance || user.balance >= minBalance)) {
userAdmitted = true
}

response
.status(200)
.setHeader('content-type', 'application/json; charset=utf8')
.send({ userAdmitted })

return
}

public async isRateLimited(request: Request, settings: Settings) {
const rateLimits = path(['limits', 'admissionCheck', 'rateLimits'], settings)
if (!Array.isArray(rateLimits) || !rateLimits.length) {
return false
}

const ipWhitelist = path(['limits', 'admissionCheck', 'ipWhitelist'], settings)
const remoteAddress = getRemoteAddress(request, settings)

let limited = false
if (Array.isArray(ipWhitelist) && !ipWhitelist.includes(remoteAddress)) {
const rateLimiter = this.rateLimiter()
for (const { rate, period } of rateLimits) {
if (await rateLimiter.hit(`${remoteAddress}:admission-check:${period}`, 1, { period, rate })) {
debug('rate limited %s: %d in %d milliseconds', remoteAddress, rate, period)
limited = true
}
}
}
return limited
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createSettings } from '../settings-factory'
import { getMasterDbClient } from '../../database/client'
import { GetSubmissionCheckController } from '../../controllers/admission/get-admission-check-controller'
import { slidingWindowRateLimiterFactory } from '../rate-limiter-factory'
import { UserRepository } from '../../repositories/user-repository'

export const createGetAdmissionCheckController = () => {
const dbClient = getMasterDbClient()
const userRepository = new UserRepository(dbClient)

return new GetSubmissionCheckController(
userRepository,
createSettings,
slidingWindowRateLimiterFactory
)
}
21 changes: 0 additions & 21 deletions src/factories/delegated-event-strategy-factory.ts

This file was deleted.

15 changes: 1 addition & 14 deletions src/factories/message-handler-factory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { IEventRepository, IUserRepository } from '../@types/repositories'
import { IncomingMessage, MessageType } from '../@types/messages'
import { createSettings } from './settings-factory'
import { DelegatedEventMessageHandler } from '../handlers/delegated-event-message-handler'
import { delegatedEventStrategyFactory } from './delegated-event-strategy-factory'
import { EventMessageHandler } from '../handlers/event-message-handler'
import { eventStrategyFactory } from './event-strategy-factory'
import { isDelegatedEvent } from '../utils/event'
import { IWebSocketAdapter } from '../@types/adapters'
import { slidingWindowRateLimiterFactory } from './rate-limiter-factory'
import { SubscribeMessageHandler } from '../handlers/subscribe-message-handler'
Expand All @@ -18,16 +15,6 @@ export const messageHandlerFactory = (
switch (message[0]) {
case MessageType.EVENT:
{
if (isDelegatedEvent(message[1])) {
return new DelegatedEventMessageHandler(
adapter,
delegatedEventStrategyFactory(eventRepository),
userRepository,
createSettings,
slidingWindowRateLimiterFactory,
)
}

return new EventMessageHandler(
adapter,
eventStrategyFactory(eventRepository),
Expand All @@ -39,7 +26,7 @@ export const messageHandlerFactory = (
case MessageType.REQ:
return new SubscribeMessageHandler(adapter, eventRepository, createSettings)
case MessageType.CLOSE:
return new UnsubscribeMessageHandler(adapter,)
return new UnsubscribeMessageHandler(adapter)
default:
throw new Error(`Unknown message type: ${String(message[0]).substring(0, 64)}`)
}
Expand Down
75 changes: 0 additions & 75 deletions src/handlers/delegated-event-message-handler.ts

This file was deleted.

Loading

0 comments on commit 246eb61

Please sign in to comment.