@payez/next-mvp 4.11.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 (100) hide show
  1. package/CHANGELOG.md +13 -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.js +2 -1
  14. package/dist/api-handlers/auth/signout.js +43 -6
  15. package/dist/api-handlers/mfa/backup-code-verify.js +2 -1
  16. package/dist/api-handlers/mfa/totp-verify.js +2 -1
  17. package/dist/api-handlers/session/refresh-viability.js +3 -2
  18. package/dist/api-handlers/session/viability.js +2 -1
  19. package/dist/auth/better-auth.js +7 -6
  20. package/dist/auth/idp-oauth.js +4 -2
  21. package/dist/auth/utils/idp-client.js +5 -4
  22. package/dist/auth/utils/token-utils.js +4 -3
  23. package/dist/config/logger.js +2 -1
  24. package/dist/config/logging-config.js +4 -3
  25. package/dist/config/vibe-log-transport.js +2 -1
  26. package/dist/lib/ensure-fresh-access-token.js +2 -1
  27. package/dist/lib/geolocation.js +5 -22
  28. package/dist/lib/idp-client-config.js +4 -3
  29. package/dist/lib/idp-fetch.d.ts +11 -0
  30. package/dist/lib/idp-fetch.js +3 -1
  31. package/dist/lib/idp-logout.d.ts +10 -0
  32. package/dist/lib/idp-logout.js +73 -1
  33. package/dist/lib/jwt-decode-client.js +2 -1
  34. package/dist/lib/jwt-decode.js +2 -1
  35. package/dist/lib/log-safe.d.ts +19 -0
  36. package/dist/lib/log-safe.js +32 -0
  37. package/dist/lib/redis.js +2 -1
  38. package/dist/lib/safe-error-response.d.ts +5 -1
  39. package/dist/lib/safe-error-response.js +7 -2
  40. package/dist/lib/session-store.js +2 -1
  41. package/dist/lib/site-logger.d.ts +2 -8
  42. package/dist/lib/site-logger.js +11 -5
  43. package/dist/lib/standardized-client-api.js +3 -2
  44. package/dist/lib/token-lifecycle.js +3 -2
  45. package/dist/middleware/create-middleware.js +2 -1
  46. package/dist/middleware/rbac-check.d.ts +0 -13
  47. package/dist/middleware/rbac-check.js +6 -5
  48. package/dist/routes/auth/logout.js +6 -2
  49. package/dist/routes/auth/settings.js +2 -1
  50. package/dist/server/auth-guard.js +4 -3
  51. package/dist/server/auth.js +5 -9
  52. package/dist/server/decode-session.js +3 -2
  53. package/dist/server/with-auth.js +2 -1
  54. package/package.json +1 -1
  55. package/src/api-handlers/account/recovery/forward.ts +2 -1
  56. package/src/api-handlers/account/recovery/initiate.ts +2 -1
  57. package/src/api-handlers/account/reset-password.ts +2 -1
  58. package/src/api-handlers/account/send-code.ts +2 -1
  59. package/src/api-handlers/account/update-phone.ts +2 -1
  60. package/src/api-handlers/account/validate-password.ts +2 -1
  61. package/src/api-handlers/account/verify-email.ts +2 -1
  62. package/src/api-handlers/account/verify-sms.ts +2 -1
  63. package/src/api-handlers/anon/preferences.ts +3 -2
  64. package/src/api-handlers/auth/credential-login-email-code.ts +3 -2
  65. package/src/api-handlers/auth/credential-login.ts +6 -5
  66. package/src/api-handlers/auth/refresh.ts +2 -1
  67. package/src/api-handlers/auth/signout.ts +54 -7
  68. package/src/api-handlers/mfa/backup-code-verify.ts +2 -1
  69. package/src/api-handlers/mfa/totp-verify.ts +2 -1
  70. package/src/api-handlers/session/refresh-viability.ts +3 -2
  71. package/src/api-handlers/session/viability.ts +2 -1
  72. package/src/auth/better-auth.ts +7 -6
  73. package/src/auth/idp-oauth.ts +7 -2
  74. package/src/auth/utils/idp-client.ts +5 -4
  75. package/src/auth/utils/token-utils.ts +4 -3
  76. package/src/config/logger.ts +2 -1
  77. package/src/config/logging-config.ts +4 -3
  78. package/src/config/vibe-log-transport.ts +2 -1
  79. package/src/lib/ensure-fresh-access-token.ts +2 -1
  80. package/src/lib/geolocation.ts +5 -4
  81. package/src/lib/idp-client-config.ts +4 -3
  82. package/src/lib/idp-fetch.ts +11 -1
  83. package/src/lib/idp-logout.ts +36 -1
  84. package/src/lib/jwt-decode-client.ts +3 -1
  85. package/src/lib/jwt-decode.ts +2 -1
  86. package/src/lib/log-safe.ts +30 -0
  87. package/src/lib/redis.ts +2 -1
  88. package/src/lib/safe-error-response.ts +7 -2
  89. package/src/lib/session-store.ts +2 -1
  90. package/src/lib/site-logger.ts +13 -7
  91. package/src/lib/standardized-client-api.ts +3 -2
  92. package/src/lib/token-lifecycle.ts +3 -2
  93. package/src/middleware/create-middleware.ts +2 -1
  94. package/src/middleware/rbac-check.ts +2 -1
  95. package/src/routes/auth/logout.ts +9 -2
  96. package/src/routes/auth/settings.ts +2 -1
  97. package/src/server/auth-guard.ts +4 -3
  98. package/src/server/auth.ts +5 -10
  99. package/src/server/decode-session.ts +3 -2
  100. package/src/server/with-auth.ts +2 -1
