-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathrateLimit.ts
More file actions
115 lines (104 loc) · 2.99 KB
/
Copy pathrateLimit.ts
File metadata and controls
115 lines (104 loc) · 2.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import type { Context, MiddlewareHandler, Next } from "hono";
import { HTTPException } from "hono/http-exception";
import { db } from "../auth";
import { rateLimit, user } from "../db/schema";
import { and, eq, gt } from "drizzle-orm";
import type { User, Session } from "../db/schema";
export interface RateLimitConfig {
// Requests per window
limit: number;
// Window size in seconds
window: number;
}
export const ratelimiter = async (c: Context<{
Bindings: Env,
Variables: {
user: User,
session: Session
}
}>, next: Next) => {
const rateLimiter = createRateLimiter(
async (user) => await getTierLimit(user)
);
return rateLimiter(c, next);
}
export const getTierLimit = async (user: User) => {
if (!user?.subscriptionId) {
return { limit: 100, window: 60 * 60 }; // Free tier
}
return { limit: 1000, window: 60 * 60 }; // Paid tier
};
// Default tier limits: You can disable this if you want.
const DEFAULT_LIMIT: RateLimitConfig = { limit: 10, window: 60 * 60 }
export const createRateLimiter = <T extends { id: string }>(
getTierLimit: (user: User) => Promise<RateLimitConfig | undefined>
): MiddlewareHandler<{
Bindings: Env;
Variables: {
user: User,
session: Session
};
}> => {
return async (c, next) => {
const user = c.get("user");
if (!user || c.req.path === "/") {
await next();
return;
}
const endpoint = new URL(c.req.url).pathname;
const now = new Date();
// Get user's tier limit
const tierLimit = (await getTierLimit(user)) ?? DEFAULT_LIMIT;
// Check existing rate limit
const existing = await db(c.env)
.select()
.from(rateLimit)
.where(
and(
eq(rateLimit.userId, user.id),
eq(rateLimit.endpoint, endpoint),
gt(rateLimit.resetAt, now)
)
)
.get();
if (!existing) {
// Create new rate limit entry
const resetAt = new Date(now.getTime() + tierLimit.window * 1000);
await db(c.env)
.insert(rateLimit)
.values({
id: crypto.randomUUID(),
userId: user.id,
endpoint,
count: 1,
resetAt,
createdAt: now,
updatedAt: now,
});
} else if (existing.count >= tierLimit.limit) {
// Rate limit exceeded
throw new HTTPException(429, {
message: `Rate limit exceeded ${JSON.stringify(tierLimit)}, existing: ${JSON.stringify(existing)}`,
});
} else {
// Update count
await db(c.env)
.update(rateLimit)
.set({
count: existing.count + 1,
updatedAt: now,
})
.where(eq(rateLimit.id, existing.id));
}
// Set rate limit headers
if (existing) {
c.header("X-RateLimit-Limit", tierLimit.limit.toString());
c.header("X-RateLimit-Remaining", (tierLimit.limit - existing.count - 1).toString());
c.header(
"X-RateLimit-Reset",
Math.floor(existing.resetAt.getTime() / 1000).toString()
);
}
await next();
};
};