@payez/next-mvp 4.4.5 → 4.4.7

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 (219) hide show
  1. package/README.md +10 -3
  2. package/dist/api/auth-handler.js +5 -3
  3. package/dist/api-handlers/account/change-password.js +33 -8
  4. package/dist/api-handlers/account/masked-info.js +2 -1
  5. package/dist/api-handlers/account/recovery/forward.d.ts +14 -0
  6. package/dist/api-handlers/account/recovery/forward.js +105 -0
  7. package/dist/api-handlers/account/recovery/initiate.js +4 -0
  8. package/dist/api-handlers/account/recovery/send-code.d.ts +12 -2
  9. package/dist/api-handlers/account/recovery/send-code.js +11 -84
  10. package/dist/api-handlers/account/recovery/verify-code.d.ts +11 -2
  11. package/dist/api-handlers/account/recovery/verify-code.js +10 -84
  12. package/dist/api-handlers/account/reset-password.js +24 -1
  13. package/dist/api-handlers/account/send-code.d.ts +1 -15
  14. package/dist/api-handlers/account/send-code.js +3 -6
  15. package/dist/api-handlers/account/update-phone.js +6 -1
  16. package/dist/api-handlers/account/validate-password.js +3 -1
  17. package/dist/api-handlers/account/verify-email.d.ts +1 -17
  18. package/dist/api-handlers/account/verify-email.js +4 -6
  19. package/dist/api-handlers/account/verify-sms.d.ts +1 -17
  20. package/dist/api-handlers/account/verify-sms.js +4 -6
  21. package/dist/api-handlers/admin/analytics.js +12 -4
  22. package/dist/api-handlers/admin/audit.js +12 -4
  23. package/dist/api-handlers/admin/redis-sessions.js +16 -8
  24. package/dist/api-handlers/admin/sessions.js +14 -6
  25. package/dist/api-handlers/admin/site-logs.js +39 -16
  26. package/dist/api-handlers/admin/stats.js +14 -4
  27. package/dist/api-handlers/admin/users.js +14 -6
  28. package/dist/api-handlers/admin/vibe-data.js +19 -11
  29. package/dist/api-handlers/auth/jwks.js +2 -1
  30. package/dist/api-handlers/auth/login.js +13 -4
  31. package/dist/api-handlers/auth/passkey-signin.js +6 -0
  32. package/dist/api-handlers/auth/refresh.js +38 -11
  33. package/dist/api-handlers/auth/signout.js +2 -1
  34. package/dist/api-handlers/auth/status.js +4 -1
  35. package/dist/api-handlers/auth/update-session.d.ts +25 -4
  36. package/dist/api-handlers/auth/update-session.js +38 -11
  37. package/dist/api-handlers/auth/validate.js +4 -2
  38. package/dist/api-handlers/auth/verify-code.js +3 -2
  39. package/dist/api-handlers/mfa/backup-code-verify.d.ts +1 -18
  40. package/dist/api-handlers/mfa/backup-code-verify.js +4 -6
  41. package/dist/api-handlers/mfa/backup-codes.js +2 -1
  42. package/dist/api-handlers/mfa/methods.js +2 -1
  43. package/dist/api-handlers/mfa/totp-confirm.js +2 -1
  44. package/dist/api-handlers/mfa/totp-enroll.js +2 -1
  45. package/dist/api-handlers/mfa/totp-verify.d.ts +1 -19
  46. package/dist/api-handlers/mfa/totp-verify.js +4 -6
  47. package/dist/api-handlers/passkeys/list.js +2 -1
  48. package/dist/api-handlers/passkeys/manage.js +2 -1
  49. package/dist/api-handlers/passkeys/register-options.js +2 -1
  50. package/dist/api-handlers/passkeys/register-verify.js +2 -1
  51. package/dist/api-handlers/session/refresh-viability.d.ts +4 -2
  52. package/dist/api-handlers/session/refresh-viability.js +10 -7
  53. package/dist/api-handlers/session/viability.js +4 -3
  54. package/dist/api-handlers/test/force-expire.js +5 -3
  55. package/dist/auth/auth-decision.js +2 -1
  56. package/dist/auth/better-auth.d.ts +58 -14
  57. package/dist/auth/better-auth.js +125 -54
  58. package/dist/auth/utils/idp-client.js +2 -1
  59. package/dist/client/better-auth-client.d.ts +101 -956
  60. package/dist/components/admin/DataBrowserTab.js +4 -1
  61. package/dist/components/admin/SessionsTab.js +9 -3
  62. package/dist/components/recovery/RequestedStep.d.ts +11 -0
  63. package/dist/components/recovery/RequestedStep.js +14 -0
  64. package/dist/components/recovery/SetPasswordStep.js +10 -1
  65. package/dist/config/env.d.ts +0 -2
  66. package/dist/config/env.js +0 -2
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +8 -1
  69. package/dist/lib/app-slug.d.ts +21 -0
  70. package/dist/lib/app-slug.js +51 -0
  71. package/dist/lib/ensure-fresh-access-token.d.ts +26 -0
  72. package/dist/lib/ensure-fresh-access-token.js +105 -4
  73. package/dist/lib/idp-client-config.d.ts +20 -0
  74. package/dist/lib/idp-client-config.js +73 -45
  75. package/dist/lib/idp-fetch.js +1 -1
  76. package/dist/lib/never-in-response.d.ts +30 -0
  77. package/dist/lib/never-in-response.js +71 -0
  78. package/dist/lib/password-rules.d.ts +22 -0
  79. package/dist/lib/password-rules.js +43 -0
  80. package/dist/lib/safe-error-response.d.ts +67 -0
  81. package/dist/lib/safe-error-response.js +130 -0
  82. package/dist/lib/session-cookie-shape.d.ts +22 -19
  83. package/dist/lib/session-cookie-shape.js +14 -18
  84. package/dist/lib/session-store.d.ts +8 -1
  85. package/dist/lib/session-store.js +170 -24
  86. package/dist/lib/startup-init.d.ts +6 -0
  87. package/dist/lib/startup-init.js +30 -4
  88. package/dist/lib/token-lifecycle.js +2 -1
  89. package/dist/lib/token-log-id.d.ts +18 -0
  90. package/dist/lib/token-log-id.js +84 -0
  91. package/dist/lib/two-factor-verify-failure.d.ts +78 -0
  92. package/dist/lib/two-factor-verify-failure.js +220 -0
  93. package/dist/middleware/create-middleware.d.ts +4 -1
  94. package/dist/middleware/create-middleware.js +79 -8
  95. package/dist/pages/login/page.js +5 -0
  96. package/dist/pages/recovery/page.js +14 -4
  97. package/dist/pages/test-env/JwtInspectPage.js +7 -149
  98. package/dist/pages/test-env/RefreshTokenPage.js +4 -1
  99. package/dist/pages/test-env/redact-credentials.d.ts +6 -0
  100. package/dist/pages/test-env/redact-credentials.js +101 -0
  101. package/dist/pages/verify-code/page.js +6 -3
  102. package/dist/routes/auth/logout.d.ts +2 -1
  103. package/dist/routes/auth/logout.js +4 -2
  104. package/dist/routes/auth/session.d.ts +0 -3
  105. package/dist/routes/auth/session.js +16 -10
  106. package/dist/routes/auth/settings.js +4 -1
  107. package/dist/routes/auth/viability.d.ts +3 -2
  108. package/dist/routes/auth/viability.js +11 -7
  109. package/dist/server/auth.d.ts +36 -294
  110. package/dist/server/auth.js +32 -31
  111. package/dist/server/decode-session.js +15 -26
  112. package/dist/server/verified-session-token.d.ts +13 -0
  113. package/dist/server/verified-session-token.js +103 -0
  114. package/dist/stores/authStore.js +14 -3
  115. package/dist/types/recovery.d.ts +1 -1
  116. package/dist/utils/api.d.ts +18 -0
  117. package/dist/utils/api.js +51 -9
  118. package/dist/vibe/client.d.ts +132 -16
  119. package/dist/vibe/client.js +288 -82
  120. package/dist/vibe/document-unwrap.d.ts +50 -0
  121. package/dist/vibe/document-unwrap.js +78 -0
  122. package/dist/vibe/errors.d.ts +17 -0
  123. package/dist/vibe/errors.js +21 -1
  124. package/dist/vibe/generic.d.ts +2 -49
  125. package/dist/vibe/generic.js +17 -78
  126. package/dist/vibe/index.d.ts +1 -1
  127. package/dist/vibe/index.js +2 -1
  128. package/dist/vibe/sessions.js +2 -1
  129. package/package.json +13 -2
  130. package/scripts/verify-release-provenance.mjs +410 -0
  131. package/src/api/auth-handler.ts +5 -3
  132. package/src/api-handlers/account/change-password.ts +36 -8
  133. package/src/api-handlers/account/masked-info.ts +2 -1
  134. package/src/api-handlers/account/recovery/forward.ts +117 -0
  135. package/src/api-handlers/account/recovery/initiate.ts +3 -0
  136. package/src/api-handlers/account/recovery/send-code.ts +13 -99
  137. package/src/api-handlers/account/recovery/verify-code.ts +12 -99
  138. package/src/api-handlers/account/reset-password.ts +30 -1
  139. package/src/api-handlers/account/send-code.ts +3 -9
  140. package/src/api-handlers/account/update-phone.ts +6 -1
  141. package/src/api-handlers/account/validate-password.ts +3 -1
  142. package/src/api-handlers/account/verify-email.ts +4 -9
  143. package/src/api-handlers/account/verify-sms.ts +4 -9
  144. package/src/api-handlers/admin/analytics.ts +13 -5
  145. package/src/api-handlers/admin/audit.ts +13 -5
  146. package/src/api-handlers/admin/redis-sessions.ts +16 -8
  147. package/src/api-handlers/admin/sessions.ts +15 -7
  148. package/src/api-handlers/admin/site-logs.ts +39 -16
  149. package/src/api-handlers/admin/stats.ts +15 -5
  150. package/src/api-handlers/admin/users.ts +15 -7
  151. package/src/api-handlers/admin/vibe-data.ts +20 -12
  152. package/src/api-handlers/auth/jwks.ts +2 -1
  153. package/src/api-handlers/auth/login.ts +13 -4
  154. package/src/api-handlers/auth/passkey-signin.ts +4 -0
  155. package/src/api-handlers/auth/refresh.ts +38 -11
  156. package/src/api-handlers/auth/signout.ts +2 -1
  157. package/src/api-handlers/auth/status.ts +4 -1
  158. package/src/api-handlers/auth/update-session.ts +45 -11
  159. package/src/api-handlers/auth/validate.ts +4 -2
  160. package/src/api-handlers/auth/verify-code.ts +3 -2
  161. package/src/api-handlers/mfa/backup-code-verify.ts +4 -9
  162. package/src/api-handlers/mfa/backup-codes.ts +2 -1
  163. package/src/api-handlers/mfa/methods.ts +2 -1
  164. package/src/api-handlers/mfa/totp-confirm.ts +2 -1
  165. package/src/api-handlers/mfa/totp-enroll.ts +2 -1
  166. package/src/api-handlers/mfa/totp-verify.ts +4 -9
  167. package/src/api-handlers/passkeys/list.ts +2 -1
  168. package/src/api-handlers/passkeys/manage.ts +2 -1
  169. package/src/api-handlers/passkeys/register-options.ts +2 -1
  170. package/src/api-handlers/passkeys/register-verify.ts +2 -1
  171. package/src/api-handlers/session/refresh-viability.ts +10 -7
  172. package/src/api-handlers/session/viability.ts +4 -3
  173. package/src/api-handlers/test/force-expire.ts +5 -3
  174. package/src/auth/auth-decision.ts +2 -1
  175. package/src/auth/better-auth.ts +128 -61
  176. package/src/auth/utils/idp-client.ts +2 -1
  177. package/src/components/admin/DataBrowserTab.tsx +4 -1
  178. package/src/components/admin/SessionsTab.tsx +10 -3
  179. package/src/components/recovery/RequestedStep.tsx +30 -0
  180. package/src/components/recovery/SetPasswordStep.tsx +24 -3
  181. package/src/config/env.ts +0 -2
  182. package/src/hooks/useViabilitySession.ts +2 -1
  183. package/src/index.ts +4 -0
  184. package/src/lib/app-slug.ts +50 -0
  185. package/src/lib/ensure-fresh-access-token.ts +133 -4
  186. package/src/lib/idp-client-config.ts +98 -45
  187. package/src/lib/idp-fetch.ts +1 -1
  188. package/src/lib/never-in-response.ts +64 -0
  189. package/src/lib/password-rules.ts +50 -0
  190. package/src/lib/safe-error-response.ts +126 -0
  191. package/src/lib/session-cookie-shape.ts +23 -22
  192. package/src/lib/session-store.ts +167 -24
  193. package/src/lib/startup-init.ts +29 -4
  194. package/src/lib/token-lifecycle.ts +2 -1
  195. package/src/lib/token-log-id.ts +69 -0
  196. package/src/lib/two-factor-verify-failure.ts +248 -0
  197. package/src/middleware/create-middleware.ts +97 -9
  198. package/src/pages/login/page.tsx +4 -0
  199. package/src/pages/recovery/page.tsx +21 -4
  200. package/src/pages/test-env/JwtInspectPage.tsx +11 -233
  201. package/src/pages/test-env/RefreshTokenPage.tsx +9 -4
  202. package/src/pages/test-env/redact-credentials.ts +96 -0
  203. package/src/pages/verify-code/page.tsx +6 -3
  204. package/src/routes/auth/logout.ts +4 -2
  205. package/src/routes/auth/session.ts +16 -10
  206. package/src/routes/auth/settings.ts +5 -1
  207. package/src/routes/auth/viability.ts +11 -7
  208. package/src/server/auth.ts +33 -35
  209. package/src/server/decode-session.ts +16 -26
  210. package/src/server/verified-session-token.ts +103 -0
  211. package/src/stores/authStore.ts +16 -5
  212. package/src/types/recovery.ts +1 -1
  213. package/src/utils/api.ts +47 -8
  214. package/src/vibe/client.ts +378 -91
  215. package/src/vibe/document-unwrap.ts +103 -0
  216. package/src/vibe/errors.ts +24 -0
  217. package/src/vibe/generic.ts +8 -100
  218. package/src/vibe/index.ts +1 -0
  219. package/src/vibe/sessions.ts +2 -1
