@payez/next-mvp 4.10.0 → 4.12.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 (123) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/api-handlers/account/recovery/forward.js +2 -1
  3. package/dist/api-handlers/account/recovery/initiate.js +2 -1
  4. package/dist/api-handlers/account/reset-password.js +2 -1
  5. package/dist/api-handlers/account/send-code.js +2 -1
  6. package/dist/api-handlers/account/update-phone.js +2 -1
  7. package/dist/api-handlers/account/validate-password.js +2 -1
  8. package/dist/api-handlers/account/verify-email.js +2 -1
  9. package/dist/api-handlers/account/verify-sms.js +2 -1
  10. package/dist/api-handlers/anon/preferences.js +3 -2
  11. package/dist/api-handlers/auth/credential-login-email-code.js +3 -2
  12. package/dist/api-handlers/auth/credential-login.js +6 -5
  13. package/dist/api-handlers/auth/refresh.d.ts +17 -58
  14. package/dist/api-handlers/auth/refresh.js +73 -773
  15. package/dist/api-handlers/auth/signout.js +43 -6
  16. package/dist/api-handlers/mfa/backup-code-verify.js +2 -1
  17. package/dist/api-handlers/mfa/totp-verify.js +2 -1
  18. package/dist/api-handlers/session/refresh-viability.js +3 -2
  19. package/dist/api-handlers/session/viability.js +2 -1
  20. package/dist/auth/better-auth.js +7 -6
  21. package/dist/auth/idp-oauth.js +4 -2
  22. package/dist/auth/utils/idp-client.js +5 -4
  23. package/dist/auth/utils/token-utils.d.ts +1 -1
  24. package/dist/auth/utils/token-utils.js +7 -5
  25. package/dist/config/logger.js +2 -1
  26. package/dist/config/logging-config.js +4 -3
  27. package/dist/config/vibe-log-transport.js +2 -1
  28. package/dist/lib/api-handler.js +4 -3
  29. package/dist/lib/ensure-fresh-access-token.d.ts +24 -1
  30. package/dist/lib/ensure-fresh-access-token.js +195 -28
  31. package/dist/lib/geolocation.js +5 -22
  32. package/dist/lib/idp-client-config.js +4 -3
  33. package/dist/lib/idp-fetch.d.ts +11 -0
  34. package/dist/lib/idp-fetch.js +6 -3
  35. package/dist/lib/idp-logout.d.ts +10 -0
  36. package/dist/lib/idp-logout.js +73 -1
  37. package/dist/lib/jwt-decode-client.js +2 -1
  38. package/dist/lib/jwt-decode.js +2 -1
  39. package/dist/lib/log-safe.d.ts +19 -0
  40. package/dist/lib/log-safe.js +32 -0
  41. package/dist/lib/redis.js +2 -1
  42. package/dist/lib/refresh-in-doubt.d.ts +31 -0
  43. package/dist/lib/refresh-in-doubt.js +81 -0
  44. package/dist/lib/refresh-token-validator.js +2 -1
  45. package/dist/lib/refresh-window.d.ts +16 -0
  46. package/dist/lib/refresh-window.js +19 -0
  47. package/dist/lib/safe-error-response.d.ts +5 -1
  48. package/dist/lib/safe-error-response.js +7 -2
  49. package/dist/lib/safe-trace-id.d.ts +11 -0
  50. package/dist/lib/safe-trace-id.js +23 -0
  51. package/dist/lib/session-store.js +16 -5
  52. package/dist/lib/site-logger.d.ts +2 -8
  53. package/dist/lib/site-logger.js +11 -5
  54. package/dist/lib/standardized-client-api.js +3 -2
  55. package/dist/lib/token-lifecycle.js +6 -4
  56. package/dist/middleware/create-middleware.js +2 -1
  57. package/dist/middleware/rbac-check.d.ts +0 -13
  58. package/dist/middleware/rbac-check.js +6 -5
  59. package/dist/models/SessionModel.d.ts +7 -0
  60. package/dist/routes/auth/logout.js +6 -2
  61. package/dist/routes/auth/refresh.d.ts +1 -15
  62. package/dist/routes/auth/settings.js +2 -1
  63. package/dist/server/auth-guard.js +4 -3
  64. package/dist/server/auth.d.ts +1 -1
  65. package/dist/server/auth.js +5 -9
  66. package/dist/server/decode-session.js +3 -2
  67. package/dist/server/with-auth.js +2 -1
  68. package/dist/stores/authStore.js +3 -2
  69. package/package.json +1 -1
  70. package/scripts/verify-release-provenance.mjs +1 -0
  71. package/src/api-handlers/account/recovery/forward.ts +2 -1
  72. package/src/api-handlers/account/recovery/initiate.ts +2 -1
  73. package/src/api-handlers/account/reset-password.ts +2 -1
  74. package/src/api-handlers/account/send-code.ts +2 -1
  75. package/src/api-handlers/account/update-phone.ts +2 -1
  76. package/src/api-handlers/account/validate-password.ts +2 -1
  77. package/src/api-handlers/account/verify-email.ts +2 -1
  78. package/src/api-handlers/account/verify-sms.ts +2 -1
  79. package/src/api-handlers/anon/preferences.ts +3 -2
  80. package/src/api-handlers/auth/credential-login-email-code.ts +3 -2
  81. package/src/api-handlers/auth/credential-login.ts +6 -5
  82. package/src/api-handlers/auth/refresh.ts +74 -813
  83. package/src/api-handlers/auth/signout.ts +54 -7
  84. package/src/api-handlers/mfa/backup-code-verify.ts +2 -1
  85. package/src/api-handlers/mfa/totp-verify.ts +2 -1
  86. package/src/api-handlers/session/refresh-viability.ts +3 -2
  87. package/src/api-handlers/session/viability.ts +2 -1
  88. package/src/auth/better-auth.ts +7 -6
  89. package/src/auth/idp-oauth.ts +7 -2
  90. package/src/auth/utils/idp-client.ts +5 -4
  91. package/src/auth/utils/token-utils.ts +7 -5
  92. package/src/config/logger.ts +2 -1
  93. package/src/config/logging-config.ts +4 -3
  94. package/src/config/vibe-log-transport.ts +2 -1
  95. package/src/lib/api-handler.ts +4 -3
  96. package/src/lib/ensure-fresh-access-token.ts +214 -29
  97. package/src/lib/geolocation.ts +5 -4
  98. package/src/lib/idp-client-config.ts +4 -3
  99. package/src/lib/idp-fetch.ts +14 -3
  100. package/src/lib/idp-logout.ts +36 -1
  101. package/src/lib/jwt-decode-client.ts +3 -1
  102. package/src/lib/jwt-decode.ts +2 -1
  103. package/src/lib/log-safe.ts +30 -0
  104. package/src/lib/redis.ts +2 -1
  105. package/src/lib/refresh-in-doubt.ts +79 -0
  106. package/src/lib/refresh-token-validator.ts +2 -1
  107. package/src/lib/refresh-window.ts +16 -0
  108. package/src/lib/safe-error-response.ts +7 -2
  109. package/src/lib/safe-trace-id.ts +21 -0
  110. package/src/lib/session-store.ts +17 -4
  111. package/src/lib/site-logger.ts +13 -7
  112. package/src/lib/standardized-client-api.ts +3 -2
  113. package/src/lib/token-lifecycle.ts +6 -4
  114. package/src/middleware/create-middleware.ts +2 -1
  115. package/src/middleware/rbac-check.ts +2 -1
  116. package/src/models/SessionModel.ts +7 -0
  117. package/src/routes/auth/logout.ts +9 -2
  118. package/src/routes/auth/settings.ts +2 -1
  119. package/src/server/auth-guard.ts +4 -3
  120. package/src/server/auth.ts +6 -11
  121. package/src/server/decode-session.ts +3 -2
  122. package/src/server/with-auth.ts +2 -1
  123. package/src/stores/authStore.ts +3 -2