@@ -9,6 +9,7 @@
9
9
  // ========================================================================================
10
10
 
11
11
  import { authClient } from '../client/better-auth-client';
12
+ import { errForLog } from './log-safe';
12
13
 
13
14
  /** Unwrap Better Auth getSession() to a flat session-like object for backward compat */
14
15
  async function getSession(): Promise<any> {
@@ -147,7 +148,7 @@ function scheduleLoginRedirect(isImmediate = false) {
147
148
  console.warn(`🔄 Redirecting to login with return URL: ${returnUrl}`);
148
149
  window.location.href = `/account-auth/login?returnUrl=${returnUrl}`;
149
150
  } catch (error) {
150
- console.error('❌ Error during login redirect:', error);
151
+ console.error('❌ Error during login redirect:', errForLog(error));
151
152
  // Final fallback
152
153
  try {
153
154
  window.location.href = '/account-auth/login';
@@ -721,7 +722,7 @@ class StandardizedClientApiService {
721
722
  }
722
723
 
723
724
  // Wrap unknown errors as network errors
724
- console.error('❌ API request failed:', error);
725
+ console.error('❌ API request failed:', errForLog(error));
725
726
  throw new ApiNetworkError(
726
727
  error instanceof Error ? error.message : 'Network error',
727
728
  0,
@@ -27,6 +27,7 @@ import { getRedis } from './redis';
27
27
  import { getAppSlug } from './app-slug';
28
28
  import { tokenLogId } from './token-log-id';
29
29
  import { REFRESH_WINDOW_MS } from './refresh-window';
30
+ import { errForLog } from './log-safe';
30
31
 
31
32
  // "needs refresh" threshold: THE one freshness window (lib/refresh-window), the same value the refresh route, ensureFreshAccessToken and idp-fetch use.
32
33
  const REFRESH_THRESHOLD_MS = REFRESH_WINDOW_MS;
@@ -434,7 +435,7 @@ export async function ensureFreshToken(
434
435
  }
435
436
  } catch (e) {
436
437
  // If we can't decode, proceed with normal logic
437
- console.warn('[TOKEN_LIFECYCLE] Could not validate JWT exp claim:', e);
438
+ console.warn('[TOKEN_LIFECYCLE] Could not validate JWT exp claim:', errForLog(e));
438
439
  }
439
440
  }
440
441
 
@@ -512,7 +513,7 @@ export async function ensureFreshToken(
512
513
  sessionData,
513
514
  };
514
515
  } catch (error) {
515
- console.error('[TOKEN_LIFECYCLE] Error:', error);
516
+ console.error('[TOKEN_LIFECYCLE] Error:', errForLog(error));
516
517
  return {
517
518
  success: false,
518
519
  error: 'NO_SESSION',
@@ -32,6 +32,7 @@ import {
32
32
  resetSecret,
33
33
  signedResetHref,
34
34
  } from '../auth/session-reset-link';
35
+ import { errForLog } from '../lib/log-safe';
35
36
 
36
37
  // =============================================================================
37
38
  // TYPES
@@ -1037,7 +1038,7 @@ async function handleAllow(
1037
1038
  );
1038
1039
  }
1039
1040
  } catch (error) {
1040
- console.error('[MIDDLEWARE] RBAC error:', error);
1041
+ console.error('[MIDDLEWARE] RBAC error:', errForLog(error));
1041
1042
 
1042
1043
  // In development, fail open
1043
1044
  if (process.env.NODE_ENV !== 'production') {
@@ -1,3 +1,4 @@
1
+ import { errForLog } from '../lib/log-safe';
1
2
  /**
2
3
  * Page RBAC Check Module
3
4
  *
@@ -262,7 +263,7 @@ export async function checkPagePermission(
262
263
  };
263
264
  }
264
265
 
265
- console.error('[RBAC] Proxy error:', error);
266
+ console.error('[RBAC] Proxy error:', errForLog(error));
266
267
  return {
267
268
  allowed: false,
268
269
  reason: 'rbac_service_unavailable',
@@ -27,6 +27,7 @@ import {
27
27
  } from '../../lib/app-slug';
28
28
  import { siteEvents, getClientIp } from '../../lib/site-logger';
29
29
  import { logExceptionWithCorrelation } from '../../lib/safe-error-response';
30
+ import { errForLog } from '../../lib/log-safe';
30
31
 
31
32
  /**
32
33
  * 4.8.9: answered (status 500) when a sign-out did not finish. 4.8.10 (BAPert 76297): TWO sentences, one per cause, each true for its
@@ -79,7 +80,13 @@ export async function POST(req: NextRequest) {
79
80
  await revokeSessionAtIdp(req);
80
81
  } catch (error) {
81
82
  idpRevokeFailed = true;
82
- console.error('[LOGOUT_ROUTE] IdP revoke failed - the IdP refresh token is STILL LIVE:', error);
83
+ // "STILL LIVE" only when the IdP showed it (a forced refresh succeeded); otherwise say what is known: it was not confirmed.
84
+ console.error(
85
+ (error as { grantLive?: boolean } | null)?.grantLive === true
86
+ ? '[LOGOUT_ROUTE] IdP revoke failed - the IdP refresh token is STILL LIVE:'
87
+ : '[LOGOUT_ROUTE] IdP revoke could not be confirmed (the grant may already be dead):',
88
+ errForLog(error),
89
+ );
83
90
  }
84
91
  }
85
92
 
@@ -91,7 +98,7 @@ export async function POST(req: NextRequest) {
91
98
  console.info('[LOGOUT_ROUTE] Session deleted from store');
92
99
  } catch (error) {
93
100
  localDeleteFailed = true;
94
- console.error('[LOGOUT_ROUTE] Failed to delete session - the server session is STILL LIVE:', error);
101
+ console.error('[LOGOUT_ROUTE] Failed to delete session - the server session is STILL LIVE:', errForLog(error));
95
102
  }
96
103
  }
97
104
 
@@ -30,6 +30,7 @@
30
30
  import { NextResponse } from 'next/server';
31
31
  import { getIDPClientConfig } from '../../lib/idp-client-config';
32
32
  import { secretSafeJson } from '../../lib/never-in-response';
33
+ import { errForLog } from '../../lib/log-safe';
33
34
 
34
35
  export interface PublicAuthSettings {
35
36
  // OAuth providers enabled for this client
@@ -95,7 +96,7 @@ export async function GET() {
95
96
  config = await getIDPClientConfig();
96
97
  } catch (error) {
97
98
  // Keep the loud line - it is what finally unmasked this in production.
98
- console.error('[AUTH_SETTINGS] Failed to get settings:', error);
99
+ console.error('[AUTH_SETTINGS] Failed to get settings:', errForLog(error));
99
100
  // The `degraded: true` shape that used to live here (240447) is gone
100
101
  // deliberately. It still answered `success: true` with a full invented
101
102
  // policy and merely LABELLED the guess - which BAPert v32 rules out:
@@ -14,6 +14,7 @@ import { decodeSession } from './decode-session';
14
14
  import { getIDPClientConfig } from '../lib/idp-client-config';
15
15
  import { getSessionCookieName, getSecureSessionCookieName } from '../lib/app-slug';
16
16
  import type { SessionData } from '../lib/session-store';
17
+ import { errForLog } from '../lib/log-safe';
17
18
 
18
19
  // =============================================================================
19
20
  // TYPES
@@ -89,7 +90,7 @@ export async function authGuard(options?: AuthGuardOptions): Promise<AuthGuardRe
89
90
  decoded = await decodeSession();
90
91
  } catch (error) {
91
92
  // Redis unreachable or startup failure → fail closed
92
- console.error('[AUTH-GUARD] Session decode failed (service error):', error instanceof Error ? error.message : String(error));
93
+ console.error('[AUTH-GUARD] Session decode failed (service error):', errForLog(error));
93
94
  redirect(serviceUnavailableUrl);
94
95
  }
95
96
 
@@ -122,7 +123,7 @@ export async function authGuard(options?: AuthGuardOptions): Promise<AuthGuardRe
122
123
  requires2FA = config.authSettings?.require2FA ?? true;
123
124
  } catch (error) {
124
125
  // If we can't check 2FA config, fail closed
125
- console.error('[AUTH-GUARD] 2FA config check failed:', error instanceof Error ? error.message : String(error));
126
+ console.error('[AUTH-GUARD] 2FA config check failed:', errForLog(error));
126
127
  redirect(serviceUnavailableUrl);
127
128
  }
128
129
 
@@ -186,7 +187,7 @@ export async function authGuard(options?: AuthGuardOptions): Promise<AuthGuardRe
186
187
  if (error && typeof error === 'object' && 'digest' in error) {
187
188
  throw error;
188
189
  }
189
- console.error(`[AUTH-GUARD] Custom check "${check.name}" error:`, error instanceof Error ? error.message : String(error));
190
+ console.error(`[AUTH-GUARD] Custom check "${check.name}" error:`, errForLog(error));
190
191
  redirect(serviceUnavailableUrl);
191
192
  }
192
193
  }
@@ -14,6 +14,7 @@ import {
14
14
  type SessionData,
15
15
  } from '../lib/session-store';
16
16
  import { tokenLogId } from '../lib/token-log-id';
17
+ import { errForLog } from '../lib/log-safe';
17
18
  import { resolveVerifiedSessionTokenFromRequest } from './verified-session-token';
18
19
 
19
20
  export type IdpTokenResult =
@@ -134,20 +135,14 @@ export async function getAuthInstance() {
134
135
  * `{error:'NO_SESSION'}`, and getSession() swallowed every Redis error into a bare `catch {}`. These helpers name
135
136
  * the origin and make a swallowed read audible. Hard rules: every helper is wrapped so it can NEVER throw or change
136
137
  * a return value; nothing here ever logs a token, a cookie VALUE or a Redis value - only a tokenLogId() fingerprint,
137
- * cookie NAMES, booleans, and an error name/message with any long opaque run redacted.
138
+ * cookie NAMES, booleans, and an error NAME with its closed code/status (errForLog; never the message).
138
139
  * -------------------------------------------------------------------------------------------------------------- */
139
140
 
140
- /** An error name/message, first 80 chars, with any 24+ char opaque run (a token, a key) replaced. */
141
- function scrubForLog(v: unknown): string {
142
- const raw = v instanceof Error ? v.message : String(v ?? '');
143
- return raw.replace(/[A-Za-z0-9_\-.+/=]{24,}/g, '[redacted]').slice(0, 80);
144
- }
145
-
146
- /** `catch {}` sites on the session read path: still swallowed, now audible. */
141
+ /** `catch {}` sites on the session read path: still swallowed, now audible. The line names the error and its closed code/status only: the
142
+ * message is dropped (BAPert 76553: a redacted-and-truncated message still leaked host names, ports and quoted input). */
147
143
  function logSessionReadSwallowed(path: 'primary' | 'A' | 'B', err: unknown): void {
148
144
  try {
149
- const name = scrubForLog(err instanceof Error ? err.name : typeof err);
150
- console.warn(`[server-auth] SESSION_READ_SWALLOWED path=${path} err=${name} msg=${scrubForLog(err)}`);
145
+ console.warn(`[server-auth] SESSION_READ_SWALLOWED path=${path} ${errForLog(err)}`);
151
146
  } catch {
152
147
  /* a diagnostic must never change the outcome */
153
148
  }
@@ -15,6 +15,7 @@ import { ensureInitialized } from '../lib/startup-init';
15
15
  import { extractSessionTokenFromCookieValue } from '../lib/session-cookie-shape';
16
16
  import { resolveVerifiedSessionCookie } from './verified-session-token';
17
17
  import { tokenLogId } from '../lib/token-log-id';
18
+ import { errForLog } from '../lib/log-safe';
18
19
 
19
20
  export interface DecodedSession {
20
21
  sessionData: SessionData;
@@ -197,7 +198,7 @@ async function tryBetterAuthSession(
197
198
 
198
199
  return { sessionData, jwtPayload };
199
200
  } catch (error) {
200
- console.warn('[DECODE-SESSION] Better Auth session check failed:', error instanceof Error ? error.message : String(error));
201
+ console.warn('[DECODE-SESSION] Better Auth session check failed:', errForLog(error));
201
202
  return null;
202
203
  }
203
204
  }
@@ -303,7 +304,7 @@ export async function decodeSession(
303
304
  jwtPayload: (payload ?? { sessionToken }) as DecodedSession['jwtPayload'],
304
305
  };
305
306
  } catch (error) {
306
- console.error('[DECODE-SESSION] Error:', error instanceof Error ? error.message : String(error));
307
+ console.error('[DECODE-SESSION] Error:', errForLog(error));
307
308
  return null;
308
309
  }
309
310
  }
@@ -17,6 +17,7 @@ import 'server-only';
17
17
  import { NextRequest, NextResponse } from 'next/server';
18
18
  import { decodeSession } from './decode-session';
19
19
  import type { SessionData } from '../lib/session-store';
20
+ import { errForLog } from '../lib/log-safe';
20
21
 
21
22
  // =============================================================================
22
23
  // TYPES
@@ -83,7 +84,7 @@ export function withAuth(
83
84
 
84
85
  return handler(req, auth);
85
86
  } catch (error) {
86
- console.error('[WITH-AUTH] Error:', error instanceof Error ? error.message : String(error));
87
+ console.error('[WITH-AUTH] Error:', errForLog(error));
87
88
  return NextResponse.json(
88
89
  { error: 'Internal Server Error', message: 'Auth check failed' },
89
90
  { status: 500 }