@payez/next-mvp 4.0.45 → 4.0.47

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 (61) 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/auth/refresh.d.ts +4 -6
  4. package/dist/api-handlers/auth/refresh.js +5 -7
  5. package/dist/api-handlers/auth/signout.d.ts +6 -15
  6. package/dist/api-handlers/auth/signout.js +9 -16
  7. package/dist/api-handlers/auth/update-session.d.ts +6 -15
  8. package/dist/api-handlers/auth/update-session.js +7 -15
  9. package/dist/api-handlers/auth/verify-code.d.ts +6 -15
  10. package/dist/api-handlers/auth/verify-code.js +7 -15
  11. package/dist/api-handlers/session/viability.js +2 -2
  12. package/dist/auth/better-auth.d.ts +3 -19
  13. package/dist/auth/better-auth.js +7 -13
  14. package/dist/client/better-auth-client.d.ts +7 -8
  15. package/dist/client/better-auth-client.js +3 -4
  16. package/dist/lib/auth-secret.d.ts +17 -0
  17. package/dist/lib/{nextauth-secret.js → auth-secret.js} +31 -15
  18. package/dist/lib/demo-mode.js +3 -1
  19. package/dist/lib/idp-client-config.d.ts +6 -2
  20. package/dist/lib/idp-client-config.js +35 -21
  21. package/dist/lib/secret-validation.d.ts +1 -1
  22. package/dist/lib/secret-validation.js +2 -2
  23. package/dist/lib/startup-init.d.ts +3 -3
  24. package/dist/lib/startup-init.js +23 -18
  25. package/dist/lib/test-aware-get-token.js +2 -2
  26. package/dist/routes/account/masked-info.d.ts +1 -1
  27. package/dist/routes/account/masked-info.js +1 -1
  28. package/dist/routes/account/send-code.d.ts +1 -1
  29. package/dist/routes/account/send-code.js +1 -1
  30. package/dist/routes/account/verify-email.d.ts +1 -1
  31. package/dist/routes/account/verify-email.js +1 -1
  32. package/dist/routes/account/verify-sms.d.ts +1 -1
  33. package/dist/routes/account/verify-sms.js +1 -1
  34. package/dist/routes/auth/refresh.js +3 -8
  35. package/dist/server/auth.d.ts +4 -7
  36. package/dist/server/auth.js +3 -6
  37. package/dist/server/decode-session.js +2 -2
  38. package/dist/vibe/hooks/index.d.ts +1 -1
  39. package/package.json +888 -893
  40. package/src/api/auth-handler.ts +0 -4
  41. package/src/api-handlers/auth/refresh.ts +5 -8
  42. package/src/api-handlers/auth/signout.ts +9 -21
  43. package/src/api-handlers/auth/update-session.ts +7 -20
  44. package/src/api-handlers/auth/verify-code.ts +7 -20
  45. package/src/api-handlers/session/viability.ts +2 -2
  46. package/src/auth/better-auth.ts +7 -32
  47. package/src/client/better-auth-client.ts +3 -4
  48. package/src/lib/{nextauth-secret.ts → auth-secret.ts} +32 -16
  49. package/src/lib/demo-mode.ts +5 -1
  50. package/src/lib/idp-client-config.ts +42 -22
  51. package/src/lib/secret-validation.ts +1 -1
  52. package/src/lib/startup-init.ts +23 -18
  53. package/src/lib/test-aware-get-token.ts +2 -2
  54. package/src/routes/account/masked-info.ts +1 -1
  55. package/src/routes/account/send-code.ts +1 -1
  56. package/src/routes/account/verify-email.ts +1 -1
  57. package/src/routes/account/verify-sms.ts +1 -1
  58. package/src/routes/auth/refresh.ts +3 -8
  59. package/src/server/auth.ts +3 -6
  60. package/src/server/decode-session.ts +2 -2
  61. package/dist/lib/nextauth-secret.d.ts +0 -10
