@payez/next-mvp 4.4.6 → 4.4.8

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 (54) hide show
  1. package/dist/api-handlers/account/change-password.js +28 -6
  2. package/dist/auth/better-auth.d.ts +62 -15
  3. package/dist/auth/better-auth.js +180 -19
  4. package/dist/client/better-auth-client.d.ts +101 -956
  5. package/dist/config/env.d.ts +0 -2
  6. package/dist/config/env.js +0 -2
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +8 -1
  9. package/dist/lib/idp-client-config.js +8 -1
  10. package/dist/lib/never-in-response.d.ts +30 -0
  11. package/dist/lib/never-in-response.js +71 -0
  12. package/dist/lib/nextauth-secret.d.ts +51 -0
  13. package/dist/lib/nextauth-secret.js +141 -0
  14. package/dist/lib/safe-error-response.d.ts +6 -6
  15. package/dist/lib/safe-error-response.js +6 -6
  16. package/dist/lib/two-factor-verify-failure.d.ts +36 -3
  17. package/dist/lib/two-factor-verify-failure.js +79 -2
  18. package/dist/middleware/create-middleware.d.ts +2 -0
  19. package/dist/middleware/create-middleware.js +36 -0
  20. package/dist/pages/login/page.js +5 -0
  21. package/dist/pages/verify-code/page.js +4 -2
  22. package/dist/routes/auth/settings.js +4 -1
  23. package/dist/server/auth.d.ts +36 -294
  24. package/dist/server/auth.js +22 -25
  25. package/dist/vibe/client.d.ts +132 -16
  26. package/dist/vibe/client.js +288 -82
  27. package/dist/vibe/document-unwrap.d.ts +50 -0
  28. package/dist/vibe/document-unwrap.js +78 -0
  29. package/dist/vibe/errors.d.ts +17 -0
  30. package/dist/vibe/errors.js +21 -1
  31. package/dist/vibe/generic.d.ts +2 -49
  32. package/dist/vibe/generic.js +17 -78
  33. package/dist/vibe/index.d.ts +1 -1
  34. package/dist/vibe/index.js +2 -1
  35. package/package.json +17 -2
  36. package/src/api-handlers/account/change-password.ts +31 -6
  37. package/src/auth/better-auth.ts +188 -21
  38. package/src/config/env.ts +0 -2
  39. package/src/index.ts +4 -0
  40. package/src/lib/idp-client-config.ts +8 -1
  41. package/src/lib/never-in-response.ts +64 -0
  42. package/src/lib/nextauth-secret.ts +168 -0
  43. package/src/lib/safe-error-response.ts +6 -6
  44. package/src/lib/two-factor-verify-failure.ts +86 -5
  45. package/src/middleware/create-middleware.ts +42 -0
  46. package/src/pages/login/page.tsx +4 -0
  47. package/src/pages/verify-code/page.tsx +4 -2
  48. package/src/routes/auth/settings.ts +5 -1
  49. package/src/server/auth.ts +23 -29
  50. package/src/vibe/client.ts +378 -91
  51. package/src/vibe/document-unwrap.ts +103 -0
  52. package/src/vibe/errors.ts +24 -0
  53. package/src/vibe/generic.ts +8 -100
  54. package/src/vibe/index.ts +1 -0
@@ -18,6 +18,7 @@
18
18
  * });
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.TWO_FACTOR_TIMEOUT_ERROR = void 0;
21
22
  exports.createMvpMiddleware = createMvpMiddleware;
22
23
  const server_1 = require("next/server");
23
24
  const route_config_1 = require("../auth/route-config");
