@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.
- package/dist/api/auth-handler.d.ts +0 -2
- package/dist/api/auth-handler.js +1 -1
- package/dist/api-handlers/admin/stats.js +24 -14
- package/dist/api-handlers/auth/refresh.d.ts +4 -6
- package/dist/api-handlers/auth/refresh.js +5 -7
- package/dist/api-handlers/auth/signout.d.ts +6 -15
- package/dist/api-handlers/auth/signout.js +9 -16
- package/dist/api-handlers/auth/update-session.d.ts +6 -15
- package/dist/api-handlers/auth/update-session.js +7 -15
- package/dist/api-handlers/auth/verify-code.d.ts +6 -15
- package/dist/api-handlers/auth/verify-code.js +7 -15
- package/dist/api-handlers/session/viability.js +2 -2
- package/dist/auth/better-auth.d.ts +3 -19
- package/dist/auth/better-auth.js +7 -13
- package/dist/client/better-auth-client.d.ts +7 -8
- package/dist/client/better-auth-client.js +3 -4
- package/dist/lib/auth-secret.d.ts +17 -0
- package/dist/lib/{nextauth-secret.js → auth-secret.js} +31 -15
- package/dist/lib/demo-mode.js +3 -1
- package/dist/lib/idp-client-config.d.ts +6 -2
- package/dist/lib/idp-client-config.js +35 -21
- package/dist/lib/secret-validation.d.ts +1 -1
- package/dist/lib/secret-validation.js +2 -2
- package/dist/lib/startup-init.d.ts +3 -3
- package/dist/lib/startup-init.js +23 -18
- package/dist/lib/test-aware-get-token.js +2 -51
- package/dist/routes/account/masked-info.d.ts +1 -1
- package/dist/routes/account/masked-info.js +1 -1
- package/dist/routes/account/send-code.d.ts +1 -1
- package/dist/routes/account/send-code.js +1 -1
- package/dist/routes/account/verify-email.d.ts +1 -1
- package/dist/routes/account/verify-email.js +1 -1
- package/dist/routes/account/verify-sms.d.ts +1 -1
- package/dist/routes/account/verify-sms.js +1 -1
- package/dist/routes/auth/refresh.js +3 -8
- package/dist/server/auth.d.ts +28 -7
- package/dist/server/auth.js +106 -55
- package/dist/server/decode-session.js +2 -2
- package/dist/vibe/hooks/index.d.ts +1 -1
- package/package.json +888 -893
- package/src/api/auth-handler.ts +0 -4
- package/src/api-handlers/admin/stats.ts +249 -238
- package/src/api-handlers/auth/refresh.ts +5 -8
- package/src/api-handlers/auth/signout.ts +9 -21
- package/src/api-handlers/auth/update-session.ts +7 -20
- package/src/api-handlers/auth/verify-code.ts +7 -20
- package/src/api-handlers/session/viability.ts +2 -2
- package/src/auth/better-auth.ts +7 -32
- package/src/client/better-auth-client.ts +3 -4
- package/src/lib/{nextauth-secret.ts → auth-secret.ts} +32 -16
- package/src/lib/demo-mode.ts +5 -1
- package/src/lib/idp-client-config.ts +42 -22
- package/src/lib/secret-validation.ts +1 -1
- package/src/lib/startup-init.ts +23 -18
- package/src/lib/test-aware-get-token.ts +2 -51
- package/src/routes/account/masked-info.ts +1 -1
- package/src/routes/account/send-code.ts +1 -1
- package/src/routes/account/verify-email.ts +1 -1
- package/src/routes/account/verify-sms.ts +1 -1
- package/src/routes/auth/refresh.ts +3 -8
- package/src/server/auth.ts +129 -22
- package/src/server/decode-session.ts +2 -2
- 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
|
-
* -
|
|
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
|
|
21
|
-
|
|
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
|
-
|
|
51
|
-
_handler = createRefreshHandler(config);
|
|
46
|
+
_handler = createRefreshHandler(getConfig());
|
|
52
47
|
}
|
|
53
48
|
return _handler(req);
|
|
54
49
|
}
|
package/src/server/auth.ts
CHANGED
|
@@ -1,20 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server-side auth utilities for Better Auth
|
|
2
|
+
* Server-side auth utilities for Better Auth.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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.
|
|
155
|
+
const secret = config.authSecret;
|
|
156
156
|
if (!secret) {
|
|
157
|
-
console.error('[DECODE-SESSION] No
|
|
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>;
|