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/validate-rate-limit-period.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostream": patch
---

Reject settings where any rateLimits[].period is not greater than 0
31 changes: 31 additions & 0 deletions src/utils/settings-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,25 @@ export const validatePathAgainstDefaults = (path: string): ValidationIssue[] =>
]
}

const validateRateLimitPeriods = (
rateLimits: { period: number }[] | undefined,
pathPrefix: string,
issues: ValidationIssue[],
): void => {
if (!Array.isArray(rateLimits)) {
return
}
rateLimits.forEach((entry, index) => {
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
issues.push({ path: `${pathPrefix}[${index}]`, message: 'rateLimits entry must be an object' })
return
}
if (!(entry.period > 0)) {
Comment thread
Ferryx349 marked this conversation as resolved.
issues.push({ path: `${pathPrefix}[${index}].period`, message: 'period must be greater than 0' })
}
})
}

export const validateSettings = (settings: Settings): ValidationIssue[] => {
const issues: ValidationIssue[] = []

Expand Down Expand Up @@ -644,6 +663,18 @@ export const validateSettings = (settings: Settings): ValidationIssue[] => {
}
}

const limits = settings.limits
if (limits) {
validateRateLimitPeriods(limits.event?.rateLimits, 'limits.event.rateLimits', issues)
validateRateLimitPeriods(limits.message?.rateLimits, 'limits.message.rateLimits', issues)
validateRateLimitPeriods(limits.connection?.rateLimits, 'limits.connection.rateLimits', issues)
validateRateLimitPeriods(limits.invoice?.rateLimits, 'limits.invoice.rateLimits', issues)
validateRateLimitPeriods(limits.admissionCheck?.rateLimits, 'limits.admissionCheck.rateLimits', issues)
validateRateLimitPeriods(limits.invite?.rateLimits, 'limits.invite.rateLimits', issues)
validateRateLimitPeriods(limits.admin?.rateLimits, 'limits.admin.rateLimits', issues)
validateRateLimitPeriods(limits.admin?.loginRateLimits, 'limits.admin.loginRateLimits', issues)
}

validateShape(loadDefaults(), settings, [], issues)
issues.push(...validateAdminNotifications(settings))

Expand Down
69 changes: 69 additions & 0 deletions test/unit/utils/settings-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,75 @@ describe('settings-config', () => {
expect(issues.some((issue) => issue.path === 'network')).to.equal(true)
})

describe('rate limit period validation', () => {
const baseSettings = () =>
({
info: { relay_url: 'wss://test.relay', name: 'test' },
network: {},
}) as any

const nonPositivePeriodCases: { issuePath: string; limits: Record<string, unknown> }[] = [
{
issuePath: 'limits.event.rateLimits[0].period',
limits: { event: { rateLimits: [{ period: 0, rate: 1 }] } },
},
{
issuePath: 'limits.message.rateLimits[0].period',
limits: { message: { rateLimits: [{ period: 0, rate: 1 }] } },
},
{
issuePath: 'limits.connection.rateLimits[0].period',
limits: { connection: { rateLimits: [{ period: -1, rate: 1 }] } },
},
{
issuePath: 'limits.invoice.rateLimits[0].period',
limits: { invoice: { rateLimits: [{ period: 0, rate: 1 }] } },
},
{
issuePath: 'limits.admissionCheck.rateLimits[0].period',
limits: { admissionCheck: { rateLimits: [{ period: 0, rate: 1 }] } },
},
{
issuePath: 'limits.invite.rateLimits[0].period',
limits: { invite: { rateLimits: [{ period: 0, rate: 1 }] } },
},
{
issuePath: 'limits.admin.rateLimits[0].period',
limits: { admin: { rateLimits: [{ period: 0, rate: 1 }] } },
},
{
issuePath: 'limits.admin.loginRateLimits[0].period',
limits: { admin: { loginRateLimits: [{ period: -1, rate: 1 }] } },
},
]

for (const { issuePath, limits } of nonPositivePeriodCases) {
it(`rejects a non-positive period on ${issuePath.replace('[0].period', '')}`, () => {
const settings = baseSettings()
settings.limits = limits

const issues = validateSettings(settings)
expect(issues.some((issue) => issue.path === issuePath)).to.equal(true)
})
}

it('accepts a positive period on limits.event.rateLimits', () => {
const settings = baseSettings()
settings.limits = { event: { rateLimits: [{ period: 60_000, rate: 10 }] } }

const issues = validateSettings(settings)
expect(issues.some((issue) => issue.path.startsWith('limits.event.rateLimits'))).to.equal(false)
})

it('rejects a null entry in limits.event.rateLimits without throwing', () => {
const settings = baseSettings()
settings.limits = { event: { rateLimits: [null] } }

const issues = validateSettings(settings)
expect(issues.some((issue) => issue.path === 'limits.event.rateLimits[0]')).to.equal(true)
})
})

describe('adaptive pow settings', () => {
const baseSettings = () =>
({
Expand Down
Loading