@payez/next-mvp 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/api-handlers/auth/refresh.d.ts +17 -58
  3. package/dist/api-handlers/auth/refresh.js +71 -772
  4. package/dist/auth/utils/token-utils.d.ts +1 -1
  5. package/dist/auth/utils/token-utils.js +3 -2
  6. package/dist/lib/api-handler.js +4 -3
  7. package/dist/lib/ensure-fresh-access-token.d.ts +24 -1
  8. package/dist/lib/ensure-fresh-access-token.js +194 -28
  9. package/dist/lib/idp-fetch.js +3 -2
  10. package/dist/lib/refresh-in-doubt.d.ts +31 -0
  11. package/dist/lib/refresh-in-doubt.js +81 -0
  12. package/dist/lib/refresh-token-validator.js +2 -1
  13. package/dist/lib/refresh-window.d.ts +16 -0
  14. package/dist/lib/refresh-window.js +19 -0
  15. package/dist/lib/safe-trace-id.d.ts +11 -0
  16. package/dist/lib/safe-trace-id.js +23 -0
  17. package/dist/lib/session-store.js +14 -4
  18. package/dist/lib/token-lifecycle.js +3 -2
  19. package/dist/models/SessionModel.d.ts +7 -0
  20. package/dist/routes/auth/refresh.d.ts +1 -15
  21. package/dist/server/auth.d.ts +1 -1
  22. package/dist/stores/authStore.js +3 -2
  23. package/package.json +1 -1
  24. package/scripts/verify-release-provenance.mjs +1 -0
  25. package/src/api-handlers/auth/refresh.ts +72 -812
  26. package/src/auth/utils/token-utils.ts +3 -2
  27. package/src/lib/api-handler.ts +4 -3
  28. package/src/lib/ensure-fresh-access-token.ts +213 -29
  29. package/src/lib/idp-fetch.ts +3 -2
  30. package/src/lib/refresh-in-doubt.ts +79 -0
  31. package/src/lib/refresh-token-validator.ts +2 -1
  32. package/src/lib/refresh-window.ts +16 -0
  33. package/src/lib/safe-trace-id.ts +21 -0
  34. package/src/lib/session-store.ts +15 -3
  35. package/src/lib/token-lifecycle.ts +3 -2
  36. package/src/models/SessionModel.ts +7 -0
  37. package/src/server/auth.ts +1 -1
  38. package/src/stores/authStore.ts +3 -2
@@ -14,6 +14,7 @@ import { SessionData } from '../models/SessionModel';
14
14
  import { getSessionPrefix, getRefreshLockPrefix, getAppSlug } from './app-slug';
15
15
  import { extractKidFromToken, decodeIdpAccessToken } from '../auth/utils/token-utils';
16
16
  import { tokenLogId } from './token-log-id';
17
+ import { refreshTokenIdentity } from './refresh-in-doubt';
17
18
 
18
19
  // Re-export SessionData for consumers
19
20
  export type { SessionData } from '../models/SessionModel';
