@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
@@ -30,7 +30,7 @@ export { POST } from '../../api-handlers/account/verify-sms';
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
@@ -15,11 +15,9 @@
15
15
  */
16
16
 
17
17
  import { createRefreshHandler } from '../../api-handlers/auth/refresh';
18
- import { getIDPClientConfig } from '../../lib/idp-client-config';
19
18
 
20
- // Configuration is read at runtime from IDP config (cached)
21
- async function getConfig() {
22
- const idpConfig = await getIDPClientConfig();
19
+ // Configuration is read at runtime from environment.
20
+ function getConfig() {
23
21
  const idpBaseUrl = process.env.IDP_URL;
24
22
  if (!idpBaseUrl) {
25
23
  throw new Error('[IDP_URL] FATAL: IDP_URL environment variable is REQUIRED.');
@@ -27,7 +25,6 @@ async function getConfig() {
27
25
  return {
28
26
  idpBaseUrl,
29
27
  clientId: process.env.CLIENT_ID || process.env.NEXT_PUBLIC_IDP_CLIENT_ID || '',
30
- nextAuthSecret: idpConfig.nextAuthSecret || '',
31
28
  refreshEndpoint: process.env.REFRESH_ENDPOINT || '/api/ExternalAuth/refresh',
32
29
  };
33
30
  }
@@ -38,7 +35,6 @@ async function getConfig() {
38
35
  * Environment variables used:
39
36
  * - IDP_URL (REQUIRED)
40
37
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
41
- * - NEXTAUTH_SECRET (required)
42
38
  * - REFRESH_ENDPOINT (default: /api/ExternalAuth/refresh)
43
39
  */
44
40
  let _handler: ReturnType<typeof createRefreshHandler> | null = null;
@@ -47,8 +43,7 @@ import { NextRequest } from 'next/server';
47
43
 
48
44
  export async function POST(req: NextRequest) {
49
45
  if (!_handler) {
50
- const config = await getConfig();
51
- _handler = createRefreshHandler(config);
46
+ _handler = createRefreshHandler(getConfig());
52
47
  }
53
48
  return _handler(req);
54
49
  }
@@ -1,20 +1,83 @@
1
1
  /**
2
- * Server-side auth utilities for Better Auth (v4.0)
2
+ * Server-side auth utilities for Better Auth.
3
3
  *
4
- * Replaces:
5
- * - getToken() from next-auth/jwt
6
- * - getServerSession() from next-auth
7
- *
8
- * All server-side auth flows go through the Better Auth instance.
4
+ * All server-side auth flows go through the Better Auth instance returned by
5
+ * getAuthInstance(); use getSession(req) for the request-scoped session.
9
6
  */
10
7
 
11
8
  import 'server-only';
12
9
  import { createBetterAuthInstance } from '../auth/better-auth';
13
10
  import { getIDPClientConfig } from '../lib/idp-client-config';
11
+ import {
12
+ getSession as getRedisSession,
13
+ getBetterAuthSession as getBetterAuthRedisSession,
14
+ type SessionData,
15
+ } from '../lib/session-store';
14
16
 
15
17
  let authInstance: ReturnType<typeof createBetterAuthInstance> | null = null;
16
18
  let authInitPromise: Promise<ReturnType<typeof createBetterAuthInstance>> | null = null;
17
19
 
20
+ export type IdpTokenResult =
21
+ | { success: true; accessToken: string; sessionData: SessionData }
22
+ | { success: false; error: 'NO_SESSION' | 'NO_TOKEN'; terminal: true };
23
+
24
+ function buildSessionDataFromAuthSession(session: any): SessionData | null {
25
+ const user = session?.user;
26
+ if (!user?.id && !user?.email) {
27
+ return null;
28
+ }
29
+
30
+ const expiresAt = session?.session?.expiresAt
31
+ ? new Date(session.session.expiresAt).getTime()
32
+ : Date.now() + 24 * 60 * 60 * 1000;
33
+
34
+ return {
35
+ userId: user.userId || user.id || '',
36
+ email: user.email || '',
37
+ name: user.name || undefined,
38
+ roles: Array.isArray(user.roles) ? user.roles : [],
39
+ idpAccessToken: user.idpAccessToken,
40
+ idpRefreshToken: user.idpRefreshToken,
41
+ idpAccessTokenExpires: user.idpAccessTokenExpires || expiresAt,
42
+ mfaVerified: user.mfaVerified ?? user.twoFactorSessionVerified ?? false,
43
+ oauthProvider: user.oauthProvider,
44
+ idpClientId: user.idpClientId,
45
+ merchantId: user.merchantId,
46
+ };
47
+ }
48
+
49
+ function attachSessionData(session: any, sessionData: SessionData | null, sessionToken?: string) {
50
+ if (!sessionData) {
51
+ return session;
52
+ }
53
+
54
+ const enrichedSessionData = {
55
+ ...sessionData,
56
+ ...(sessionToken ? { sessionToken } : {}),
57
+ };
58
+
59
+ (session as any).sessionData = enrichedSessionData;
60
+
61
+ if (session?.user) {
62
+ const user = session.user as any;
63
+ user.userId = enrichedSessionData.userId || user.userId;
64
+ user.email = enrichedSessionData.email || user.email;
65
+ user.name = enrichedSessionData.name || user.name;
66
+ user.roles = enrichedSessionData.roles || user.roles || [];
67
+ user.idpAccessToken = enrichedSessionData.idpAccessToken;
68
+ user.idpRefreshToken = enrichedSessionData.idpRefreshToken;
69
+ user.idpAccessTokenExpires = enrichedSessionData.idpAccessTokenExpires;
70
+ user.mfaVerified = enrichedSessionData.mfaVerified;
71
+ user.twoFactorSessionVerified =
72
+ enrichedSessionData.mfaVerified ?? user.twoFactorSessionVerified;
73
+ user.oauthProvider = enrichedSessionData.oauthProvider || user.oauthProvider;
74
+ user.idpClientId = enrichedSessionData.idpClientId || user.idpClientId;
75
+ user.merchantId = enrichedSessionData.merchantId || user.merchantId;
76
+ }
77
+
78
+ return session;
79
+ }
80
+
18
81
  /**
19
82
  * Get the initialized Better Auth instance (singleton).
20
83
  */
@@ -43,31 +106,75 @@ export async function getSession(request?: Request): Promise<any> {
43
106
  const session = await auth.api.getSession({ headers: request.headers });
44
107
  if (!session?.session?.token || !session?.user) return session;
45
108
 
46
- // Enrich with IDP tokens from Redis (stored by post-login hook)
109
+ const sessionToken = session.session.token as string;
110
+ let sessionData: SessionData | null = null;
111
+
112
+ // Prefer the app's normalized Redis session. Fall back to Better Auth's
113
+ // secondary storage record, then finally to whatever Better Auth already
114
+ // put on the request session object.
47
115
  try {
48
- const { getRedis } = await import('../lib/redis');
49
- const { getAppSlug } = await import('../lib/app-slug');
50
- const baKey = `ba:${getAppSlug()}:${session.session.token}`;
51
- const baRaw = await getRedis().get(baKey);
52
- if (baRaw) {
53
- const baData = JSON.parse(baRaw);
54
- if (baData.idpTokens) {
55
- const u = session.user as any;
56
- u.roles = baData.idpTokens.roles || [];
57
- u.userId = baData.idpTokens.userId;
58
- u.idpAccessToken = baData.idpTokens.idpAccessToken;
59
- u.idpRefreshToken = baData.idpTokens.idpRefreshToken;
60
- u.idpAccessTokenExpires = baData.idpTokens.idpAccessTokenExpires;
61
- }
116
+ sessionData = await getRedisSession(sessionToken);
117
+ if (!sessionData) {
118
+ sessionData = await getBetterAuthRedisSession(sessionToken);
62
119
  }
63
120
  } catch { /* Redis unavailable */ }
64
121
 
65
- return session;
122
+ if (!sessionData) {
123
+ sessionData = buildSessionDataFromAuthSession(session);
124
+ }
125
+
126
+ return attachSessionData(session, sessionData, sessionToken);
66
127
  } catch {
67
128
  return null;
68
129
  }
69
130
  }
70
131
 
132
+ /**
133
+ * Get normalized session data for the current request.
134
+ *
135
+ * This prefers the app's Redis session because it carries the canonical
136
+ * IDP token, roles, and tenant-specific user identity used by app routes.
137
+ */
138
+ export async function getSessionData(request?: Request): Promise<SessionData | null> {
139
+ const session = await getSession(request);
140
+ const sessionData =
141
+ ((session as any)?.sessionData as SessionData | undefined) ||
142
+ buildSessionDataFromAuthSession(session);
143
+
144
+ if (!sessionData) {
145
+ return null;
146
+ }
147
+
148
+ const sessionToken = session?.session?.token as string | undefined;
149
+ return sessionToken
150
+ ? { ...sessionData, sessionToken }
151
+ : sessionData;
152
+ }
153
+
154
+ /**
155
+ * Get the current request's IDP access token without triggering a refresh.
156
+ *
157
+ * Use this for routes that only need the currently-issued bearer token and
158
+ * should fail closed instead of performing token lifecycle work.
159
+ */
160
+ export async function getIdpToken(request?: Request): Promise<IdpTokenResult> {
161
+ const sessionData = await getSessionData(request);
162
+ if (!sessionData) {
163
+ return { success: false, error: 'NO_SESSION', terminal: true };
164
+ }
165
+
166
+ const accessToken = sessionData.idpAccessToken || (sessionData as any).accessToken;
167
+ if (!accessToken) {
168
+ return { success: false, error: 'NO_TOKEN', terminal: true };
169
+ }
170
+
171
+ return {
172
+ success: true,
173
+ accessToken,
174
+ sessionData,
175
+ };
176
+ }
177
+
71
178
  /**
72
179
  * Get the current session, throwing if not authenticated.
73
180
  * Use in API handlers that require auth.
@@ -152,9 +152,9 @@ export async function decodeSession(
152
152
  }
153
153
 
154
154
  const config = await getIDPClientConfig();
155
- const secret = config.nextAuthSecret;
155
+ const secret = config.authSecret;
156
156
  if (!secret) {
157
- console.error('[DECODE-SESSION] No nextAuthSecret available from IDP config');
157
+ console.error('[DECODE-SESSION] No authSecret available from IDP config');
158
158
  return null;
159
159
  }
160
160
 
@@ -1,10 +0,0 @@
1
- import 'server-only';
2
- /**
3
- * Resolve the NextAuth secret (server-only).
4
- *
5
- * Priority:
6
- * 1) Use process.env.NEXTAUTH_SECRET if present (allows overrides/production)
7
- * 2) Fetch from IDP broker endpoint - IDP handles all Key Vault/signing
8
- * 3) Cache result in-memory and set process.env.NEXTAUTH_SECRET for subsequent calls
9
- */
10
- export declare function resolveNextAuthSecret(): Promise<string>;