@@ -20,6 +20,8 @@ const server_1 = require("next/server");
20
20
  const auth_1 = require("../../server/auth");
21
21
  const session_store_1 = require("../../lib/session-store");
22
22
  const idp_client_config_1 = require("../../lib/idp-client-config");
23
+ const safe_error_response_1 = require("../../lib/safe-error-response");
24
+ const token_log_id_1 = require("../../lib/token-log-id");
23
25
  /**
24
26
  * Get tenant-wide 2FA requirement from cached client config (from broker handshake)
25
27
  */
@@ -165,7 +167,7 @@ async function GET(req) {
165
167
  viable: false,
166
168
  needsRefresh: false,
167
169
  authenticated: false,
168
- sessionToken, // Return sessionToken so middleware can detect and clear stale cookie
170
+ sessionTokenId: (0, token_log_id_1.tokenLogId)(sessionToken), // PAY-1481: a correlation id, never the token; middleware only needs presence to clear the stale cookie
169
171
  reason: 'Stale session - cookie exists but session not found in Redis'
170
172
  });
171
173
  }
@@ -289,7 +291,7 @@ async function GET(req) {
289
291
  viable: true,
290
292
  needsRefresh: true,
291
293
  authenticated: true,
292
- sessionToken,
294
+ sessionTokenId: sessionToken ? (0, token_log_id_1.tokenLogId)(sessionToken) : null,
293
295
  // Clear names for middleware decision-making
294
296
  tenantRequiresTwoFactor,
295
297
  userHasCompletedTenantTwoFactorRequirements,
@@ -325,7 +327,7 @@ async function GET(req) {
325
327
  const mfaVerifiedInSession = session?.mfaVerified ?? false;
326
328
  // DEBUG: Log what we're reading from the session
327
329
  console.log('[VIABILITY] Session 2FA state:', {
328
- sessionToken: sessionToken?.substring(0, 8) + '...',
330
+ sessionToken: (0, token_log_id_1.tokenLogId)(sessionToken),
329
331
  'session.mfaVerified': session?.mfaVerified,
330
332
  'session.twoFactorComplete': session?.twoFactorComplete,
331
333
  mfaVerifiedInSession,
@@ -349,7 +351,7 @@ async function GET(req) {
349
351
  expiresIn: 0,
350
352
  hasRefreshToken,
351
353
  authenticated: true,
352
- sessionToken,
354
+ sessionTokenId: sessionToken ? (0, token_log_id_1.tokenLogId)(sessionToken) : null,
353
355
  // Clear names
354
356
  tenantRequiresTwoFactor,
355
357
  userHasCompletedTenantTwoFactorRequirements,
@@ -372,7 +374,7 @@ async function GET(req) {
372
374
  expiresIn,
373
375
  hasRefreshToken,
374
376
  authenticated: true,
375
- sessionToken,
377
+ sessionTokenId: sessionToken ? (0, token_log_id_1.tokenLogId)(sessionToken) : null,
376
378
  // Clear names
377
379
  tenantRequiresTwoFactor,
378
380
  userHasCompletedTenantTwoFactorRequirements,
@@ -390,13 +392,15 @@ async function GET(req) {
390
392
  });
391
393
  }
392
394
  catch (error) {
393
- console.error('[VIABILITY_ROUTE] Error checking session viability:', error);
395
+ // PAY-1438: pre-auth probe - the exception text stays in the log, keyed by correlationId.
396
+ const correlationId = (0, safe_error_response_1.logExceptionWithCorrelation)('[VIABILITY_ROUTE] Error checking session viability:', error);
394
397
  return server_1.NextResponse.json({
395
398
  viable: false,
396
399
  needsRefresh: false,
397
400
  authenticated: false,
398
401
  error: 'Failed to check session',
399
- details: error instanceof Error ? error.message : 'Unknown error'
402
+ code: 'INTERNAL_ERROR',
403
+ correlationId
400
404
  }, { status: 500 });
401
405
  }
402
406
  }
@@ -24,300 +24,42 @@ export type IdpTokenResult = {
24
24
  terminal: false;
25
25
  sessionData: SessionData;
26
26
  };
27
- export declare function getAuthInstance(): Promise<import("better-auth/types").Auth<{
28
- baseURL: string;
29
- secret: string;
30
- socialProviders: Record<string, import("../auth/better-auth").BetterAuthSocialProvider>;
31
- trustedOrigins: string[];
32
- secondaryStorage: {
33
- get: (key: string) => Promise<string | null>;
34
- set: (key: string, value: string, ttl?: number) => Promise<void>;
35
- delete: (key: string) => Promise<void>;
36
- };
37
- session: {
38
- cookieCache: {
39
- enabled: false;
40
- maxAge: number;
41
- refreshCache: false;
42
- };
43
- };
44
- advanced: {
45
- cookiePrefix: string;
46
- cookies: {
47
- session_token: {
48
- name: string;
49
- };
50
- };
51
- };
52
- plugins: [{
53
- id: "next-cookies";
54
- hooks: {
55
- before: {
56
- matcher(ctx: import("@better-auth/core").HookEndpointContext): boolean;
57
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
58
- }[];
59
- after: {
60
- matcher(ctx: import("@better-auth/core").HookEndpointContext): true;
61
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
62
- }[];
63
- };
64
- }, ...(import("@better-auth/core").BetterAuthPlugin | {
65
- id: "magic-link";
66
- endpoints: {
67
- signInMagicLink: import("better-call").StrictEndpoint<"/sign-in/magic-link", {
68
- method: "POST";
69
- requireHeaders: true;
70
- body: import("zod").ZodObject<{
71
- email: import("zod").ZodEmail;
72
- name: import("zod").ZodOptional<import("zod").ZodString>;
73
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
74
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
75
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
76
- metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
77
- }, import("better-auth/*").$strip>;
78
- metadata: {
79
- openapi: {
80
- operationId: string;
81
- description: string;
82
- responses: {
83
- 200: {
84
- description: string;
85
- content: {
86
- "application/json": {
87
- schema: {
88
- type: "object";
89
- properties: {
90
- status: {
91
- type: string;
92
- };
93
- };
94
- };
95
- };
96
- };
97
- };
98
- };
99
- };
100
- };
101
- }, {
102
- status: boolean;
103
- }>;
104
- magicLinkVerify: import("better-call").StrictEndpoint<"/magic-link/verify", {
105
- method: "GET";
106
- query: import("zod").ZodObject<{
107
- token: import("zod").ZodString;
108
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
109
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
110
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
111
- }, import("better-auth/*").$strip>;
112
- use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>)[];
113
- requireHeaders: true;
114
- metadata: {
115
- openapi: {
116
- operationId: string;
117
- description: string;
118
- responses: {
119
- 200: {
120
- description: string;
121
- content: {
122
- "application/json": {
123
- schema: {
124
- type: "object";
125
- properties: {
126
- session: {
127
- $ref: string;
128
- };
129
- user: {
130
- $ref: string;
131
- };
132
- };
133
- };
134
- };
135
- };
136
- };
137
- };
138
- };
139
- };
140
- }, {
141
- token: string;
142
- user: {
143
- id: string;
144
- createdAt: Date;
145
- updatedAt: Date;
146
- email: string;
147
- emailVerified: boolean;
148
- name: string;
149
- image?: string | null | undefined;
150
- };
151
- }>;
152
- };
153
- rateLimit: {
154
- pathMatcher(path: string): boolean;
155
- window: number;
156
- max: number;
157
- }[];
158
- options: import("better-auth/plugins/magic-link").MagicLinkOptions;
159
- } | {
160
- id: "generic-oauth";
161
- init: (ctx: import("@better-auth/core").AuthContext) => {
162
- context: {
163
- socialProviders: import("better-auth/*").OAuthProvider<Record<string, any>, Partial<import("better-auth/*").ProviderOptions<any>>>[];
164
- };
165
- };
166
- endpoints: {
167
- signInWithOAuth2: import("better-call").StrictEndpoint<"/sign-in/oauth2", {
168
- method: "POST";
169
- body: import("zod").ZodObject<{
170
- providerId: import("zod").ZodString;
171
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
172
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
173
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
174
- disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
175
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
176
- requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
177
- additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
178
- }, import("better-auth/*").$strip>;
179
- metadata: {
180
- openapi: {
181
- description: string;
182
- responses: {
183
- 200: {
184
- description: string;
185
- content: {
186
- "application/json": {
187
- schema: {
188
- type: "object";
189
- properties: {
190
- url: {
191
- type: string;
192
- };
193
- redirect: {
194
- type: string;
195
- };
196
- };
197
- };
198
- };
199
- };
200
- };
201
- };
202
- };
203
- };
204
- }, {
205
- url: string;
206
- redirect: boolean;
207
- }>;
208
- oAuth2Callback: import("better-call").StrictEndpoint<"/oauth2/callback/:providerId", {
209
- method: "GET";
210
- query: import("zod").ZodObject<{
211
- code: import("zod").ZodOptional<import("zod").ZodString>;
212
- error: import("zod").ZodOptional<import("zod").ZodString>;
213
- error_description: import("zod").ZodOptional<import("zod").ZodString>;
214
- state: import("zod").ZodOptional<import("zod").ZodString>;
215
- iss: import("zod").ZodOptional<import("zod").ZodString>;
216
- }, import("better-auth/*").$strip>;
217
- metadata: {
218
- allowedMediaTypes: string[];
219
- openapi: {
220
- description: string;
221
- responses: {
222
- 200: {
223
- description: string;
224
- content: {
225
- "application/json": {
226
- schema: {
227
- type: "object";
228
- properties: {
229
- url: {
230
- type: string;
231
- };
232
- };
233
- };
234
- };
235
- };
236
- };
237
- };
238
- };
239
- scope: "server";
240
- };
241
- }, void>;
242
- oAuth2LinkAccount: import("better-call").StrictEndpoint<"/oauth2/link", {
243
- method: "POST";
244
- body: import("zod").ZodObject<{
245
- providerId: import("zod").ZodString;
246
- callbackURL: import("zod").ZodString;
247
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
248
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
249
- }, import("better-auth/*").$strip>;
250
- use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
251
- session: {
252
- session: Record<string, any> & {
253
- id: string;
254
- createdAt: Date;
255
- updatedAt: Date;
256
- userId: string;
257
- expiresAt: Date;
258
- token: string;
259
- ipAddress?: string | null | undefined;
260
- userAgent?: string | null | undefined;
261
- };
262
- user: Record<string, any> & {
263
- id: string;
264
- createdAt: Date;
265
- updatedAt: Date;
266
- email: string;
267
- emailVerified: boolean;
268
- name: string;
269
- image?: string | null | undefined;
270
- };
271
- };
272
- }>)[];
273
- metadata: {
274
- openapi: {
275
- description: string;
276
- responses: {
277
- "200": {
278
- description: string;
279
- content: {
280
- "application/json": {
281
- schema: {
282
- type: "object";
283
- properties: {
284
- url: {
285
- type: string;
286
- format: string;
287
- description: string;
288
- };
289
- redirect: {
290
- type: string;
291
- description: string;
292
- enum: boolean[];
293
- };
294
- };
295
- required: string[];
296
- };
297
- };
298
- };
299
- };
300
- };
301
- };
302
- };
303
- }, {
304
- url: string;
305
- redirect: boolean;
306
- }>;
307
- };
308
- options: import("better-auth/plugins/generic-oauth").GenericOAuthOptions;
309
- $ERROR_CODES: {
310
- INVALID_OAUTH_CONFIGURATION: import("better-auth/*").RawError<"INVALID_OAUTH_CONFIGURATION">;
311
- TOKEN_URL_NOT_FOUND: import("better-auth/*").RawError<"TOKEN_URL_NOT_FOUND">;
312
- PROVIDER_CONFIG_NOT_FOUND: import("better-auth/*").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
313
- PROVIDER_ID_REQUIRED: import("better-auth/*").RawError<"PROVIDER_ID_REQUIRED">;
314
- INVALID_OAUTH_CONFIG: import("better-auth/*").RawError<"INVALID_OAUTH_CONFIG">;
315
- SESSION_REQUIRED: import("better-auth/*").RawError<"SESSION_REQUIRED">;
316
- ISSUER_MISMATCH: import("better-auth/*").RawError<"ISSUER_MISMATCH">;
317
- ISSUER_MISSING: import("better-auth/*").RawError<"ISSUER_MISSING">;
318
- };
319
- })[]];
320
- }>>;
27
+ /**
28
+ * Get the initialized Better Auth instance (singleton).
29
+ *
30
+ * 244549: the single-flight memoisation lives in lib/single-flight.ts rather
31
+ * than inline here, and that placement is deliberate rather than tidiness.
32
+ * The bug being fixed -- a REJECTED init promise cached for the life of the
33
+ * process, so recovery was restart-only -- went unnoticed precisely because
34
+ * this module cannot be loaded in a unit test: importing it pulls in
35
+ * better-auth and the whole IDP config stack. Extracted into a zero-import
36
+ * module, the retry semantics are covered by
37
+ * test/single-flight-retry.test.cjs, which imports dist/ and therefore
38
+ * exercises the artefact that actually ships.
39
+ *
40
+ * PAY-1623 (package half, BAPert 62449): THIS FUNNEL NO LONGER BUILDS ITS OWN
41
+ * INSTANCE. It used to call `createBetterAuthInstance(config)` with no opts, so
42
+ * it carried NONE of the app-supplied plugins registered by the host app at
43
+ * startup (`configureBetterAuth()` -> `state.configuredOpts`). `samlAcs()` and
44
+ * `passkeySignIn()` arrive only that way, so `getAuthInstance()` returned an
45
+ * instance on which `auth.api.samlAcs` was `undefined` — the exact package twin
46
+ * of the portal ACS crash `t.api.samlAcs is not a function` (Jon's walk,
47
+ * 2026-09-21; Portal PR 517 fixed the portal by importing the CONFIGURED
48
+ * instance, `getBetterAuthInstance`, which is why this file must now resolve the
49
+ * same one). It also silently created a SECOND, divergent instance beside the
50
+ * one every /api/auth/* route uses — two instances, two secrets, one process.
51
+ *
52
+ * It now DELEGATES to that configured funnel, which is the single source of
53
+ * truth for the process's Better Auth instance:
54
+ * - it carries `state.configuredOpts` (better-auth.ts:930), so the app
55
+ * plugins are present;
56
+ * - its init is already single-flighted with the retry semantics described in
57
+ * lib/single-flight.ts (see getBetterAuthInstance, card 244549/338215), so
58
+ * this function keeps the same no-latch recovery contract;
59
+ * - there is now ONE instance, so the double-init / divergent-secret hazard
60
+ * this path used to create is gone by construction.
61
+ */
62
+ export declare function getAuthInstance(): Promise<any>;
321
63
  /**
322
64
  * Get the current session from a request.
323
65
  *
@@ -47,11 +47,8 @@ exports.getFreshIdpToken = getFreshIdpToken;
47
47
  exports.requireSession = requireSession;
48
48
  require("server-only");
49
49
  const better_auth_1 = require("../auth/better-auth");
50
- const idp_client_config_1 = require("../lib/idp-client-config");
51
50
  const session_store_1 = require("../lib/session-store");
52
- const session_cookie_shape_1 = require("../lib/session-cookie-shape");
53
- const single_flight_1 = require("../lib/single-flight");
54
- let authInstance = null;
51
+ const verified_session_token_1 = require("./verified-session-token");
55
52
  function buildSessionDataFromAuthSession(session) {
56
53
  const user = session?.user;
57
54
  if (!user?.id && !user?.email) {
@@ -116,30 +113,30 @@ function attachSessionData(session, sessionData, sessionToken) {
116
113
  * test/single-flight-retry.test.cjs, which imports dist/ and therefore
117
114
  * exercises the artefact that actually ships.
118
115
  *
119
- * Semantics, all asserted by that suite: concurrent callers share ONE
120
- * underlying init; a rejection reaches every caller awaiting that attempt
121
- * (failures stay loud -- getIDPClientConfig is fail-loud by design and callers
122
- * depend on it); the NEXT call after a failure starts a fresh attempt; and a
123
- * slow failure never evicts a newer in-flight attempt.
116
+ * PAY-1623 (package half, BAPert 62449): THIS FUNNEL NO LONGER BUILDS ITS OWN
117
+ * INSTANCE. It used to call `createBetterAuthInstance(config)` with no opts, so
118
+ * it carried NONE of the app-supplied plugins registered by the host app at
119
+ * startup (`configureBetterAuth()` -> `state.configuredOpts`). `samlAcs()` and
120
+ * `passkeySignIn()` arrive only that way, so `getAuthInstance()` returned an
121
+ * instance on which `auth.api.samlAcs` was `undefined` — the exact package twin
122
+ * of the portal ACS crash `t.api.samlAcs is not a function` (Jon's walk,
123
+ * 2026-09-21; Portal PR 517 fixed the portal by importing the CONFIGURED
124
+ * instance, `getBetterAuthInstance`, which is why this file must now resolve the
125
+ * same one). It also silently created a SECOND, divergent instance beside the
126
+ * one every /api/auth/* route uses — two instances, two secrets, one process.
127
+ *
128
+ * It now DELEGATES to that configured funnel, which is the single source of
129
+ * truth for the process's Better Auth instance:
130
+ * - it carries `state.configuredOpts` (better-auth.ts:930), so the app
131
+ * plugins are present;
132
+ * - its init is already single-flighted with the retry semantics described in
133
+ * lib/single-flight.ts (see getBetterAuthInstance, card 244549/338215), so
134
+ * this function keeps the same no-latch recovery contract;
135
+ * - there is now ONE instance, so the double-init / divergent-secret hazard
136
+ * this path used to create is gone by construction.
124
137
  */
