Files
breakpilot-core/pitch-deck/lib/rate-limit.ts
Sharang Parnerkar 645973141c
All checks were successful
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 25s
CI / test-bqas (push) Successful in 27s
CI / Deploy (push) Successful in 6s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
feat(pitch-deck): passwordless investor auth, audit logs, snapshots & PWA (#2)
Adds investor-facing access controls, persistence, and PWA support to the pitch deck:

- Passwordless magic-link auth (jose JWT + nodemailer SMTP)
- Per-investor audit logging (logins, slide views, assumption changes, chat)
- Financial model snapshot persistence (auto-save/restore per investor)
- PWA support (manifest, service worker, offline caching, branded icons)
- Safeguards: email watermark overlay, security headers, content protection,
  rate limiting, IP/new-IP detection, single active session per investor
- Admin API: invite, list investors, revoke, query audit logs
- pitch-deck service added to docker-compose.coolify.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:48:38 +00:00

53 lines
1.5 KiB
TypeScript

interface RateLimitEntry {
count: number
resetAt: number
}
const store = new Map<string, RateLimitEntry>()
// Cleanup stale entries every 60 seconds
setInterval(() => {
const now = Date.now()
for (const [key, entry] of store) {
if (entry.resetAt <= now) store.delete(key)
}
}, 60_000)
export interface RateLimitConfig {
/** Max requests in the window */
limit: number
/** Window size in seconds */
windowSec: number
}
export interface RateLimitResult {
allowed: boolean
remaining: number
resetAt: number
}
export function checkRateLimit(key: string, config: RateLimitConfig): RateLimitResult {
const now = Date.now()
const entry = store.get(key)
if (!entry || entry.resetAt <= now) {
store.set(key, { count: 1, resetAt: now + config.windowSec * 1000 })
return { allowed: true, remaining: config.limit - 1, resetAt: now + config.windowSec * 1000 }
}
if (entry.count >= config.limit) {
return { allowed: false, remaining: 0, resetAt: entry.resetAt }
}
entry.count++
return { allowed: true, remaining: config.limit - entry.count, resetAt: entry.resetAt }
}
// Preset configurations
export const RATE_LIMITS = {
magicLink: { limit: 3, windowSec: 3600 } as RateLimitConfig, // 3 per email per hour
authVerify: { limit: 10, windowSec: 900 } as RateLimitConfig, // 10 per IP per 15min
api: { limit: 60, windowSec: 60 } as RateLimitConfig, // 60 per session per minute
chat: { limit: 20, windowSec: 60 } as RateLimitConfig, // 20 per session per minute
} as const