mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-16 22:35:32 +08:00
* fix: harden scoped API inputs and isolate login throttling * fix: close script path gaps and harden security boundaries * fix: preserve subscription aliases and avoid extension eval * fix: allow cancelling invalid historical subscriptions * fix: make script parent validation explicit for CodeQL * fix: limit command execution and clarify trusted path branches * fix: separate diff tool script access from config APIs * fix: clean up obsolete subscription SSH credentials
48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
const RETRY_TTL = 15 * 60 * 1000;
|
|
const MAX_SOURCES = 10000;
|
|
const MAX_RETRY_SECONDS = 5 * 60;
|
|
|
|
/** Source-specific, bounded backoff; persisted account history is not a lock. */
|
|
export class LoginThrottle {
|
|
private sources = new Map<
|
|
string,
|
|
{ failures: number; lastFailure: number }
|
|
>();
|
|
|
|
private get(source: string, now: number) {
|
|
const state = this.sources.get(source);
|
|
if (state && now - state.lastFailure >= RETRY_TTL) {
|
|
this.sources.delete(source);
|
|
return undefined;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
retryAfter(source: string, now: number): number {
|
|
const state = this.get(source, now);
|
|
if (!state || state.failures < 3) return 0;
|
|
const seconds = Math.min(3 ** state.failures, MAX_RETRY_SECONDS);
|
|
return Math.max(
|
|
0,
|
|
Math.ceil((seconds * 1000 - (now - state.lastFailure)) / 1000),
|
|
);
|
|
}
|
|
|
|
fail(source: string, now: number): number {
|
|
const failures = Math.min((this.get(source, now)?.failures || 0) + 1, 6);
|
|
this.sources.delete(source);
|
|
if (this.sources.size >= MAX_SOURCES) {
|
|
this.sources.delete(this.sources.keys().next().value!);
|
|
}
|
|
this.sources.set(source, { failures, lastFailure: now });
|
|
return failures;
|
|
}
|
|
|
|
reset(source: string) {
|
|
this.sources.delete(source);
|
|
}
|
|
clear() {
|
|
this.sources.clear();
|
|
}
|
|
}
|