Files
gbn_ln_bot/src/api/ratelimit.ts
T
goyban e857bf4ec6 Initial commit — federated self-custodial Spark/Lightning tip bot
- grammY bot: /start, /unlock, /tip, /contact, /claim, /settings, /wallet
- AES-256-GCM mnemonic encryption with scrypt key derivation
- In-memory unlock sessions with background sweep
- Atomic claim handling (TOCTOU-safe)
- PIN rate limiting (5 attempts → 15 min lockout)
- Fastify API server + Telegram Mini App (setup, unlock, send, receive, history)
- One-time seed reveal via Mini App or auto-deleted DM message
- Federated registry client
- Docker Compose deployment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 13:21:43 +00:00

50 lines
1.3 KiB
TypeScript

/**
* Simple in-memory rate limiter for PIN attempts.
*
* After MAX_FAILURES consecutive wrong PINs the account is locked for
* LOCKOUT_MS. The counter resets on a successful unlock.
*/
const MAX_FAILURES = 5;
const LOCKOUT_MS = 15 * 60 * 1000; // 15 minutes
interface Entry {
failures: number;
lockedUntil: number | null;
}
const store = new Map<number, Entry>();
function entry(userId: number): Entry {
if (!store.has(userId)) store.set(userId, { failures: 0, lockedUntil: null });
return store.get(userId)!;
}
export function checkPinRateLimit(userId: number): void {
const e = entry(userId);
if (e.lockedUntil && Date.now() < e.lockedUntil) {
const secondsLeft = Math.ceil((e.lockedUntil - Date.now()) / 1000);
throw new RateLimitError(`Too many incorrect PINs. Try again in ${secondsLeft}s.`);
}
}
export function recordPinFailure(userId: number): void {
const e = entry(userId);
e.failures += 1;
if (e.failures >= MAX_FAILURES) {
e.lockedUntil = Date.now() + LOCKOUT_MS;
e.failures = 0;
}
}
export function recordPinSuccess(userId: number): void {
store.delete(userId); // reset counter on success
}
export class RateLimitError extends Error {
constructor(message: string) {
super(message);
this.name = "RateLimitError";
}
}