@oauth42/next 0.5.0 → 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.
- package/README.md +117 -0
- package/dist/build-manifest.json +59 -0
- package/dist/client/index.d.mts +7 -6
- package/dist/client/index.d.ts +7 -6
- package/dist/client/index.js +13 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +13 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/{index-Cfqxl9r2.d.mts → index-BuZTiUds.d.mts} +51 -1
- package/dist/{index-Cfqxl9r2.d.ts → index-BuZTiUds.d.ts} +51 -1
- package/dist/index.d.mts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +469 -159
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +465 -158
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +2 -1
- package/dist/middleware/index.d.ts +2 -1
- package/dist/middleware/index.js +218 -66
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +219 -66
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/{auth-D3tAruwx.d.mts → par-Cyu6M1e6.d.mts} +27 -12
- package/dist/{auth-CYMPe3k0.d.ts → par-DQNt8j-s.d.ts} +27 -12
- package/dist/server/index.d.mts +7 -3
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.js +503 -178
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +499 -177
- package/dist/server/index.mjs.map +1 -1
- package/package.json +14 -5
- package/src/types/index.ts +2 -0
- package/src/types/next-auth.d.ts +2 -0
package/dist/middleware/index.js
CHANGED
|
@@ -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,10 +32,128 @@ __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");
|
|
31
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
|
+
|
|
32
157
|
// src/server/backend-session.ts
|
|
33
158
|
function isBackendSessionMode(mode) {
|
|
34
159
|
return mode === "backend";
|
|
@@ -36,38 +161,56 @@ function isBackendSessionMode(mode) {
|
|
|
36
161
|
function sessionEndpoint(issuer) {
|
|
37
162
|
return `${issuer.replace(/\/$/, "")}/api/sdk/sessions`;
|
|
38
163
|
}
|
|
39
|
-
async function
|
|
40
|
-
|
|
41
|
-
|
|
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),
|
|
42
171
|
headers: { "Content-Type": "application/json" },
|
|
43
172
|
body: JSON.stringify({
|
|
44
|
-
|
|
173
|
+
...body,
|
|
45
174
|
client_id: params.clientId,
|
|
46
|
-
|
|
47
|
-
|
|
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 }
|
|
48
179
|
})
|
|
49
180
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!response.ok) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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);
|
|
57
194
|
}
|
|
58
195
|
|
|
59
196
|
// src/server/middleware.ts
|
|
60
197
|
var ALLOWED_COOKIE_SIZE = 4096;
|
|
61
198
|
var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
|
|
62
199
|
var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
|
|
63
|
-
var REFRESH_RACE_GRACE_SECONDS = 60;
|
|
64
200
|
var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
|
|
65
201
|
var pendingRefreshes = /* @__PURE__ */ new Map();
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
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("; "));
|
|
69
212
|
}
|
|
70
|
-
return
|
|
213
|
+
return headers;
|
|
71
214
|
}
|
|
72
215
|
function resolveRefreshConfig(options, token) {
|
|
73
216
|
return {
|
|
@@ -76,11 +219,15 @@ function resolveRefreshConfig(options, token) {
|
|
|
76
219
|
issuer: options.issuer || token.issuer || process.env.OAUTH42_ISSUER || "https://localhost:8443"
|
|
77
220
|
};
|
|
78
221
|
}
|
|
79
|
-
async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
222
|
+
async function refreshTokens(refreshToken, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
|
|
80
223
|
try {
|
|
81
224
|
const tokenUrl = `${issuer}/oauth2/token`;
|
|
82
|
-
const
|
|
225
|
+
const fetcher = clientAssertionKey ? (url, init) => clientAssertionKey.fetch(clientId, url, init, dpop) : dpop ? dpop.fetch.bind(dpop) : fetch;
|
|
226
|
+
const response = await fetcher(tokenUrl, {
|
|
83
227
|
method: "POST",
|
|
228
|
+
redirect: "error",
|
|
229
|
+
cache: "no-store",
|
|
230
|
+
signal: AbortSignal.timeout(3e4),
|
|
84
231
|
headers: {
|
|
85
232
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
86
233
|
},
|
|
@@ -93,9 +240,10 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
|
93
240
|
});
|
|
94
241
|
const data = await response.json();
|
|
95
242
|
if (!response.ok) {
|
|
96
|
-
console.error("[OAuth42 Middleware] Token refresh failed:",
|
|
243
|
+
console.error("[OAuth42 Middleware] Token refresh failed:", response.status);
|
|
97
244
|
return { success: false, error: data.error || "refresh_failed" };
|
|
98
245
|
}
|
|
246
|
+
dpop?.assertTokenResponse(data);
|
|
99
247
|
console.log("[OAuth42 Middleware] Token refreshed successfully");
|
|
100
248
|
return {
|
|
101
249
|
success: true,
|
|
@@ -104,14 +252,14 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
|
|
|
104
252
|
expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
|
|
105
253
|
};
|
|
106
254
|
} catch (error) {
|
|
107
|
-
console.error("[OAuth42 Middleware] Token refresh
|
|
255
|
+
console.error("[OAuth42 Middleware] Token refresh request failed");
|
|
108
256
|
return { success: false, error: "refresh_error" };
|
|
109
257
|
}
|
|
110
258
|
}
|
|
111
259
|
function withOAuth42Auth(options = {}) {
|
|
112
260
|
const secret = process.env.NEXTAUTH_SECRET;
|
|
113
261
|
if (!secret) {
|
|
114
|
-
|
|
262
|
+
throw new Error("NEXTAUTH_SECRET is required for OAuth42 middleware");
|
|
115
263
|
}
|
|
116
264
|
return async function middleware(req) {
|
|
117
265
|
const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
|
|
@@ -122,22 +270,41 @@ function withOAuth42Auth(options = {}) {
|
|
|
122
270
|
});
|
|
123
271
|
const pathname = req.nextUrl.pathname;
|
|
124
272
|
if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
|
|
125
|
-
return import_server.NextResponse.next();
|
|
273
|
+
return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
|
|
126
274
|
}
|
|
127
275
|
const needsProtection = options.protectedPaths ? options.protectedPaths.some((path) => pathname.startsWith(path)) : true;
|
|
128
276
|
if (!needsProtection) {
|
|
129
|
-
return import_server.NextResponse.next();
|
|
277
|
+
return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
|
|
130
278
|
}
|
|
131
|
-
if (!token) {
|
|
279
|
+
if (!token || token.error) {
|
|
132
280
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
133
281
|
const url = new URL(signInUrl, req.url);
|
|
134
282
|
url.searchParams.set("callbackUrl", pathname);
|
|
135
283
|
return import_server.NextResponse.redirect(url);
|
|
136
284
|
}
|
|
137
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
|
+
};
|
|
138
305
|
if (isBackendSessionMode(options.sessionStore)) {
|
|
139
306
|
const sessionId = token.oauth42SessionId;
|
|
140
|
-
if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret) {
|
|
307
|
+
if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret && !options.clientAssertionKey) {
|
|
141
308
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
142
309
|
const url = new URL(signInUrl, req.url);
|
|
143
310
|
url.searchParams.set("callbackUrl", pathname);
|
|
@@ -146,9 +313,11 @@ function withOAuth42Auth(options = {}) {
|
|
|
146
313
|
return response;
|
|
147
314
|
}
|
|
148
315
|
const backendSession = await resolveBackendSession({
|
|
316
|
+
dpop: options.dpop,
|
|
317
|
+
clientAssertionKey: options.clientAssertionKey,
|
|
149
318
|
issuer: refreshConfig.issuer,
|
|
150
319
|
clientId: refreshConfig.clientId,
|
|
151
|
-
clientSecret: refreshConfig.clientSecret,
|
|
320
|
+
clientSecret: refreshConfig.clientSecret || "",
|
|
152
321
|
sessionId
|
|
153
322
|
}).catch((error) => {
|
|
154
323
|
console.error("[OAuth42 Middleware] Backend session resolve failed:", error);
|
|
@@ -162,8 +331,9 @@ function withOAuth42Auth(options = {}) {
|
|
|
162
331
|
response.cookies.delete(cookieName);
|
|
163
332
|
return response;
|
|
164
333
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
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);
|
|
167
337
|
return import_server.NextResponse.next({
|
|
168
338
|
request: {
|
|
169
339
|
headers: requestHeaders
|
|
@@ -179,13 +349,12 @@ function withOAuth42Auth(options = {}) {
|
|
|
179
349
|
expiresAt,
|
|
180
350
|
now,
|
|
181
351
|
expiredAgo: expiresAt ? now - expiresAt : "n/a",
|
|
182
|
-
accessTokenPrefix: token.accessToken?.substring(0, 20),
|
|
183
352
|
path: pathname
|
|
184
353
|
});
|
|
185
354
|
}
|
|
186
|
-
if (needsRefresh && token.refreshToken && refreshConfig.clientId && refreshConfig.clientSecret) {
|
|
187
|
-
const
|
|
188
|
-
let refreshPromise = pendingRefreshes.get(
|
|
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);
|
|
189
358
|
if (refreshPromise) {
|
|
190
359
|
console.log("[OAuth42 Middleware] Waiting for in-flight refresh...");
|
|
191
360
|
} else {
|
|
@@ -193,15 +362,17 @@ function withOAuth42Auth(options = {}) {
|
|
|
193
362
|
refreshPromise = refreshTokens(
|
|
194
363
|
token.refreshToken,
|
|
195
364
|
refreshConfig.clientId,
|
|
196
|
-
refreshConfig.clientSecret,
|
|
197
|
-
refreshConfig.issuer
|
|
365
|
+
refreshConfig.clientSecret || "",
|
|
366
|
+
refreshConfig.issuer,
|
|
367
|
+
options.dpop,
|
|
368
|
+
options.clientAssertionKey
|
|
198
369
|
).finally(() => {
|
|
199
|
-
const cleanupTimer = setTimeout(() => pendingRefreshes.delete(
|
|
370
|
+
const cleanupTimer = setTimeout(() => pendingRefreshes.delete(grantKey), 3e4);
|
|
200
371
|
if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
201
372
|
cleanupTimer.unref();
|
|
202
373
|
}
|
|
203
374
|
});
|
|
204
|
-
pendingRefreshes.set(
|
|
375
|
+
pendingRefreshes.set(grantKey, refreshPromise);
|
|
205
376
|
}
|
|
206
377
|
const refreshed = await refreshPromise;
|
|
207
378
|
if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
|
|
@@ -211,12 +382,13 @@ function withOAuth42Auth(options = {}) {
|
|
|
211
382
|
refreshToken: refreshed.refreshToken,
|
|
212
383
|
expiresAt: refreshed.expiresAt
|
|
213
384
|
};
|
|
385
|
+
if (options.clientAssertionKey) delete updatedToken.clientSecret;
|
|
386
|
+
if (!await verifyAndAuthorize(updatedToken)) return denied();
|
|
214
387
|
const newJwt = await (0, import_jwt.encode)({
|
|
215
388
|
token: updatedToken,
|
|
216
389
|
secret
|
|
217
390
|
});
|
|
218
|
-
const requestHeaders =
|
|
219
|
-
requestHeaders.set("x-oauth42-refreshed-token", refreshed.accessToken);
|
|
391
|
+
const requestHeaders = forwardedHeaders(req, cookieName, newJwt);
|
|
220
392
|
const response = import_server.NextResponse.next({
|
|
221
393
|
request: {
|
|
222
394
|
headers: requestHeaders
|
|
@@ -269,28 +441,11 @@ function withOAuth42Auth(options = {}) {
|
|
|
269
441
|
chunked: newJwt.length > CHUNK_SIZE,
|
|
270
442
|
existingBaseCooke: req.cookies.has(cookieName),
|
|
271
443
|
existingChunks: existingChunks.length > 0 ? existingChunks : "none",
|
|
272
|
-
newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),
|
|
273
444
|
newExpiresAt: refreshed.expiresAt,
|
|
274
445
|
cookieName
|
|
275
446
|
});
|
|
276
447
|
return response;
|
|
277
448
|
} else {
|
|
278
|
-
if (shouldPreserveSessionOnRefreshFailure({
|
|
279
|
-
error: refreshed.error,
|
|
280
|
-
expiresAt,
|
|
281
|
-
now
|
|
282
|
-
})) {
|
|
283
|
-
console.warn(
|
|
284
|
-
"[OAuth42 Middleware] Refresh failed with invalid_grant for a recently expired token; preserving session cookie for likely parallel refresh race",
|
|
285
|
-
{
|
|
286
|
-
expiresAt,
|
|
287
|
-
now,
|
|
288
|
-
expiredAgo: now - expiresAt,
|
|
289
|
-
path: pathname
|
|
290
|
-
}
|
|
291
|
-
);
|
|
292
|
-
return import_server.NextResponse.next();
|
|
293
|
-
}
|
|
294
449
|
console.log("[OAuth42 Middleware] Refresh failed, clearing session");
|
|
295
450
|
const signInUrl = options.pages?.signIn || "/auth/signin";
|
|
296
451
|
const url = new URL(signInUrl, req.url);
|
|
@@ -308,16 +463,13 @@ function withOAuth42Auth(options = {}) {
|
|
|
308
463
|
return response;
|
|
309
464
|
}
|
|
310
465
|
}
|
|
311
|
-
if (
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const url = new URL(signInUrl, req.url);
|
|
316
|
-
url.searchParams.set("callbackUrl", pathname);
|
|
317
|
-
return import_server.NextResponse.redirect(url);
|
|
318
|
-
}
|
|
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);
|
|
319
470
|
}
|
|
320
|
-
return
|
|
471
|
+
if (!await verifyAndAuthorize(token)) return denied();
|
|
472
|
+
return import_server.NextResponse.next({ request: { headers: forwardedHeaders(req) } });
|
|
321
473
|
};
|
|
322
474
|
}
|
|
323
475
|
function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = ["/auth", "/api/auth"]) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/middleware/index.ts","../../src/server/middleware.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 { 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 REFRESH_RACE_GRACE_SECONDS = 60;\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 /**\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\nexport function shouldPreserveSessionOnRefreshFailure(params: {\n error?: string;\n expiresAt?: number;\n now: number;\n}): boolean {\n if (params.error !== 'invalid_grant' || !params.expiresAt) {\n return false;\n }\n\n return params.now - params.expiresAt <= REFRESH_RACE_GRACE_SECONDS;\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): 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\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 const refreshConfig = resolveRefreshConfig(options, token);\n\n if (isBackendSessionMode(options.sessionStore)) {\n const sessionId = token.oauth42SessionId as string | undefined;\n if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret) {\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 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 requestHeaders = new Headers(req.headers);\n requestHeaders.set('x-oauth42-refreshed-token', backendSession.access_token);\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 accessTokenPrefix: (token.accessToken as string)?.substring(0, 20),\n path: pathname,\n });\n }\n\n if (needsRefresh && token.refreshToken && refreshConfig.clientId && refreshConfig.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 refreshConfig.clientId,\n refreshConfig.clientSecret,\n refreshConfig.issuer\n ).finally(() => {\n const cleanupTimer = setTimeout(() => pendingRefreshes.delete(userId), 30000);\n if (typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {\n cleanupTimer.unref();\n }\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 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 newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),\n newExpiresAt: refreshed.expiresAt,\n cookieName,\n });\n return response;\n } else {\n if (shouldPreserveSessionOnRefreshFailure({\n error: refreshed.error,\n expiresAt,\n now,\n })) {\n console.warn(\n '[OAuth42 Middleware] Refresh failed with invalid_grant for a recently expired token; preserving session cookie for likely parallel refresh race',\n {\n expiresAt,\n now,\n expiredAgo: now - expiresAt,\n path: pathname,\n }\n );\n return NextResponse.next();\n }\n\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 // 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","export 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 session_id: string;\n access_token: string;\n expires_at: number;\n id_token?: string;\n user?: BackendSessionUser;\n}\n\nexport interface BackendSessionConfig {\n issuer: string;\n clientId: string;\n clientSecret: string;\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\nexport async function createBackendSession(params: BackendSessionConfig & {\n accessToken: string;\n refreshToken: string;\n expiresAt?: number;\n idToken?: string;\n user?: BackendSessionUser;\n}): Promise<BackendSessionResponse> {\n const response = await fetch(sessionEndpoint(params.issuer), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n access_token: params.accessToken,\n refresh_token: params.refreshToken,\n expires_at: params.expiresAt ?? Math.floor(Date.now() / 1000) + 3600,\n id_token: params.idToken,\n client_id: params.clientId,\n client_secret: params.clientSecret,\n issuer: params.issuer,\n user: params.user,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`OAuth42 backend session create failed: ${response.status}`);\n }\n\n return response.json();\n}\n\nexport async function resolveBackendSession(params: BackendSessionConfig & {\n sessionId: string;\n}): Promise<BackendSessionResponse | null> {\n const response = await fetch(`${sessionEndpoint(params.issuer)}/resolve`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_id: params.sessionId,\n client_id: params.clientId,\n client_secret: params.clientSecret,\n issuer: params.issuer,\n }),\n });\n\n if (response.status === 401 || response.status === 403 || response.status === 404) {\n return null;\n }\n\n if (!response.ok) {\n throw new Error(`OAuth42 backend session resolve failed: ${response.status}`);\n }\n\n return response.json();\n}\n\nexport async function revokeBackendSession(params: BackendSessionConfig & {\n sessionId: string;\n}): Promise<void> {\n await fetch(sessionEndpoint(params.issuer), {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_id: params.sessionId,\n client_id: params.clientId,\n client_secret: params.clientSecret,\n }),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAA0C;AAC1C,iBAAiC;;;ACwB1B,SAAS,qBAAqB,MAAkC;AACrE,SAAO,SAAS;AAClB;AAEA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,GAAG,OAAO,QAAQ,OAAO,EAAE,CAAC;AACrC;AA+BA,eAAsB,sBAAsB,QAED;AACzC,QAAM,WAAW,MAAM,MAAM,GAAG,gBAAgB,OAAO,MAAM,CAAC,YAAY;AAAA,IACxE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,YAAY,OAAO;AAAA,MACnB,WAAW,OAAO;AAAA,MAClB,eAAe,OAAO;AAAA,MACtB,QAAQ,OAAO;AAAA,IACjB,CAAC;AAAA,EACH,CAAC;AAED,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACjF,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,2CAA2C,SAAS,MAAM,EAAE;AAAA,EAC9E;AAEA,SAAO,SAAS,KAAK;AACvB;;;ADpEA,IAAM,sBAAsB;AAC5B,IAAM,8BAA8B;AACpC,IAAM,aAAa,sBAAsB;AACzC,IAAM,6BAA6B;AACnC,IAAM,kCAAkC,MAAM,KAAK,KAAK;AAMxD,IAAM,mBAAmB,oBAAI,IAMzB;AAuCG,SAAS,sCAAsC,QAI1C;AACV,MAAI,OAAO,UAAU,mBAAmB,CAAC,OAAO,WAAW;AACzD,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,MAAM,OAAO,aAAa;AAC1C;AAEO,SAAS,qBACd,SACA,OAC8D;AAC9D,SAAO;AAAA,IACL,UACE,QAAQ,YACP,MAAM,YACP,QAAQ,IAAI;AAAA,IACd,cACE,QAAQ,gBACP,MAAM,gBACP,QAAQ,IAAI;AAAA,IACd,QACE,QAAQ,UACP,MAAM,UACP,QAAQ,IAAI,kBACZ;AAAA,EACJ;AACF;AAKA,eAAe,cACb,cACA,UACA,cACA,QACgH;AAChH,MAAI;AACF,UAAM,WAAW,GAAG,MAAM;AAE1B,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,eAAe;AAAA,QACf,WAAW;AAAA,QACX,eAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,MAAM,8CAA8C,IAAI;AAChE,aAAO,EAAE,SAAS,OAAO,OAAO,KAAK,SAAS,iBAAiB;AAAA,IACjE;AAEA,YAAQ,IAAI,mDAAmD;AAC/D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,KAAK,KAAK,cAAc;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,6CAA6C,KAAK;AAChE,WAAO,EAAE,SAAS,OAAO,OAAO,gBAAgB;AAAA,EAClD;AACF;AASO,SAAS,gBAAgB,UAA8B,CAAC,GAAG;AAChE,QAAM,SAAS,QAAQ,IAAI;AAE3B,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,8CAA8C;AAAA,EAC7D;AAEA,SAAO,eAAe,WAAW,KAAkB;AAEjD,UAAM,aAAa,QAAQ,eACvB,GAAG,QAAQ,YAAY,mBACvB;AAEJ,UAAM,QAAQ,UAAM,qBAAS;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,WAAW,IAAI,QAAQ;AAG7B,QAAI,QAAQ,aAAa,KAAK,UAAQ,SAAS,WAAW,IAAI,CAAC,GAAG;AAChE,aAAO,2BAAa,KAAK;AAAA,IAC3B;AAGA,UAAM,kBAAkB,QAAQ,iBAC5B,QAAQ,eAAe,KAAK,UAAQ,SAAS,WAAW,IAAI,CAAC,IAC7D;AAEJ,QAAI,CAAC,iBAAiB;AACpB,aAAO,2BAAa,KAAK;AAAA,IAC3B;AAGA,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,YAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,UAAI,aAAa,IAAI,eAAe,QAAQ;AAC5C,aAAO,2BAAa,SAAS,GAAG;AAAA,IAClC;AAEA,UAAM,gBAAgB,qBAAqB,SAAS,KAAK;AAEzD,QAAI,qBAAqB,QAAQ,YAAY,GAAG;AAC9C,YAAM,YAAY,MAAM;AACxB,UAAI,CAAC,aAAa,CAAC,cAAc,YAAY,CAAC,cAAc,cAAc;AACxE,cAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,cAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,YAAI,aAAa,IAAI,eAAe,QAAQ;AAC5C,cAAM,WAAW,2BAAa,SAAS,GAAG;AAC1C,iBAAS,QAAQ,OAAO,UAAU;AAClC,eAAO;AAAA,MACT;AAEA,YAAM,iBAAiB,MAAM,sBAAsB;AAAA,QACjD,QAAQ,cAAc;AAAA,QACtB,UAAU,cAAc;AAAA,QACxB,cAAc,cAAc;AAAA,QAC5B;AAAA,MACF,CAAC,EAAE,MAAM,WAAS;AAChB,gBAAQ,MAAM,wDAAwD,KAAK;AAC3E,eAAO;AAAA,MACT,CAAC;AAED,UAAI,CAAC,gBAAgB;AACnB,cAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,cAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,YAAI,aAAa,IAAI,eAAe,QAAQ;AAC5C,cAAM,WAAW,2BAAa,SAAS,GAAG;AAC1C,iBAAS,QAAQ,OAAO,UAAU;AAClC,eAAO;AAAA,MACT;AAEA,YAAM,iBAAiB,IAAI,QAAQ,IAAI,OAAO;AAC9C,qBAAe,IAAI,6BAA6B,eAAe,YAAY;AAC3E,aAAO,2BAAa,KAAK;AAAA,QACvB,SAAS;AAAA,UACP,SAAS;AAAA,QACX;AAAA,MACF,CAAC;AAAA,IACH;AAIA,UAAM,YAAY,MAAM;AACxB,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,UAAM,gBAAgB;AACtB,UAAM,eAAe,aAAa,OAAO,YAAY;AAErD,QAAI,cAAc;AAChB,cAAQ,IAAI,6CAA6C;AAAA,QACvD;AAAA,QACA;AAAA,QACA,YAAY,YAAY,MAAM,YAAY;AAAA,QAC1C,mBAAoB,MAAM,aAAwB,UAAU,GAAG,EAAE;AAAA,QACjE,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,QAAI,gBAAgB,MAAM,gBAAgB,cAAc,YAAY,cAAc,cAAc;AAC9F,YAAM,SAAS,MAAM;AAGrB,UAAI,iBAAiB,iBAAiB,IAAI,MAAM;AAEhD,UAAI,gBAAgB;AAClB,gBAAQ,IAAI,uDAAuD;AAAA,MACrE,OAAO;AACL,gBAAQ,IAAI,0DAA0D;AAMtE,yBAAiB;AAAA,UACf,MAAM;AAAA,UACN,cAAc;AAAA,UACd,cAAc;AAAA,UACd,cAAc;AAAA,QAChB,EAAE,QAAQ,MAAM;AACd,gBAAM,eAAe,WAAW,MAAM,iBAAiB,OAAO,MAAM,GAAG,GAAK;AAC5E,cAAI,OAAO,iBAAiB,YAAY,WAAW,cAAc;AAC/D,yBAAa,MAAM;AAAA,UACrB;AAAA,QACF,CAAC;AAED,yBAAiB,IAAI,QAAQ,cAAc;AAAA,MAC7C;AAEA,YAAM,YAAY,MAAM;AAExB,UAAI,UAAU,WAAW,UAAU,eAAe,UAAU,cAAc;AAExE,cAAM,eAAe;AAAA,UACnB,GAAG;AAAA,UACH,aAAa,UAAU;AAAA,UACvB,cAAc,UAAU;AAAA,UACxB,WAAW,UAAU;AAAA,QACvB;AAGA,cAAM,SAAS,UAAM,mBAAO;AAAA,UAC1B,OAAO;AAAA,UACP;AAAA,QACF,CAAC;AAID,cAAM,iBAAiB,IAAI,QAAQ,IAAI,OAAO;AAC9C,uBAAe,IAAI,6BAA6B,UAAU,WAAW;AAErE,cAAM,WAAW,2BAAa,KAAK;AAAA,UACjC,SAAS;AAAA,YACP,SAAS;AAAA,UACX;AAAA,QACF,CAAC;AAED,cAAM,gBAAgB;AAAA,UACpB,UAAU;AAAA,UACV,UAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAQ,QAAQ,IAAI,aAAa;AAAA,UACjC,QAAQ,QAAQ,SAAS,UAAU;AAAA,QACrC;AAMA,YAAI,OAAO,UAAU,YAAY;AAE/B,mBAAS,QAAQ,IAAI,YAAY,QAAQ,aAAa;AAEtD,mBAAS,IAAI,KAAK,KAAK;AACrB,kBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,gBAAI,IAAI,QAAQ,IAAI,SAAS,GAAG;AAC9B,uBAAS,QAAQ,IAAI,WAAW,IAAI,EAAE,GAAG,eAAe,QAAQ,EAAE,CAAC;AAAA,YACrE,OAAO;AACL;AAAA,YACF;AAAA,UACF;AAAA,QACF,OAAO;AAEL,gBAAM,aAAa,KAAK,KAAK,OAAO,SAAS,UAAU;AACvD,mBAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,kBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,kBAAM,aAAa,OAAO,MAAM,IAAI,aAAa,IAAI,KAAK,UAAU;AACpE,qBAAS,QAAQ,IAAI,WAAW,YAAY,aAAa;AAAA,UAC3D;AAEA,mBAAS,QAAQ,IAAI,YAAY,IAAI,EAAE,GAAG,eAAe,QAAQ,EAAE,CAAC;AAEpE,mBAAS,IAAI,cAAc,KAAK;AAC9B,kBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,gBAAI,IAAI,QAAQ,IAAI,SAAS,GAAG;AAC9B,uBAAS,QAAQ,IAAI,WAAW,IAAI,EAAE,GAAG,eAAe,QAAQ,EAAE,CAAC;AAAA,YACrE,OAAO;AACL;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,cAAM,iBAAiB,CAAC;AACxB,iBAAS,IAAI,KAAK,KAAK;AACrB,cAAI,IAAI,QAAQ,IAAI,GAAG,UAAU,IAAI,CAAC,EAAE,GAAG;AACzC,2BAAe,KAAK,CAAC;AAAA,UACvB,OAAO;AACL;AAAA,UACF;AAAA,QACF;AACA,gBAAQ,IAAI,gDAAgD;AAAA,UAC1D,UAAU,OAAO;AAAA,UACjB,SAAS,OAAO,SAAS;AAAA,UACzB,mBAAmB,IAAI,QAAQ,IAAI,UAAU;AAAA,UAC7C,gBAAgB,eAAe,SAAS,IAAI,iBAAiB;AAAA,UAC7D,sBAAsB,UAAU,aAAa,UAAU,GAAG,EAAE;AAAA,UAC5D,cAAc,UAAU;AAAA,UACxB;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT,OAAO;AACL,YAAI,sCAAsC;AAAA,UACxC,OAAO,UAAU;AAAA,UACjB;AAAA,UACA;AAAA,QACF,CAAC,GAAG;AACF,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,cACE;AAAA,cACA;AAAA,cACA,YAAY,MAAM;AAAA,cAClB,MAAM;AAAA,YACR;AAAA,UACF;AACA,iBAAO,2BAAa,KAAK;AAAA,QAC3B;AAGA,gBAAQ,IAAI,uDAAuD;AACnE,cAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,cAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,YAAI,aAAa,IAAI,eAAe,QAAQ;AAG5C,cAAM,WAAW,2BAAa,SAAS,GAAG;AAE1C,iBAAS,QAAQ,OAAO,UAAU;AAClC,iBAAS,IAAI,KAAK,KAAK;AACrB,gBAAM,YAAY,GAAG,UAAU,IAAI,CAAC;AACpC,cAAI,IAAI,QAAQ,IAAI,SAAS,GAAG;AAC9B,qBAAS,QAAQ,OAAO,SAAS;AAAA,UACnC,OAAO;AACL;AAAA,UACF;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,QAAQ,WAAW,YAAY;AACjC,YAAM,eAAe,MAAM,QAAQ,UAAU,WAAW,EAAE,OAAO,IAAI,CAAC;AACtE,UAAI,CAAC,cAAc;AACjB,cAAM,YAAY,QAAQ,OAAO,UAAU;AAC3C,cAAM,MAAM,IAAI,IAAI,WAAW,IAAI,GAAG;AACtC,YAAI,aAAa,IAAI,eAAe,QAAQ;AAC5C,eAAO,2BAAa,SAAS,GAAG;AAAA,MAClC;AAAA,IACF;AAEA,WAAO,2BAAa,KAAK;AAAA,EAC3B;AACF;AAKO,SAAS,uBACd,iBAA2B,CAAC,YAAY,GACxC,cAAwB,CAAC,SAAS,WAAW,GAC7C;AACA,SAAO;AAAA,IACL,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQP;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","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":[]}
|