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>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user