huge update.
fix issues with failing tests (I'm just gonna fucking assume directx isn't installed on the github actions machines). Added MUCH better RBAC support and control Fixed an issue when you have over a million clients RBAC would be slow asf Add settings page with some basic settings and configs you can change.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { logger } from "./logger";
|
||||
import { getConfig } from "./config";
|
||||
|
||||
interface RateLimitEntry {
|
||||
attempts: number;
|
||||
@@ -8,15 +9,21 @@ interface RateLimitEntry {
|
||||
|
||||
const rateLimitStore = new Map<string, RateLimitEntry>();
|
||||
|
||||
const MAX_ATTEMPTS = 5;
|
||||
const WINDOW_MS = 15 * 60 * 1000;
|
||||
const LOCKOUT_MS = 30 * 60 * 1000;
|
||||
const CLEANUP_INTERVAL = 60 * 1000;
|
||||
|
||||
function getRateLimitPolicy() {
|
||||
const security = getConfig().security;
|
||||
const maxAttempts = Math.min(50, Math.max(1, Number(security.loginMaxAttempts) || 5));
|
||||
const windowMs = Math.min(24 * 60 * 60 * 1000, Math.max(60 * 1000, (Number(security.loginWindowMinutes) || 15) * 60 * 1000));
|
||||
const lockoutMs = Math.min(24 * 60 * 60 * 1000, Math.max(60 * 1000, (Number(security.loginLockoutMinutes) || 30) * 60 * 1000));
|
||||
return { maxAttempts, windowMs, lockoutMs };
|
||||
}
|
||||
|
||||
export function isRateLimited(ip: string): {
|
||||
limited: boolean;
|
||||
retryAfter?: number;
|
||||
} {
|
||||
const policy = getRateLimitPolicy();
|
||||
const entry = rateLimitStore.get(ip);
|
||||
|
||||
if (!entry) {
|
||||
@@ -30,14 +37,14 @@ export function isRateLimited(ip: string): {
|
||||
return { limited: true, retryAfter };
|
||||
}
|
||||
|
||||
if (now - entry.firstAttempt > WINDOW_MS) {
|
||||
if (now - entry.firstAttempt > policy.windowMs) {
|
||||
rateLimitStore.delete(ip);
|
||||
return { limited: false };
|
||||
}
|
||||
|
||||
if (entry.attempts >= MAX_ATTEMPTS) {
|
||||
entry.lockedUntil = now + LOCKOUT_MS;
|
||||
const retryAfter = Math.ceil(LOCKOUT_MS / 1000);
|
||||
if (entry.attempts >= policy.maxAttempts) {
|
||||
entry.lockedUntil = now + policy.lockoutMs;
|
||||
const retryAfter = Math.ceil(policy.lockoutMs / 1000);
|
||||
logger.warn(
|
||||
`[rate-limit] IP ${ip} locked out for ${retryAfter}s after ${entry.attempts} failed attempts`,
|
||||
);
|
||||
@@ -48,6 +55,7 @@ export function isRateLimited(ip: string): {
|
||||
}
|
||||
|
||||
export function recordFailedAttempt(ip: string): void {
|
||||
const policy = getRateLimitPolicy();
|
||||
const now = Date.now();
|
||||
const entry = rateLimitStore.get(ip);
|
||||
|
||||
@@ -59,7 +67,7 @@ export function recordFailedAttempt(ip: string): void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (now - entry.firstAttempt > WINDOW_MS) {
|
||||
if (now - entry.firstAttempt > policy.windowMs) {
|
||||
rateLimitStore.set(ip, {
|
||||
attempts: 1,
|
||||
firstAttempt: now,
|
||||
@@ -69,7 +77,7 @@ export function recordFailedAttempt(ip: string): void {
|
||||
|
||||
entry.attempts++;
|
||||
logger.debug(
|
||||
`[rate-limit] IP ${ip} failed attempt ${entry.attempts}/${MAX_ATTEMPTS}`,
|
||||
`[rate-limit] IP ${ip} failed attempt ${entry.attempts}/${policy.maxAttempts}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,11 +86,12 @@ export function recordSuccessfulAttempt(ip: string): void {
|
||||
}
|
||||
|
||||
function cleanupExpired(): void {
|
||||
const policy = getRateLimitPolicy();
|
||||
const now = Date.now();
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [ip, entry] of rateLimitStore.entries()) {
|
||||
const windowExpired = now - entry.firstAttempt > WINDOW_MS;
|
||||
const windowExpired = now - entry.firstAttempt > policy.windowMs;
|
||||
const lockoutExpired = entry.lockedUntil && entry.lockedUntil < now;
|
||||
|
||||
if ((windowExpired && !entry.lockedUntil) || lockoutExpired) {
|
||||
@@ -114,6 +123,7 @@ export function getRateLimitStats(): { total: number; locked: number } {
|
||||
|
||||
setInterval(cleanupExpired, CLEANUP_INTERVAL);
|
||||
|
||||
const initialPolicy = getRateLimitPolicy();
|
||||
logger.info(
|
||||
`[rate-limit] Initialized: ${MAX_ATTEMPTS} attempts per ${WINDOW_MS / 60000} minutes, ${LOCKOUT_MS / 60000} minute lockout`,
|
||||
`[rate-limit] Initialized: ${initialPolicy.maxAttempts} attempts per ${Math.round(initialPolicy.windowMs / 60000)} minutes, ${Math.round(initialPolicy.lockoutMs / 60000)} minute lockout`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user