feat(server): 管理面与成员视图 API + Scalar 在线文档

契约 19 路径全量:引导/会话/改密、engine 桩、keys、overview/logs、payments、
qpu、members、audit、成员视图。鉴权优先、422 信封、登录节流、上游验证接线。
This commit is contained in:
2026-10-06 01:30:52 +08:00
parent 6980d09750
commit 6454f4acfa
18 changed files with 2358 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
/**
* 登录节流(契约:429,最小实现)
* 同一用户名失败计数 + 固定退避;进程内存即可(单机母体)。
*/
const WINDOW_MS = 5 * 60_000;
const MAX_FAILURES = 5;
const LOCK_MS = 60_000;
type Entry = { count: number; firstAt: number; lockedUntil: number };
const buckets = new Map<string, Entry>();
export function isLoginThrottled(username: string): boolean {
const e = buckets.get(username);
if (!e) return false;
if (e.lockedUntil > Date.now()) return true;
if (Date.now() - e.firstAt > WINDOW_MS) buckets.delete(username);
return false;
}
export function recordLoginFailure(username: string): void {
const now = Date.now();
const e = buckets.get(username);
if (!e || now - e.firstAt > WINDOW_MS) {
buckets.set(username, { count: 1, firstAt: now, lockedUntil: 0 });
return;
}
e.count += 1;
if (e.count >= MAX_FAILURES) {
e.lockedUntil = now + LOCK_MS;
e.count = 0;
e.firstAt = now;
}
}
export function recordLoginSuccess(username: string): void {
buckets.delete(username);
}
/** 测试用 */
export function resetLoginThrottle(): void {
buckets.clear();
}