rokojori-auth/source/server/rateLimiter.ts

86 lines
2.2 KiB
TypeScript
Raw Permalink Normal View History

2026-07-13 03:48:45 +00:00
interface Entry
{
count: number;
windowStart: number;
}
function makeRateLimiter( maxAttempts: number, windowMs: number )
{
const store = new Map<string, Entry>();
return function check( ip: string ): { blocked: boolean }
{
const now = Date.now();
let entry = store.get( ip );
2026-07-13 03:48:45 +00:00
if ( !entry || now - entry.windowStart > windowMs )
entry = { count: 0, windowStart: now };
entry.count++;
store.set( ip, entry );
return { blocked: entry.count > maxAttempts };
};
}
// forgot-password — strict, with escalating delay to prevent email spam
const FORGOT_WINDOW_MS = 20 * 60 * 1000;
const FORGOT_MAX_ATTEMPTS = 20;
const forgotStore = new Map<string, Entry>();
function forgotDelayMs( count: number ): number
2026-07-13 03:48:45 +00:00
{
if ( count <= 5 ) return 5_000;
if ( count <= 10 ) return 15_000;
return 30_000;
}
export function checkForgotPasswordRate( ip: string ): { blocked: boolean; delay: number }
{
const now = Date.now();
let entry = forgotStore.get( ip );
if ( !entry || now - entry.windowStart > FORGOT_WINDOW_MS )
entry = { count: 0, windowStart: now };
entry.count++;
forgotStore.set( ip, entry );
if ( entry.count > FORGOT_MAX_ATTEMPTS ) return { blocked: true, delay: 0 };
return { blocked: false, delay: forgotDelayMs( entry.count ) };
}
// login — 10 attempts per 15 min, small delay after 5
const loginStore = new Map<string, Entry>();
const LOGIN_WINDOW_MS = 15 * 60 * 1000;
const LOGIN_MAX_ATTEMPTS = 10;
function loginDelayMs( count: number ): number
{
if ( count <= 5 ) return 0;
return 3_000;
}
export function checkLoginRate( ip: string ): { blocked: boolean; delay: number }
{
const now = Date.now();
let entry = loginStore.get( ip );
2026-07-13 03:48:45 +00:00
if ( !entry || now - entry.windowStart > LOGIN_WINDOW_MS )
2026-07-13 03:48:45 +00:00
entry = { count: 0, windowStart: now };
entry.count++;
loginStore.set( ip, entry );
2026-07-13 03:48:45 +00:00
if ( entry.count > LOGIN_MAX_ATTEMPTS ) return { blocked: true, delay: 0 };
return { blocked: false, delay: loginDelayMs( entry.count ) };
2026-07-13 03:48:45 +00:00
}
// register — 5 attempts per hour, no delay
export const checkRegisterRate = makeRateLimiter( 5, 60 * 60 * 1000 );
2026-07-13 03:48:45 +00:00
export function sleep( ms: number ): Promise<void>
{
return new Promise( resolve => setTimeout( resolve, ms ) );
}