@@ -143,6 +144,26 @@ function refreshFailureResponse(request, location, errorCode, clearCookies = fal
143
144
  function makeAuthDecision(ctx) {
144
145
  const { pathname, isPublicRoute, isLoginPage: onLoginPage, searchParams, sessionPointer, sessionStatus, circuitBreakerOpen } = ctx;
145
146
  const safeCallback = getSafeCallbackUrl(pathname);
147
+ // --- PAY-884: a provisional (pre-2FA) session whose token has expired can never finish 2FA ---
148
+ // The IdP issues no refresh token until 2FA completes, and it validates token lifetime, so
149
+ // once the provisional access token expires neither refresh nor code verification can
150
+ // succeed. Sending the user to the verify-code page (or to error=SessionExpired, which the
151
+ // login page deliberately shows as NO message) strands them mid-2FA with no explanation.
152
+ // Say what happened instead, and clear the dead session so the next sign-in starts clean.
153
+ // Scoped to the verify-code page and protected pages: /api/ keeps its 401-JSON carve-out
154
+ // (307290), the login page stays reachable, and public pages are left to the existing arms.
155
+ if (isExpiredProvisionalSession(sessionPointer, sessionStatus) &&
156
+ !pathname.startsWith('/api/') &&
157
+ !onLoginPage &&
158
+ (pathname === VERIFY_CODE_PAGE || !isPublicRoute)) {
159
+ const callback = pathname === VERIFY_CODE_PAGE ? getSafeCallbackUrl(pathname, searchParams) : safeCallback;
160
+ return {
161
+ type: 'redirect',
162
+ location: `${LOGIN_PAGE}?callbackUrl=${encodeURIComponent(callback)}&error=${exports.TWO_FACTOR_TIMEOUT_ERROR}`,
163
+ reason: 'pre_2fa_token_expired',
164
+ clearCookies: true,
165
+ };
166
+ }
146
167
  // --- Public routes: Allow, but enforce 2FA for authenticated users ---
147
168
  if (isPublicRoute && !onLoginPage) {
148
169
  if (needsTwoFactorRedirect(pathname, sessionPointer, sessionStatus)) {
@@ -249,6 +270,21 @@ function makeAuthDecision(ctx) {
249
270
  // --- All checks passed ---
250
271
  return { type: 'allow' };
251
272
  }
273
+ /** Login error code for a pre-2FA session that expired before the code was verified (PAY-884). */
274
+ exports.TWO_FACTOR_TIMEOUT_ERROR = 'TwoFactorTimeout';
275
+ /**
276
+ * PAY-884: 2FA outstanding, access token expired, and no refresh token to renew it.
277
+ * Only a viability answer that positively says the session exists qualifies (null = check failed).
278
+ */
279
+ function isExpiredProvisionalSession(session, status) {
280
+ return (session.exists &&
281
+ status.exists === true &&
282
+ status.forceInvalid === false &&
283
+ status.requires2FA &&
284
+ !status.twoFactorComplete &&
285
+ session.expired === true &&
286
+ !session.hasRefreshToken);
287
+ }
252
288
  /** Check if user needs 2FA redirect */
253
289
  function needsTwoFactorRedirect(pathname, session, status) {
254
290
  if (!session.exists || !status.exists)
@@ -131,6 +131,11 @@ function LoginForm({ altAuthSlot }) {
131
131
  console.log('[LOGIN] SessionExpired detected - normal behavior');
132
132
  friendlyError = null;
133
133
  }
134
+ else if (urlError === 'TwoFactorTimeout') {
135
+ // PAY-884: the sign-in expired while the verification code was outstanding. Unlike
136
+ // SessionExpired this is NOT silent - the user was mid-2FA and must know why they are here.
137
+ friendlyError = 'Your verification step timed out before the code was confirmed. Please sign in again.';
138
+ }
134
139
  else if (urlError === 'ServiceUnavailable') {
135
140
  friendlyError = 'Authentication service is temporarily unavailable. Please try again in a few minutes.';
136
141
  }
@@ -302,7 +302,8 @@ function VerifyCodeForm({ enrollHref }) {
302
302
  sessionStorage.removeItem(VERIFY_IN_PROGRESS_KEY);
303
303
  }
304
304
  const safeCallback = callbackUrl.startsWith('/account-auth/') ? '/dashboard' : callbackUrl;
305
- const errorParam = isJwtExpired ? '&error=SessionExpired' : '';
305
+ // PAY-884: an expired JWT here is the provisional pre-2FA token - say so on the login page.
306
+ const errorParam = isJwtExpired ? '&error=TwoFactorTimeout' : '';
306
307
  router.push(`/account-auth/login?callbackUrl=${encodeURIComponent(safeCallback)}${errorParam}`);
307
308
  }, 1500);
308
309
  return;
@@ -366,7 +367,8 @@ function VerifyCodeForm({ enrollHref }) {
366
367
  sessionStorage.removeItem(VERIFY_IN_PROGRESS_KEY);
367
368
  }
368
369
  const safeCallback = callbackUrl.startsWith('/account-auth/') ? '/dashboard' : callbackUrl;
369
- const errorParam = isJwtExpired ? '&error=SessionExpired' : '';
370
+ // PAY-884: an expired JWT here is the provisional pre-2FA token - say so on the login page.
371
+ const errorParam = isJwtExpired ? '&error=TwoFactorTimeout' : '';
370
372
  router.push(`/account-auth/login?callbackUrl=${encodeURIComponent(safeCallback)}${errorParam}`);
371
373
  }, 1500);
372
374
  return;
@@ -31,6 +31,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.GET = GET;
32
32
  const server_1 = require("next/server");
33
33
  const idp_client_config_1 = require("../../lib/idp-client-config");
34
+ const never_in_response_1 = require("../../lib/never-in-response");
34
35
  /**
35
36
  * Fail closed and LOUD. 503 + success:false + a code, and CRUCIALLY no policy
36
37
  * fields at all - not defaults, not partials. A consumer cannot mistake an
@@ -105,5 +106,7 @@ async function GET() {
105
106
  require2FA: authSettings.require2FA,
106
107
  allowed2FAMethods: authSettings.allowed2FAMethods,
107
108
  };
108
- return server_1.NextResponse.json({ success: true, data: settings }, { headers: { 'Cache-Control': 'no-store' } });
109
+ // PAY-1529: the public policy is built from an allowlist above; secretSafeJson is the runtime backstop
110
+ // that answers 500 instead of serving it if a registered secret ever reaches this body anyway.
111
+ return (0, never_in_response_1.secretSafeJson)({ success: true, data: settings }, { headers: { 'Cache-Control': 'no-store' } }, 'GET /api/auth/settings');
109
112
  }
@@ -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
51
  const verified_session_token_1 = require("./verified-session-token");
53
- const single_flight_1 = require("../lib/single-flight");
54
- let authInstance = null;
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.