Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

rate-limit.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

rate-limit.tsBlame85 lines · 1 contributor
45e31d0Claude1/**
2 * In-memory rate limiter middleware.
3 *
4 * Provides per-IP rate limiting with sliding window counters.
5 * For production, replace with Redis-based implementation.
6 */
7
8import { createMiddleware } from "hono/factory";
9
10interface RateLimitEntry {
11 count: number;
12 resetAt: number;
13}
14
15const store = new Map<string, RateLimitEntry>();
16
17// Cleanup stale entries every 60 seconds
18setInterval(() => {
19 const now = Date.now();
20 for (const [key, entry] of store) {
21 if (entry.resetAt < now) {
22 store.delete(key);
23 }
24 }
25}, 60_000);
26
27/**
28 * Create a rate limiter middleware.
29 * @param maxRequests Maximum requests per window
30 * @param windowMs Window duration in milliseconds
31 * @param keyPrefix Prefix for the rate limit key (allows different limits per route group)
32 */
33export function rateLimit(
34 maxRequests: number,
35 windowMs: number,
36 keyPrefix = "global"
37) {
38 return createMiddleware(async (c, next) => {
39 const ip =
40 c.req.header("x-forwarded-for")?.split(",")[0]?.trim() ||
41 c.req.header("x-real-ip") ||
42 "unknown";
43
44 const key = `${keyPrefix}:${ip}`;
45 const now = Date.now();
46 let entry = store.get(key);
47
48 if (!entry || entry.resetAt < now) {
49 entry = { count: 0, resetAt: now + windowMs };
50 store.set(key, entry);
51 }
52
53 entry.count++;
54
55 // Set rate limit headers
56 c.header("X-RateLimit-Limit", String(maxRequests));
57 c.header("X-RateLimit-Remaining", String(Math.max(0, maxRequests - entry.count)));
58 c.header("X-RateLimit-Reset", String(Math.ceil(entry.resetAt / 1000)));
59
60 if (entry.count > maxRequests) {
61 c.header("Retry-After", String(Math.ceil((entry.resetAt - now) / 1000)));
62 return c.json(
63 {
64 error: "Rate limit exceeded",
65 retryAfter: Math.ceil((entry.resetAt - now) / 1000),
66 },
67 429
68 );
69 }
70
71 return next();
72 });
73}
74
75/**
76 * Pre-configured rate limiters for different route groups.
77 */
3e8f8e8Claude78export function clearRateLimitStore() {
79 store.clear();
80}
81
45e31d0Claude82export const apiRateLimit = rateLimit(100, 60_000, "api"); // 100 req/min
83export const authRateLimit = rateLimit(10, 60_000, "auth"); // 10 req/min (login/register)
84export const gitRateLimit = rateLimit(60, 60_000, "git"); // 60 req/min
85export const searchRateLimit = rateLimit(30, 60_000, "search"); // 30 req/min