@oauth42/next 0.4.8 → 0.5.1

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.
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -16,6 +19,10 @@ var __copyProps = (to, from, except, desc) => {
16
19
  return to;
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
23
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
19
26
 
20
27
  // src/middleware/index.ts
21
28
  var middleware_exports = {};
@@ -25,18 +32,202 @@ __export(middleware_exports, {
25
32
  });
26
33
  module.exports = __toCommonJS(middleware_exports);
27
34
 
35
+ // src/server/verified-userinfo.ts
36
+ async function verifiedUserInfo(issuer, accessToken, subject, dpop) {
37
+ if (typeof accessToken !== "string" || !accessToken || typeof subject !== "string" || !subject) return null;
38
+ try {
39
+ const target = `${issuer.replace(/\/$/, "")}/oauth2/userinfo`;
40
+ const fetcher = dpop ? (url, init) => dpop.fetch(url, init, accessToken) : fetch;
41
+ const response = await fetcher(target, {
42
+ headers: { Authorization: `Bearer ${accessToken}` },
43
+ redirect: "error",
44
+ cache: "no-store",
45
+ signal: AbortSignal.timeout(3e4)
46
+ });
47
+ if (!response.ok) return null;
48
+ const user = await response.json();
49
+ return parseVerifiedUserInfo(user, subject);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ function parseVerifiedUserInfo(value, subject) {
55
+ if (typeof subject !== "string" || !subject || !value || typeof value !== "object" || Array.isArray(value)) return null;
56
+ const user = value;
57
+ if (user.sub !== subject) return null;
58
+ if (user.groups !== void 0 && (!Array.isArray(user.groups) || user.groups.some((group) => typeof group !== "string"))) return null;
59
+ if (user.email !== void 0 && typeof user.email !== "string") return null;
60
+ if (user.email_verified !== void 0 && typeof user.email_verified !== "boolean") return null;
61
+ const profile = { sub: user.sub };
62
+ for (const field of ["email", "name", "preferred_username", "username", "given_name", "family_name", "company_id", "company_name"]) {
63
+ if (user[field] != null) {
64
+ if (typeof user[field] !== "string") return null;
65
+ profile[field] = user[field];
66
+ }
67
+ }
68
+ if (user.email_verified !== void 0) profile.email_verified = user.email_verified;
69
+ if (user.groups !== void 0) profile.groups = user.groups;
70
+ return profile;
71
+ }
72
+ async function verifiedBackendSessionUserInfo(issuer, session, subject, dpop) {
73
+ if (typeof session.access_token !== "string" || !session.access_token) return null;
74
+ if (Object.prototype.hasOwnProperty.call(session, "user_info")) {
75
+ return parseVerifiedUserInfo(session.user_info, subject);
76
+ }
77
+ return verifiedUserInfo(issuer, session.access_token, subject, dpop);
78
+ }
79
+
28
80
  // src/server/middleware.ts
29
81
  var import_server = require("next/server");
30
82
  var import_jwt = require("next-auth/jwt");
83
+
84
+ // src/server/client-assertion.ts
85
+ var import_node_crypto = require("crypto");
86
+ var _key, _public;
87
+ var _OAuth42ClientAssertion = class _OAuth42ClientAssertion {
88
+ constructor(kid, key) {
89
+ __privateAdd(this, _key);
90
+ __privateAdd(this, _public);
91
+ if (typeof kid !== "string" || !/^[\x21-\x7e]{1,128}$/.test(kid)) throw new Error("Invalid client signing key ID");
92
+ if (key.type !== "private" || key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
93
+ throw new Error("Client assertions require a private P-256 key");
94
+ }
95
+ __privateSet(this, _key, key);
96
+ this.kid = kid;
97
+ const publicKey = (0, import_node_crypto.createPublicKey)(key).export({ format: "jwk" });
98
+ __privateSet(this, _public, { crv: "P-256", kty: "EC", x: publicKey.x, y: publicKey.y });
99
+ this.thumbprint = (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(__privateGet(this, _public))).digest("base64url");
100
+ }
101
+ static generate(kid) {
102
+ return new _OAuth42ClientAssertion(kid, (0, import_node_crypto.generateKeyPairSync)("ec", { namedCurve: "prime256v1" }).privateKey);
103
+ }
104
+ get publicJwk() {
105
+ return { ...__privateGet(this, _public), kid: this.kid };
106
+ }
107
+ /** Private keys for the server-only NextAuth/openid-client configuration, never registration. */
108
+ get oidcJwks() {
109
+ return { keys: [{
110
+ kty: "EC",
111
+ crv: "P-256",
112
+ x: __privateGet(this, _public).x,
113
+ y: __privateGet(this, _public).y,
114
+ d: __privateGet(this, _key).export({ format: "jwk" }).d,
115
+ kid: this.kid
116
+ }] };
117
+ }
118
+ static validateEndpoint(target) {
119
+ const url = new URL(target);
120
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash || url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) {
121
+ throw new Error("Client authentication requires HTTPS outside loopback development");
122
+ }
123
+ }
124
+ assertion(clientId, audience) {
125
+ _OAuth42ClientAssertion.validateEndpoint(audience);
126
+ if (!clientId || Buffer.byteLength(clientId) > 256) throw new Error("Invalid client identity");
127
+ const now = Math.floor(Date.now() / 1e3);
128
+ const header = { alg: "ES256", typ: "JWT", kid: this.kid };
129
+ const payload = { iss: clientId, sub: clientId, aud: audience, iat: now, exp: now + 60, jti: (0, import_node_crypto.randomBytes)(32).toString("base64url") };
130
+ const data = `${Buffer.from(JSON.stringify(header)).toString("base64url")}.${Buffer.from(JSON.stringify(payload)).toString("base64url")}`;
131
+ return `${data}.${(0, import_node_crypto.sign)("sha256", Buffer.from(data), { key: __privateGet(this, _key), dsaEncoding: "ieee-p1363" }).toString("base64url")}`;
132
+ }
133
+ async fetch(clientId, target, init, dpop) {
134
+ if (init.method?.toUpperCase() !== "POST" || !(typeof init.body === "string" || init.body instanceof URLSearchParams)) {
135
+ throw new Error("Client assertions require a form POST");
136
+ }
137
+ const prepare = () => {
138
+ const body = new URLSearchParams(init.body);
139
+ body.delete("client_secret");
140
+ body.set("client_id", clientId);
141
+ body.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
142
+ body.set("client_assertion", this.assertion(clientId, target));
143
+ const headers = new Headers(init.headers);
144
+ headers.delete("Authorization");
145
+ headers.delete("Content-Length");
146
+ headers.delete("Transfer-Encoding");
147
+ headers.set("Content-Type", "application/x-www-form-urlencoded");
148
+ return { ...init, body, headers, redirect: "error", cache: "no-store", signal: init.signal || AbortSignal.timeout(3e4) };
149
+ };
150
+ return dpop ? dpop.fetch(target, init, void 0, prepare) : fetch(target, prepare());
151
+ }
152
+ };
153
+ _key = new WeakMap();
154
+ _public = new WeakMap();
155
+ var OAuth42ClientAssertion = _OAuth42ClientAssertion;
156
+
157
+ // src/server/backend-session.ts
158
+ function isBackendSessionMode(mode) {
159
+ return mode === "backend";
160
+ }
161
+ function sessionEndpoint(issuer) {
162
+ return `${issuer.replace(/\/$/, "")}/api/sdk/sessions`;
163
+ }
164
+ async function sessionFetch(params, target, body, credential, method = "POST") {
165
+ OAuth42ClientAssertion.validateEndpoint(target);
166
+ const prepare = () => ({
167
+ method,
168
+ redirect: "error",
169
+ cache: "no-store",
170
+ signal: AbortSignal.timeout(3e4),
171
+ headers: { "Content-Type": "application/json" },
172
+ body: JSON.stringify({
173
+ ...body,
174
+ client_id: params.clientId,
175
+ ...params.clientAssertionKey ? {
176
+ client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
177
+ client_assertion: params.clientAssertionKey.assertion(params.clientId, target)
178
+ } : { client_secret: params.clientSecret }
179
+ })
180
+ });
181
+ return params.dpop ? params.dpop.fetch(target, {}, credential, prepare) : fetch(target, prepare());
182
+ }
183
+ async function sessionResponse(response, params) {
184
+ if (!response.ok) throw new Error(`OAuth42 backend session request failed: ${response.status}`);
185
+ const session = await response.json();
186
+ if (params.dpop) params.dpop.assertTokenResponse(session);
187
+ else if (session.token_type?.toLowerCase() === "dpop") throw new Error("Backend session requires its retained proof key");
188
+ return session;
189
+ }
190
+ async function resolveBackendSession(params) {
191
+ const response = await sessionFetch(params, `${sessionEndpoint(params.issuer)}/resolve`, { session_id: params.sessionId }, params.sessionId);
192
+ if ([401, 403, 404].includes(response.status)) return null;
193
+ return sessionResponse(response, params);
194
+ }
195
+
196
+ // src/server/middleware.ts
31
197
  var ALLOWED_COOKIE_SIZE = 4096;
32
198
  var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
33
199
  var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
200
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
34
201
  var pendingRefreshes = /* @__PURE__ */ new Map();
35
- async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
202
+ function forwardedHeaders(req, cookieName, jwt) {
203
+ const headers = new Headers(req.headers);
204
+ headers.delete("x-oauth42-refreshed-token");
205
+ if (cookieName && jwt) {
206
+ const cookies = req.cookies.getAll().filter((cookie) => cookie.name !== cookieName && !cookie.name.startsWith(`${cookieName}.`));
207
+ if (jwt.length <= CHUNK_SIZE) cookies.push({ name: cookieName, value: jwt });
208
+ else for (let i = 0; i * CHUNK_SIZE < jwt.length; i++) {
209
+ cookies.push({ name: `${cookieName}.${i}`, value: jwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) });
210
+ }
211
+ headers.set("cookie", cookies.map((cookie) => `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`).join("; "));
212
+ }
213
+ return headers;
214
+ }
215
+ function resolveRefreshConfig(options, token) {
216
+ return {
217
+ clientId: options.clientId || token.clientId || process.env.OAUTH42_CLIENT_ID,
218
+ clientSecret: options.clientSecret || token.clientSecret || process.env.OAUTH42_CLIENT_SECRET,
219
+ issuer: options.issuer || token.issuer || process.env.OAUTH42_ISSUER || "https://localhost:8443"
220
+ };
221
+ }
222
+ async function refreshTokens(refreshToken, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
36
223
  try {
37
224
  const tokenUrl = `${issuer}/oauth2/token`;
38
- const response = await fetch(tokenUrl, {
225
+ const fetcher = clientAssertionKey ? (url, init) => clientAssertionKey.fetch(clientId, url, init, dpop) : dpop ? dpop.fetch.bind(dpop) : fetch;
226
+ const response = await fetcher(tokenUrl, {
39
227
  method: "POST",
228
+ redirect: "error",
229
+ cache: "no-store",
230
+ signal: AbortSignal.timeout(3e4),
40
231
  headers: {
41
232
  "Content-Type": "application/x-www-form-urlencoded"
42
233
  },
@@ -49,9 +240,10 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
49
240
  });
50
241
  const data = await response.json();
51
242
  if (!response.ok) {
52
- console.error("[OAuth42 Middleware] Token refresh failed:", data);
243
+ console.error("[OAuth42 Middleware] Token refresh failed:", response.status);
53
244
  return { success: false, error: data.error || "refresh_failed" };
54
245
  }
246
+ dpop?.assertTokenResponse(data);
55
247
  console.log("[OAuth42 Middleware] Token refreshed successfully");
56
248
  return {
57
249
  success: true,
@@ -60,17 +252,14 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
60
252
  expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
61
253
  };
62
254
  } catch (error) {
63
- console.error("[OAuth42 Middleware] Token refresh error:", error);
255
+ console.error("[OAuth42 Middleware] Token refresh request failed");
64
256
  return { success: false, error: "refresh_error" };
65
257
  }
66
258
  }
67
259
  function withOAuth42Auth(options = {}) {
68
260
  const secret = process.env.NEXTAUTH_SECRET;
69
- const clientId = process.env.OAUTH42_CLIENT_ID;
70
- const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
71
- const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
72
261
  if (!secret) {
73
- console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
262
+ throw new Error("NEXTAUTH_SECRET is required for OAuth42 middleware");
74
263
  }
75
264
  return async function middleware(req) {
76
265
  const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
@@ -81,18 +270,76 @@ function withOAuth42Auth(options = {}) {
81
270
  });
82
271
  const pathname = req.nextUrl.pathname;
83
272
  if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
84
- return import_server.NextResponse.next();
273
+ return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
85
274
  }
86
275
  const needsProtection = options.protectedPaths ? options.protectedPaths.some((path) => pathname.startsWith(path)) : true;
87
276
  if (!needsProtection) {
88
- return import_server.NextResponse.next();
277
+ return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
89
278
  }
90
- if (!token) {
279
+ if (!token || token.error) {
91
280
  const signInUrl = options.pages?.signIn || "/auth/signin";
92
281
  const url = new URL(signInUrl, req.url);
93
282
  url.searchParams.set("callbackUrl", pathname);
94
283
  return import_server.NextResponse.redirect(url);
95
284
  }
285
+ const refreshConfig = resolveRefreshConfig(options, token);
286
+ const denied = () => {
287
+ const url = new URL(options.pages?.signIn || "/auth/signin", req.url);
288
+ url.searchParams.set("callbackUrl", pathname);
289
+ const response = import_server.NextResponse.redirect(url);
290
+ for (const cookie of req.cookies.getAll()) {
291
+ if (cookie.name === cookieName || cookie.name.startsWith(`${cookieName}.`)) response.cookies.delete(cookie.name);
292
+ }
293
+ return response;
294
+ };
295
+ const verifyAndAuthorize = async (current, resolved) => {
296
+ const user = resolved === void 0 ? await verifiedUserInfo(refreshConfig.issuer, current.accessToken, token.sub, options.dpop) : resolved;
297
+ if (!user) return false;
298
+ current.groups = user.groups ?? [];
299
+ current.email = user.email;
300
+ current.name = user.name;
301
+ current.username = user.preferred_username ?? user.username;
302
+ current.emailVerified = user.email_verified;
303
+ return options.callbacks?.authorized ? options.callbacks.authorized({ token: current, req }) : true;
304
+ };
305
+ if (isBackendSessionMode(options.sessionStore)) {
306
+ const sessionId = token.oauth42SessionId;
307
+ if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret && !options.clientAssertionKey) {
308
+ const signInUrl = options.pages?.signIn || "/auth/signin";
309
+ const url = new URL(signInUrl, req.url);
310
+ url.searchParams.set("callbackUrl", pathname);
311
+ const response = import_server.NextResponse.redirect(url);
312
+ response.cookies.delete(cookieName);
313
+ return response;
314
+ }
315
+ const backendSession = await resolveBackendSession({
316
+ dpop: options.dpop,
317
+ clientAssertionKey: options.clientAssertionKey,
318
+ issuer: refreshConfig.issuer,
319
+ clientId: refreshConfig.clientId,
320
+ clientSecret: refreshConfig.clientSecret || "",
321
+ sessionId
322
+ }).catch((error) => {
323
+ console.error("[OAuth42 Middleware] Backend session resolve failed:", error);
324
+ return null;
325
+ });
326
+ if (!backendSession) {
327
+ const signInUrl = options.pages?.signIn || "/auth/signin";
328
+ const url = new URL(signInUrl, req.url);
329
+ url.searchParams.set("callbackUrl", pathname);
330
+ const response = import_server.NextResponse.redirect(url);
331
+ response.cookies.delete(cookieName);
332
+ return response;
333
+ }
334
+ const verified = await verifiedBackendSessionUserInfo(refreshConfig.issuer, backendSession, token.sub, options.dpop);
335
+ if (!await verifyAndAuthorize({ ...token, accessToken: backendSession.access_token }, verified)) return denied();
336
+ const requestHeaders = forwardedHeaders(req);
337
+ return import_server.NextResponse.next({
338
+ request: {
339
+ headers: requestHeaders
340
+ }
341
+ });
342
+ }
96
343
  const expiresAt = token.expiresAt;
97
344
  const now = Math.floor(Date.now() / 1e3);
98
345
  const bufferSeconds = 15;
@@ -102,26 +349,30 @@ function withOAuth42Auth(options = {}) {
102
349
  expiresAt,
103
350
  now,
104
351
  expiredAgo: expiresAt ? now - expiresAt : "n/a",
105
- accessTokenPrefix: token.accessToken?.substring(0, 20),
106
352
  path: pathname
107
353
  });
108
354
  }
109
- if (needsRefresh && token.refreshToken && clientId && clientSecret) {
110
- const userId = token.sub;
111
- let refreshPromise = pendingRefreshes.get(userId);
355
+ if (needsRefresh && token.refreshToken && refreshConfig.clientId && (refreshConfig.clientSecret || options.clientAssertionKey)) {
356
+ const grantKey = JSON.stringify([refreshConfig.issuer, refreshConfig.clientId, refreshConfig.clientSecret, token.sub, token.refreshToken, options.dpop?.thumbprint, options.clientAssertionKey?.thumbprint, options.clientAssertionKey?.kid]);
357
+ let refreshPromise = pendingRefreshes.get(grantKey);
112
358
  if (refreshPromise) {
113
359
  console.log("[OAuth42 Middleware] Waiting for in-flight refresh...");
114
360
  } else {
115
361
  console.log("[OAuth42 Middleware] Access token expired, refreshing...");
116
362
  refreshPromise = refreshTokens(
117
363
  token.refreshToken,
118
- clientId,
119
- clientSecret,
120
- issuer
364
+ refreshConfig.clientId,
365
+ refreshConfig.clientSecret || "",
366
+ refreshConfig.issuer,
367
+ options.dpop,
368
+ options.clientAssertionKey
121
369
  ).finally(() => {
122
- setTimeout(() => pendingRefreshes.delete(userId), 3e4);
370
+ const cleanupTimer = setTimeout(() => pendingRefreshes.delete(grantKey), 3e4);
371
+ if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
372
+ cleanupTimer.unref();
373
+ }
123
374
  });
124
- pendingRefreshes.set(userId, refreshPromise);
375
+ pendingRefreshes.set(grantKey, refreshPromise);
125
376
  }
126
377
  const refreshed = await refreshPromise;
127
378
  if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
@@ -131,12 +382,13 @@ function withOAuth42Auth(options = {}) {
131
382
  refreshToken: refreshed.refreshToken,
132
383
  expiresAt: refreshed.expiresAt
133
384
  };
385
+ if (options.clientAssertionKey) delete updatedToken.clientSecret;
386
+ if (!await verifyAndAuthorize(updatedToken)) return denied();
134
387
  const newJwt = await (0, import_jwt.encode)({
135
388
  token: updatedToken,
136
389
  secret
137
390
  });
138
- const requestHeaders = new Headers(req.headers);
139
- requestHeaders.set("x-oauth42-refreshed-token", refreshed.accessToken);
391
+ const requestHeaders = forwardedHeaders(req, cookieName, newJwt);
140
392
  const response = import_server.NextResponse.next({
141
393
  request: {
142
394
  headers: requestHeaders
@@ -146,7 +398,8 @@ function withOAuth42Auth(options = {}) {
146
398
  httpOnly: true,
147
399
  sameSite: "lax",
148
400
  path: "/",
149
- secure: process.env.NODE_ENV === "production"
401
+ secure: process.env.NODE_ENV === "production",
402
+ maxAge: options.session?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS
150
403
  };
151
404
  if (newJwt.length <= CHUNK_SIZE) {
152
405
  response.cookies.set(cookieName, newJwt, cookieOptions);
@@ -188,13 +441,12 @@ function withOAuth42Auth(options = {}) {
188
441
  chunked: newJwt.length > CHUNK_SIZE,
189
442
  existingBaseCooke: req.cookies.has(cookieName),
190
443
  existingChunks: existingChunks.length > 0 ? existingChunks : "none",
191
- newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),
192
444
  newExpiresAt: refreshed.expiresAt,
193
445
  cookieName
194
446
  });
195
447
  return response;
196
448
  } else {
197
- console.log("[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session");
449
+ console.log("[OAuth42 Middleware] Refresh failed, clearing session");
198
450
  const signInUrl = options.pages?.signIn || "/auth/signin";
199
451
  const url = new URL(signInUrl, req.url);
200
452
  url.searchParams.set("callbackUrl", pathname);
@@ -211,16 +463,13 @@ function withOAuth42Auth(options = {}) {
211
463
  return response;
212
464
  }
213
465
  }
214
- if (options.callbacks?.authorized) {
215
- const isAuthorized = await options.callbacks.authorized({ token, req });
216
- if (!isAuthorized) {
217
- const signInUrl = options.pages?.signIn || "/auth/signin";
218
- const url = new URL(signInUrl, req.url);
219
- url.searchParams.set("callbackUrl", pathname);
220
- return import_server.NextResponse.redirect(url);
221
- }
466
+ if (!Number.isFinite(expiresAt) || now >= expiresAt || !token.accessToken) {
467
+ const url = new URL(options.pages?.signIn || "/auth/signin", req.url);
468
+ url.searchParams.set("callbackUrl", pathname);
469
+ return import_server.NextResponse.redirect(url);
222
470
  }
223
- return import_server.NextResponse.next();
471
+ if (!await verifyAndAuthorize(token)) return denied();
472
+ return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
224
473
  };
225
474
  }
226
475
  function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = ["/auth", "/api/auth"]) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/index.ts","../../src/server/middleware.ts"],"sourcesContent":["// Edge-compatible middleware exports\n// This file is separate from server/index.ts to avoid pulling in Node.js modules\n\nexport { withOAuth42Auth, createMiddlewareConfig } from '../server/middleware';\nexport type { OAuth42AuthOptions } from '../server/middleware';\n","import { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\n\n/**\n * Cookie chunking constants — must match NextAuth's SessionStore behavior.\n * NextAuth splits cookies at ALLOWED_COOKIE_SIZE (4096) minus overhead (163).\n * When middleware re-encodes the JWT after a token refresh, the cookie must be\n * chunked the same way, otherwise:\n * - A single cookie > ~4 KB is silently rejected by the browser\n * - Leftover chunk cookies from the original session cause SessionStore to\n * concatenate old chunks with the new base cookie, producing garbage\n */\nconst ALLOWED_COOKIE_SIZE = 4096;\nconst ESTIMATED_EMPTY_COOKIE_SIZE = 163;\nconst CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;\n\n/**\n * In-flight refresh tracking to prevent parallel refresh calls.\n * Key: user ID (from token.sub), Value: Promise that resolves with refresh result\n */\nconst pendingRefreshes = new Map<string, Promise<{\n success: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n error?: string;\n}>>();\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', data);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh error:', error);\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n const clientId = process.env.OAUTH42_CLIENT_ID;\n const clientSecret = process.env.OAUTH42_CLIENT_SECRET;\n const issuer = process.env.OAUTH42_ISSUER || 'https://localhost:8443';\n\n if (!secret) {\n console.warn('[OAuth42 Middleware] NEXTAUTH_SECRET not set');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next();\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next();\n }\n\n // No token at all - redirect to sign in\n if (!token) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n // Check if access token is expired or expiring soon (15 second buffer)\n // Buffer must be less than the token TTL to have a valid window\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 15;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh) {\n console.log('[OAuth42 Middleware] Token needs refresh:', {\n expiresAt,\n now,\n expiredAgo: expiresAt ? now - expiresAt : 'n/a',\n accessTokenPrefix: (token.accessToken as string)?.substring(0, 20),\n path: pathname,\n });\n }\n\n if (needsRefresh && token.refreshToken && clientId && clientSecret) {\n const userId = token.sub as string;\n\n // Check if there's already a refresh in progress for this user\n let refreshPromise = pendingRefreshes.get(userId);\n\n if (refreshPromise) {\n console.log('[OAuth42 Middleware] Waiting for in-flight refresh...');\n } else {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n // Create the refresh promise and store it.\n // Keep the entry for 30s after completion so late-arriving requests\n // (with stale cookies) reuse the cached result instead of triggering\n // a new refresh with the already-rotated token. See issue #470.\n refreshPromise = refreshTokens(\n token.refreshToken as string,\n clientId,\n clientSecret,\n issuer\n ).finally(() => {\n setTimeout(() => pendingRefreshes.delete(userId), 30000);\n });\n\n pendingRefreshes.set(userId, refreshPromise);\n }\n\n const refreshed = await refreshPromise;\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response with request headers that pass the new token to API routes\n // This is necessary because API routes read from the request, not the response cookie\n const requestHeaders = new Headers(req.headers);\n requestHeaders.set('x-oauth42-refreshed-token', refreshed.accessToken);\n\n const response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n\n const cookieOptions = {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n };\n\n // Set cookie with chunking matching NextAuth's SessionStore behavior.\n // Without chunking, a JWT > ~4 KB is silently rejected by the browser,\n // causing an infinite refresh loop where every request re-triggers\n // token refresh but the cookie never persists.\n if (newJwt.length <= CHUNK_SIZE) {\n // Fits in a single cookie\n response.cookies.set(cookieName, newJwt, cookieOptions);\n // Clean up any leftover chunk cookies from a previously chunked session\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n } else {\n // Cookie too large — chunk it like NextAuth does\n const chunkCount = Math.ceil(newJwt.length / CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i++) {\n const chunkName = `${cookieName}.${i}`;\n const chunkValue = newJwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n response.cookies.set(chunkName, chunkValue, cookieOptions);\n }\n // Delete the base cookie so SessionStore only reads chunks\n response.cookies.set(cookieName, '', { ...cookieOptions, maxAge: 0 });\n // Clean up any extra old chunks beyond the new count\n for (let i = chunkCount; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n }\n\n // Log details to help diagnose cookie persistence issues\n const existingChunks = [];\n for (let i = 0; ; i++) {\n if (req.cookies.has(`${cookieName}.${i}`)) {\n existingChunks.push(i);\n } else {\n break;\n }\n }\n console.log(`[OAuth42 Middleware] Token refresh complete:`, {\n jwtBytes: newJwt.length,\n chunked: newJwt.length > CHUNK_SIZE,\n existingBaseCooke: req.cookies.has(cookieName),\n existingChunks: existingChunks.length > 0 ? existingChunks : 'none',\n newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),\n newExpiresAt: refreshed.expiresAt,\n cookieName,\n });\n return response;\n } else {\n // Refresh failed - clear cookie and redirect to sign in (no error, just let user log in fresh)\n console.log('[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n // DO NOT set error parameter - just redirect silently\n\n const response = NextResponse.redirect(url);\n // Clear the base session cookie and any chunk cookies\n response.cookies.delete(cookieName);\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.delete(chunkName);\n } else {\n break;\n }\n }\n return response;\n }\n }\n\n // Check custom authorization callback\n if (options.callbacks?.authorized) {\n const isAuthorized = await options.callbacks.authorized({ token, req });\n if (!isAuthorized) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/middleware/index.ts","../../src/server/verified-userinfo.ts","../../src/server/middleware.ts","../../src/server/client-assertion.ts","../../src/server/backend-session.ts"],"sourcesContent":["// Edge-compatible middleware exports\n// This file is separate from server/index.ts to avoid pulling in Node.js modules\n\nexport { withOAuth42Auth, createMiddlewareConfig } from '../server/middleware';\nexport type { OAuth42AuthOptions } from '../server/middleware';\n","import type { OAuth42DPoP } from './dpop';\n/** Live verification binds an application session to the issuer's current user. */\nexport interface VerifiedUserInfo {\n sub: string;\n email?: string;\n name?: string;\n preferred_username?: string;\n username?: string;\n email_verified?: boolean;\n groups?: string[];\n given_name?: string;\n family_name?: string;\n company_id?: string;\n company_name?: string;\n}\n\nexport async function verifiedUserInfo(issuer: string, accessToken: unknown, subject: unknown, dpop?: OAuth42DPoP): Promise<VerifiedUserInfo | null> {\n if (typeof accessToken !== 'string' || !accessToken || typeof subject !== 'string' || !subject) return null;\n try {\n const target = `${issuer.replace(/\\/$/, '')}/oauth2/userinfo`;\n const fetcher = dpop ? (url: string, init: RequestInit) => dpop.fetch(url, init, accessToken) : fetch;\n const response = await fetcher(target, {\n headers: { Authorization: `Bearer ${accessToken}` },\n redirect: 'error', cache: 'no-store', signal: AbortSignal.timeout(30_000),\n });\n if (!response.ok) return null;\n const user = await response.json();\n return parseVerifiedUserInfo(user, subject);\n } catch {\n return null;\n }\n}\n\n\n/** Validate only live issuer response data, never cached JWT/session metadata. */\nexport function parseVerifiedUserInfo(value: unknown, subject: unknown): VerifiedUserInfo | null {\n if (typeof subject !== 'string' || !subject || !value || typeof value !== 'object' || Array.isArray(value)) return null;\n const user = value as Record<string, unknown>;\n if (user.sub !== subject) return null;\n if (user.groups !== undefined && (!Array.isArray(user.groups) || user.groups.some((group: unknown) => typeof group !== 'string'))) return null;\n // Email is required by O42's contract when present. Optional display fields\n // serialize as null for existing email-only accounts and are omitted below.\n if (user.email !== undefined && typeof user.email !== 'string') return null;\n if (user.email_verified !== undefined && typeof user.email_verified !== 'boolean') return null;\n // Return only the profile fields applications display. Never forward arbitrary\n // issuer properties into the browser session.\n const profile: VerifiedUserInfo = { sub: user.sub };\n for (const field of ['email', 'name', 'preferred_username', 'username', 'given_name', 'family_name', 'company_id', 'company_name'] as const) {\n if (user[field] != null) {\n if (typeof user[field] !== 'string') return null;\n profile[field] = user[field];\n }\n }\n if (user.email_verified !== undefined) profile.email_verified = user.email_verified;\n if (user.groups !== undefined) profile.groups = user.groups;\n return profile;\n}\n\n/** New backends verify identity within resolve; old backends retain live /userinfo. */\nexport async function verifiedBackendSessionUserInfo(\n issuer: string,\n session: { access_token: unknown; user_info?: unknown },\n subject: unknown,\n dpop?: OAuth42DPoP,\n): Promise<VerifiedUserInfo | null> {\n if (typeof session.access_token !== 'string' || !session.access_token) return null;\n if (Object.prototype.hasOwnProperty.call(session, 'user_info')) {\n // A malformed/null response fails closed. Never fall back to cached `user`\n // or let a second endpoint hide an invalid consolidated identity response.\n return parseVerifiedUserInfo(session.user_info, subject);\n }\n return verifiedUserInfo(issuer, session.access_token, subject, dpop);\n}\n","import type { OAuth42ClientAssertion } from './client-assertion';\nimport type { OAuth42DPoP } from './dpop';\nimport { verifiedBackendSessionUserInfo, verifiedUserInfo, type VerifiedUserInfo } from './verified-userinfo';\nimport { NextRequest, NextResponse } from 'next/server';\nimport { getToken, encode } from 'next-auth/jwt';\nimport {\n isBackendSessionMode,\n resolveBackendSession,\n type SessionStoreMode,\n} from './backend-session';\n\n/**\n * Cookie chunking constants — must match NextAuth's SessionStore behavior.\n * NextAuth splits cookies at ALLOWED_COOKIE_SIZE (4096) minus overhead (163).\n * When middleware re-encodes the JWT after a token refresh, the cookie must be\n * chunked the same way, otherwise:\n * - A single cookie > ~4 KB is silently rejected by the browser\n * - Leftover chunk cookies from the original session cause SessionStore to\n * concatenate old chunks with the new base cookie, producing garbage\n */\nconst ALLOWED_COOKIE_SIZE = 4096;\nconst ESTIMATED_EMPTY_COOKIE_SIZE = 163;\nconst CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;\nconst DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;\n\n/**\n * In-flight refresh tracking to prevent parallel refresh calls.\n * Key: user ID (from token.sub), Value: Promise that resolves with refresh result\n */\nconst pendingRefreshes = new Map<string, Promise<{\n success: boolean;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n error?: string;\n}>>();\n\nexport interface OAuth42AuthOptions {\n pages?: {\n signIn?: string;\n error?: string;\n };\n callbacks?: {\n authorized?: (params: { token: any; req: NextRequest }) => boolean | Promise<boolean>;\n };\n protectedPaths?: string[];\n publicPaths?: string[];\n /**\n * Cookie prefix for custom cookie names. Must match the prefix used in createAuth().\n * E.g., 'oauth42-portal' will look for cookie 'oauth42-portal.session-token'\n */\n cookiePrefix?: string;\n /**\n * OAuth client credentials for middleware refresh. When omitted, the middleware\n * prefers credentials embedded in the NextAuth token by createAuth(), then\n * falls back to OAUTH42_CLIENT_ID / OAUTH42_CLIENT_SECRET.\n */\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n dpop?: OAuth42DPoP;\n clientAssertionKey?: OAuth42ClientAssertion;\n /**\n * \"backend\" keeps OAuth token material in the Rust backend and stores only\n * an opaque backend session id in the NextAuth JWT cookie.\n */\n sessionStore?: SessionStoreMode;\n /**\n * Session cookie lifetime. Should match createAuth({ session.maxAge }).\n * Defaults to OAuth42's persistent-session policy of 365 days.\n */\n session?: {\n maxAge?: number;\n };\n}\n\n/** @deprecated Invalid refresh grants must fail closed, including concurrent requests. */\nexport function shouldPreserveSessionOnRefreshFailure(_params: {\n error?: string; expiresAt?: number; now: number;\n}): boolean {\n return false;\n}\n\nfunction forwardedHeaders(req: NextRequest, cookieName?: string, jwt?: string): Headers {\n const headers = new Headers(req.headers);\n headers.delete('x-oauth42-refreshed-token');\n if (cookieName && jwt) {\n const cookies = req.cookies.getAll().filter(cookie => cookie.name !== cookieName && !cookie.name.startsWith(`${cookieName}.`));\n if (jwt.length <= CHUNK_SIZE) cookies.push({ name: cookieName, value: jwt });\n else for (let i = 0; i * CHUNK_SIZE < jwt.length; i++) {\n cookies.push({ name: `${cookieName}.${i}`, value: jwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) });\n }\n headers.set('cookie', cookies.map(cookie => `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`).join('; '));\n }\n return headers;\n}\n\nexport function resolveRefreshConfig(\n options: OAuth42AuthOptions,\n token: any\n): { clientId?: string; clientSecret?: string; issuer: string } {\n return {\n clientId:\n options.clientId ||\n (token.clientId as string | undefined) ||\n process.env.OAUTH42_CLIENT_ID,\n clientSecret:\n options.clientSecret ||\n (token.clientSecret as string | undefined) ||\n process.env.OAUTH42_CLIENT_SECRET,\n issuer:\n options.issuer ||\n (token.issuer as string | undefined) ||\n process.env.OAUTH42_ISSUER ||\n 'https://localhost:8443',\n };\n}\n\n/**\n * Refresh tokens by calling the OAuth42 backend directly\n */\nasync function refreshTokens(\n refreshToken: string,\n clientId: string,\n clientSecret: string,\n issuer: string,\n dpop?: OAuth42DPoP,\n clientAssertionKey?: OAuth42ClientAssertion\n): Promise<{ success: boolean; accessToken?: string; refreshToken?: string; expiresAt?: number; error?: string }> {\n try {\n const tokenUrl = `${issuer}/oauth2/token`;\n\n const fetcher = clientAssertionKey ? (url: string, init: RequestInit) => clientAssertionKey.fetch(clientId, url, init, dpop) : dpop ? dpop.fetch.bind(dpop) : fetch;\n const response = await fetcher(tokenUrl, {\n method: 'POST',\n redirect: 'error',\n cache: 'no-store',\n signal: AbortSignal.timeout(30_000),\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n console.error('[OAuth42 Middleware] Token refresh failed:', response.status);\n return { success: false, error: data.error || 'refresh_failed' };\n }\n\n dpop?.assertTokenResponse(data);\n console.log('[OAuth42 Middleware] Token refreshed successfully');\n return {\n success: true,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),\n };\n } catch (error) {\n console.error('[OAuth42 Middleware] Token refresh request failed');\n return { success: false, error: 'refresh_error' };\n }\n}\n\n/**\n * Middleware helper for protecting routes with OAuth42\n *\n * This middleware handles:\n * 1. Route protection (redirect to login if no session)\n * 2. Token refresh (refresh expired tokens and update cookie)\n */\nexport function withOAuth42Auth(options: OAuth42AuthOptions = {}) {\n const secret = process.env.NEXTAUTH_SECRET;\n\n if (!secret) {\n throw new Error('NEXTAUTH_SECRET is required for OAuth42 middleware');\n }\n\n return async function middleware(req: NextRequest) {\n // Build cookie name - if prefix is provided, use custom name\n const cookieName = options.cookiePrefix\n ? `${options.cookiePrefix}.session-token`\n : 'next-auth.session-token';\n\n const token = await getToken({\n req: req as any,\n secret,\n cookieName,\n });\n\n const pathname = req.nextUrl.pathname;\n\n // Check if path is explicitly public\n if (options.publicPaths?.some(path => pathname.startsWith(path))) {\n return NextResponse.next({ request: { headers: forwardedHeaders(req) } });\n }\n\n // Check if path needs protection\n const needsProtection = options.protectedPaths\n ? options.protectedPaths.some(path => pathname.startsWith(path))\n : true; // Default to protecting all paths\n\n if (!needsProtection) {\n return NextResponse.next({ request: { headers: forwardedHeaders(req) } });\n }\n\n // No token at all - redirect to sign in\n if (!token || token.error) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n\n const refreshConfig = resolveRefreshConfig(options, token);\n\n const denied = () => {\n const url = new URL(options.pages?.signIn || '/auth/signin', req.url);\n url.searchParams.set('callbackUrl', pathname);\n const response = NextResponse.redirect(url);\n for (const cookie of req.cookies.getAll()) {\n if (cookie.name === cookieName || cookie.name.startsWith(`${cookieName}.`)) response.cookies.delete(cookie.name);\n }\n return response;\n };\n const verifyAndAuthorize = async (current: typeof token, resolved?: VerifiedUserInfo | null) => {\n const user = resolved === undefined\n ? await verifiedUserInfo(refreshConfig.issuer, current.accessToken, token.sub, options.dpop)\n : resolved;\n if (!user) return false;\n current.groups = user.groups ?? [];\n current.email = user.email;\n current.name = user.name;\n current.username = user.preferred_username ?? user.username;\n current.emailVerified = user.email_verified;\n return options.callbacks?.authorized ? options.callbacks.authorized({ token: current, req }) : true;\n };\n\n if (isBackendSessionMode(options.sessionStore)) {\n const sessionId = token.oauth42SessionId as string | undefined;\n if (!sessionId || !refreshConfig.clientId || (!refreshConfig.clientSecret && !options.clientAssertionKey)) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n const response = NextResponse.redirect(url);\n response.cookies.delete(cookieName);\n return response;\n }\n\n const backendSession = await resolveBackendSession({\n dpop: options.dpop, clientAssertionKey: options.clientAssertionKey,\n issuer: refreshConfig.issuer,\n clientId: refreshConfig.clientId,\n clientSecret: refreshConfig.clientSecret || '',\n sessionId,\n }).catch(error => {\n console.error('[OAuth42 Middleware] Backend session resolve failed:', error);\n return null;\n });\n\n if (!backendSession) {\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n const response = NextResponse.redirect(url);\n response.cookies.delete(cookieName);\n return response;\n }\n\n const verified = await verifiedBackendSessionUserInfo(refreshConfig.issuer, backendSession, token.sub, options.dpop);\n if (!(await verifyAndAuthorize({ ...token, accessToken: backendSession.access_token }, verified))) return denied();\n const requestHeaders = forwardedHeaders(req);\n return NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n }\n\n // Check if access token is expired or expiring soon (15 second buffer)\n // Buffer must be less than the token TTL to have a valid window\n const expiresAt = token.expiresAt as number | undefined;\n const now = Math.floor(Date.now() / 1000);\n const bufferSeconds = 15;\n const needsRefresh = expiresAt && now >= expiresAt - bufferSeconds;\n\n if (needsRefresh) {\n console.log('[OAuth42 Middleware] Token needs refresh:', {\n expiresAt,\n now,\n expiredAgo: expiresAt ? now - expiresAt : 'n/a',\n path: pathname,\n });\n }\n\n if (needsRefresh && token.refreshToken && refreshConfig.clientId && (refreshConfig.clientSecret || options.clientAssertionKey)) {\n const grantKey = JSON.stringify([refreshConfig.issuer, refreshConfig.clientId, refreshConfig.clientSecret, token.sub, token.refreshToken, options.dpop?.thumbprint, options.clientAssertionKey?.thumbprint, options.clientAssertionKey?.kid]);\n\n // Check if there's already a refresh in progress for this user\n let refreshPromise = pendingRefreshes.get(grantKey);\n\n if (refreshPromise) {\n console.log('[OAuth42 Middleware] Waiting for in-flight refresh...');\n } else {\n console.log('[OAuth42 Middleware] Access token expired, refreshing...');\n\n // Create the refresh promise and store it.\n // Keep the entry for 30s after completion so late-arriving requests\n // (with stale cookies) reuse the cached result instead of triggering\n // a new refresh with the already-rotated token. See issue #470.\n refreshPromise = refreshTokens(\n token.refreshToken as string,\n refreshConfig.clientId,\n refreshConfig.clientSecret || '',\n refreshConfig.issuer,\n options.dpop,\n options.clientAssertionKey\n ).finally(() => {\n const cleanupTimer = setTimeout(() => pendingRefreshes.delete(grantKey), 30000);\n if (typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {\n cleanupTimer.unref();\n }\n });\n\n pendingRefreshes.set(grantKey, refreshPromise);\n }\n\n const refreshed = await refreshPromise;\n\n if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {\n // Update the token with new values\n const updatedToken: typeof token = {\n ...token,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: refreshed.expiresAt,\n };\n\n if (options.clientAssertionKey) delete updatedToken.clientSecret;\n if (!(await verifyAndAuthorize(updatedToken))) return denied();\n // Re-encode the JWT\n const newJwt = await encode({\n token: updatedToken,\n secret: secret!,\n });\n\n // Create response with request headers that pass the new token to API routes\n // This is necessary because API routes read from the request, not the response cookie\n const requestHeaders = forwardedHeaders(req, cookieName, newJwt);\n\n const response = NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n });\n\n const cookieOptions = {\n httpOnly: true,\n sameSite: 'lax' as const,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n maxAge: options.session?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS,\n };\n\n // Set cookie with chunking matching NextAuth's SessionStore behavior.\n // Without chunking, a JWT > ~4 KB is silently rejected by the browser,\n // causing an infinite refresh loop where every request re-triggers\n // token refresh but the cookie never persists.\n if (newJwt.length <= CHUNK_SIZE) {\n // Fits in a single cookie\n response.cookies.set(cookieName, newJwt, cookieOptions);\n // Clean up any leftover chunk cookies from a previously chunked session\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n } else {\n // Cookie too large — chunk it like NextAuth does\n const chunkCount = Math.ceil(newJwt.length / CHUNK_SIZE);\n for (let i = 0; i < chunkCount; i++) {\n const chunkName = `${cookieName}.${i}`;\n const chunkValue = newJwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n response.cookies.set(chunkName, chunkValue, cookieOptions);\n }\n // Delete the base cookie so SessionStore only reads chunks\n response.cookies.set(cookieName, '', { ...cookieOptions, maxAge: 0 });\n // Clean up any extra old chunks beyond the new count\n for (let i = chunkCount; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.set(chunkName, '', { ...cookieOptions, maxAge: 0 });\n } else {\n break;\n }\n }\n }\n\n // Log details to help diagnose cookie persistence issues\n const existingChunks = [];\n for (let i = 0; ; i++) {\n if (req.cookies.has(`${cookieName}.${i}`)) {\n existingChunks.push(i);\n } else {\n break;\n }\n }\n console.log(`[OAuth42 Middleware] Token refresh complete:`, {\n jwtBytes: newJwt.length,\n chunked: newJwt.length > CHUNK_SIZE,\n existingBaseCooke: req.cookies.has(cookieName),\n existingChunks: existingChunks.length > 0 ? existingChunks : 'none',\n newExpiresAt: refreshed.expiresAt,\n cookieName,\n });\n return response;\n } else {\n // Refresh failed - clear cookie and redirect to sign in (no error, just let user log in fresh)\n console.log('[OAuth42 Middleware] Refresh failed, clearing session');\n const signInUrl = options.pages?.signIn || '/auth/signin';\n const url = new URL(signInUrl, req.url);\n url.searchParams.set('callbackUrl', pathname);\n // DO NOT set error parameter - just redirect silently\n\n const response = NextResponse.redirect(url);\n // Clear the base session cookie and any chunk cookies\n response.cookies.delete(cookieName);\n for (let i = 0; ; i++) {\n const chunkName = `${cookieName}.${i}`;\n if (req.cookies.has(chunkName)) {\n response.cookies.delete(chunkName);\n } else {\n break;\n }\n }\n return response;\n }\n }\n\n\n if (!Number.isFinite(expiresAt) || now >= (expiresAt as number) || !token.accessToken) {\n const url = new URL(options.pages?.signIn || '/auth/signin', req.url);\n url.searchParams.set('callbackUrl', pathname);\n return NextResponse.redirect(url);\n }\n if (!(await verifyAndAuthorize(token))) return denied();\n return NextResponse.next({ request: { headers: forwardedHeaders(req) } });\n };\n}\n\n/**\n * Helper to create middleware configuration\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[] = ['/protected'],\n publicPaths: string[] = ['/auth', '/api/auth']\n) {\n return {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - public folder\n */\n '/((?!_next/static|_next/image|favicon.ico|public).*)',\n ],\n protectedPaths,\n publicPaths,\n };\n}\n","import { createHash, createPublicKey, generateKeyPairSync, KeyObject, randomBytes, sign } from 'node:crypto';\nimport type { OAuth42DPoP } from './dpop';\n\n/** Retained software signing key for a confidential backend. Never put this in a session. */\nexport class OAuth42ClientAssertion {\n readonly #key: KeyObject;\n readonly #public: Record<string, string>;\n readonly kid: string;\n readonly thumbprint: string;\n\n constructor(kid: string, key: KeyObject) {\n if (typeof kid !== 'string' || !/^[\\x21-\\x7e]{1,128}$/.test(kid)) throw new Error('Invalid client signing key ID');\n if (key.type !== 'private' || key.asymmetricKeyType !== 'ec' || key.asymmetricKeyDetails?.namedCurve !== 'prime256v1') {\n throw new Error('Client assertions require a private P-256 key');\n }\n this.#key = key;\n this.kid = kid;\n const publicKey = createPublicKey(key).export({ format: 'jwk' });\n this.#public = { crv: 'P-256', kty: 'EC', x: publicKey.x!, y: publicKey.y! };\n this.thumbprint = createHash('sha256').update(JSON.stringify(this.#public)).digest('base64url');\n }\n\n static generate(kid: string): OAuth42ClientAssertion {\n return new OAuth42ClientAssertion(kid, generateKeyPairSync('ec', { namedCurve: 'prime256v1' }).privateKey);\n }\n\n get publicJwk(): Record<string, string> { return { ...this.#public, kid: this.kid }; }\n\n /** Private keys for the server-only NextAuth/openid-client configuration, never registration. */\n get oidcJwks() {\n return { keys: [{ kty: 'EC', crv: 'P-256', x: this.#public.x, y: this.#public.y,\n d: this.#key.export({ format: 'jwk' }).d!, kid: this.kid }] };\n }\n\n static validateEndpoint(target: string): void {\n const url = new URL(target);\n if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash ||\n (url.protocol !== 'https:' && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))) {\n throw new Error('Client authentication requires HTTPS outside loopback development');\n }\n }\n\n assertion(clientId: string, audience: string): string {\n OAuth42ClientAssertion.validateEndpoint(audience);\n if (!clientId || Buffer.byteLength(clientId) > 256) throw new Error('Invalid client identity');\n const now = Math.floor(Date.now() / 1000);\n const header = { alg: 'ES256', typ: 'JWT', kid: this.kid };\n const payload = { iss: clientId, sub: clientId, aud: audience, iat: now, exp: now + 60, jti: randomBytes(32).toString('base64url') };\n const data = `${Buffer.from(JSON.stringify(header)).toString('base64url')}.${Buffer.from(JSON.stringify(payload)).toString('base64url')}`;\n return `${data}.${sign('sha256', Buffer.from(data), { key: this.#key, dsaEncoding: 'ieee-p1363' }).toString('base64url')}`;\n }\n\n async fetch(clientId: string, target: string, init: RequestInit, dpop?: OAuth42DPoP): Promise<Response> {\n if (init.method?.toUpperCase() !== 'POST' || !(typeof init.body === 'string' || init.body instanceof URLSearchParams)) {\n throw new Error('Client assertions require a form POST');\n }\n const prepare = (): RequestInit => {\n const body = new URLSearchParams(init.body as string | URLSearchParams);\n body.delete('client_secret');\n body.set('client_id', clientId);\n body.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');\n body.set('client_assertion', this.assertion(clientId, target));\n const headers = new Headers(init.headers);\n headers.delete('Authorization'); headers.delete('Content-Length'); headers.delete('Transfer-Encoding');\n headers.set('Content-Type', 'application/x-www-form-urlencoded');\n return { ...init, body, headers, redirect: 'error', cache: 'no-store', signal: init.signal || AbortSignal.timeout(30_000) };\n };\n return dpop ? dpop.fetch(target, init, undefined, prepare) : fetch(target, prepare());\n }\n}\n","import type { OAuth42DPoP } from './dpop';\nimport { OAuth42ClientAssertion } from './client-assertion';\nexport type SessionStoreMode = 'jwt' | 'backend';\n\nexport interface BackendSessionUser {\n sub?: string;\n email?: string;\n username?: string;\n name?: string;\n emailVerified?: boolean;\n groups?: string[];\n}\n\nexport interface BackendSessionResponse {\n token_type?: string;\n session_id: string;\n access_token: string;\n expires_at: number;\n id_token?: string;\n user?: BackendSessionUser;\n /** Wire data must pass subject/shape validation before becoming a session. */\n user_info?: unknown;\n}\n\nexport interface BackendSessionConfig {\n issuer: string;\n clientId: string;\n clientSecret: string;\n dpop?: OAuth42DPoP;\n clientAssertionKey?: OAuth42ClientAssertion;\n}\n\nexport function isBackendSessionMode(mode?: SessionStoreMode): boolean {\n return mode === 'backend';\n}\n\nfunction sessionEndpoint(issuer: string): string {\n return `${issuer.replace(/\\/$/, '')}/api/sdk/sessions`;\n}\n\nasync function sessionFetch(params: BackendSessionConfig, target: string, body: Record<string, unknown>, credential: string, method = 'POST'): Promise<Response> {\n OAuth42ClientAssertion.validateEndpoint(target);\n const prepare = (): RequestInit => ({\n method, redirect: 'error', cache: 'no-store', signal: AbortSignal.timeout(30_000),\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...body, client_id: params.clientId,\n ...(params.clientAssertionKey ? {\n client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',\n client_assertion: params.clientAssertionKey.assertion(params.clientId, target),\n } : { client_secret: params.clientSecret }),\n }),\n });\n return params.dpop ? params.dpop.fetch(target, {}, credential, prepare) : fetch(target, prepare());\n}\n\nasync function sessionResponse(response: Response, params: BackendSessionConfig): Promise<BackendSessionResponse> {\n if (!response.ok) throw new Error(`OAuth42 backend session request failed: ${response.status}`);\n const session: BackendSessionResponse = await response.json();\n if (params.dpop) params.dpop.assertTokenResponse(session);\n else if (session.token_type?.toLowerCase() === 'dpop') throw new Error('Backend session requires its retained proof key');\n return session;\n}\n\nexport async function createBackendSession(params: BackendSessionConfig & {\n accessToken: string; refreshToken: string; expiresAt?: number; idToken?: string; user?: BackendSessionUser;\n}): Promise<BackendSessionResponse> {\n return sessionResponse(await sessionFetch(params, sessionEndpoint(params.issuer), {\n access_token: params.accessToken, refresh_token: params.refreshToken,\n id_token: params.idToken, user: params.user,\n }, params.accessToken), params);\n}\n\nexport async function resolveBackendSession(params: BackendSessionConfig & { sessionId: string }): Promise<BackendSessionResponse | null> {\n const response = await sessionFetch(params, `${sessionEndpoint(params.issuer)}/resolve`, { session_id: params.sessionId }, params.sessionId);\n if ([401, 403, 404].includes(response.status)) return null;\n return sessionResponse(response, params);\n}\n\nexport async function revokeBackendSession(params: BackendSessionConfig & { sessionId: string }): Promise<void> {\n const response = await sessionFetch(params, sessionEndpoint(params.issuer), { session_id: params.sessionId }, params.sessionId, 'DELETE');\n if (!response.ok) throw new Error(`OAuth42 backend session revoke failed: ${response.status}`);\n}\n"],"mappings":"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","names":[]}