@oauth42/next 0.4.8 → 0.5.0
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/README.md +47 -2
- package/dist/{auth-BThRQMzh.d.mts → auth-CYMPe3k0.d.ts} +10 -0
- package/dist/{auth-BThRQMzh.d.ts → auth-D3tAruwx.d.mts} +10 -0
- package/dist/client/index.d.mts +19 -11
- package/dist/client/index.d.ts +19 -11
- package/dist/client/index.js +10 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +10 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/index-Cfqxl9r2.d.mts +94 -0
- package/dist/index-Cfqxl9r2.d.ts +94 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +225 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +225 -22
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +2 -39
- package/dist/middleware/index.d.ts +2 -39
- package/dist/middleware/index.js +107 -10
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +107 -10
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +231 -22
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +228 -22
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -33,12 +33,15 @@ __export(server_exports, {
|
|
|
33
33
|
NextAuth: () => import_next_auth3.default,
|
|
34
34
|
OAuth42Provider: () => OAuth42Provider,
|
|
35
35
|
createAuth: () => createAuth,
|
|
36
|
+
createBackendSession: () => createBackendSession,
|
|
36
37
|
createHandlers: () => createHandlers,
|
|
37
38
|
createHostedAuthCallback: () => createHostedAuthCallback,
|
|
38
39
|
createMiddlewareConfig: () => createMiddlewareConfig,
|
|
39
40
|
getOAuth42Session: () => getOAuth42Session,
|
|
40
41
|
getServerSession: () => getServerSession,
|
|
41
42
|
refreshAccessToken: () => refreshAccessToken,
|
|
43
|
+
resolveBackendSession: () => resolveBackendSession,
|
|
44
|
+
revokeBackendSession: () => revokeBackendSession,
|
|
42
45
|
withOAuth42Auth: () => withOAuth42Auth,
|
|
43
46
|
withOAuth42ServerSideProps: () => withOAuth42ServerSideProps,
|
|
44
47
|
withOAuth42Session: () => withOAuth42Session
|
|
@@ -163,12 +166,72 @@ function withOAuth42ServerSideProps(getServerSideProps, authOptions) {
|
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
168
|
|
|
169
|
+
// src/server/backend-session.ts
|
|
170
|
+
function isBackendSessionMode(mode) {
|
|
171
|
+
return mode === "backend";
|
|
172
|
+
}
|
|
173
|
+
function sessionEndpoint(issuer) {
|
|
174
|
+
return `${issuer.replace(/\/$/, "")}/api/sdk/sessions`;
|
|
175
|
+
}
|
|
176
|
+
async function createBackendSession(params) {
|
|
177
|
+
const response = await fetch(sessionEndpoint(params.issuer), {
|
|
178
|
+
method: "POST",
|
|
179
|
+
headers: { "Content-Type": "application/json" },
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
access_token: params.accessToken,
|
|
182
|
+
refresh_token: params.refreshToken,
|
|
183
|
+
expires_at: params.expiresAt ?? Math.floor(Date.now() / 1e3) + 3600,
|
|
184
|
+
id_token: params.idToken,
|
|
185
|
+
client_id: params.clientId,
|
|
186
|
+
client_secret: params.clientSecret,
|
|
187
|
+
issuer: params.issuer,
|
|
188
|
+
user: params.user
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
throw new Error(`OAuth42 backend session create failed: ${response.status}`);
|
|
193
|
+
}
|
|
194
|
+
return response.json();
|
|
195
|
+
}
|
|
196
|
+
async function resolveBackendSession(params) {
|
|
197
|
+
const response = await fetch(`${sessionEndpoint(params.issuer)}/resolve`, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { "Content-Type": "application/json" },
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
session_id: params.sessionId,
|
|
202
|
+
client_id: params.clientId,
|
|
203
|
+
client_secret: params.clientSecret,
|
|
204
|
+
issuer: params.issuer
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
if (response.status === 401 || response.status === 403 || response.status === 404) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
throw new Error(`OAuth42 backend session resolve failed: ${response.status}`);
|
|
212
|
+
}
|
|
213
|
+
return response.json();
|
|
214
|
+
}
|
|
215
|
+
async function revokeBackendSession(params) {
|
|
216
|
+
await fetch(sessionEndpoint(params.issuer), {
|
|
217
|
+
method: "DELETE",
|
|
218
|
+
headers: { "Content-Type": "application/json" },
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
session_id: params.sessionId,
|
|
221
|
+
client_id: params.clientId,
|
|
222
|
+
client_secret: params.clientSecret
|
|
223
|
+
})
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
166
227
|
// src/server/auth.ts
|
|
167
228
|
var NextAuth = import_next_auth2.default.default || import_next_auth2.default;
|
|
168
229
|
var activeRefresh = null;
|
|
169
230
|
function createAuth(options = {}) {
|
|
170
231
|
const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;
|
|
171
232
|
const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
|
|
233
|
+
const issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER || "https://localhost:8443";
|
|
234
|
+
const useBackendSessions = isBackendSessionMode(options.sessionStore);
|
|
172
235
|
if (!clientId || !clientSecret) {
|
|
173
236
|
throw new Error(
|
|
174
237
|
"OAuth42 client credentials are required. Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables or pass them in the options."
|
|
@@ -179,7 +242,7 @@ function createAuth(options = {}) {
|
|
|
179
242
|
OAuth42Provider({
|
|
180
243
|
clientId,
|
|
181
244
|
clientSecret,
|
|
182
|
-
issuer
|
|
245
|
+
issuer,
|
|
183
246
|
scopes: options.scopes,
|
|
184
247
|
pkceEnabled: options.pkceEnabled
|
|
185
248
|
})
|
|
@@ -188,14 +251,41 @@ function createAuth(options = {}) {
|
|
|
188
251
|
async jwt({ token, account, profile }) {
|
|
189
252
|
console.log("[OAuth42 SDK] JWT callback called", { hasAccount: !!account, hasProfile: !!profile });
|
|
190
253
|
if (account) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
254
|
+
if (useBackendSessions) {
|
|
255
|
+
if (!account.access_token || !account.refresh_token) {
|
|
256
|
+
throw new Error("OAuth42 backend sessions require access and refresh tokens");
|
|
257
|
+
}
|
|
258
|
+
const oauth42Profile = profile;
|
|
259
|
+
const backendSession = await createBackendSession({
|
|
260
|
+
issuer,
|
|
261
|
+
clientId,
|
|
262
|
+
clientSecret,
|
|
263
|
+
accessToken: account.access_token,
|
|
264
|
+
refreshToken: account.refresh_token,
|
|
265
|
+
expiresAt: account.expires_at,
|
|
266
|
+
idToken: account.id_token,
|
|
267
|
+
user: {
|
|
268
|
+
sub: oauth42Profile?.sub,
|
|
269
|
+
email: oauth42Profile?.email,
|
|
270
|
+
username: oauth42Profile?.username,
|
|
271
|
+
name: token.name,
|
|
272
|
+
emailVerified: oauth42Profile?.email_verified,
|
|
273
|
+
groups: oauth42Profile?.groups
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
token.oauth42SessionId = backendSession.session_id;
|
|
277
|
+
token.expiresAt = backendSession.expires_at;
|
|
278
|
+
token.issuer = issuer;
|
|
279
|
+
} else {
|
|
280
|
+
console.log("[OAuth42 SDK] Initial sign in - storing tokens in JWT");
|
|
281
|
+
token.accessToken = account.access_token;
|
|
282
|
+
token.refreshToken = account.refresh_token;
|
|
283
|
+
token.expiresAt = account.expires_at;
|
|
284
|
+
token.idToken = account.id_token;
|
|
285
|
+
token.clientId = clientId;
|
|
286
|
+
token.clientSecret = clientSecret;
|
|
287
|
+
token.issuer = issuer;
|
|
288
|
+
}
|
|
199
289
|
}
|
|
200
290
|
if (profile) {
|
|
201
291
|
const oauth42Profile = profile;
|
|
@@ -212,12 +302,40 @@ function createAuth(options = {}) {
|
|
|
212
302
|
},
|
|
213
303
|
async session({ session, token }) {
|
|
214
304
|
console.log("[OAuth42 SDK] Session callback called", { hasToken: !!token, hasSession: !!session });
|
|
215
|
-
|
|
216
|
-
|
|
305
|
+
if (useBackendSessions) {
|
|
306
|
+
const sessionId = token.oauth42SessionId;
|
|
307
|
+
if (!sessionId) {
|
|
308
|
+
session.error = "SessionRevoked";
|
|
309
|
+
return session;
|
|
310
|
+
}
|
|
311
|
+
const backendSession = await resolveBackendSession({
|
|
312
|
+
issuer,
|
|
313
|
+
clientId,
|
|
314
|
+
clientSecret,
|
|
315
|
+
sessionId
|
|
316
|
+
});
|
|
317
|
+
if (!backendSession) {
|
|
318
|
+
session.error = "SessionRevoked";
|
|
319
|
+
return session;
|
|
320
|
+
}
|
|
321
|
+
session.accessToken = backendSession.access_token;
|
|
322
|
+
session.idToken = backendSession.id_token;
|
|
323
|
+
token.expiresAt = backendSession.expires_at;
|
|
324
|
+
if (backendSession.user && session.user) {
|
|
325
|
+
session.user.email = backendSession.user.email;
|
|
326
|
+
session.user.name = backendSession.user.name;
|
|
327
|
+
session.user.username = backendSession.user.username;
|
|
328
|
+
session.user.emailVerified = backendSession.user.emailVerified;
|
|
329
|
+
session.user.groups = backendSession.user.groups;
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
session.accessToken = token.accessToken;
|
|
333
|
+
session.idToken = token.idToken;
|
|
334
|
+
}
|
|
217
335
|
if (token.error) {
|
|
218
336
|
session.error = token.error;
|
|
219
337
|
}
|
|
220
|
-
if (session.user) {
|
|
338
|
+
if (!useBackendSessions && session.user) {
|
|
221
339
|
session.user.email = token.email;
|
|
222
340
|
session.user.name = token.name;
|
|
223
341
|
session.user.username = token.username;
|
|
@@ -241,6 +359,26 @@ function createAuth(options = {}) {
|
|
|
241
359
|
strategy: "jwt",
|
|
242
360
|
...options.session
|
|
243
361
|
},
|
|
362
|
+
events: {
|
|
363
|
+
async signOut(message) {
|
|
364
|
+
if (useBackendSessions) {
|
|
365
|
+
const sessionId = message.token?.oauth42SessionId;
|
|
366
|
+
if (sessionId) {
|
|
367
|
+
await revokeBackendSession({
|
|
368
|
+
issuer,
|
|
369
|
+
clientId,
|
|
370
|
+
clientSecret,
|
|
371
|
+
sessionId
|
|
372
|
+
}).catch((error) => {
|
|
373
|
+
console.warn("[OAuth42 SDK] Failed to revoke backend session on signOut:", error);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (options.events?.signOut) {
|
|
378
|
+
await options.events.signOut(message);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
},
|
|
244
382
|
debug: options.debug || process.env.NODE_ENV === "development",
|
|
245
383
|
secret: process.env.NEXTAUTH_SECRET,
|
|
246
384
|
// Configure unique cookie names per app to prevent session conflicts on localhost
|
|
@@ -394,7 +532,22 @@ var import_jwt = require("next-auth/jwt");
|
|
|
394
532
|
var ALLOWED_COOKIE_SIZE = 4096;
|
|
395
533
|
var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
|
|
396
534
|
var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
|
|
535
|
+
var REFRESH_RACE_GRACE_SECONDS = 60;
|
|
536
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
|
|
397
537
|
var pendingRefreshes = /* @__PURE__ */ new Map();
|
|
538
|
+
function shouldPreserveSessionOnRefreshFailure(params) {
|
|
539
|
+
if (params.error !== "invalid_grant" || !params.expiresAt) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
return params.now - params.expiresAt <= REFRESH_RACE_GRACE_SECONDS;
|
|
543
|
+
}
|
|
544
|
+
function resolveRefreshConfig(options, token) {
|
|
545
|
+
return {
|
|
546
|
+
clientId: options.clientId || token.clientId || process.env.OAUTH42_CLIENT_ID,
|
|
547
|
+
clientSecret: options.clientSecret || token.clientSecret || process.env.OAUTH42_CLIENT_SECRET,
|
|
548
|
+
issuer: options.issuer || token.issuer || process.env.OAUTH42_ISSUER || "https://localhost:8443"
|
|
549
|
+
};
|
|
550
|
+
}
|
|
398
551
|
async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
399
552
|
try {
|
|
400
553
|
const tokenUrl = `${issuer}/oauth2/token`;
|
|
@@ -429,9 +582,6 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
|
429
582
|
}
|
|
430
583
|
function withOAuth42Auth(options = {}) {
|
|
431
584
|
const secret = process.env.NEXTAUTH_SECRET;
|
|
432
|
-
const clientId = process.env.OAUTH42_CLIENT_ID;
|
|
433
|
-
const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
|
|
434
|
-
const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
|
|
435
585
|
if (!secret) {
|
|
436
586
|
console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
|
|
437
587
|
}
|
|
@@ -456,6 +606,42 @@ function withOAuth42Auth(options = {}) {
|
|
|
456
606
|
url.searchParams.set("callbackUrl", pathname);
|
|
457
607
|
return import_server.NextResponse.redirect(url);
|
|
458
608
|
}
|
|
609
|
+
const refreshConfig = resolveRefreshConfig(options, token);
|
|
610
|
+
if (isBackendSessionMode(options.sessionStore)) {
|
|
611
|
+
const sessionId = token.oauth42SessionId;
|
|
612
|
+
if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret) {
|
|
613
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
614
|
+
const url = new URL(signInUrl, req.url);
|
|
615
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
616
|
+
const response = import_server.NextResponse.redirect(url);
|
|
617
|
+
response.cookies.delete(cookieName);
|
|
618
|
+
return response;
|
|
619
|
+
}
|
|
620
|
+
const backendSession = await resolveBackendSession({
|
|
621
|
+
issuer: refreshConfig.issuer,
|
|
622
|
+
clientId: refreshConfig.clientId,
|
|
623
|
+
clientSecret: refreshConfig.clientSecret,
|
|
624
|
+
sessionId
|
|
625
|
+
}).catch((error) => {
|
|
626
|
+
console.error("[OAuth42 Middleware] Backend session resolve failed:", error);
|
|
627
|
+
return null;
|
|
628
|
+
});
|
|
629
|
+
if (!backendSession) {
|
|
630
|
+
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
631
|
+
const url = new URL(signInUrl, req.url);
|
|
632
|
+
url.searchParams.set("callbackUrl", pathname);
|
|
633
|
+
const response = import_server.NextResponse.redirect(url);
|
|
634
|
+
response.cookies.delete(cookieName);
|
|
635
|
+
return response;
|
|
636
|
+
}
|
|
637
|
+
const requestHeaders = new Headers(req.headers);
|
|
638
|
+
requestHeaders.set("x-oauth42-refreshed-token", backendSession.access_token);
|
|
639
|
+
return import_server.NextResponse.next({
|
|
640
|
+
request: {
|
|
641
|
+
headers: requestHeaders
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
459
645
|
const expiresAt = token.expiresAt;
|
|
460
646
|
const now = Math.floor(Date.now() / 1e3);
|
|
461
647
|
const bufferSeconds = 15;
|
|
@@ -469,7 +655,7 @@ function withOAuth42Auth(options = {}) {
|
|
|
469
655
|
path: pathname
|
|
470
656
|
});
|
|
471
657
|
}
|
|
472
|
-
if (needsRefresh && token.refreshToken && clientId && clientSecret) {
|
|
658
|
+
if (needsRefresh && token.refreshToken && refreshConfig.clientId && refreshConfig.clientSecret) {
|
|
473
659
|
const userId = token.sub;
|
|
474
660
|
let refreshPromise = pendingRefreshes.get(userId);
|
|
475
661
|
if (refreshPromise) {
|
|
@@ -478,11 +664,14 @@ function withOAuth42Auth(options = {}) {
|
|
|
478
664
|
console.log("[OAuth42 Middleware] Access token expired, refreshing...");
|
|
479
665
|
refreshPromise = refreshTokens(
|
|
480
666
|
token.refreshToken,
|
|
481
|
-
clientId,
|
|
482
|
-
clientSecret,
|
|
483
|
-
issuer
|
|
667
|
+
refreshConfig.clientId,
|
|
668
|
+
refreshConfig.clientSecret,
|
|
669
|
+
refreshConfig.issuer
|
|
484
670
|
).finally(() => {
|
|
485
|
-
setTimeout(() => pendingRefreshes.delete(userId), 3e4);
|
|
671
|
+
const cleanupTimer = setTimeout(() => pendingRefreshes.delete(userId), 3e4);
|
|
672
|
+
if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
673
|
+
cleanupTimer.unref();
|
|
674
|
+
}
|
|
486
675
|
});
|
|
487
676
|
pendingRefreshes.set(userId, refreshPromise);
|
|
488
677
|
}
|
|
@@ -509,7 +698,8 @@ function withOAuth42Auth(options = {}) {
|
|
|
509
698
|
httpOnly: true,
|
|
510
699
|
sameSite: "lax",
|
|
511
700
|
path: "/",
|
|
512
|
-
secure: process.env.NODE_ENV === "production"
|
|
701
|
+
secure: process.env.NODE_ENV === "production",
|
|
702
|
+
maxAge: options.session?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS
|
|
513
703
|
};
|
|
514
704
|
if (newJwt.length <= CHUNK_SIZE) {
|
|
515
705
|
response.cookies.set(cookieName, newJwt, cookieOptions);
|
|
@@ -557,7 +747,23 @@ function withOAuth42Auth(options = {}) {
|
|
|
557
747
|
});
|
|
558
748
|
return response;
|
|
559
749
|
} else {
|
|
560
|
-
|
|
750
|
+
if (shouldPreserveSessionOnRefreshFailure({
|
|
751
|
+
error: refreshed.error,
|
|
752
|
+
expiresAt,
|
|
753
|
+
now
|
|
754
|
+
})) {
|
|
755
|
+
console.warn(
|
|
756
|
+
"[OAuth42 Middleware] Refresh failed with invalid_grant for a recently expired token; preserving session cookie for likely parallel refresh race",
|
|
757
|
+
{
|
|
758
|
+
expiresAt,
|
|
759
|
+
now,
|
|
760
|
+
expiredAgo: now - expiresAt,
|
|
761
|
+
path: pathname
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
return import_server.NextResponse.next();
|
|
765
|
+
}
|
|
766
|
+
console.log("[OAuth42 Middleware] Refresh failed, clearing session");
|
|
561
767
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
562
768
|
const url = new URL(signInUrl, req.url);
|
|
563
769
|
url.searchParams.set("callbackUrl", pathname);
|
|
@@ -704,12 +910,15 @@ function createHostedAuthCallback(options = {}) {
|
|
|
704
910
|
NextAuth,
|
|
705
911
|
OAuth42Provider,
|
|
706
912
|
createAuth,
|
|
913
|
+
createBackendSession,
|
|
707
914
|
createHandlers,
|
|
708
915
|
createHostedAuthCallback,
|
|
709
916
|
createMiddlewareConfig,
|
|
710
917
|
getOAuth42Session,
|
|
711
918
|
getServerSession,
|
|
712
919
|
refreshAccessToken,
|
|
920
|
+
resolveBackendSession,
|
|
921
|
+
revokeBackendSession,
|
|
713
922
|
withOAuth42Auth,
|
|
714
923
|
withOAuth42ServerSideProps,
|
|
715
924
|
withOAuth42Session
|