125
- const authInit = (0, single_flight_1.createSingleFlight)(async () => {
126
- const config = await (0, idp_client_config_1.getIDPClientConfig)(true);
127
- authInstance = (0, better_auth_1.createBetterAuthInstance)(config);
128
- // 185121 line 1, path 2: this module-scope instance had NO creation log
129
- // line — first-called during a config transition window it pinned a
130
- // divergent secret INVISIBLY. Name the path and the secret fingerprint
131
- // (the createBetterAuthInstance line already logged the same hash;
132
- // this line is what attributes it to THIS init path).
133
- // Guard matches createBetterAuthInstance's: getIDPClientConfig currently
134
- // throws FATAL on a missing authSecret (QAPert 27328 verified), but that
135
- // guarantee is one refactor away — log ABSENT rather than ever throw here.
136
- console.log(`[AUTH] getAuthInstance: created BA instance via server/auth.ts — authSecret ${config.authSecret ? `sha256:${(0, better_auth_1.secretFingerprint)(config.authSecret)}` : 'ABSENT'}`);
137
- return authInstance;
138
- });
139
138
  async function getAuthInstance() {
140
- if (authInstance)
141
- return authInstance;
142
- return authInit.get();
139
+ return (0, better_auth_1.getBetterAuthInstance)();
143
140
  }
