Loopback Fastify auth-broker + esbuild SPA. Operator password login (constant-time, signed HttpOnly session cookie, per-forwarded-IP rate-limit) → session-gated proxy that mints a fresh 60s manage capability token per call to the control-plane admin API: list hosts, mint pairing codes (with QR + pair command), revoke hosts. Security headers + CSP, CP_URL pinned loopback (anti-SSRF), hostId dot-segment guard. 55 tests pass; security-reviewed. Deployed behind nginx panel.terminal.yaojia.wang.
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { createSlidingWindowLimiter } from '../src/security/rate-limit.js'
|
|
|
|
describe('sliding-window rate limiter', () => {
|
|
it('allows up to max, then blocks within the window', () => {
|
|
let t = 0
|
|
const limiter = createSlidingWindowLimiter(3, 1000, () => t)
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
expect(limiter.allow('ip')).toBe(false)
|
|
})
|
|
|
|
it('does not record a rejected attempt (window frees up after it elapses)', () => {
|
|
let t = 0
|
|
const limiter = createSlidingWindowLimiter(2, 1000, () => t)
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
expect(limiter.allow('ip')).toBe(false) // blocked, NOT recorded
|
|
t = 1001 // original two hits now outside the window
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
expect(limiter.allow('ip')).toBe(true)
|
|
})
|
|
|
|
it('tracks buckets independently per key', () => {
|
|
let t = 0
|
|
const limiter = createSlidingWindowLimiter(1, 1000, () => t)
|
|
expect(limiter.allow('a')).toBe(true)
|
|
expect(limiter.allow('a')).toBe(false)
|
|
expect(limiter.allow('b')).toBe(true)
|
|
})
|
|
})
|