@payez/next-mvp 4.0.46 → 4.0.49

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 (63) hide show
  1. package/dist/api/auth-handler.d.ts +0 -2
  2. package/dist/api/auth-handler.js +1 -1
  3. package/dist/api-handlers/admin/stats.js +24 -14
  4. package/dist/api-handlers/auth/refresh.d.ts +4 -6
  5. package/dist/api-handlers/auth/refresh.js +5 -7
  6. package/dist/api-handlers/auth/signout.d.ts +6 -15
  7. package/dist/api-handlers/auth/signout.js +9 -16
  8. package/dist/api-handlers/auth/update-session.d.ts +6 -15
  9. package/dist/api-handlers/auth/update-session.js +7 -15
  10. package/dist/api-handlers/auth/verify-code.d.ts +6 -15
  11. package/dist/api-handlers/auth/verify-code.js +7 -15
  12. package/dist/api-handlers/session/viability.js +2 -2
  13. package/dist/auth/better-auth.d.ts +3 -19
  14. package/dist/auth/better-auth.js +7 -13
  15. package/dist/client/better-auth-client.d.ts +7 -8
  16. package/dist/client/better-auth-client.js +3 -4
  17. package/dist/lib/auth-secret.d.ts +17 -0
  18. package/dist/lib/{nextauth-secret.js → auth-secret.js} +31 -15
  19. package/dist/lib/demo-mode.js +3 -1
  20. package/dist/lib/idp-client-config.d.ts +6 -2
  21. package/dist/lib/idp-client-config.js +35 -21
  22. package/dist/lib/secret-validation.d.ts +1 -1
  23. package/dist/lib/secret-validation.js +2 -2
  24. package/dist/lib/startup-init.d.ts +3 -3
  25. package/dist/lib/startup-init.js +23 -18
  26. package/dist/lib/test-aware-get-token.js +2 -51
  27. package/dist/routes/account/masked-info.d.ts +1 -1
  28. package/dist/routes/account/masked-info.js +1 -1
  29. package/dist/routes/account/send-code.d.ts +1 -1
  30. package/dist/routes/account/send-code.js +1 -1
  31. package/dist/routes/account/verify-email.d.ts +1 -1
  32. package/dist/routes/account/verify-email.js +1 -1
  33. package/dist/routes/account/verify-sms.d.ts +1 -1
  34. package/dist/routes/account/verify-sms.js +1 -1
  35. package/dist/routes/auth/refresh.js +3 -8
  36. package/dist/server/auth.d.ts +28 -7
  37. package/dist/server/auth.js +106 -55
  38. package/dist/server/decode-session.js +2 -2
  39. package/dist/vibe/hooks/index.d.ts +1 -1
  40. package/package.json +888 -893
  41. package/src/api/auth-handler.ts +0 -4
  42. package/src/api-handlers/admin/stats.ts +249 -238
  43. package/src/api-handlers/auth/refresh.ts +5 -8
  44. package/src/api-handlers/auth/signout.ts +9 -21
  45. package/src/api-handlers/auth/update-session.ts +7 -20
  46. package/src/api-handlers/auth/verify-code.ts +7 -20
  47. package/src/api-handlers/session/viability.ts +2 -2
  48. package/src/auth/better-auth.ts +7 -32
  49. package/src/client/better-auth-client.ts +3 -4
  50. package/src/lib/{nextauth-secret.ts → auth-secret.ts} +32 -16
  51. package/src/lib/demo-mode.ts +5 -1
  52. package/src/lib/idp-client-config.ts +42 -22
  53. package/src/lib/secret-validation.ts +1 -1
  54. package/src/lib/startup-init.ts +23 -18
  55. package/src/lib/test-aware-get-token.ts +2 -51
  56. package/src/routes/account/masked-info.ts +1 -1
  57. package/src/routes/account/send-code.ts +1 -1
  58. package/src/routes/account/verify-email.ts +1 -1
  59. package/src/routes/account/verify-sms.ts +1 -1
  60. package/src/routes/auth/refresh.ts +3 -8
  61. package/src/server/auth.ts +129 -22
  62. package/src/server/decode-session.ts +2 -2
  63. package/dist/lib/nextauth-secret.d.ts +0 -10
@@ -38,29 +38,19 @@ interface UpdateSessionResponse {
38
38
  twoFactorMethod?: string;
39
39
  }
40
40
 
