@payez/next-mvp 4.1.0 → 4.1.2
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/auth/better-auth.d.ts +3 -0
- package/dist/auth/better-auth.js +22 -1
- package/dist/client/better-auth-client.d.ts +407 -218
- package/dist/client/better-auth-client.js +3 -1
- package/dist/lib/ensure-fresh-access-token.d.ts +30 -0
- package/dist/lib/ensure-fresh-access-token.js +269 -0
- package/dist/lib/session-store.js +24 -21
- package/dist/lib/token-lifecycle.js +2 -0
- package/dist/models/SessionModel.d.ts +3 -0
- package/dist/models/SessionModel.js +3 -0
- package/dist/routes/auth/session.js +1 -1
- package/dist/server/auth.d.ts +59 -0
- package/dist/server/auth.js +156 -16
- package/dist/server/decode-session.js +2 -0
- package/package.json +6 -1
- package/src/auth/better-auth.ts +434 -408
- package/src/client/better-auth-client.ts +2 -0
- package/src/lib/ensure-fresh-access-token.ts +320 -0
- package/src/lib/session-store.ts +692 -689
- package/src/lib/token-lifecycle.ts +470 -468
- package/src/models/SessionModel.ts +264 -258
- package/src/routes/auth/session.ts +166 -166
- package/src/server/auth.ts +272 -78
- package/src/server/decode-session.ts +202 -200
package/dist/server/auth.js
CHANGED
|
@@ -41,12 +41,67 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
42
|
exports.getAuthInstance = getAuthInstance;
|
|
43
43
|
exports.getSession = getSession;
|
|
44
|
+
exports.getSessionData = getSessionData;
|
|
45
|
+
exports.getIdpToken = getIdpToken;
|
|
46
|
+
exports.getFreshIdpToken = getFreshIdpToken;
|
|
44
47
|
exports.requireSession = requireSession;
|
|
45
48
|
require("server-only");
|
|
46
49
|
const better_auth_1 = require("../auth/better-auth");
|
|
47
50
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
51
|
+
const session_store_1 = require("../lib/session-store");
|
|
48
52
|
let authInstance = null;
|
|
49
53
|
let authInitPromise = null;
|
|
54
|
+
function buildSessionDataFromAuthSession(session) {
|
|
55
|
+
const user = session?.user;
|
|
56
|
+
if (!user?.id && !user?.email) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const expiresAt = session?.session?.expiresAt
|
|
60
|
+
? new Date(session.session.expiresAt).getTime()
|
|
61
|
+
: Date.now() + 24 * 60 * 60 * 1000;
|
|
62
|
+
return {
|
|
63
|
+
userId: user.userId || user.id || '',
|
|
64
|
+
email: user.email || '',
|
|
65
|
+
name: user.name || undefined,
|
|
66
|
+
image: user.image || undefined,
|
|
67
|
+
roles: Array.isArray(user.roles) ? user.roles : [],
|
|
68
|
+
idpAccessToken: user.idpAccessToken,
|
|
69
|
+
idpRefreshToken: user.idpRefreshToken,
|
|
70
|
+
idpAccessTokenExpires: user.idpAccessTokenExpires || expiresAt,
|
|
71
|
+
mfaVerified: user.mfaVerified ?? user.twoFactorSessionVerified ?? false,
|
|
72
|
+
oauthProvider: user.oauthProvider,
|
|
73
|
+
idpClientId: user.idpClientId,
|
|
74
|
+
merchantId: user.merchantId,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function attachSessionData(session, sessionData, sessionToken) {
|
|
78
|
+
if (!sessionData) {
|
|
79
|
+
return session;
|
|
80
|
+
}
|
|
81
|
+
const enrichedSessionData = {
|
|
82
|
+
...sessionData,
|
|
83
|
+
...(sessionToken ? { sessionToken } : {}),
|
|
84
|
+
};
|
|
85
|
+
session.sessionData = enrichedSessionData;
|
|
86
|
+
if (session?.user) {
|
|
87
|
+
const user = session.user;
|
|
88
|
+
user.userId = enrichedSessionData.userId || user.userId;
|
|
89
|
+
user.email = enrichedSessionData.email || user.email;
|
|
90
|
+
user.name = enrichedSessionData.name || user.name;
|
|
91
|
+
user.image = enrichedSessionData.image || user.image;
|
|
92
|
+
user.roles = enrichedSessionData.roles || user.roles || [];
|
|
93
|
+
user.idpAccessToken = enrichedSessionData.idpAccessToken;
|
|
94
|
+
user.idpRefreshToken = enrichedSessionData.idpRefreshToken;
|
|
95
|
+
user.idpAccessTokenExpires = enrichedSessionData.idpAccessTokenExpires;
|
|
96
|
+
user.mfaVerified = enrichedSessionData.mfaVerified;
|
|
97
|
+
user.twoFactorSessionVerified =
|
|
98
|
+
enrichedSessionData.mfaVerified ?? user.twoFactorSessionVerified;
|
|
99
|
+
user.oauthProvider = enrichedSessionData.oauthProvider || user.oauthProvider;
|
|
100
|
+
user.idpClientId = enrichedSessionData.idpClientId || user.idpClientId;
|
|
101
|
+
user.merchantId = enrichedSessionData.merchantId || user.merchantId;
|
|
102
|
+
}
|
|
103
|
+
return session;
|
|
104
|
+
}
|
|
50
105
|
/**
|
|
51
106
|
* Get the initialized Better Auth instance (singleton).
|
|
52
107
|
*/
|
|
@@ -75,31 +130,116 @@ async function getSession(request) {
|
|
|
75
130
|
const session = await auth.api.getSession({ headers: request.headers });
|
|
76
131
|
if (!session?.session?.token || !session?.user)
|
|
77
132
|
return session;
|
|
78
|
-
|
|
133
|
+
const sessionToken = session.session.token;
|
|
134
|
+
let sessionData = null;
|
|
135
|
+
// Prefer the app's normalized Redis session. Fall back to Better Auth's
|
|
136
|
+
// secondary storage record, then finally to whatever Better Auth already
|
|
137
|
+
// put on the request session object.
|
|
79
138
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const baRaw = await getRedis().get(baKey);
|
|
84
|
-
if (baRaw) {
|
|
85
|
-
const baData = JSON.parse(baRaw);
|
|
86
|
-
if (baData.idpTokens) {
|
|
87
|
-
const u = session.user;
|
|
88
|
-
u.roles = baData.idpTokens.roles || [];
|
|
89
|
-
u.userId = baData.idpTokens.userId;
|
|
90
|
-
u.idpAccessToken = baData.idpTokens.idpAccessToken;
|
|
91
|
-
u.idpRefreshToken = baData.idpTokens.idpRefreshToken;
|
|
92
|
-
u.idpAccessTokenExpires = baData.idpTokens.idpAccessTokenExpires;
|
|
93
|
-
}
|
|
139
|
+
sessionData = await (0, session_store_1.getSession)(sessionToken);
|
|
140
|
+
if (!sessionData) {
|
|
141
|
+
sessionData = await (0, session_store_1.getBetterAuthSession)(sessionToken);
|
|
94
142
|
}
|
|
95
143
|
}
|
|
96
144
|
catch { /* Redis unavailable */ }
|
|
97
|
-
|
|
145
|
+
if (!sessionData) {
|
|
146
|
+
sessionData = buildSessionDataFromAuthSession(session);
|
|
147
|
+
}
|
|
148
|
+
return attachSessionData(session, sessionData, sessionToken);
|
|
98
149
|
}
|
|
99
150
|
catch {
|
|
100
151
|
return null;
|
|
101
152
|
}
|
|
102
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Get normalized session data for the current request.
|
|
156
|
+
*
|
|
157
|
+
* This prefers the app's Redis session because it carries the canonical
|
|
158
|
+
* IDP token, roles, and tenant-specific user identity used by app routes.
|
|
159
|
+
*/
|
|
160
|
+
async function getSessionData(request) {
|
|
161
|
+
const session = await getSession(request);
|
|
162
|
+
const sessionData = session?.sessionData ||
|
|
163
|
+
buildSessionDataFromAuthSession(session);
|
|
164
|
+
if (!sessionData) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const sessionToken = session?.session?.token;
|
|
168
|
+
return sessionToken
|
|
169
|
+
? { ...sessionData, sessionToken }
|
|
170
|
+
: sessionData;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get the current request's IDP access token without triggering a refresh.
|
|
174
|
+
*
|
|
175
|
+
* Use this for routes that only need the currently-issued bearer token and
|
|
176
|
+
* should fail closed instead of performing token lifecycle work. For backend
|
|
177
|
+
* proxy routes that forward the token to a downstream API, prefer
|
|
178
|
+
* `getFreshIdpToken` — it preflights expiry and refreshes single-flight, so
|
|
179
|
+
* the proxy never sends a credential it already knows is invalid.
|
|
180
|
+
*/
|
|
181
|
+
async function getIdpToken(request) {
|
|
182
|
+
const sessionData = await getSessionData(request);
|
|
183
|
+
if (!sessionData) {
|
|
184
|
+
return { success: false, error: 'NO_SESSION', terminal: true };
|
|
185
|
+
}
|
|
186
|
+
const accessToken = sessionData.idpAccessToken || sessionData.accessToken;
|
|
187
|
+
if (!accessToken) {
|
|
188
|
+
return { success: false, error: 'NO_TOKEN', terminal: true };
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
accessToken,
|
|
193
|
+
sessionData,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the current request's IDP access token, preflight-refreshing if it is
|
|
198
|
+
* expired or within the safety window. Single-flight via Redis lock, so
|
|
199
|
+
* concurrent calls on the same session share one IDP round-trip and one
|
|
200
|
+
* single-use refresh-token consumption.
|
|
201
|
+
*
|
|
202
|
+
* Use this in proxy routes. The returned `accessToken` is safe to forward to
|
|
203
|
+
* a downstream API without expecting a 401. If `success` is false, surface a
|
|
204
|
+
* 401/redirect — there is no recoverable token for this session.
|
|
205
|
+
*/
|
|
206
|
+
async function getFreshIdpToken(request, config) {
|
|
207
|
+
const session = await getSession(request);
|
|
208
|
+
const sessionToken = session?.session?.token;
|
|
209
|
+
if (!sessionToken) {
|
|
210
|
+
return { success: false, error: 'NO_SESSION', status: 401, terminal: true };
|
|
211
|
+
}
|
|
212
|
+
const { ensureFreshAccessToken } = await Promise.resolve().then(() => __importStar(require('../lib/ensure-fresh-access-token')));
|
|
213
|
+
const result = await ensureFreshAccessToken(sessionToken, {
|
|
214
|
+
idpBaseUrl: config.idpBaseUrl,
|
|
215
|
+
clientId: config.clientId,
|
|
216
|
+
refreshEndpoint: config.refreshEndpoint,
|
|
217
|
+
}, {
|
|
218
|
+
safetyWindowMs: config.safetyWindowMs,
|
|
219
|
+
requestId: request?.headers?.get('x-request-id') ?? undefined,
|
|
220
|
+
});
|
|
221
|
+
if (!result.ok) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
error: result.code,
|
|
225
|
+
status: result.status,
|
|
226
|
+
terminal: result.terminal,
|
|
227
|
+
discardToken: result.discardToken,
|
|
228
|
+
retryable: result.retryable,
|
|
229
|
+
resolution: result.resolution,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const sessionData = await (0, session_store_1.getSession)(sessionToken);
|
|
233
|
+
if (!sessionData) {
|
|
234
|
+
return { success: false, error: 'NO_SESSION', status: 401, terminal: true };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
accessToken: result.accessToken,
|
|
239
|
+
sessionData,
|
|
240
|
+
refreshed: result.refreshed,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
103
243
|
/**
|
|
104
244
|
* Get the current session, throwing if not authenticated.
|
|
105
245
|
* Use in API handlers that require auth.
|
|
@@ -116,6 +116,8 @@ async function tryBetterAuthSession(requestCookies) {
|
|
|
116
116
|
|| (result.session.expiresAt ? new Date(result.session.expiresAt).getTime() : Date.now() + 24 * 60 * 60 * 1000),
|
|
117
117
|
mfaVerified: idpTokens?.mfaVerified ?? false,
|
|
118
118
|
oauthProvider: 'google',
|
|
119
|
+
idpClientId: idpTokens?.idpClientId ?? idpTokens?.clientId,
|
|
120
|
+
merchantId: idpTokens?.merchantId,
|
|
119
121
|
};
|
|
120
122
|
// Backwards compat: session.user.email works alongside session.email
|
|
121
123
|
sessionData.user = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payez/next-mvp",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -112,6 +112,11 @@
|
|
|
112
112
|
"require": "./dist/lib/session-store.js",
|
|
113
113
|
"default": "./dist/lib/session-store.js"
|
|
114
114
|
},
|
|
115
|
+
"./lib/ensure-fresh-access-token": {
|
|
116
|
+
"types": "./dist/lib/ensure-fresh-access-token.d.ts",
|
|
117
|
+
"require": "./dist/lib/ensure-fresh-access-token.js",
|
|
118
|
+
"default": "./dist/lib/ensure-fresh-access-token.js"
|
|
119
|
+
},
|
|
115
120
|
"./lib/token-lifecycle": {
|
|
116
121
|
"types": "./dist/lib/token-lifecycle.d.ts",
|
|
117
122
|
"require": "./dist/lib/token-lifecycle.js",
|