optimize docker, more rate limiting, remove not needed shit in build-prod-package.

This commit is contained in:
KingKDot
2026-05-30 23:20:15 -05:00
parent 2a90053eee
commit 707ba172c0
9 changed files with 235 additions and 14 deletions
+90
View File
@@ -7,10 +7,22 @@ interface RateLimitEntry {
lockedUntil?: number;
}
type RequestRateLimitPolicy = {
maxRequests: number;
windowMs: number;
lockoutMs: number;
};
const rateLimitStore = new Map<string, RateLimitEntry>();
const requestRateLimitStore = new Map<string, RateLimitEntry>();
const CLEANUP_INTERVAL = 60 * 1000;
function numberFromEnv(name: string, fallback: number): number {
const parsed = Number(process.env[name]);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
function getRateLimitPolicy() {
const security = getConfig().security;
const maxAttempts = Math.min(50, Math.max(1, Number(security.loginMaxAttempts) || 5));
@@ -85,6 +97,75 @@ export function recordSuccessfulAttempt(ip: string): void {
rateLimitStore.delete(ip);
}
function makeRequestPolicy(prefix: string, defaults: RequestRateLimitPolicy): RequestRateLimitPolicy {
return {
maxRequests: Math.min(
1000,
Math.max(1, numberFromEnv(`OVERLORD_${prefix}_RATE_LIMIT_MAX`, defaults.maxRequests)),
),
windowMs: Math.min(
60 * 60 * 1000,
Math.max(10 * 1000, numberFromEnv(`OVERLORD_${prefix}_RATE_LIMIT_WINDOW_SECONDS`, defaults.windowMs / 1000) * 1000),
),
lockoutMs: Math.min(
60 * 60 * 1000,
Math.max(10 * 1000, numberFromEnv(`OVERLORD_${prefix}_RATE_LIMIT_LOCKOUT_SECONDS`, defaults.lockoutMs / 1000) * 1000),
),
};
}
function consumeRequestRateLimit(
key: string,
label: string,
policy: RequestRateLimitPolicy,
): { limited: boolean; retryAfter?: number } {
const now = Date.now();
const entry = requestRateLimitStore.get(key);
if (entry?.lockedUntil && entry.lockedUntil > now) {
return { limited: true, retryAfter: Math.ceil((entry.lockedUntil - now) / 1000) };
}
if (!entry || now - entry.firstAttempt > policy.windowMs) {
requestRateLimitStore.set(key, { attempts: 1, firstAttempt: now });
return { limited: false };
}
entry.attempts++;
if (entry.attempts > policy.maxRequests) {
entry.lockedUntil = now + policy.lockoutMs;
const retryAfter = Math.ceil(policy.lockoutMs / 1000);
logger.warn(
`[rate-limit] ${label} rate limit exceeded for ${key}; locked for ${retryAfter}s after ${entry.attempts} requests`,
);
return { limited: true, retryAfter };
}
return { limited: false };
}
export function consumeLoginPageRateLimit(ip: string): { limited: boolean; retryAfter?: number } {
const policy = makeRequestPolicy("LOGIN_PAGE", {
maxRequests: 60,
windowMs: 5 * 60 * 1000,
lockoutMs: 10 * 60 * 1000,
});
return consumeRequestRateLimit(`login-page:${ip}`, "Login page", policy);
}
export function consumeUnauthorizedRateLimit(ip: string): { limited: boolean; retryAfter?: number } {
const policy = makeRequestPolicy("UNAUTHORIZED", {
maxRequests: 120,
windowMs: 5 * 60 * 1000,
lockoutMs: 10 * 60 * 1000,
});
return consumeRequestRateLimit(`unauthorized:${ip}`, "Unauthorized request", policy);
}
export function clearRequestRateLimitsForTests(): void {
requestRateLimitStore.clear();
}
function cleanupExpired(): void {
const policy = getRateLimitPolicy();
const now = Date.now();
@@ -100,6 +181,15 @@ function cleanupExpired(): void {
}
}
for (const [key, entry] of requestRateLimitStore.entries()) {
const expired = now - entry.firstAttempt > 60 * 60 * 1000;
const lockoutExpired = entry.lockedUntil && entry.lockedUntil < now;
if (expired || lockoutExpired) {
requestRateLimitStore.delete(key);
cleaned++;
}
}
if (cleaned > 0) {
logger.debug(`[rate-limit] Cleaned up ${cleaned} expired entries`);
}