auth-yes/server/ratelimit.ts
google-labs-jules[bot] 9988df3218 audit: verify 3-tier auth, rbac, and decouple server side effects
- Add `app` export and wrap startup logic behind `if (import.meta.main)`
- Extract `hono` middleware into `sdk/hono.ts` for clean separation
- Refactor module imports slightly to support in-memory native mocking (`db`, `valkey`, `spire_ffi`, `ratelimit`, `audit`)
- Implement comprehensive native Deno mock tests in `server/main.test.ts`
- Fix type checking across project files

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-22 00:29:19 +00:00

68 lines
2.0 KiB
TypeScript

import { valkey } from "./valkey.ts";
/**
* Implements a sliding window rate limiter backed by Valkey using Sorted Sets.
*
* @param key - The unique identifier for the limit (e.g. "rate:public:ip:192.168.1.1")
* @param limit - Maximum requests allowed in the window.
* @param windowMs - Size of the window in milliseconds.
* @returns boolean - true if allowed, false if limit exceeded.
*/
export let checkRateLimit = async function checkRateLimit(
key: string,
limit: number,
windowMs: number,
): Promise<boolean> {
const now = Date.now();
const windowStart = now - windowMs;
// Use a multi block to ensure atomicity
const multi = valkey.multi();
// 1. Remove all elements outside the current window
multi.zremrangebyscore(key, 0, windowStart);
// 2. Add the current request timestamp
// Using the timestamp itself as the member and score.
// To avoid collisions if multiple requests happen in the exact same millisecond,
// we could append a random string, but for simple sliding window,
// just the timestamp with a random suffix is safer.
const member = `${now}-${crypto.randomUUID()}`;
multi.zadd(key, now, member);
// 3. Count elements in the current window
multi.zcount(key, "-inf", "+inf");
// 4. Update the key's TTL to automatically clean up
multi.pexpire(key, windowMs);
try {
const results = await multi.exec();
if (!results) {
return false; // Fail closed
}
// The third command in multi is zcount
const countResult = results[2];
if (countResult[0]) {
// If there was an error executing zcount
throw countResult[0];
}
const currentCount = countResult[1] as number;
return currentCount <= limit;
} catch (error) {
console.error("[RateLimit] Error executing multi block:", error);
return false; // Fail closed if Valkey throws an error
}
};
export const rateLimitWrapper = {
get checkRateLimit() {
return checkRateLimit;
},
set checkRateLimit(val: any) {
checkRateLimit = val;
},
};