Rate Limiting
Fixed-window rate limiting backed by Cloudflare KV, protecting login, MFA, checkout, and payment webhook endpoints.
A fixed-window rate limiter backed by Cloudflare KV. The algorithm lives once, in packages/rate-limit, over a small counter store. Each Worker ties it to its own KV through a thin wrapper:
apps/app/server/utils/rate-limit.ts— binds the algorithm tohubKV()inside the App Worker's Nitro runtime.apps/functions/src/rate-limit.ts— binds it to the rawKVNamespace(AuthKV), since the auth Worker has no Nitro/NuxtHub to callhubKV()from.
The two Workers keep separate counters: same algorithm, different namespaces.
export async function rateLimit(store, key, { limit, windowSeconds }, now = Date.now) {
// Cloudflare KV rejects a TTL under 60s, so the window can't be shorter.
const window = Math.max(windowSeconds, MIN_WINDOW_SECONDS);
const storeKey = `ratelimit:${key}:${Math.floor(now() / 1000 / window)}`;
const count = ((await store.get(storeKey)) ?? 0) + 1;
await store.set(storeKey, count, window);
return { allowed: count <= limit, count, limit };
}
The store is a two-method port (get, set with a TTL). kvNamespaceStore and hubKvStore in the same package adapt the two KV shapes to it. Read, increment and write are not atomic, so concurrent calls can undercount: the limit is a brake, not an exact quota.
The App Worker wrapper exports rateLimit(key, rule) and assertNotRateLimited(event, routeKey, options). The second one is for HTTP routes: it keys by <routeKey>:<ip> and throws 429 Too Many Requests directly when the limit is exceeded.
What's currently protected
| Where | Keyed by | Purpose |
|---|---|---|
apps/functions/src/auth.ts (enforceCredentialRateLimit) | path + IP + email | Password/code login on the auth Worker |
layers/account/server/api/mfa/verify.post.ts | user id | Account MFA enrollment attempts |
layers/account/server/api/mfa/challenge.post.ts | user id | Account MFA challenge attempts |
layers/account/server/api/mfa/recovery-codes.post.ts | user id | Recovery code regeneration |
layers/billing/server/trpc/checkout.ts | IP | Starting a purchase without an account |
layers/billing/server/api/webhooks/stripe.post.ts | IP | Stripe webhook, on top of signature validation |
This isn't a general-purpose API throttle — it's applied specifically where credential/signature validation alone doesn't stop a high-volume replay: authentication attempts, MFA codes and unauthenticated webhook or checkout endpoints. There's no rate limiting on ordinary tRPC procedures today.
Ready to build and launch your SaaS?
Get 100% full source code ownership, no proprietary wrappers, with architecture ready for millions of requests on Cloudflare.