144
141
  /**
145
142
  * Get the current session from a request.
@@ -184,14 +181,18 @@ async function getSession(request) {
184
181
  sessionData = buildSessionDataFromAuthSession(betterAuthSession);
185
182
  return attachSessionData(betterAuthSession, sessionData, sessionToken);
186
183
  }
187
- // Path B — Better Auth said null/incomplete. Fall back to manual cookie
184
+ // Path B — Better Auth said null/incomplete. Fall back to a VERIFIED cookie
188
185
  // decode + Redis-canonical liveness check. This catches cases where the
189
186
  // canonical app session at `{slug}:{token}` is still alive but Better
190
187
  // Auth's `ba:{slug}:{token}` secondary record was evicted or the
191
- // in-memory cookie cache returned stale-null. No JWT signature check —
192
- // Redis lookup is the validation: an attacker forging a session-token
193
- // claim hits NO_SESSION because Redis has no matching entry.
194
- const sessionToken = (0, session_cookie_shape_1.extractSessionTokenFromCookie)(request);
188
+ // in-memory cookie cache returned stale-null.
189
+ //
190
+ // PAY-1476: this path used to take the token with NO signature check, on the
191
+ // theory that "Redis lookup is the validation". It is not: a leaked token has
192
+ // a live record, so the token string alone authenticated, with any garbage in
193
+ // the signature position, and a forged JWS too. The token now comes only from
194
+ // the shared verified resolver (HMAC or JWS signature checked first).
195
+ const sessionToken = await (0, verified_session_token_1.resolveVerifiedSessionTokenFromRequest)(request);
195
196
  if (!sessionToken)
196
197
  return null;
197
198
  let sessionData = null;
@@ -42,12 +42,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.decodeSession = decodeSession;
43
43
  require("server-only");
44
44
  const headers_1 = require("next/headers");
45
- const jose_1 = require("jose");
46
45
  const session_store_1 = require("../lib/session-store");
47
46
  const idp_client_config_1 = require("../lib/idp-client-config");
48
47
  const app_slug_1 = require("../lib/app-slug");
49
48
  const startup_init_1 = require("../lib/startup-init");
50
49
  const session_cookie_shape_1 = require("../lib/session-cookie-shape");
50
+ const verified_session_token_1 = require("./verified-session-token");
51
+ const token_log_id_1 = require("../lib/token-log-id");
51
52
  /**
52
53
  * Try Better Auth's server-side session API.
53
54
  * Returns a DecodedSession if Better Auth has an active session, null otherwise.
@@ -161,7 +162,7 @@ async function tryBetterAuthSession(requestCookies) {
161
162
  // session. Read the commit message before treating a sign-out here as a
162
163
  // bug introduced by this fix.
163
164
  if (!idpTokens?.roles?.length) {
164
- console.warn('[DECODE-SESSION] Leg 1 declining: idpTokens unusable (no roles) for session', result.session.token.substring(0, 10), '— falling through to the sess: leg');
165
+ console.warn('[DECODE-SESSION] Leg 1 declining: idpTokens unusable (no roles) for session', (0, token_log_id_1.tokenLogId)(result.session.token), '— falling through to the sess: leg');
165
166
  return null;
166
167
  }
167
168
  // Map Better Auth session + IDP tokens to SessionData
@@ -261,31 +262,19 @@ async function decodeSession(requestCookies) {
261
262
  // death, because the ONLY fallback was structurally blind to the only
262
263
  // shape it would ever be handed.
263
264
  //
264
- // Check the shape FIRST. A BA-hmac cookie skips jose entirely and goes
265
- // straight to the Redis-canonical liveness check below — same posture
266
- // as the JWS path always had (no signature re-check here either; Redis
267
- // presence is the authority). A JWS-shaped cookie (legacy NextAuth-era,
268
- // if any still linger) keeps the original jwtVerify path unchanged.
265
+ // PAY-1476: the shape still decides HOW to verify, but BOTH shapes are now
266
+ // verified, by the shared resolver. This branch used to take a BA-hmac
267
+ // cookie's token with no signature check ("Redis presence is the
268
+ // authority"), so `{leaked token}.{anything}` authenticated here - including
269
+ // behind the portal's admin API gate. The resolver checks the HMAC with
270
+ // timingSafeEqual (ba-hmac) or runs jwtVerify (jws) against the same
271
+ // env-first auth secret, and returns null on any failure.
269
272
  const shaped = (0, session_cookie_shape_1.extractSessionTokenFromCookieValue)(cookieValue);
270
- let sessionToken;
271
- let payload;
272
- if (shaped?.shape === 'ba-hmac') {
273
- sessionToken = shaped.token;
274
- }
275
- else {
276
- const secretKey = new TextEncoder().encode(secret);
277
- try {
278
- const result = await (0, jose_1.jwtVerify)(cookieValue, secretKey);
279
- payload = result.payload;
280
- }
281
- catch (jwtError) {
282
- console.warn('[DECODE-SESSION] JWT verification failed:', jwtError instanceof Error ? jwtError.message : String(jwtError));
283
- return null;
284
- }
285
- sessionToken = payload.sessionToken || payload.redisSessionId;
286
- }
273
+ const verified = await (0, verified_session_token_1.resolveVerifiedSessionCookie)(cookieValue);
274
+ const sessionToken = verified?.token;
275
+ const payload = verified?.payload;
287
276
  if (!sessionToken) {
288
- console.warn('[DECODE-SESSION] No sessionToken resolved from cookie (shape:', shaped?.shape ?? 'unrecognized', ')');
277
+ console.warn('[DECODE-SESSION] Session cookie did not verify (shape:', shaped?.shape ?? 'unrecognized', ')');
289
278
  return null;
290
279
  }
291
280
  const sessionData = await (0, session_store_1.getSession)(sessionToken);
@@ -297,7 +286,7 @@ async function decodeSession(requestCookies) {
297
286
  // either and the caller is about to be signed out. Different meanings,
298
287
  // different remediations — do not ship a second silent path while
299
288
  // fixing a silent path.
300
- console.warn('[DECODE-SESSION] No sess: record for token', sessionToken.substring(0, 10), '— no fallback available, session will be signed out');
289
+ console.warn('[DECODE-SESSION] No sess: record for token', (0, token_log_id_1.tokenLogId)(sessionToken), '— no fallback available, session will be signed out');
301
290
  return null;
302
291
  }
303
292
  // Backwards compat: session.user.email works alongside session.email
@@ -0,0 +1,13 @@
1
+ import { type JWTPayload } from 'jose';
2
+ export interface VerifiedSessionCookie {
3
+ token: string;
4
+ shape: 'ba-hmac' | 'jws';
5
+ /** The verified JWS payload (jws shape only). */
6
+ payload?: JWTPayload;
7
+ }
8
+ /** The verified session cookie (token, shape, JWS payload), or null unless its signature verifies. */
9
+ export declare function resolveVerifiedSessionCookie(rawValue: string | null | undefined): Promise<VerifiedSessionCookie | null>;
10
+ /** The session token carried by a raw session-cookie value, or null unless its signature verifies. */
11
+ export declare function resolveVerifiedSessionToken(rawValue: string | null | undefined): Promise<string | null>;
12
+ /** Same, reading the session cookie (every name it is minted under, chunks joined) from a Request. */
13
+ export declare function resolveVerifiedSessionTokenFromRequest(request: Request): Promise<string | null>;