@@ -50,9 +50,6 @@ export interface AuthHandlerOptions {
50
50
  /** Maximum number of retry attempts on 401 (default: 1) */
51
51
  maxRetries?: number;
52
52
 
53
- /** NextAuth secret for JWT decoding */
54
- nextAuthSecret?: string;
55
-
56
53
  /** IDP base URL for refresh requests */
57
54
  idpBaseUrl?: string;
58
55
 
@@ -99,7 +96,6 @@ export function createAuthHandler(options: AuthHandlerOptions = {}) {
99
96
  refreshBuffer = 60, // 60 seconds - matches website-membership proven threshold
100
97
  retryOn401 = true,
101
98
  maxRetries = 1,
102
- nextAuthSecret = process.env.NEXTAUTH_SECRET,
103
99
  idpBaseUrl = process.env.IDP_URL,
104
100
  clientId = process.env.CLIENT_ID || process.env.NEXT_PUBLIC_IDP_CLIENT_ID
105
101
  } = options;
@@ -4,7 +4,7 @@
4
4
  * ASK BEFORE EDITING - TESTED AND WORKING SYSTEM
5
5
  *
6
6
  * This handler manages the server-side refresh token cycle with:
7
- * - NextAuth JWT token extraction
7
+ * - Better Auth session extraction
8
8
  * - Session token fallback for internal calls
9
9
  * - PayEz IDP refresh token exchange
10
10
  * - Session state updates with new tokens
@@ -23,14 +23,13 @@ import { extractKidFromToken } from '../../auth/utils/token-utils';
23
23
  interface RefreshConfig {
24
24
  idpBaseUrl: string;
25
25
  clientId: string;
26
- nextAuthSecret: string;
27
26
  refreshEndpoint?: string;
28
27
  }
29
28
 
30
29
  /**
31
30
  * Creates a refresh token handler for Next.js API routes
32
31
  *
33
- * @param config Configuration for IDP connection and NextAuth
32
+ * @param config IDP connection settings (Better Auth handles session crypto)
34
33
  * @returns Next.js POST handler function
35
34
  *
36
35
  * @example
@@ -41,13 +40,12 @@ interface RefreshConfig {
41
40
  * export const POST = createRefreshHandler({
42
41
  * idpBaseUrl: process.env.IDP_URL!,
43
42
  * clientId: process.env.CLIENT_ID!,
44
- * nextAuthSecret: process.env.NEXTAUTH_SECRET!,
45
43
  * refreshEndpoint: '/api/ExternalAuth/refresh'
46
44
  * });
47
45
  * ```
48
46
  */
49
47
  export function createRefreshHandler(config: RefreshConfig) {
50
- const { idpBaseUrl, clientId, nextAuthSecret, refreshEndpoint = '/api/ExternalAuth/refresh' } = config;
48
+ const { idpBaseUrl, clientId, refreshEndpoint = '/api/ExternalAuth/refresh' } = config;
51
49
 
52
50
  return async function POST(req: NextRequest) {
53
51
  try {
@@ -674,12 +672,11 @@ export function createRefreshHandler(config: RefreshConfig) {
674
672
  }
675
673
 
676
674
  /**
677
- * Default export for backward compatibility
678
- * Requires environment variables: IDP_URL, CLIENT_ID, NEXTAUTH_SECRET
675
+ * Default POST export — drop-in for `app/api/auth/refresh/route.ts`.
676
+ * Requires environment variables: IDP_URL, CLIENT_ID
679
677
  */
680
678
  export const POST = createRefreshHandler({
681
679
  idpBaseUrl: process.env.IDP_URL!,
682
680
  clientId: process.env.CLIENT_ID || 'payez_default_client',
683
- nextAuthSecret: process.env.NEXTAUTH_SECRET || '',
684
681
  refreshEndpoint: '/api/ExternalAuth/refresh'
685
682
  });
@@ -57,29 +57,19 @@ interface SignoutResponse {
57
57
  chunkCookiesDeleted?: number;
58
58
  }
59
59
 
60
- interface SignoutConfig {
61
- nextAuthSecret: string;
62
- }
63
-
64
60
  /**
65
- * Creates a signout handler for Next.js API routes
61
+ * Creates a signout handler for Next.js API routes.
66
62
  *
67
- * @param config Configuration for NextAuth
68
- * @returns Next.js POST handler function
63
+ * Better Auth resolves its session from cookies, so this handler takes no
64
+ * configuration. Use the default `POST` export below for typical usage.
69
65
  *
70
66
  * @example
71
67
  * ```typescript
72
68
  * // In your app's /app/api/auth/signout/route.ts
73
- * import { createSignoutHandler } from '@payez/next-mvp/api-handlers/auth/signout';
74
- *
75
- * export const POST = createSignoutHandler({
76
- * nextAuthSecret: process.env.NEXTAUTH_SECRET!
77
- * });
69
+ * export { POST } from '@payez/next-mvp/api-handlers/auth/signout';
78
70
  * ```
79
71
  */
80
- export function createSignoutHandler(config: SignoutConfig) {
81
- const { nextAuthSecret } = config;
82
-
72
+ export function createSignoutHandler() {
83
73
  return async function POST(req: NextRequest) {
84
74
  const cookieStore = await cookies();
85
75
 
@@ -113,7 +103,8 @@ export function createSignoutHandler(config: SignoutConfig) {
113
103
  const chunkCookies = cookieStore.getAll()
114
104
  .filter(cookie => cookie.name.startsWith(`${sessionCookieName}.`));
115
105
 
116
- // Decode NextAuth JWT to extract the Redis session UUID before deletion
106
+ // Decode the Better Auth session JWT to extract the Redis session UUID
107
+ // before deletion.
117
108
  let redisSessionToken: string | null = null;
118
109
 
119
110
  // First attempt: Better Auth getSession
@@ -203,9 +194,6 @@ export function createSignoutHandler(config: SignoutConfig) {
203
194
  }
204
195
 
205
196
  /**
206
- * Default export for backward compatibility
207
- * Requires environment variable: NEXTAUTH_SECRET
197
+ * Default POST export — drop-in for `app/api/auth/signout/route.ts`.
208
198
  */
209
- export const POST = createSignoutHandler({
210
- nextAuthSecret: process.env.NEXTAUTH_SECRET || ''
211
- });
199
+ export const POST = createSignoutHandler();
@@ -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]/];