41
- interface UpdateSessionConfig {
42
- nextAuthSecret?: string; // Legacy - no longer used by Better Auth
43
- }
44
-
45
41
  /**
46
- * Creates an update-session handler for Next.js API routes
42
+ * Creates an update-session handler for Next.js API routes.
47
43
  *
48
- * @param config Configuration for NextAuth
49
- * @returns Next.js POST handler function
44
+ * Better Auth resolves its session from cookies, so this handler takes no
45
+ * configuration. Use the default `POST` export below for typical usage.
50
46
  *
51
47
  * @example
52
48
  * ```typescript
53
49
  * // In your app's /app/api/auth/update-session/route.ts
54
- * import { createUpdateSessionHandler } from '@payez/next-mvp/api-handlers/auth/update-session';
55
- *
56
- * export const POST = createUpdateSessionHandler({
57
- * nextAuthSecret: process.env.NEXTAUTH_SECRET!
58
- * });
50
+ * export { POST } from '@payez/next-mvp/api-handlers/auth/update-session';
59
51
  * ```
60
52
  */
61
- export function createUpdateSessionHandler(config: UpdateSessionConfig) {
62
- const { nextAuthSecret } = config;
63
-
53
+ export function createUpdateSessionHandler() {
64
54
  return async function POST(req: NextRequest) {
65
55
  try {
66
56
  let body: UpdateSessionRequest;
@@ -115,9 +105,6 @@ export function createUpdateSessionHandler(config: UpdateSessionConfig) {
115
105
  }
116
106
 
117
107
  /**
118
- * Default export for backward compatibility
119
- * Requires environment variable: NEXTAUTH_SECRET
108
+ * Default POST export — drop-in for `app/api/auth/update-session/route.ts`.
120
109
  */
121
- export const POST = createUpdateSessionHandler({
122
- nextAuthSecret: process.env.NEXTAUTH_SECRET || ''
123
- });
110
+ export const POST = createUpdateSessionHandler();
@@ -20,29 +20,19 @@ interface VerifyCodeRequest {
20
20
  refreshTokenExpires?: number;
21
21
  }
22
22
 
23
- interface VerifyCodeConfig {
24
- nextAuthSecret?: string; // Legacy - no longer used by Better Auth
25
- }
26
-
27
23
  /**
28
- * Creates a verify-code/complete-2FA handler for Next.js API routes
24
+ * Creates a verify-code/complete-2FA handler for Next.js API routes.
29
25
  *
30
- * @param config Configuration for NextAuth
31
- * @returns Next.js POST handler function
26
+ * Better Auth resolves its session from cookies, so this handler takes no
27
+ * configuration. Use the default `POST` export below for typical usage.
32
28
  *
33
29
  * @example
34
30
  * ```typescript
35
31
  * // In your app's /app/api/auth/verify-code/route.ts
36
- * import { createVerifyCodeHandler } from '@payez/next-mvp/api-handlers/auth/verify-code';
37
- *
38
- * export const POST = createVerifyCodeHandler({
39
- * nextAuthSecret: process.env.NEXTAUTH_SECRET!
40
- * });
32
+ * export { POST } from '@payez/next-mvp/api-handlers/auth/verify-code';
41
33
  * ```
42
34
  */
43
- export function createVerifyCodeHandler(config: VerifyCodeConfig) {
44
- const { nextAuthSecret } = config;
45
-
35
+ export function createVerifyCodeHandler() {
46
36
  return async function POST(req: NextRequest) {
47
37
  try {
48
38
  let body: VerifyCodeRequest;
@@ -117,9 +107,6 @@ export function createVerifyCodeHandler(config: VerifyCodeConfig) {
117
107
  }
118
108
 
119
109
  /**
120
- * Default export for backward compatibility
121
- * Requires environment variable: NEXTAUTH_SECRET
110
+ * Default POST export — drop-in for `app/api/auth/verify-code/route.ts`.
122
111
  */
123
- export const POST = createVerifyCodeHandler({
124
- nextAuthSecret: process.env.NEXTAUTH_SECRET || ''
125
- });
112
+ export const POST = createVerifyCodeHandler();
@@ -12,8 +12,8 @@ import { getIDPClientConfig } from '../../lib/idp-client-config';
12
12
 
13
13
  export async function GET(req: NextRequest) {
14
14
  try {
15
- // Ensure initialization is complete
16
- if (!process.env.NEXTAUTH_SECRET) {
15
+ // Ensure initialization is complete (auth signing secret resolved from IDP)
16
+ if (!process.env.BETTER_AUTH_SECRET && !process.env.NEXTAUTH_SECRET) {
17
17
  try {
18
18
  await ensureInitialized();
19
19
  } catch (error) {
@@ -48,49 +48,25 @@ export function buildBetterAuthProviders(
48
48
  return providers;
49
49
  }
50
50
 
51
- /**
52
- * Optional extra plugins for createBetterAuthInstance.
53
- * Use to add credentials/custom signin endpoints from the host app.
54
- */
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- export type BetterAuthExtraPlugins = any[];
57
-
58
- /**
59
- * Optional overrides for createBetterAuthInstance.
60
- */
61
- export interface BetterAuthInstanceOptions {
62
- /** Path suffix for the BA mount (default: '/api/auth'). Use '/api/ba-auth' for migration scenarios. */
63
- basePath?: string;
64
- }
65
-
66
51
  /**
67
52
  * Create Better Auth instance from IDP config.
68
53
  *
69
54
  * No database — runs in stateless mode with JWE cookie cache.
70
55
  * Call after getIDPClientConfig() resolves.
71
- *
72
- * @param idpConfig IDP client config (from getIDPClientConfig)
73
- * @param extraPlugins Optional plugins to add (e.g., credentials plugin from host app)
74
- * @param options Optional overrides (e.g., basePath for migration scenarios)
75
56
  */
76
- export function createBetterAuthInstance(
77
- idpConfig: IDPClientConfig,
78
- extraPlugins: BetterAuthExtraPlugins = [],
79
- options: BetterAuthInstanceOptions = {}
80
- ) {
57
+ export function createBetterAuthInstance(idpConfig: IDPClientConfig) {
81
58
  const appSlug = idpConfig.clientSlug || getAppSlug();
82
- const basePath = options.basePath || '/api/auth';
83
59
 
84
60
  // Resolve base URL: BETTER_AUTH_URL env > IDP config > localhost fallback
85
- // basePath defaults to /api/auth but can be overridden via options for migration scenarios
61
+ // Must include /api/auth since that's where the catch-all route is mounted
86
62
  const rawBaseURL = process.env.BETTER_AUTH_URL
87
63
  || idpConfig.baseClientUrl
88
64
  || `http://localhost:${process.env.PORT || '3000'}`;
89
- const baseURL = rawBaseURL.replace(/\/+$/, '') + basePath;
65
+ const baseURL = rawBaseURL.replace(/\/+$/, '') + '/api/auth';
90
66
 
91
67
  return betterAuth({
92
68
  baseURL,
93
- secret: idpConfig.nextAuthSecret as string,
69
+ secret: idpConfig.authSecret as string,
94
70
 
95
71
  socialProviders: buildBetterAuthProviders(idpConfig),
96
72
 
@@ -147,7 +123,6 @@ export function createBetterAuthInstance(
147
123
  },
148
124
 
149
125
  plugins: [
150
- ...extraPlugins,
151
126
  nextCookies(),
152
127
  ],
153
128
  });
@@ -173,12 +148,12 @@ let initPromise: Promise<any> | null = null;
173
148
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
149
  export { cachedInstance as __betterAuthInstance };
175
150
 
176
- export async function getBetterAuthInstance(extraPlugins: BetterAuthExtraPlugins = []) {
151
+ export async function getBetterAuthInstance() {
177
152
  if (cachedInstance) return cachedInstance;
178
153
 
179
154
  if (!initPromise) {
180
155
  initPromise = getIDPClientConfig(true).then(config => {
181
- const instance = createBetterAuthInstance(config, extraPlugins);
156
+ const instance = createBetterAuthInstance(config);
182
157
  cachedInstance = instance;
183
158
  console.log('[BETTER_AUTH] Instance created for', config.clientSlug || config.clientId);
184
159
  return instance;
@@ -287,7 +262,7 @@ export async function exchangeOAuthForIdpTokens(
287
262
  }
288
263
 
289
264
  // Build IDP token data
290
- const requiresTwoFactor = result.requires_two_factor ?? result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
265
+ const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
291
266
  const idpTokenData = {
292
267
  idpAccessToken: result.access_token,
293
268
  idpRefreshToken: result.refresh_token,
@@ -1,11 +1,10 @@
1
1
  /**
2
- * Better Auth Client (Phase 3)
2
+ * Better Auth Client.
3
3
  *
4
- * Drop-in replacement for next-auth/react hooks and functions.
5
4
  * Import from '@payez/next-mvp/client/better-auth-client'.
6
5
  *
7
- * Includes useSessionCompat() — returns NextAuth-shaped { data, status }
8
- * so existing components don't need destructure pattern changes.
6
+ * Includes useSessionCompat() — returns a { data, status } shape so existing
7
+ * components written against the legacy hook don't need destructure changes.
9
8
  */
10
9
 
11
10
  import { createAuthClient } from 'better-auth/react';
@@ -1,23 +1,37 @@
1
1
  import 'server-only';
2
- import { validateNextAuthSecret } from './secret-validation';
2
+ import { validateAuthSecret } from './secret-validation';
3
3
  import { randomUUID } from 'crypto';
4
4
 
5
5
  let cachedSecret: string | null = null;
6
6
  let lastFetchedAt = 0;
7
7
 
8
8
  /**
9
- * Resolve the NextAuth secret (server-only).
9
+ * Resolve the Better Auth signing secret (server-only).
10
10
  *
11
11
  * Priority:
12
- * 1) Use process.env.NEXTAUTH_SECRET if present (allows overrides/production)
13
- * 2) Fetch from IDP broker endpoint - IDP handles all Key Vault/signing
14
- * 3) Cache result in-memory and set process.env.NEXTAUTH_SECRET for subsequent calls
12
+ * 1) Use process.env.BETTER_AUTH_SECRET (preferred) or NEXTAUTH_SECRET (legacy)
13
+ * if present allows overrides/production via env.
14
+ * 2) Fetch from IDP broker endpoint IDP handles all Key Vault/signing.
15
+ * 3) Cache result in-memory and set process.env.BETTER_AUTH_SECRET for
16
+ * subsequent calls.
17
+ *
18
+ * NOTE on naming: this secret is the cryptographic key Better Auth uses to
19
+ * sign session JWTs. The IDP backend still names the broker endpoint and
20
+ * response field with the legacy "next-auth" / "nextAuthSecret" names; we
21
+ * read both new and legacy on the wire during the migration window.
15
22
  */
16
- export async function resolveNextAuthSecret(): Promise<string> {
17
- // Check if already in environment
18
- if (process.env.NEXTAUTH_SECRET && process.env.NEXTAUTH_SECRET.trim() !== '') {
23
+ export async function resolveAuthSecret(): Promise<string> {
24
+ // Check if already in environment (prefer new name, fall back to legacy)
25
+ const envSecret =
26
+ (process.env.BETTER_AUTH_SECRET && process.env.BETTER_AUTH_SECRET.trim() !== ''
27
+ ? process.env.BETTER_AUTH_SECRET
28
+ : undefined) ||
29
+ (process.env.NEXTAUTH_SECRET && process.env.NEXTAUTH_SECRET.trim() !== ''
30
+ ? process.env.NEXTAUTH_SECRET
31
+ : undefined);
32
+ if (envSecret) {
19
33
  // Silent - already configured
20
- return process.env.NEXTAUTH_SECRET;
34
+ return envSecret;
21
35
  }
22
36
 
23
37
  // Check if cached and fresh (within 5 minutes)
@@ -74,7 +88,8 @@ export async function resolveNextAuthSecret(): Promise<string> {
74
88
  throw new Error('IDP did not return a valid signed client assertion');
75
89
  }
76
90
 
77
- // Step 2: Use the signed assertion to fetch the NextAuth secret
91
+ // Step 2: Use the signed assertion to fetch the auth secret
92
+ // (Endpoint is still served at /next-auth/secret on the IDP — legacy path.)
78
93
 
79
94
  const proxyUrl = new URL(`${base.replace(/\/$/, '')}/api/ExternalAuth/next-auth/secret`);
80
95
 
@@ -98,24 +113,25 @@ export async function resolveNextAuthSecret(): Promise<string> {
98
113
  const proxyBody: any = await proxyResp.json().catch(() => ({}));
99
114
 
100
115
  const secret = (proxyBody?.data?.secret ?? proxyBody?.secret) as string | undefined;
101
- const configuration = (proxyBody?.data?.configuration ?? proxyBody?.configuration) as any | undefined;
102
-
103
116
  // Configuration is available but we don't log it verbosely
104
117
 
105
118
  if (!secret || typeof secret !== 'string') {
106
- throw new Error('Proxy did not return a valid NextAuth secret');
119
+ throw new Error('Proxy did not return a valid auth secret');
107
120
  }
108
121
 
109
- const validation = validateNextAuthSecret(secret);
122
+ const validation = validateAuthSecret(secret);
110
123
  if (!validation.valid) {
111
- throw new Error(`Fetched NextAuth secret failed validation: ${validation.reason}`);
124
+ throw new Error(`Fetched auth secret failed validation: ${validation.reason}`);
112
125
  }
113
126
 
114
127
  cachedSecret = secret;
115
128
  lastFetchedAt = Date.now();
129
+ process.env.BETTER_AUTH_SECRET = secret;
130
+ // Also set legacy name during transition so any consumer still reading
131
+ // process.env.NEXTAUTH_SECRET keeps working until they upgrade.
116
132
  process.env.NEXTAUTH_SECRET = secret;
117
133
 
118
- console.log('[NEXTAUTH-SECRET] Resolved from IDP (length:', secret.length + ')');
134
+ console.log('[AUTH-SECRET] Resolved from IDP (length:', secret.length + ')');
119
135
 
120
136
  return secret;
121
137
  }
@@ -9,5 +9,9 @@ export function isDemoMode(): boolean {
9
9
 
10
10
  export function isAuthConfigured(): boolean {
11
11
  if (isDemoMode()) return false;
12
- return !!(process.env.NEXTAUTH_SECRET || (process.env.NEXT_CLIENT_ID && process.env.NEXT_CLIENT_PRIVATE_KEY_PEM));
12
+ return !!(
13
+ process.env.BETTER_AUTH_SECRET ||
14
+ process.env.NEXTAUTH_SECRET ||
15
+ (process.env.NEXT_CLIENT_ID && process.env.NEXT_CLIENT_PRIVATE_KEY_PEM)
16
+ );
13
17
  }
@@ -5,7 +5,7 @@
5
5
  * - OAuth provider credentials (from Key Vault)
6
6
  * - 2FA/MFA settings
7
7
  * - Session configuration
8
- * - NextAuth secret
8
+ * - Better Auth signing secret
9
9
  * - Branding
10
10
  *
11
11
  * CACHING STRATEGY:
@@ -58,7 +58,11 @@ export interface BrandingConfig {
58
58
  export interface IDPClientConfig {
59
59
  clientId: string;
60
60
  clientSlug: string;
61
- nextAuthSecret: string;
61
+ /**
62
+ * Cryptographic secret used by Better Auth to sign session JWTs.
63
+ * Historically named "nextAuthSecret" — kept under the new name now.
64
+ */
65
+ authSecret: string;
62
66
  configCacheTtlSeconds: number;
63
67
  oauthProviders: OAuthProviderConfig[];
64
68
  authSettings: AuthSettings;
@@ -170,14 +174,15 @@ export async function getIDPClientConfig(forceRefresh: boolean = false): Promise
170
174
  cachedConfig = redisConfig;
171
175
  cacheExpiry = Date.now() + ((redisConfig.configCacheTtlSeconds || 300) * 1000);
172
176
 
173
- // Set NEXTAUTH_SECRET from cached config
174
- if (redisConfig.nextAuthSecret) {
175
- process.env.NEXTAUTH_SECRET = redisConfig.nextAuthSecret;
177
+ // Set BETTER_AUTH_SECRET from cached config (also set legacy
178
+ // NEXTAUTH_SECRET during the rename transition).
179
+ if (redisConfig.authSecret) {
180
+ process.env.BETTER_AUTH_SECRET = redisConfig.authSecret;
181
+ process.env.NEXTAUTH_SECRET = redisConfig.authSecret;
176
182
  }
177
183
 
178
- // Set IDENTITY_CLIENT_BASE_EXTERNAL_URL from cached config
179
- // AUTH_TRUST_HOST=true tells NextAuth to derive OAuth callback URLs from headers.
180
- // Only set if not already defined (allows deployment override for beta/staging)
184
+ // Set IDENTITY_CLIENT_BASE_EXTERNAL_URL from cached config.
185
+ // Only set if not already defined (allows deployment override for beta/staging).
181
186
  if (redisConfig.baseClientUrl && !process.env.IDENTITY_CLIENT_BASE_EXTERNAL_URL) {
182
187
  process.env.IDENTITY_CLIENT_BASE_EXTERNAL_URL = redisConfig.baseClientUrl;
183
188
  }
@@ -211,16 +216,17 @@ export async function getIDPClientConfig(forceRefresh: boolean = false): Promise
211
216
  // Store in Redis for persistence across module reloads
212
217
  await setConfigInRedis(config);
213
218
 
214
- // Set NEXTAUTH_SECRET from config
215
- if (config.nextAuthSecret) {
216
- process.env.NEXTAUTH_SECRET = config.nextAuthSecret;
219
+ // Set BETTER_AUTH_SECRET from config (also set legacy
220
+ // NEXTAUTH_SECRET during the rename transition).
221
+ if (config.authSecret) {
222
+ process.env.BETTER_AUTH_SECRET = config.authSecret;
223
+ process.env.NEXTAUTH_SECRET = config.authSecret;
217
224
  } else {
218
- throw new Error('[IDP_CONFIG] FATAL: IDP did not return nextAuthSecret');
225
+ throw new Error('[IDP_CONFIG] FATAL: IDP did not return authSecret');
219
226
  }
220
227
 
221
- // Set IDENTITY_CLIENT_BASE_EXTERNAL_URL from config
222
- // AUTH_TRUST_HOST=true tells NextAuth to derive OAuth callback URLs from headers.
223
- // Only set if not already defined (allows deployment override for beta/staging)
228
+ // Set IDENTITY_CLIENT_BASE_EXTERNAL_URL from config.
229
+ // Only set if not already defined (allows deployment override for beta/staging).
224
230
  if (config.baseClientUrl && !process.env.IDENTITY_CLIENT_BASE_EXTERNAL_URL) {
225
231
  process.env.IDENTITY_CLIENT_BASE_EXTERNAL_URL = config.baseClientUrl;
226
232
  console.log("[IDP_CONFIG] Set IDENTITY_CLIENT_BASE_EXTERNAL_URL:", config.baseClientUrl);
@@ -305,7 +311,14 @@ async function fetchConfigFromInternalIDP(internalIdpUrl: string, clientIdStr: s
305
311
  const config: IDPClientConfig = {
306
312
  clientId: String(rawClientId),
307
313
  clientSlug: configData.clientSlug ?? configData.client_slug ?? configData.slug ?? '',
308
- nextAuthSecret: configData.nextAuthSecret ?? configData.next_auth_secret ?? '',
314
+ // Wire compatibility: accept new authSecret first, fall back to legacy
315
+ // nextAuthSecret/next_auth_secret while IDP rename rolls out.
316
+ authSecret:
317
+ configData.authSecret ??
318
+ configData.auth_secret ??
319
+ configData.nextAuthSecret ??
320
+ configData.next_auth_secret ??
321
+ '',
309
322
  configCacheTtlSeconds: configData.configCacheTtlSeconds ?? configData.config_cache_ttl_seconds ?? 300,
310
323
  oauthProviders: (configData.oauthProviders ?? configData.oauth_providers ?? []).map((p: any) => ({
311
324
  provider: p.provider ?? '',
@@ -336,8 +349,8 @@ async function fetchConfigFromInternalIDP(internalIdpUrl: string, clientIdStr: s
336
349
  baseClientUrl: configData.baseClientUrl ?? configData.base_client_url ?? configData.BaseClientUrl
337
350
  };
338
351
 
339
- if (!config.nextAuthSecret) {
340
- throw new Error('[IDP_CONFIG] FATAL: Internal IDP did not return nextAuthSecret');
352
+ if (!config.authSecret) {
353
+ throw new Error('[IDP_CONFIG] FATAL: Internal IDP did not return authSecret');
341
354
  }
342
355
 
343
356
  console.log(`[IDP_CONFIG] Internal IDP config loaded for ${clientIdStr}`);
@@ -464,11 +477,18 @@ async function fetchConfigFromIDP(idpUrl: string, clientIdStr: string): Promise<
464
477
  throw new Error(`[IDP_CONFIG] FATAL: IDP response missing clientId/client_id. Got: ${JSON.stringify(Object.keys(configData))}`);
465
478
  }
466
479
 
467
- // Map response to our interface (IDP always returns snake_case)
480
+ // Map response to our interface (IDP returns camelCase or snake_case).
481
+ // Wire compatibility: accept new authSecret first, fall back to legacy
482
+ // nextAuthSecret/next_auth_secret while IDP rename rolls out.
468
483
  const config: IDPClientConfig = {
469
484
  clientId: String(rawClientId),
470
485
  clientSlug: configData.clientSlug ?? configData.client_slug ?? configData.slug ?? '',
471
- nextAuthSecret: configData.nextAuthSecret ?? configData.next_auth_secret ?? '',
486
+ authSecret:
487
+ configData.authSecret ??
488
+ configData.auth_secret ??
489
+ configData.nextAuthSecret ??
490
+ configData.next_auth_secret ??
491
+ '',
472
492
  configCacheTtlSeconds: configData.configCacheTtlSeconds ?? configData.config_cache_ttl_seconds ?? 300,
473
493
  oauthProviders: (configData.oauthProviders ?? configData.oauth_providers ?? []).map((p: any) => ({
474
494
  provider: p.provider ?? '',
@@ -517,8 +537,8 @@ async function fetchConfigFromIDP(idpUrl: string, clientIdStr: string): Promise<
517
537
  if (!config.clientId) {
518
538
  throw new Error('[IDP_CONFIG] FATAL: clientId is empty or missing after parsing');
519
539
  }
520
- if (!config.nextAuthSecret) {
521
- throw new Error('[IDP_CONFIG] FATAL: nextAuthSecret is empty after parsing');
540
+ if (!config.authSecret) {
541
+ throw new Error('[IDP_CONFIG] FATAL: authSecret is empty after parsing');
522
542
  }
523
543
 
524
544
  // Success - reset failure tracking
@@ -1,4 +1,4 @@
1
- export function validateNextAuthSecret(secret: string): { valid: boolean; reason?: string } {
1
+ export function validateAuthSecret(secret: string): { valid: boolean; reason?: string } {
2
2
  if (!secret || typeof secret !== 'string') return { valid: false, reason: 'missing' };
3
3
  if (secret.length < 32) return { valid: false, reason: 'too_short' };
4
4
  const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^a-zA-Z0-9]/];
@@ -4,8 +4,8 @@
4
4
  * This module ensures that critical initialization tasks are completed
5
5
  * before the application serves requests.
6
6
  *
7
- * Now uses unified IDP client config for:
8
- * - NEXTAUTH_SECRET
7
+ * Uses unified IDP client config for:
8
+ * - BETTER_AUTH_SECRET (the Better Auth signing secret)
9
9
  * - OAuth provider configuration
10
10
  * - Auth settings (2FA, session timeouts, etc.)
11
11
  */
@@ -75,7 +75,7 @@ export function logStartupStatus(): void {
75
75
  console.log('║ 🚀 PayEz Next MVP - Starting Up ║');
76
76
  console.log('║ ║');
77
77
  console.log('║ Async initialization in progress... ║');
78
- console.log('║ - Resolving NEXTAUTH_SECRET from IDP ║');
78
+ console.log('║ - Resolving BETTER_AUTH_SECRET from IDP ║');
79
79
  console.log('║ - Verifying environment configuration ║');
80
80
  console.log('║ ║');
81
81
  console.log('║ Check logs below for detailed initialization status: ║');
@@ -117,16 +117,21 @@ async function performInitialization(): Promise<void> {
117
117
  console.log('[STARTUP] Client config loaded successfully');
118
118
  console.log('[STARTUP] - Client ID:', config.clientId);
119
119
  console.log('[STARTUP] - Client Slug:', config.clientSlug);
120
- console.log('[STARTUP] - Secret length:', config.nextAuthSecret?.length || 0, 'chars');
120
+ console.log('[STARTUP] - Secret length:', config.authSecret?.length || 0, 'chars');
121
121
  console.log('[STARTUP] - OAuth Providers:', config.oauthProviders?.filter(p => p.enabled).map(p => p.provider).join(', ') || 'none');
122
122
  console.log('[STARTUP] - Require 2FA:', config.authSettings?.require2FA);
123
123
  console.log('[STARTUP] - Cache TTL:', config.configCacheTtlSeconds, 'seconds');
124
124
  console.log('[STARTUP] - Base Client URL:', config.baseClientUrl || '(not set)');
125
125
 
126
- // Set NEXTAUTH_SECRET from IDP response if not already set
127
- if (config.nextAuthSecret && !process.env.NEXTAUTH_SECRET) {
128
- process.env.NEXTAUTH_SECRET = config.nextAuthSecret;
129
- console.log('[STARTUP] Set NEXTAUTH_SECRET from IDP config');
126
+ // Set BETTER_AUTH_SECRET from IDP response if not already set.
127
+ // Also mirror to legacy NEXTAUTH_SECRET during the rename transition
128
+ // so any consumer code still reading the old name keeps working.
129
+ if (config.authSecret && !process.env.BETTER_AUTH_SECRET) {
130
+ process.env.BETTER_AUTH_SECRET = config.authSecret;
131
+ console.log('[STARTUP] Set BETTER_AUTH_SECRET from IDP config');
132
+ }
133
+ if (config.authSecret && !process.env.NEXTAUTH_SECRET) {
134
+ process.env.NEXTAUTH_SECRET = config.authSecret;
130
135
  }
131
136
  } catch (error) {
132
137
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -136,16 +141,16 @@ async function performInitialization(): Promise<void> {
136
141
  console.error('[STARTUP] No fallback available for auth secret resolution');
137
142
  }
138
143
 
139
- // Step 2: Verify NEXTAUTH_SECRET is available - FAIL FAST if not
140
- console.log('[STARTUP] Step 2/2: Verifying NEXTAUTH_SECRET...');
144
+ // Step 2: Verify BETTER_AUTH_SECRET is available - FAIL FAST if not
145
+ console.log('[STARTUP] Step 2/2: Verifying BETTER_AUTH_SECRET...');
141
146
 
142
- const secret = process.env.NEXTAUTH_SECRET;
147
+ const secret = process.env.BETTER_AUTH_SECRET || process.env.NEXTAUTH_SECRET;
143
148
  if (!secret || secret.trim() === '') {
144
149
  console.error('');
145
150
  console.error('╔══════════════════════════════════════════════════════════════╗');
146
- console.error('║ ❌ FATAL: NEXTAUTH_SECRET NOT AVAILABLE ║');
151
+ console.error('║ ❌ FATAL: BETTER_AUTH_SECRET NOT AVAILABLE ║');
147
152
  console.error('║ ║');
148
- console.error('║ The app cannot start without a valid NEXTAUTH_SECRET. ║');
153
+ console.error('║ The app cannot start without a valid auth signing secret. ║');
149
154
  console.error('║ This should be fetched from IDP at startup. ║');
150
155
  console.error('║ ║');
151
156
  console.error('║ Possible causes: ║');
@@ -155,10 +160,10 @@ async function performInitialization(): Promise<void> {
155
160
  console.error('║ • Network connectivity issue ║');
156
161
  console.error('╚══════════════════════════════════════════════════════════════╝');
157
162
  console.error('');
158
- throw new Error('FATAL: NEXTAUTH_SECRET not available - cannot start without valid secret from IDP');
163
+ throw new Error('FATAL: BETTER_AUTH_SECRET not available - cannot start without valid secret from IDP');
159
164
  }
160
165
 
161
- console.log('[STARTUP] NEXTAUTH_SECRET verified (' + secret.length + ' chars)');
166
+ console.log('[STARTUP] BETTER_AUTH_SECRET verified (' + secret.length + ' chars)');
162
167
 
163
168
  // Step 3: Validate cookie name consistency
164
169
  // This catches bugs where getJwtCookieName() returns a different name than
@@ -192,9 +197,9 @@ async function performInitialization(): Promise<void> {
192
197
 
193
198
  console.error(`
194
199
  ╔══════════════════════════════════════════════════════════════╗
195
- ║ ❌ FATAL: NEXTAUTH_SECRET NOT AVAILABLE
200
+ ║ ❌ FATAL: BETTER_AUTH_SECRET NOT AVAILABLE
196
201
  ║ ║
197
- ║ The app cannot start without a valid NEXTAUTH_SECRET.
202
+ ║ The app cannot start without a valid auth signing secret.
198
203
  ║ This should be fetched from IDP at startup. ║
199
204
  ║ ║
200
205
  ${connectionLine}║ Possible causes: ║
@@ -225,7 +230,7 @@ export function getStartupIDPConfig(): IDPClientConfig | null {
225
230
  }
226
231
 
227
232
  /**
228
- * Check if initialization failed (NEXTAUTH_SECRET couldn't be retrieved)
233
+ * Check if initialization failed (auth signing secret couldn't be retrieved)
229
234
  */
230
235
  export function isInitializationFailed(): boolean {
231
236
  return initializationFailed;
@@ -6,11 +6,11 @@ import { getSessionCookieName } from './app-slug';
6
6
  export async function getTokenTestAware(req: NextRequest): Promise<any> {
7
7
  if (process.env.TEST_MODE === 'true') {
8
8
  try {
9
- let secret = process.env.NEXTAUTH_SECRET;
9
+ let secret = process.env.BETTER_AUTH_SECRET || process.env.NEXTAUTH_SECRET;
10
10
  if (!secret || secret.trim() === '') {
11
11
  const { getIDPClientConfig } = await import('./idp-client-config');
12
12
  const idpConfig = await getIDPClientConfig();
13
- secret = idpConfig.nextAuthSecret as string;
13
+ secret = idpConfig.authSecret;
14
14
  }
15
15
  // Use app-slug prefixed cookie name
16
16
  const cookieName = getSessionCookieName();
@@ -37,54 +37,5 @@ export async function getTokenTestAware(req: NextRequest): Promise<any> {
37
37
  };
38
38
  }
39
39
 
40
- // Fallback for legacy NextAuth-based sites: try next-auth/jwt with the
41
- // app-slug-prefixed cookie name. Uses runtime require so consumers without
42
- // next-auth installed are unaffected.
43
- try {
44
- // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-eval
45
- const dynamicRequire = eval('require') as NodeRequire;
46
- let nextAuthJwt: any = null;
47
- try {
48
- nextAuthJwt = dynamicRequire('next-auth/jwt');
49
- } catch {
50
- return null; // next-auth not installed → BA-only consumer
51
- }
52
- if (!nextAuthJwt?.getToken) return null;
53
-
54
- const { resolveNextAuthSecret } = await import('./nextauth-secret');
55
- const secret = await resolveNextAuthSecret();
56
- if (!secret) return null;
57
-
58
- const cookieName = getSessionCookieName();
59
- let nextAuthToken = await nextAuthJwt.getToken({
60
- req,
61
- secret,
62
- cookieName,
63
- secureCookie: false,
64
- });
65
- if (nextAuthToken) {
66
- logger.debug('[GET_TOKEN] Resolved via NextAuth JWT (cookieName=' + cookieName + ')');
67
- return nextAuthToken;
68
- }
69
-
70
- // Try secure cookie variant for production
71
- const { getSecureSessionCookieName } = await import('./app-slug');
72
- const secureCookieName = getSecureSessionCookieName();
73
- nextAuthToken = await nextAuthJwt.getToken({
74
- req,
75
- secret,
76
- cookieName: secureCookieName,
77
- secureCookie: true,
78
- });
79
- if (nextAuthToken) {
80
- logger.debug('[GET_TOKEN] Resolved via NextAuth JWT (secure cookie)');
81
- return nextAuthToken;
82
- }
83
- } catch (error) {
84
- logger.debug('[GET_TOKEN] NextAuth fallback error', {
85
- error: error instanceof Error ? error.message : String(error),
86
- });
87
- }
88
-
89
40
  return null;
90
41
  }
@@ -28,7 +28,7 @@ export { POST } from '../../api-handlers/account/masked-info';
28
28
  * Environment variables used:
29
29
  * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
30
30
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
31
- * - NEXTAUTH_SECRET (required)
31
+ * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
32
32
  *
33
33
  * Returns:
34
34
  * - Masked email addresses
@@ -31,7 +31,7 @@ export { POST } from '../../api-handlers/account/send-code';
31
31
  * Environment variables used:
32
32
  * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
33
33
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
34
- * - NEXTAUTH_SECRET (required)
34
+ * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
35
35
  *
36
36
  * Returns:
37
37
  * - Success status
@@ -30,7 +30,7 @@ export { POST } from '../../api-handlers/account/verify-email';
30
30
  * Environment variables used:
31
31
  * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
32
32
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
33
- * - NEXTAUTH_SECRET (required)
33
+ * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
34
34
  *
35
35
  * Returns:
36
36
  * - Upgraded access token with MFA claim