@@ -293,12 +294,23 @@ export async function deleteSessionEverywhere(sessionToken: string, appSlug?: st
293
294
  * today; this is the defence BAPert ruled to keep anyway.
294
295
  */
295
296
  function dropStaleDiscardMarker<T extends object>(session: T): T {
296
- const s = session as any;
297
+ let s = session as any;
297
298
  if (typeof s.idpRefreshToken === 'string' && s.idpRefreshToken.length > 0 && (s.refreshTokenClearedReason !== undefined || s.refreshTokenClearedAt !== undefined)) {
298
299
  const { refreshTokenClearedReason: _reason, refreshTokenClearedAt: _at, ...rest } = s;
299
- return rest as T;
300
+ s = rest;
300
301
  }
301
- return session;
302
+ // BAPert 76403: the refresh-IN-DOUBT marker (lib/refresh-in-doubt) is cleared by the same write that stores a NEW refresh token, i.e. one
303
+ // whose identity differs from the one left in doubt. A write that stores the SAME token (any caller spreading the whole row back) does
304
+ // NOT clear it: that token is still the in-doubt one.
305
+ if (
306
+ typeof s.refreshInDoubtTokenHash === 'string' &&
307
+ typeof s.idpRefreshToken === 'string' && s.idpRefreshToken.length > 0 &&
308
+ refreshTokenIdentity(s.idpRefreshToken) !== s.refreshInDoubtTokenHash
309
+ ) {
310
+ const { refreshInDoubtTokenHash: _hash, refreshInDoubtAt: _doubtAt, ...rest } = s;
311
+ s = rest;
312
+ }
313
+ return s as T;
302
314
  }
303
315
 
304
316
  export async function setSession(sessionToken: string, data: SessionData): Promise<void> {
@@ -26,9 +26,10 @@ import { getSession as getBetterAuthSession } from '../server/auth';
26
26
  import { getRedis } from './redis';
27
27
  import { getAppSlug } from './app-slug';
28
28
  import { tokenLogId } from './token-log-id';
29
+ import { REFRESH_WINDOW_MS } from './refresh-window';
29
30
 
30
- // 5 minute threshold for "needs refresh" - matches refresh handler pattern
31
- const REFRESH_THRESHOLD_MS = 5 * 60 * 1000;
31
+ // "needs refresh" threshold: THE one freshness window (lib/refresh-window), the same value the refresh route, ensureFreshAccessToken and idp-fetch use.
32
+ const REFRESH_THRESHOLD_MS = REFRESH_WINDOW_MS;
32
33
 
33
34
  // Concurrent refresh handling configuration
34
35
  const CONCURRENT_REFRESH_POLL_INTERVAL_MS = 200; // How often to poll session during concurrent refresh
@@ -59,6 +59,13 @@ export interface SessionData {
59
59
 
60
60
  /** When the IDP refresh token expires (Unix timestamp ms) */
61
61
  idpRefreshTokenExpires?: number;
62
+ /**
63
+ * BAPert 76403 (lib/refresh-in-doubt): set when a refresh fetch TIMED OUT. A SHA-256 identity of the refresh token that was sent (never the
64
+ * token), and when. A later attempt whose token-to-send matches refuses without calling the IdP. Cleared by the write that stores a different
65
+ * refresh token.
66
+ */
67
+ refreshInDoubtTokenHash?: string;
68
+ refreshInDoubtAt?: number;
62
69
 
63
70
  /** Decoded IDP access token claims (for quick access without re-decoding) */
64
71
  decodedAccessToken?: any;
@@ -407,7 +407,7 @@ export interface FreshIdpTokenConfig {
407
407
  idpBaseUrl: string;
408
408
  clientId: string;
409
409
  refreshEndpoint?: string;
410
- /** Refresh if the access token is within this many ms of expiry. Default 60_000. */
410
+ /** Refresh if the access token is within this many ms of expiry. Default REFRESH_WINDOW_MS (5 min, lib/refresh-window). */
411
411
  safetyWindowMs?: number;
412
412
  }
413
413
 
@@ -27,6 +27,7 @@ import {
27
27
  getSecureCsrfCookieName
28
28
  } from '../lib/app-slug';
29
29
  import { tokenLogId } from '../lib/token-log-id';
30
+ import { REFRESH_WINDOW_MS } from '../lib/refresh-window';
30
31
 
31
32
  // ===============================
32
33
  // INTERFACES & TYPES
@@ -752,8 +753,8 @@ export const useAuthStore = create<AuthStore>()(
752
753
  const now = Date.now();
753
754
  const timeUntilExpiry = tokenExpiry - now;
754
755
 
755
- // Token should be fresh (not expiring in next 5 minutes)
756
- if (timeUntilExpiry < (5 * 60 * 1000)) {
756
+ // Token should be fresh (not expiring within the one refresh window)
757
+ if (timeUntilExpiry < REFRESH_WINDOW_MS) {
757
758
  throw new Error(`Fresh token still expires soon: ${timeUntilExpiry}ms`);
758
759
  }
759
760