@@ -15,6 +15,9 @@ exports.createSignoutHandler = createSignoutHandler;
15
15
  const server_1 = require("next/server");
16
16
  const headers_1 = require("next/headers");
17
17
  const session_store_1 = require("../../lib/session-store");
18
+ const idp_logout_1 = require("../../lib/idp-logout");
19
+ const log_safe_1 = require("../../lib/log-safe");
20
+ const logout_1 = require("../../routes/auth/logout");
18
21
  const auth_1 = require("../../server/auth");
19
22
  const app_slug_1 = require("../../lib/app-slug");
20
23
  const token_log_id_1 = require("../../lib/token-log-id");
@@ -130,9 +133,39 @@ function createSignoutHandler() {
130
133
  console.warn('[SIGNOUT] Manual JWT decode failed to extract session token');
131
134
  }
132
135
  }
133
- // Delete the session from EVERY store (181922): canonical + BA secondary.
134
- // Canonical-only deletion let the ba: record resurrect the session.
136
+ // GOV-023 (BAPert 76687): ending the session ends the IdP GRANT first, the same IdP-first path as routes/auth/logout. Before this, this
137
+ // handler only deleted the local record, so a consumer who mounted it left the IdP refresh token live until it expired.
138
+ // A failed or unresolved revoke is the same typed failure as a failed local delete (SESSION_NOT_ENDED + cause); the local delete still
139
+ // runs and the cookies are cleared either way.
140
+ let idpRevokeFailed = false;
141
+ let localDeleteFailed = false;
135
142
  if (redisSessionToken) {
143
+ // Does the stored session hold an IdP refresh token? (revokeSessionAtIdp reads the canonical row, then the Better Auth record.)
144
+ let heldRefreshToken = false;
145
+ try {
146
+ const stored = (await (0, session_store_1.getSession)(redisSessionToken)) ?? (await (0, session_store_1.getBetterAuthSession)(redisSessionToken));
147
+ heldRefreshToken = !!stored?.idpRefreshToken;
148
+ }
149
+ catch {
150
+ // unknown: treated as "none held", so an unreadable store does not turn a clean sign-out into a failure on its own
151
+ }
152
+ try {
153
+ const outcome = await (0, idp_logout_1.revokeSessionAtIdp)(req);
154
+ // 'nothing-to-revoke' while a refresh token IS held means the helper could not resolve this session from the request (this
155
+ // handler also finds the session by decoding the cookie when Better Auth cannot): the grant is NOT ended, so say so.
156
+ if (outcome === 'nothing-to-revoke' && heldRefreshToken) {
157
+ idpRevokeFailed = true;
158
+ console.error('[SIGNOUT] IdP revoke could not resolve the session - the IdP refresh token is STILL LIVE');
159
+ }
160
+ }
161
+ catch (revokeError) {
162
+ idpRevokeFailed = true;
163
+ console.error(revokeError?.grantLive === true
164
+ ? '[SIGNOUT] IdP revoke failed - the IdP refresh token is STILL LIVE:'
165
+ : '[SIGNOUT] IdP revoke could not be confirmed (the grant may already be dead):', (0, log_safe_1.errForLog)(revokeError));
166
+ }
167
+ // Delete the session from EVERY store (181922): canonical + BA secondary.
168
+ // Canonical-only deletion let the ba: record resurrect the session.
136
169
  try {
137
170
  await (0, session_store_1.deleteSessionEverywhere)(redisSessionToken);
138
171
  console.info('[SIGNOUT] Redis session cleanup successful', {
@@ -140,21 +173,25 @@ function createSignoutHandler() {
140
173
  });
141
174
  }
142
175
  catch (sessionError) {
143
- // Log error but don't fail the signout process
144
- console.error('[SIGNOUT] Redis session cleanup failed', { error: sessionError });
176
+ localDeleteFailed = true;
177
+ console.error('[SIGNOUT] Redis session cleanup failed - the server session is STILL LIVE:', (0, log_safe_1.errForLog)(sessionError));
145
178
  }
146
179
  }
147
180
  else {
148
181
  console.warn('[SIGNOUT] No Redis session token (UUID) extracted from cookie; skipping Redis deletion');
149
182
  }
150
- // Build response
183
+ // Build response. A sign-out that did not end the session must not say it did (same typed answer as routes/auth/logout).
151
184
  const responseData = {
152
185
  success: true,
153
186
  message: sessionToken ? 'Session deleted successfully' : 'No active session found',
154
187
  sessionDeleted: !!sessionToken,
155
188
  chunkCookiesDeleted: chunkCookies.length
156
189
  };
157
- const response = server_1.NextResponse.json(responseData);
190
+ const response = (localDeleteFailed || idpRevokeFailed)
191
+ ? server_1.NextResponse.json(localDeleteFailed
192
+ ? { success: false, code: logout_1.SESSION_NOT_ENDED_CODE, cause: 'local_delete_failed', message: logout_1.SESSION_NOT_ENDED_LOCAL_MESSAGE }
193
+ : { success: false, code: logout_1.SESSION_NOT_ENDED_CODE, cause: 'idp_unconfirmed', message: logout_1.SESSION_NOT_ENDED_IDP_MESSAGE }, { status: 500 })
194
+ : server_1.NextResponse.json(responseData);
158
195
  // Always clear cookies, regardless of success/failure (using app-slug prefixed names)
159
196
  try {
160
197
  response.cookies.delete(sessionCookieName);
@@ -62,6 +62,7 @@ const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
62
62
  const session_store_1 = require("../../lib/session-store");
63
63
  const jwt_decode_1 = require("../../lib/jwt-decode");
64
64
  const two_factor_verify_failure_1 = require("../../lib/two-factor-verify-failure");
65
+ const log_safe_1 = require("../../lib/log-safe");
65
66
  async function POST(req) {
66
67
  try {
67
68
  const body = await req.json();
@@ -141,7 +142,7 @@ async function POST(req) {
141
142
  }, { status: 200 });
142
143
  }
143
144
  catch (error) {
144
- console.error('[VERIFY_BACKUP_CODE] Error:', error);
145
+ console.error('[VERIFY_BACKUP_CODE] Error:', (0, log_safe_1.errForLog)(error));
145
146
  return server_1.NextResponse.json({
146
147
  success: false,
147
148
  error: 'Failed to verify backup code',
@@ -37,6 +37,7 @@ const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
37
37
  const session_store_1 = require("../../lib/session-store");
38
38
  const jwt_decode_1 = require("../../lib/jwt-decode");
39
39
  const two_factor_verify_failure_1 = require("../../lib/two-factor-verify-failure");
40
+ const log_safe_1 = require("../../lib/log-safe");
40
41
  async function POST(req) {
41
42
  try {
42
43
  const body = await req.json();
@@ -141,7 +142,7 @@ async function POST(req) {
141
142
  }, { status: 200 });
142
143
  }
143
144
  catch (error) {
144
- console.error('[VERIFY_TOTP] Error:', error);
145
+ console.error('[VERIFY_TOTP] Error:', (0, log_safe_1.errForLog)(error));
145
146
  return server_1.NextResponse.json({
146
147
  success: false,
147
148
  error: 'Failed to verify code',
@@ -7,6 +7,7 @@ const refresh_token_validator_1 = require("../../lib/refresh-token-validator");
7
7
  const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
8
8
  const logger_1 = require("../../config/logger");
9
9
  const token_log_id_1 = require("../../lib/token-log-id");
10
+ const log_safe_1 = require("../../lib/log-safe");
10
11
  const auth_1 = require("../../server/auth");
11
12
  async function GET(req) {
12
13
  try {
@@ -53,8 +54,8 @@ async function GET(req) {
53
54
  }
54
55
  catch (error) {
55
56
  const correlationId = globalThis.crypto.randomUUID();
56
- logger_1.logger.error('[REFRESH-VIABILITY] Error checking refresh viability', { correlationId, error: error instanceof Error ? error.message : String(error) });
57
- // PAY-1438: the exception text stays in the log above; the caller gets the id.
57
+ logger_1.logger.error('[REFRESH-VIABILITY] Error checking refresh viability', { correlationId, error: (0, log_safe_1.errForLog)(error) });
58
+ // PAY-1438: the caller gets the id; the log line names the error only (errForLog), never its message (PII-in-logs group 1, BAPert 76553).
58
59
  return server_1.NextResponse.json({ canRefresh: false, reason: 'check_error', error: 'Failed to check refresh viability', code: 'INTERNAL_ERROR', correlationId }, { status: 500 });
59
60
  }
60
61
  }
@@ -14,6 +14,7 @@ const startup_init_1 = require("../../lib/startup-init");
14
14
  const idp_client_config_1 = require("../../lib/idp-client-config");
15
15
  const token_log_id_1 = require("../../lib/token-log-id");
16
16
  const sessions_1 = require("../../vibe/sessions");
17
+ const log_safe_1 = require("../../lib/log-safe");
17
18
  async function GET(req) {
18
19
  try {
19
20
  // Ensure initialization is complete (auth signing secret resolved from IDP)
@@ -213,7 +214,7 @@ async function GET(req) {
213
214
  return server_1.NextResponse.json({ authenticated: false });
214
215
  }
215
216
  catch (error) {
216
- console.error('[API Viability] Error checking session viability:', error);
217
+ console.error('[API Viability] Error checking session viability:', (0, log_safe_1.errForLog)(error));
217
218
  return server_1.NextResponse.json({ authenticated: false }, { status: 500 });
218
219
  }
219
220
  }
@@ -76,6 +76,7 @@ const redis_1 = require("../lib/redis");
76
76
  const single_flight_1 = require("../lib/single-flight");
77
77
  const session_store_1 = require("../lib/session-store");
78
78
  const token_log_id_1 = require("../lib/token-log-id");
79
+ const log_safe_1 = require("../lib/log-safe");
79
80
  /**
80
81
  * 185121: short sha256 fingerprint for secrets/tokens in log lines — enough
81
82
  * to tell "same" from "divergent", never enough to reconstruct. 12 hex chars
@@ -740,7 +741,7 @@ function createBetterAuthInstance(idpConfig, opts = {}) {
740
741
  await deleteSession(key);
741
742
  }
742
743
  catch (err) {
743
- console.warn('[BETTER_AUTH] secondary delete: canonical store cleanup failed:', err);
744
+ console.warn('[BETTER_AUTH] secondary delete: canonical store cleanup failed:', (0, log_safe_1.errForLog)(err));
744
745
  }
745
746
  },
746
747
  },
@@ -1188,7 +1189,7 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
1188
1189
  return stored;
1189
1190
  }
1190
1191
  catch (err) {
1191
- console.error('[BETTER_AUTH] IDP token exchange failed:', err instanceof Error ? err.message : String(err));
1192
+ console.error('[BETTER_AUTH] IDP token exchange failed:', (0, log_safe_1.errForLog)(err));
1192
1193
  return false;
1193
1194
  }
1194
1195
  }
@@ -1270,7 +1271,7 @@ async function storeIdpTokensInSession(sessionToken, idpTokenData, authProvider)
1270
1271
  return true;
1271
1272
  }
1272
1273
  catch (err) {
1273
- console.error('[BETTER_AUTH] storeIdpTokensInSession failed:', err instanceof Error ? err.message : String(err));
1274
+ console.error('[BETTER_AUTH] storeIdpTokensInSession failed:', (0, log_safe_1.errForLog)(err));
1274
1275
  return false;
1275
1276
  }
1276
1277
  }
@@ -1359,14 +1360,14 @@ async function abortOAuthSignIn(callbackResponse, sessionToken, cookieHeader, lo
1359
1360
  headers.append('set-cookie', sc);
1360
1361
  }
1361
1362
  catch (err) {
1362
- console.warn('[BETTER_AUTH] abort: Better Auth signOut failed, deleting the record directly:', err instanceof Error ? err.message : String(err));
1363
+ console.warn('[BETTER_AUTH] abort: Better Auth signOut failed, deleting the record directly:', (0, log_safe_1.errForLog)(err));
1363
1364
  }
1364
1365
  try {
1365
1366
  const config = await (0, idp_client_config_1.getIDPClientConfig)();
1366
1367
  await (0, redis_1.getRedis)().del(`ba:${config.clientSlug || (0, app_slug_1.getAppSlug)()}:${sessionToken}`);
1367
1368
  }
1368
1369
  catch (err) {
1369
- console.error('[BETTER_AUTH] abort: could not delete the Better Auth session record', (0, token_log_id_1.tokenLogId)(sessionToken), err instanceof Error ? err.message : String(err));
1370
+ console.error('[BETTER_AUTH] abort: could not delete the Better Auth session record', (0, token_log_id_1.tokenLogId)(sessionToken), (0, log_safe_1.errForLog)(err));
1370
1371
  }
1371
1372
  }
1372
1373
  for (const sc of expireSetCookies(callbackResponse.headers.getSetCookie?.() ?? []))
@@ -1484,7 +1485,7 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
1484
1485
  }
1485
1486
  }
1486
1487
  catch (err) {
1487
- console.error('[BETTER_AUTH] IDP token exchange failed:', err.message);
1488
+ console.error('[BETTER_AUTH] IDP token exchange failed:', (0, log_safe_1.errForLog)(err));
1488
1489
  }
1489
1490
  if (exchange !== true) {
1490
1491
  // A specific IdP error code goes on the URL; otherwise the generic OAUTH_SIGN_IN_FAILED.
@@ -27,6 +27,7 @@ exports.createIdpOAuthHooks = createIdpOAuthHooks;
27
27
  * case-insensitively, at the authorize step). Both come from ONE value: the `redirectURI` of the provider config.
28
28
  */
29
29
  const jose_1 = require("jose");
30
+ const log_safe_1 = require("../lib/log-safe");
30
31
  const DISCOVERY_TTL_MS = 10 * 60 * 1000;
31
32
  const HTTP_TIMEOUT_MS = 10_000;
32
33
  /** The URI sent at authorize AND token time. Explicit env wins; else the registered {app}/api/auth/callback form. */
@@ -252,7 +253,8 @@ function createIdpOAuthHooks(opts) {
252
253
  console.warn('[BETTER_AUTH] payez-idp token response address is masked or unusable; ignored');
253
254
  const email = resolveEmail(payload, responseEmailUsable ? responseEmailRaw : undefined);
254
255
  if (!email) {
255
- throw new Error(responseMasked ? 'response address is masked' : 'no usable email (needs data.user.email or an email claim, address-shaped, equal when both are present)');
256
+ // A closed `code` rides on the error so the refusal log tells an operator WHY (errForLog prints a closed UPPER_SNAKE code) with no value (BAPert 76998 E11).
257
+ throw Object.assign(new Error(responseMasked ? 'response address is masked' : 'no usable email (needs data.user.email or an email claim, address-shaped, equal when both are present)'), { code: responseMasked ? 'RESPONSE_ADDRESS_MASKED' : 'NO_USABLE_EMAIL' });
256
258
  }
257
259
  const name = (typeof payload.name === 'string' && payload.name.trim()) ||
258
260
  (typeof payload.full_name === 'string' && payload.full_name.trim()) ||
@@ -272,7 +274,7 @@ function createIdpOAuthHooks(opts) {
272
274
  }
273
275
  catch (err) {
274
276
  // Reason only, never the token.
275
- console.warn('[BETTER_AUTH] payez-idp access token refused:', err instanceof Error ? err.message : String(err));
277
+ console.warn('[BETTER_AUTH] payez-idp access token refused:', (0, log_safe_1.errForLog)(err));
276
278
  return null;
277
279
  }
278
280
  }
@@ -19,6 +19,7 @@ exports.idpLogin = idpLogin;
19
19
  exports.idpRefreshToken = idpRefreshToken;
20
20
  exports.idpVerify2FA = idpVerify2FA;
21
21
  exports.idpSend2FACode = idpSend2FACode;
22
+ const log_safe_1 = require("../../lib/log-safe");
22
23
  // ============================================================================
23
24
  // CONFIGURATION
24
25
  // ============================================================================
@@ -95,7 +96,7 @@ async function idpLogin(credentials, clientHeaders) {
95
96
  };
96
97
  }
97
98
  catch (error) {
98
- console.error('[IDP_CLIENT] Login request failed:', error);
99
+ console.error('[IDP_CLIENT] Login request failed:', (0, log_safe_1.errForLog)(error));
99
100
  return {
100
101
  success: false,
101
102
  error: {
@@ -211,7 +212,7 @@ async function idpRefreshToken(refreshToken, mfaContext) {
211
212
  };
212
213
  }
213
214
  catch (error) {
214
- console.error('[IDP_CLIENT] Token refresh request failed:', error);
215
+ console.error('[IDP_CLIENT] Token refresh request failed:', (0, log_safe_1.errForLog)(error));
215
216
  return {
216
217
  success: false,
217
218
  error: {
@@ -261,7 +262,7 @@ async function idpVerify2FA(accessToken, code, method) {
261
262
  return { success: true };
262
263
  }
263
264
  catch (error) {
264
- console.error('[IDP_CLIENT] 2FA verification failed:', error);
265
+ console.error('[IDP_CLIENT] 2FA verification failed:', (0, log_safe_1.errForLog)(error));
265
266
  return {
266
267
  success: false,
267
268
  error: {
@@ -303,7 +304,7 @@ async function idpSend2FACode(accessToken, method) {
303
304
  return { success: true };
304
305
  }
305
306
  catch (error) {
306
- console.error('[IDP_CLIENT] Send 2FA code failed:', error);
307
+ console.error('[IDP_CLIENT] Send 2FA code failed:', (0, log_safe_1.errForLog)(error));
307
308
  return {
308
309
  success: false,
309
310
  error: {
@@ -46,7 +46,7 @@ export declare function extractAmrFromToken(decoded: DecodedIdpAccessToken): str
46
46
  * Check if a token expiry timestamp indicates the token needs refresh.
47
47
  *
48
48
  * @param expiresAt - Token expiry timestamp (Unix milliseconds)
49
- * @param bufferMs - How early to trigger refresh (default 5 minutes)
49
+ * @param bufferMs - How early to trigger refresh (default: the one refresh window, `REFRESH_WINDOW_MS`, 5 minutes)
50
50
  * @returns true if token is expired or will expire within buffer period
51
51
  */
52
52
  export declare function tokenNeedsRefresh(expiresAt: number | undefined, bufferMs?: number): boolean;
@@ -20,6 +20,8 @@ exports.msUntilExpiry = msUntilExpiry;
20
20
  exports.expClaimToMs = expClaimToMs;
21
21
  exports.validateTokenExpiry = validateTokenExpiry;
22
22
  const jwt_decode_1 = require("../../lib/jwt-decode");
23
+ const refresh_window_1 = require("../../lib/refresh-window");
24
+ const log_safe_1 = require("../../lib/log-safe");
23
25
  // Re-export header utilities for consumers
24
26
  var jwt_decode_2 = require("../../lib/jwt-decode");
25
27
  Object.defineProperty(exports, "decodeJwtHeader", { enumerable: true, get: function () { return jwt_decode_2.decodeJwtHeader; } });
@@ -38,7 +40,7 @@ function decodeIdpAccessToken(token) {
38
40
  return (0, jwt_decode_1.jwtDecode)(token);
39
41
  }
40
42
  catch (error) {
41
- console.error('[TOKEN_UTILS] Failed to decode access token:', error);
43
+ console.error('[TOKEN_UTILS] Failed to decode access token:', (0, log_safe_1.errForLog)(error));
42
44
  return null;
43
45
  }
44
46
  }
@@ -63,7 +65,7 @@ function decodeIdpAccessTokenFull(token) {
63
65
  };
64
66
  }
65
67
  catch (error) {
66
- console.error('[TOKEN_UTILS] Failed to decode access token (full):', error);
68
+ console.error('[TOKEN_UTILS] Failed to decode access token (full):', (0, log_safe_1.errForLog)(error));
67
69
  return null;
68
70
  }
69
71
  }
@@ -129,10 +131,10 @@ function extractAmrFromToken(decoded) {
129
131
  * Check if a token expiry timestamp indicates the token needs refresh.
130
132
  *
131
133
  * @param expiresAt - Token expiry timestamp (Unix milliseconds)
132
- * @param bufferMs - How early to trigger refresh (default 5 minutes)
134
+ * @param bufferMs - How early to trigger refresh (default: the one refresh window, `REFRESH_WINDOW_MS`, 5 minutes)
133
135
  * @returns true if token is expired or will expire within buffer period
134
136
  */
135
- function tokenNeedsRefresh(expiresAt, bufferMs = 5 * 60 * 1000) {
137
+ function tokenNeedsRefresh(expiresAt, bufferMs = refresh_window_1.REFRESH_WINDOW_MS) {
136
138
  if (!expiresAt) {
137
139
  return true; // No expiry info = assume needs refresh
138
140
  }
@@ -212,7 +214,7 @@ function validateTokenExpiry(accessToken, cachedExpiresAt) {
212
214
  };
213
215
  }
214
216
  catch (error) {
215
- console.error('[TOKEN_UTILS] Failed to validate token expiry:', error);
217
+ console.error('[TOKEN_UTILS] Failed to validate token expiry:', (0, log_safe_1.errForLog)(error));
216
218
  return { valid: false, actualExpiresAt: null, mismatch: false };
217
219
  }
218
220
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.redisLogger = exports.tokenRefreshLogger = exports.tokenSyncLogger = exports.tokenLogger = exports.circuitBreakerLogger = exports.apiLogger = exports.idpLogger = exports.authLogger = exports.logger = exports.log = void 0;
4
4
  const logging_config_1 = require("./logging-config");
5
5
  const vibe_log_transport_1 = require("./vibe-log-transport");
6
+ const log_safe_1 = require("../lib/log-safe");
6
7
  let loggingConfig = (0, logging_config_1.getLoggingConfigSync)();
7
8
  (0, logging_config_1.startConfigWatching)();
8
9
  const isEdgeRuntime = () => {
@@ -48,7 +49,7 @@ const createLogger = () => {
48
49
  return winston.createLogger({ level: loggingConfig.logLevel, levels: loggingConfig.levels, format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()), transports, exitOnError: false });
49
50
  }
50
51
  catch (error) {
51
- console.warn('Failed to initialize Winston logger, falling back to EdgeLogger:', error);
52
+ console.warn('Failed to initialize Winston logger, falling back to EdgeLogger:', (0, log_safe_1.errForLog)(error));
52
53
  return new EdgeLogger();
53
54
  }
54
55
  };
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.removeConfigWatcher = exports.addConfigWatcher = exports.stopConfigWatching = exports.startConfigWatching = exports.getLoggingConfigSync = exports.getLoggingConfig = exports.loggingConfigManager = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const fs_2 = require("fs");
6
+ const log_safe_1 = require("../lib/log-safe");
6
7
  const isEdgeRuntime = typeof globalThis.EdgeRuntime !== 'undefined' || process.env.NEXT_RUNTIME === 'edge';
7
8
  class LoggingConfigManager {
8
9
  config = null;
@@ -39,7 +40,7 @@ class LoggingConfigManager {
39
40
  return config;
40
41
  }
41
42
  catch (error) {
42
- console.warn(`Failed to load logging config from ${this.configPath}:`, error);
43
+ console.warn(`Failed to load logging config from ${this.configPath}:`, (0, log_safe_1.errForLog)(error));
43
44
  return this.getDefaultConfig();
44
45
  }
45
46
  }
@@ -61,7 +62,7 @@ class LoggingConfigManager {
61
62
  } });
62
63
  }
63
64
  catch (error) {
64
- console.warn('Failed to start watching logging config file:', error);
65
+ console.warn('Failed to start watching logging config file:', (0, log_safe_1.errForLog)(error));
65
66
  } }
66
67
  stopWatching() { if (this.isEdgeRuntime || this.isBrowser) {
67
68
  return;
@@ -75,7 +76,7 @@ class LoggingConfigManager {
75
76
  cb(config);
76
77
  }
77
78
  catch (error) {
78
- console.error('Error in logging config watcher:', error);
79
+ console.error('Error in logging config watcher:', (0, log_safe_1.errForLog)(error));
79
80
  } }); }
80
81
  validateAndMergeConfig(rawConfig) {
81
82
  const defaults = this.getDefaultConfig();
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.VibeLogTransport = void 0;
16
16
  exports.createVibeLogTransport = createVibeLogTransport;
17
17
  const redis_1 = require("../lib/redis");
18
+ const log_safe_1 = require("../lib/log-safe");
18
19
  // Dynamic import — winston is a peerDependency and may not be installed.
19
20
  // We resolve the base class lazily so builds don't break without it.
20
21
  let TransportBase;
@@ -144,7 +145,7 @@ class VibeLogTransport extends TransportBase {
144
145
  this.batch = [...entries, ...this.batch].slice(0, 100);
145
146
  }
146
147
  // Log to console as fallback
147
- console.error('[VibeLogTransport] Failed to flush logs:', err);
148
+ console.error('[VibeLogTransport] Failed to flush logs:', (0, log_safe_1.errForLog)(err));
148
149
  }
149
150
  finally {
150
151
  this.isFlushing = false;
@@ -21,6 +21,7 @@ const nanoid_1 = require("nanoid");
21
21
  const session_store_1 = require("./session-store");
22
22
  const test_aware_get_token_1 = require("./test-aware-get-token");
23
23
  const internal_api_1 = require("./internal-api");
24
+ const refresh_window_1 = require("./refresh-window");
24
25
  // ============================================================================
25
26
  // API Response Builder (for backward compatibility)
26
27
  // ============================================================================
@@ -189,7 +190,7 @@ class ApiHandler {
189
190
  // Skip the optimization (hasRefreshToken check) when access token is missing —
190
191
  // some session shapes store the refresh token under different field names,
191
192
  // and we still want a refresh attempt to populate the access token.
192
- const thresholdMs = 5 * 60 * 1000;
193
+ const thresholdMs = refresh_window_1.REFRESH_WINDOW_MS;
193
194
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
195
  const expires = sessionData.idpAccessTokenExpires || sessionData.accessTokenExpires || 0;
195
196
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -305,7 +306,7 @@ class ApiHandler {
305
306
  try {
306
307
  // Double-check if still needs refresh
307
308
  const latest = await (0, session_store_1.getSession)(sessionToken);
308
- const thresholdMs = 5 * 60 * 1000;
309
+ const thresholdMs = refresh_window_1.REFRESH_WINDOW_MS;
309
310
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
311
  const latestAny = latest;
311
312
  // Sessions may store the access token under either `accessToken` or `idpAccessToken`,
@@ -348,7 +349,7 @@ class ApiHandler {
348
349
  const session = await (0, session_store_1.getSession)(sessionToken);
349
350
  if (session?.accessToken) {
350
351
  const expires = session.accessTokenExpires || 0;
351
- if ((expires - Date.now()) > (5 * 60 * 1000)) {
352
+ if ((expires - Date.now()) > refresh_window_1.REFRESH_WINDOW_MS) {
352
353
  return { success: true };
353
354
  }
354
355
  }
@@ -4,10 +4,15 @@ export interface EnsureFreshConfig {
4
4
  refreshEndpoint?: string;
5
5
  }
6
6
  export interface EnsureFreshOptions {
7
- /** Refresh if the access token is within this many ms of expiry. Default 60_000. */
7
+ /** Refresh if the access token is within this many ms of expiry. Default REFRESH_WINDOW_MS (5 min, lib/refresh-window). */
8
8
  safetyWindowMs?: number;
9
9
  /** Max wait while another caller holds the refresh lock. Default 5000. */
10
10
  lockWaitMs?: number;
11
+ /**
12
+ * The refresh ROUTE sets this (BAPert 76449, R-over-E PR 2): a session with no refresh token is refused with the terminal NO_REFRESH_TOKEN
13
+ * even when its access token is still fresh, as the route always did. Other callers leave it unset and get `already_fresh` for a fresh token.
14
+ */
15
+ requireRefreshToken?: boolean;
11
16
  /**
12
17
  * Optional caller request id, for LOG CORRELATION ONLY. It is never the lock identity: the lock id is minted here with
13
18
  * crypto.randomUUID(). (BAPert 76298 (b): the lock id used to be this value, taken from the request's x-request-id header.)
@@ -20,6 +25,14 @@ export type EnsureFreshResult = {
20
25
  accessTokenExpires: number;
21
26
  /** True if we refreshed (or a concurrent refresh completed); false if the stored token was already fresh. */
22
27
  refreshed: boolean;
28
+ /**
29
+ * WHY this result is ok (BAPert 76449, additive; `api-handlers/auth/refresh` reproduces its bodies from it and `lib/token-lifecycle` reads
30
+ * `already_fresh`): 'already_fresh' = nothing was needed; 'completed_by_concurrent_request' = another request refreshed and we returned
31
+ * its result; 'refreshed' = THIS call refreshed.
32
+ */
33
+ reason: 'refreshed' | 'already_fresh' | 'completed_by_concurrent_request';
34
+ /** Whether the stored session holds a refresh token (the refresh route reports it in its body). */
35
+ hasRefreshToken: boolean;
23
36
  /**
24
37
  * CARD 320072. Is the token in THIS result actually in the session store?
25
38
  *
@@ -56,4 +69,14 @@ export type EnsureFreshResult = {
56
69
  retryable?: boolean;
57
70
  resolution?: string;
58
71
  };
72
+ /**
73
+ * The refresh envelope rule (BAPert 76455 (B)): `success` is a boolean; `success:true` needs a `data` object holding a string `access_token`;
74
+ * `success:false` needs `error.code` as a string; `meta`, `request_id` and `time_stamp` are optional; a non-2xx can not say success:true.
75
+ */
76
+ export declare function checkRefreshEnvelope(body: any, httpOk: boolean): {
77
+ ok: true;
78
+ } | {
79
+ ok: false;
80
+ why: string;
81
+ };
59
82
  export declare function ensureFreshAccessToken(sessionToken: string, config: EnsureFreshConfig, options?: EnsureFreshOptions): Promise<EnsureFreshResult>;