@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.
package/dist/index.mjs CHANGED
@@ -1,14 +1,102 @@
1
+ var __typeError = (msg) => {
2
+ throw TypeError(msg);
3
+ };
4
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6
+ 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);
7
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
+
9
+ // src/server/client-assertion.ts
10
+ import { createHash, createPublicKey, generateKeyPairSync, randomBytes, sign } from "crypto";
11
+ var _key, _public;
12
+ var _OAuth42ClientAssertion = class _OAuth42ClientAssertion {
13
+ constructor(kid, key) {
14
+ __privateAdd(this, _key);
15
+ __privateAdd(this, _public);
16
+ if (typeof kid !== "string" || !/^[\x21-\x7e]{1,128}$/.test(kid)) throw new Error("Invalid client signing key ID");
17
+ if (key.type !== "private" || key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
18
+ throw new Error("Client assertions require a private P-256 key");
19
+ }
20
+ __privateSet(this, _key, key);
21
+ this.kid = kid;
22
+ const publicKey = createPublicKey(key).export({ format: "jwk" });
23
+ __privateSet(this, _public, { crv: "P-256", kty: "EC", x: publicKey.x, y: publicKey.y });
24
+ this.thumbprint = createHash("sha256").update(JSON.stringify(__privateGet(this, _public))).digest("base64url");
25
+ }
26
+ static generate(kid) {
27
+ return new _OAuth42ClientAssertion(kid, generateKeyPairSync("ec", { namedCurve: "prime256v1" }).privateKey);
28
+ }
29
+ get publicJwk() {
30
+ return { ...__privateGet(this, _public), kid: this.kid };
31
+ }
32
+ /** Private keys for the server-only NextAuth/openid-client configuration, never registration. */
33
+ get oidcJwks() {
34
+ return { keys: [{
35
+ kty: "EC",
36
+ crv: "P-256",
37
+ x: __privateGet(this, _public).x,
38
+ y: __privateGet(this, _public).y,
39
+ d: __privateGet(this, _key).export({ format: "jwk" }).d,
40
+ kid: this.kid
41
+ }] };
42
+ }
43
+ static validateEndpoint(target) {
44
+ const url = new URL(target);
45
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash || url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) {
46
+ throw new Error("Client authentication requires HTTPS outside loopback development");
47
+ }
48
+ }
49
+ assertion(clientId, audience) {
50
+ _OAuth42ClientAssertion.validateEndpoint(audience);
51
+ if (!clientId || Buffer.byteLength(clientId) > 256) throw new Error("Invalid client identity");
52
+ const now = Math.floor(Date.now() / 1e3);
53
+ const header = { alg: "ES256", typ: "JWT", kid: this.kid };
54
+ const payload = { iss: clientId, sub: clientId, aud: audience, iat: now, exp: now + 60, jti: randomBytes(32).toString("base64url") };
55
+ const data = `${Buffer.from(JSON.stringify(header)).toString("base64url")}.${Buffer.from(JSON.stringify(payload)).toString("base64url")}`;
56
+ return `${data}.${sign("sha256", Buffer.from(data), { key: __privateGet(this, _key), dsaEncoding: "ieee-p1363" }).toString("base64url")}`;
57
+ }
58
+ async fetch(clientId, target, init, dpop) {
59
+ if (init.method?.toUpperCase() !== "POST" || !(typeof init.body === "string" || init.body instanceof URLSearchParams)) {
60
+ throw new Error("Client assertions require a form POST");
61
+ }
62
+ const prepare = () => {
63
+ const body = new URLSearchParams(init.body);
64
+ body.delete("client_secret");
65
+ body.set("client_id", clientId);
66
+ body.set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
67
+ body.set("client_assertion", this.assertion(clientId, target));
68
+ const headers = new Headers(init.headers);
69
+ headers.delete("Authorization");
70
+ headers.delete("Content-Length");
71
+ headers.delete("Transfer-Encoding");
72
+ headers.set("Content-Type", "application/x-www-form-urlencoded");
73
+ return { ...init, body, headers, redirect: "error", cache: "no-store", signal: init.signal || AbortSignal.timeout(3e4) };
74
+ };
75
+ return dpop ? dpop.fetch(target, init, void 0, prepare) : fetch(target, prepare());
76
+ }
77
+ };
78
+ _key = new WeakMap();
79
+ _public = new WeakMap();
80
+ var OAuth42ClientAssertion = _OAuth42ClientAssertion;
81
+
1
82
  // src/provider.ts
2
83
  function OAuth42Provider(options) {
3
84
  const issuer = options.issuer || process.env.OAUTH42_ISSUER || "https://api.oauth42.com";
4
85
  const baseUrl = issuer.replace(/\/$/, "");
86
+ const { dpop, clientAssertionKey, ...providerOptions } = options;
87
+ if (clientAssertionKey) {
88
+ OAuth42ClientAssertion.validateEndpoint(`${baseUrl}/oauth2/token`);
89
+ providerOptions.clientSecret = void 0;
90
+ }
91
+ if (options.pkceEnabled === false) throw new Error("OAuth42 requires S256 PKCE");
5
92
  return {
6
93
  id: "oauth42",
7
94
  name: "OAuth42",
8
95
  type: "oauth",
9
96
  version: "2.0",
10
- // Use OIDC discovery to automatically find endpoints
11
- wellKnown: `${baseUrl}/.well-known/openid-configuration`,
97
+ // Pin O42 metadata: NextAuth v4 discovery does not compare the returned issuer.
98
+ // Constructing its OIDC client from these trusted endpoints preserves issuer binding.
99
+ jwks_endpoint: `${baseUrl}/.well-known/jwks.json`,
12
100
  // Use ID token for user profile (standard OIDC pattern)
13
101
  // This avoids an extra HTTP call to /userinfo on every auth flow
14
102
  // Apps can call /userinfo separately when they need extended claims (groups, company_id, etc.)
@@ -21,17 +109,26 @@ function OAuth42Provider(options) {
21
109
  response_type: "code"
22
110
  }
23
111
  },
24
- token: `${baseUrl}/oauth2/token`,
112
+ token: dpop ? {
113
+ url: `${baseUrl}/oauth2/token`,
114
+ async request({ client, provider, params, checks }) {
115
+ const tokens = await client.callback(provider.callbackUrl, params, checks, { DPoP: dpop.oidcKey });
116
+ dpop.assertTokenResponse(tokens);
117
+ return { tokens };
118
+ }
119
+ } : `${baseUrl}/oauth2/token`,
25
120
  userinfo: `${baseUrl}/oauth2/userinfo`,
121
+ ...clientAssertionKey ? { jwks: clientAssertionKey.oidcJwks } : {},
26
122
  client: {
27
123
  id: options.clientId,
28
- secret: options.clientSecret,
29
- token_endpoint_auth_method: "client_secret_post",
124
+ secret: clientAssertionKey ? void 0 : options.clientSecret,
125
+ token_endpoint_auth_method: clientAssertionKey ? "private_key_jwt" : "client_secret_post",
126
+ ...clientAssertionKey ? { token_endpoint_auth_signing_alg: "ES256" } : {},
30
127
  id_token_signed_response_alg: "RS256"
31
128
  // OAuth42 uses RS256 for ID tokens
32
129
  },
33
- issuer: baseUrl,
34
- checks: options.pkceEnabled !== false ? ["pkce", "state"] : ["state"],
130
+ issuer,
131
+ checks: ["pkce", "state", "nonce"],
35
132
  profile(profile, tokens) {
36
133
  return {
37
134
  id: profile.sub || profile.id || profile.email,
@@ -47,8 +144,115 @@ function OAuth42Provider(options) {
47
144
  bg: "#1e40af",
48
145
  text: "#ffffff"
49
146
  },
50
- options
147
+ // NextAuth v4 types require a string even when private_key_jwt sends no secret.
148
+ options: { ...providerOptions, clientSecret: providerOptions.clientSecret || "" }
149
+ };
150
+ }
151
+
152
+ // src/server/par.ts
153
+ async function pushAuthorizationURL(value, config) {
154
+ const base = config.issuer.replace(/\/$/, "");
155
+ let url;
156
+ try {
157
+ url = new URL(value);
158
+ } catch {
159
+ return value;
160
+ }
161
+ if (`${url.origin}${url.pathname}` !== `${base}/oauth2/authorize`) return value;
162
+ if (url.username || url.password || url.hash) throw new Error("Invalid authorization endpoint");
163
+ const body = new URLSearchParams(url.search);
164
+ if (body.getAll("client_id").length !== 1 || body.get("client_id") !== config.clientId || body.get("response_type") !== "code" || !body.get("state") || !body.get("nonce") || body.get("code_challenge_method") !== "S256" || !/^[A-Za-z0-9_-]{43}$/.test(body.get("code_challenge") || "") || ["request_uri", "client_secret", "client_assertion", "client_assertion_type"].some((k) => body.has(k))) {
165
+ throw new Error("Invalid PAR authorization transaction");
166
+ }
167
+ const target = `${base}/oauth2/par`;
168
+ OAuth42ClientAssertion.validateEndpoint(target);
169
+ if (!config.clientAssertionKey) body.set("client_secret", config.clientSecret || "");
170
+ const init = {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
173
+ body,
174
+ redirect: "error",
175
+ cache: "no-store",
176
+ signal: AbortSignal.timeout(3e4)
51
177
  };
178
+ const response = config.clientAssertionKey ? await config.clientAssertionKey.fetch(config.clientId, target, init, config.dpop) : config.dpop ? await config.dpop.fetch(target, init) : await fetch(target, init);
179
+ if (response.status !== 201) throw new Error("Pushed authorization failed");
180
+ const pushed = await response.json();
181
+ if (typeof pushed.request_uri !== "string" || !/^urn:ietf:params:oauth:request_uri:[A-Za-z0-9_-]{43}$/.test(pushed.request_uri) || !Number.isInteger(pushed.expires_in) || pushed.expires_in <= 0 || pushed.expires_in > 600) throw new Error("Invalid PAR response");
182
+ url.search = new URLSearchParams({ client_id: config.clientId, request_uri: pushed.request_uri }).toString();
183
+ return url.href;
184
+ }
185
+ function withPushedAuthorization(handler, config) {
186
+ return async (request, context) => {
187
+ if (!(request instanceof Request)) throw new Error("PAR handlers require Next.js App Router Request/Response");
188
+ const response = await handler(request, context);
189
+ const location = response.headers.get("Location");
190
+ if (location) {
191
+ const next = await pushAuthorizationURL(location, config);
192
+ if (next === location) return response;
193
+ const headers = new Headers(response.headers);
194
+ headers.set("Location", next);
195
+ headers.delete("content-length");
196
+ return new Response(response.body, { status: response.status, headers });
197
+ }
198
+ if (response.headers.get("content-type")?.includes("application/json")) {
199
+ const body = await response.clone().json();
200
+ if (typeof body?.url === "string") {
201
+ const next = await pushAuthorizationURL(body.url, config);
202
+ if (next !== body.url) {
203
+ const headers = new Headers(response.headers);
204
+ headers.delete("content-length");
205
+ return new Response(JSON.stringify({ ...body, url: next }), { status: response.status, headers });
206
+ }
207
+ }
208
+ }
209
+ return response;
210
+ };
211
+ }
212
+
213
+ // src/server/verified-userinfo.ts
214
+ async function verifiedUserInfo(issuer, accessToken, subject, dpop) {
215
+ if (typeof accessToken !== "string" || !accessToken || typeof subject !== "string" || !subject) return null;
216
+ try {
217
+ const target = `${issuer.replace(/\/$/, "")}/oauth2/userinfo`;
218
+ const fetcher = dpop ? (url, init) => dpop.fetch(url, init, accessToken) : fetch;
219
+ const response = await fetcher(target, {
220
+ headers: { Authorization: `Bearer ${accessToken}` },
221
+ redirect: "error",
222
+ cache: "no-store",
223
+ signal: AbortSignal.timeout(3e4)
224
+ });
225
+ if (!response.ok) return null;
226
+ const user = await response.json();
227
+ return parseVerifiedUserInfo(user, subject);
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ function parseVerifiedUserInfo(value, subject) {
233
+ if (typeof subject !== "string" || !subject || !value || typeof value !== "object" || Array.isArray(value)) return null;
234
+ const user = value;
235
+ if (user.sub !== subject) return null;
236
+ if (user.groups !== void 0 && (!Array.isArray(user.groups) || user.groups.some((group) => typeof group !== "string"))) return null;
237
+ if (user.email !== void 0 && typeof user.email !== "string") return null;
238
+ if (user.email_verified !== void 0 && typeof user.email_verified !== "boolean") return null;
239
+ const profile = { sub: user.sub };
240
+ for (const field of ["email", "name", "preferred_username", "username", "given_name", "family_name", "company_id", "company_name"]) {
241
+ if (user[field] != null) {
242
+ if (typeof user[field] !== "string") return null;
243
+ profile[field] = user[field];
244
+ }
245
+ }
246
+ if (user.email_verified !== void 0) profile.email_verified = user.email_verified;
247
+ if (user.groups !== void 0) profile.groups = user.groups;
248
+ return profile;
249
+ }
250
+ async function verifiedBackendSessionUserInfo(issuer, session, subject, dpop) {
251
+ if (typeof session.access_token !== "string" || !session.access_token) return null;
252
+ if (Object.prototype.hasOwnProperty.call(session, "user_info")) {
253
+ return parseVerifiedUserInfo(session.user_info, subject);
254
+ }
255
+ return verifiedUserInfo(issuer, session.access_token, subject, dpop);
52
256
  }
53
257
 
54
258
  // src/server/auth.ts
@@ -56,36 +260,11 @@ import NextAuthDefault from "next-auth";
56
260
 
57
261
  // src/server/session.ts
58
262
  import { getServerSession as getNextAuthSession } from "next-auth";
59
- import { headers } from "next/headers";
60
- var REFRESHED_TOKEN_HEADER = "x-oauth42-refreshed-token";
61
263
  async function getOAuth42Session(...args) {
62
264
  const session = await getNextAuthSession(...args);
63
- if (!session) {
265
+ if (!session || session.error) {
64
266
  return null;
65
267
  }
66
- let refreshedToken = null;
67
- try {
68
- if (args.length === 1) {
69
- const headersList = await headers();
70
- refreshedToken = headersList.get(REFRESHED_TOKEN_HEADER);
71
- } else if (args.length === 3) {
72
- const req = args[0];
73
- refreshedToken = req.headers[REFRESHED_TOKEN_HEADER] || null;
74
- }
75
- } catch {
76
- }
77
- if (refreshedToken) {
78
- const sessionToken = session.accessToken;
79
- console.log("[OAuth42 Session] Using refreshed token from middleware:", {
80
- refreshedPrefix: refreshedToken.substring(0, 20),
81
- sessionPrefix: sessionToken?.substring(0, 20),
82
- tokensMatch: refreshedToken === sessionToken
83
- });
84
- return {
85
- ...session,
86
- accessToken: refreshedToken
87
- };
88
- }
89
268
  return session;
90
269
  }
91
270
  function withOAuth42Session(handler, authOptions) {
@@ -116,13 +295,66 @@ function withOAuth42ServerSideProps(getServerSideProps, authOptions) {
116
295
  };
117
296
  }
118
297
 
298
+ // src/server/backend-session.ts
299
+ function isBackendSessionMode(mode) {
300
+ return mode === "backend";
301
+ }
302
+ function sessionEndpoint(issuer) {
303
+ return `${issuer.replace(/\/$/, "")}/api/sdk/sessions`;
304
+ }
305
+ async function sessionFetch(params, target, body, credential, method = "POST") {
306
+ OAuth42ClientAssertion.validateEndpoint(target);
307
+ const prepare = () => ({
308
+ method,
309
+ redirect: "error",
310
+ cache: "no-store",
311
+ signal: AbortSignal.timeout(3e4),
312
+ headers: { "Content-Type": "application/json" },
313
+ body: JSON.stringify({
314
+ ...body,
315
+ client_id: params.clientId,
316
+ ...params.clientAssertionKey ? {
317
+ client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
318
+ client_assertion: params.clientAssertionKey.assertion(params.clientId, target)
319
+ } : { client_secret: params.clientSecret }
320
+ })
321
+ });
322
+ return params.dpop ? params.dpop.fetch(target, {}, credential, prepare) : fetch(target, prepare());
323
+ }
324
+ async function sessionResponse(response, params) {
325
+ if (!response.ok) throw new Error(`OAuth42 backend session request failed: ${response.status}`);
326
+ const session = await response.json();
327
+ if (params.dpop) params.dpop.assertTokenResponse(session);
328
+ else if (session.token_type?.toLowerCase() === "dpop") throw new Error("Backend session requires its retained proof key");
329
+ return session;
330
+ }
331
+ async function createBackendSession(params) {
332
+ return sessionResponse(await sessionFetch(params, sessionEndpoint(params.issuer), {
333
+ access_token: params.accessToken,
334
+ refresh_token: params.refreshToken,
335
+ id_token: params.idToken,
336
+ user: params.user
337
+ }, params.accessToken), params);
338
+ }
339
+ async function resolveBackendSession(params) {
340
+ const response = await sessionFetch(params, `${sessionEndpoint(params.issuer)}/resolve`, { session_id: params.sessionId }, params.sessionId);
341
+ if ([401, 403, 404].includes(response.status)) return null;
342
+ return sessionResponse(response, params);
343
+ }
344
+ async function revokeBackendSession(params) {
345
+ const response = await sessionFetch(params, sessionEndpoint(params.issuer), { session_id: params.sessionId }, params.sessionId, "DELETE");
346
+ if (!response.ok) throw new Error(`OAuth42 backend session revoke failed: ${response.status}`);
347
+ }
348
+
119
349
  // src/server/auth.ts
120
350
  var NextAuth = NextAuthDefault.default || NextAuthDefault;
121
- var activeRefresh = null;
351
+ var activeRefreshes = /* @__PURE__ */ new Map();
122
352
  function createAuth(options = {}) {
123
353
  const clientId = options.clientId || process.env.OAUTH42_CLIENT_ID;
124
- const clientSecret = options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
125
- if (!clientId || !clientSecret) {
354
+ const clientSecret = options.clientAssertionKey ? "" : options.clientSecret || process.env.OAUTH42_CLIENT_SECRET;
355
+ const issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER || "https://localhost:8443";
356
+ const useBackendSessions = isBackendSessionMode(options.sessionStore);
357
+ if (!clientId || !clientSecret && !options.clientAssertionKey) {
126
358
  throw new Error(
127
359
  "OAuth42 client credentials are required. Set OAUTH42_CLIENT_ID and OAUTH42_CLIENT_SECRET environment variables or pass them in the options."
128
360
  );
@@ -131,24 +363,57 @@ function createAuth(options = {}) {
131
363
  providers: [
132
364
  OAuth42Provider({
133
365
  clientId,
134
- clientSecret,
135
- issuer: options.issuer,
366
+ clientSecret: clientSecret || "",
367
+ issuer,
136
368
  scopes: options.scopes,
137
- pkceEnabled: options.pkceEnabled
369
+ pkceEnabled: options.pkceEnabled,
370
+ dpop: options.dpop,
371
+ clientAssertionKey: options.clientAssertionKey
138
372
  })
139
373
  ],
140
374
  callbacks: {
141
375
  async jwt({ token, account, profile }) {
376
+ if (options.clientAssertionKey) delete token.clientSecret;
142
377
  console.log("[OAuth42 SDK] JWT callback called", { hasAccount: !!account, hasProfile: !!profile });
143
378
  if (account) {
144
- console.log("[OAuth42 SDK] Initial sign in - storing tokens in JWT");
145
- token.accessToken = account.access_token;
146
- token.refreshToken = account.refresh_token;
147
- token.expiresAt = account.expires_at;
148
- token.idToken = account.id_token;
149
- token.clientId = clientId;
150
- token.clientSecret = clientSecret;
151
- token.issuer = options.issuer || process.env.NEXT_PUBLIC_OAUTH_ISSUER || process.env.OAUTH42_ISSUER;
379
+ if (useBackendSessions) {
380
+ if (!account.access_token || !account.refresh_token) {
381
+ throw new Error("OAuth42 backend sessions require access and refresh tokens");
382
+ }
383
+ const oauth42Profile = profile;
384
+ const backendSession = await createBackendSession({
385
+ dpop: options.dpop,
386
+ clientAssertionKey: options.clientAssertionKey,
387
+ issuer,
388
+ clientId,
389
+ clientSecret: clientSecret || "",
390
+ accessToken: account.access_token,
391
+ refreshToken: account.refresh_token,
392
+ expiresAt: account.expires_at,
393
+ idToken: account.id_token,
394
+ user: {
395
+ sub: oauth42Profile?.sub,
396
+ email: oauth42Profile?.email,
397
+ username: oauth42Profile?.username,
398
+ name: token.name,
399
+ emailVerified: oauth42Profile?.email_verified,
400
+ groups: oauth42Profile?.groups
401
+ }
402
+ });
403
+ token.oauth42SessionId = backendSession.session_id;
404
+ token.expiresAt = backendSession.expires_at;
405
+ token.issuer = issuer;
406
+ } else {
407
+ console.log("[OAuth42 SDK] Initial sign in - storing tokens in JWT");
408
+ token.accessToken = account.access_token;
409
+ token.refreshToken = account.refresh_token;
410
+ token.expiresAt = account.expires_at;
411
+ token.idToken = account.id_token;
412
+ token.clientId = clientId;
413
+ if (options.clientAssertionKey) delete token.clientSecret;
414
+ else token.clientSecret = clientSecret;
415
+ token.issuer = issuer;
416
+ }
152
417
  }
153
418
  if (profile) {
154
419
  const oauth42Profile = profile;
@@ -165,12 +430,68 @@ function createAuth(options = {}) {
165
430
  },
166
431
  async session({ session, token }) {
167
432
  console.log("[OAuth42 SDK] Session callback called", { hasToken: !!token, hasSession: !!session });
168
- session.accessToken = token.accessToken;
169
- session.idToken = token.idToken;
433
+ const revokedSession = () => {
434
+ session.error = "SessionRevoked";
435
+ delete session.accessToken;
436
+ delete session.idToken;
437
+ delete session.userInfo;
438
+ delete session.user;
439
+ return session;
440
+ };
441
+ if (token.error) return revokedSession();
442
+ let verified;
443
+ if (useBackendSessions) {
444
+ const sessionId = token.oauth42SessionId;
445
+ if (!sessionId) {
446
+ return revokedSession();
447
+ }
448
+ const backendSession = await resolveBackendSession({
449
+ dpop: options.dpop,
450
+ clientAssertionKey: options.clientAssertionKey,
451
+ issuer,
452
+ clientId,
453
+ clientSecret: clientSecret || "",
454
+ sessionId
455
+ });
456
+ if (!backendSession) {
457
+ return revokedSession();
458
+ }
459
+ session.accessToken = backendSession.access_token;
460
+ session.idToken = backendSession.id_token;
461
+ token.expiresAt = backendSession.expires_at;
462
+ verified = await verifiedBackendSessionUserInfo(issuer, backendSession, token.sub, options.dpop);
463
+ if (backendSession.user && session.user) {
464
+ session.user.email = backendSession.user.email;
465
+ session.user.name = backendSession.user.name;
466
+ session.user.username = backendSession.user.username;
467
+ session.user.emailVerified = backendSession.user.emailVerified;
468
+ session.user.groups = backendSession.user.groups;
469
+ }
470
+ } else {
471
+ session.accessToken = token.accessToken;
472
+ session.idToken = token.idToken;
473
+ verified = await verifiedUserInfo(issuer, session.accessToken, token.sub, options.dpop);
474
+ }
475
+ if (!verified) {
476
+ return revokedSession();
477
+ }
478
+ session.userInfo = verified;
479
+ token.email = verified.email;
480
+ token.name = verified.name;
481
+ token.username = verified.preferred_username ?? verified.username;
482
+ token.emailVerified = verified.email_verified;
483
+ token.groups = verified.groups ?? [];
484
+ if (session.user) {
485
+ session.user.email = verified.email;
486
+ session.user.name = verified.name;
487
+ session.user.username = verified.preferred_username ?? verified.username;
488
+ session.user.emailVerified = verified.email_verified;
489
+ session.user.groups = verified.groups ?? [];
490
+ }
170
491
  if (token.error) {
171
492
  session.error = token.error;
172
493
  }
173
- if (session.user) {
494
+ if (!useBackendSessions && session.user) {
174
495
  session.user.email = token.email;
175
496
  session.user.name = token.name;
176
497
  session.user.username = token.username;
@@ -194,6 +515,28 @@ function createAuth(options = {}) {
194
515
  strategy: "jwt",
195
516
  ...options.session
196
517
  },
518
+ events: {
519
+ async signOut(message) {
520
+ if (useBackendSessions) {
521
+ const sessionId = message.token?.oauth42SessionId;
522
+ if (sessionId) {
523
+ await revokeBackendSession({
524
+ dpop: options.dpop,
525
+ clientAssertionKey: options.clientAssertionKey,
526
+ issuer,
527
+ clientId,
528
+ clientSecret: clientSecret || "",
529
+ sessionId
530
+ }).catch((error) => {
531
+ console.warn("[OAuth42 SDK] Failed to revoke backend session on signOut:", error);
532
+ });
533
+ }
534
+ }
535
+ if (options.events?.signOut) {
536
+ await options.events.signOut(message);
537
+ }
538
+ }
539
+ },
197
540
  debug: options.debug || process.env.NODE_ENV === "development",
198
541
  secret: process.env.NEXTAUTH_SECRET,
199
542
  // Configure unique cookie names per app to prevent session conflicts on localhost
@@ -269,31 +612,48 @@ function createAuth(options = {}) {
269
612
  }
270
613
  }
271
614
  };
272
- const handler = NextAuth(authOptions);
615
+ const nextAuthHandler = NextAuth(authOptions);
616
+ const handler = options.pushedAuthorization ? withPushedAuthorization(nextAuthHandler, { issuer, clientId, clientSecret, dpop: options.dpop, clientAssertionKey: options.clientAssertionKey }) : nextAuthHandler;
273
617
  return {
274
618
  auth: authOptions,
275
619
  handlers: { GET: handler, POST: handler }
276
620
  };
277
621
  }
278
622
  var getServerSession = getOAuth42Session;
279
- async function refreshAccessToken(token, clientId, clientSecret, issuer) {
280
- if (activeRefresh) {
281
- console.log("[OAuth42] Refresh already in progress, waiting...");
282
- return await activeRefresh;
623
+ async function refreshAccessToken(token, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
624
+ if (typeof token.refreshToken !== "string" || !token.refreshToken) {
625
+ return { ...token, error: "RefreshAccessTokenError" };
626
+ }
627
+ const key = JSON.stringify([issuer || process.env.OAUTH42_ISSUER || "https://api.oauth42.com", clientId, clientSecret, token.sub, token.refreshToken, dpop?.thumbprint, clientAssertionKey?.thumbprint, clientAssertionKey?.kid]);
628
+ let refresh = activeRefreshes.get(key);
629
+ if (!refresh) {
630
+ refresh = doRefresh(token, clientId, clientSecret, issuer, dpop, clientAssertionKey);
631
+ activeRefreshes.set(key, refresh);
283
632
  }
284
- activeRefresh = doRefresh(token, clientId, clientSecret, issuer);
285
633
  try {
286
- return await activeRefresh;
634
+ const result = await refresh;
635
+ const updated = {
636
+ ...token,
637
+ accessToken: result.accessToken,
638
+ refreshToken: result.refreshToken,
639
+ expiresAt: result.expiresAt,
640
+ error: result.error
641
+ };
642
+ if (clientAssertionKey) delete updated.clientSecret;
643
+ return updated;
287
644
  } finally {
288
- activeRefresh = null;
645
+ if (activeRefreshes.get(key) === refresh) activeRefreshes.delete(key);
289
646
  }
290
647
  }
291
- async function doRefresh(token, clientId, clientSecret, issuer) {
648
+ async function doRefresh(token, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
292
649
  try {
293
650
  const baseUrl = issuer || process.env.OAUTH42_ISSUER || "https://api.oauth42.com";
294
651
  const tokenUrl = `${baseUrl}/oauth2/token`;
295
652
  const fetchOptions = {
296
653
  method: "POST",
654
+ redirect: "error",
655
+ cache: "no-store",
656
+ signal: AbortSignal.timeout(3e4),
297
657
  headers: {
298
658
  "Content-Type": "application/x-www-form-urlencoded"
299
659
  },
@@ -304,17 +664,12 @@ async function doRefresh(token, clientId, clientSecret, issuer) {
304
664
  client_secret: clientSecret
305
665
  })
306
666
  };
307
- if (process.env.NODE_ENV !== "production" && tokenUrl.startsWith("https://")) {
308
- const https = await import("https");
309
- fetchOptions.agent = new https.Agent({
310
- rejectUnauthorized: false
311
- });
312
- }
313
- const response = await fetch(tokenUrl, fetchOptions);
667
+ const response = clientAssertionKey ? await clientAssertionKey.fetch(clientId, tokenUrl, fetchOptions, dpop) : dpop ? await dpop.fetch(tokenUrl, fetchOptions) : await fetch(tokenUrl, fetchOptions);
314
668
  const refreshedTokens = await response.json();
315
669
  if (!response.ok) {
316
670
  throw refreshedTokens;
317
671
  }
672
+ dpop?.assertTokenResponse(refreshedTokens);
318
673
  console.log("[OAuth42] Token refreshed successfully");
319
674
  return {
320
675
  ...token,
@@ -326,7 +681,7 @@ async function doRefresh(token, clientId, clientSecret, issuer) {
326
681
  error: void 0
327
682
  };
328
683
  } catch (error) {
329
- console.error("[OAuth42] Failed to refresh access token:", error);
684
+ console.error("[OAuth42] Failed to refresh access token");
330
685
  return {
331
686
  ...token,
332
687
  error: "RefreshAccessTokenError"
@@ -340,12 +695,37 @@ import { getToken, encode } from "next-auth/jwt";
340
695
  var ALLOWED_COOKIE_SIZE = 4096;
341
696
  var ESTIMATED_EMPTY_COOKIE_SIZE = 163;
342
697
  var CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
698
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
343
699
  var pendingRefreshes = /* @__PURE__ */ new Map();
344
- async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
700
+ function forwardedHeaders(req, cookieName, jwt) {
701
+ const headers = new Headers(req.headers);
702
+ headers.delete("x-oauth42-refreshed-token");
703
+ if (cookieName && jwt) {
704
+ const cookies = req.cookies.getAll().filter((cookie) => cookie.name !== cookieName && !cookie.name.startsWith(`${cookieName}.`));
705
+ if (jwt.length <= CHUNK_SIZE) cookies.push({ name: cookieName, value: jwt });
706
+ else for (let i = 0; i * CHUNK_SIZE < jwt.length; i++) {
707
+ cookies.push({ name: `${cookieName}.${i}`, value: jwt.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) });
708
+ }
709
+ headers.set("cookie", cookies.map((cookie) => `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`).join("; "));
710
+ }
711
+ return headers;
712
+ }
713
+ function resolveRefreshConfig(options, token) {
714
+ return {
715
+ clientId: options.clientId || token.clientId || process.env.OAUTH42_CLIENT_ID,
716
+ clientSecret: options.clientSecret || token.clientSecret || process.env.OAUTH42_CLIENT_SECRET,
717
+ issuer: options.issuer || token.issuer || process.env.OAUTH42_ISSUER || "https://localhost:8443"
718
+ };
719
+ }
720
+ async function refreshTokens(refreshToken, clientId, clientSecret, issuer, dpop, clientAssertionKey) {
345
721
  try {
346
722
  const tokenUrl = `${issuer}/oauth2/token`;
347
- const response = await fetch(tokenUrl, {
723
+ const fetcher = clientAssertionKey ? (url, init) => clientAssertionKey.fetch(clientId, url, init, dpop) : dpop ? dpop.fetch.bind(dpop) : fetch;
724
+ const response = await fetcher(tokenUrl, {
348
725
  method: "POST",
726
+ redirect: "error",
727
+ cache: "no-store",
728
+ signal: AbortSignal.timeout(3e4),
349
729
  headers: {
350
730
  "Content-Type": "application/x-www-form-urlencoded"
351
731
  },
@@ -358,9 +738,10 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
358
738
  });
359
739
  const data = await response.json();
360
740
  if (!response.ok) {
361
- console.error("[OAuth42 Middleware] Token refresh failed:", data);
741
+ console.error("[OAuth42 Middleware] Token refresh failed:", response.status);
362
742
  return { success: false, error: data.error || "refresh_failed" };
363
743
  }
744
+ dpop?.assertTokenResponse(data);
364
745
  console.log("[OAuth42 Middleware] Token refreshed successfully");
365
746
  return {
366
747
  success: true,
@@ -369,17 +750,14 @@ async function refreshTokens(refreshToken, clientId, clientSecret, issuer) {
369
750
  expiresAt: Math.floor(Date.now() / 1e3) + (data.expires_in || 3600)
370
751
  };
371
752
  } catch (error) {
372
- console.error("[OAuth42 Middleware] Token refresh error:", error);
753
+ console.error("[OAuth42 Middleware] Token refresh request failed");
373
754
  return { success: false, error: "refresh_error" };
374
755
  }
375
756
  }
376
757
  function withOAuth42Auth(options = {}) {
377
758
  const secret = process.env.NEXTAUTH_SECRET;
378
- const clientId = process.env.OAUTH42_CLIENT_ID;
379
- const clientSecret = process.env.OAUTH42_CLIENT_SECRET;
380
- const issuer = process.env.OAUTH42_ISSUER || "https://localhost:8443";
381
759
  if (!secret) {
382
- console.warn("[OAuth42 Middleware] NEXTAUTH_SECRET not set");
760
+ throw new Error("NEXTAUTH_SECRET is required for OAuth42 middleware");
383
761
  }
384
762
  return async function middleware(req) {
385
763
  const cookieName = options.cookiePrefix ? `${options.cookiePrefix}.session-token` : "next-auth.session-token";
@@ -390,18 +768,76 @@ function withOAuth42Auth(options = {}) {
390
768
  });
391
769
  const pathname = req.nextUrl.pathname;
392
770
  if (options.publicPaths?.some((path) => pathname.startsWith(path))) {
393
- return NextResponse.next();
771
+ return NextResponse.next({ request: { headers: forwardedHeaders(req) } });
394
772
  }
395
773
  const needsProtection = options.protectedPaths ? options.protectedPaths.some((path) => pathname.startsWith(path)) : true;
396
774
  if (!needsProtection) {
397
- return NextResponse.next();
775
+ return NextResponse.next({ request: { headers: forwardedHeaders(req) } });
398
776
  }
399
- if (!token) {
777
+ if (!token || token.error) {
400
778
  const signInUrl = options.pages?.signIn || "/auth/signin";
401
779
  const url = new URL(signInUrl, req.url);
402
780
  url.searchParams.set("callbackUrl", pathname);
403
781
  return NextResponse.redirect(url);
404
782
  }
783
+ const refreshConfig = resolveRefreshConfig(options, token);
784
+ const denied = () => {
785
+ const url = new URL(options.pages?.signIn || "/auth/signin", req.url);
786
+ url.searchParams.set("callbackUrl", pathname);
787
+ const response = NextResponse.redirect(url);
788
+ for (const cookie of req.cookies.getAll()) {
789
+ if (cookie.name === cookieName || cookie.name.startsWith(`${cookieName}.`)) response.cookies.delete(cookie.name);
790
+ }
791
+ return response;
792
+ };
793
+ const verifyAndAuthorize = async (current, resolved) => {
794
+ const user = resolved === void 0 ? await verifiedUserInfo(refreshConfig.issuer, current.accessToken, token.sub, options.dpop) : resolved;
795
+ if (!user) return false;
796
+ current.groups = user.groups ?? [];
797
+ current.email = user.email;
798
+ current.name = user.name;
799
+ current.username = user.preferred_username ?? user.username;
800
+ current.emailVerified = user.email_verified;
801
+ return options.callbacks?.authorized ? options.callbacks.authorized({ token: current, req }) : true;
802
+ };
803
+ if (isBackendSessionMode(options.sessionStore)) {
804
+ const sessionId = token.oauth42SessionId;
805
+ if (!sessionId || !refreshConfig.clientId || !refreshConfig.clientSecret && !options.clientAssertionKey) {
806
+ const signInUrl = options.pages?.signIn || "/auth/signin";
807
+ const url = new URL(signInUrl, req.url);
808
+ url.searchParams.set("callbackUrl", pathname);
809
+ const response = NextResponse.redirect(url);
810
+ response.cookies.delete(cookieName);
811
+ return response;
812
+ }
813
+ const backendSession = await resolveBackendSession({
814
+ dpop: options.dpop,
815
+ clientAssertionKey: options.clientAssertionKey,
816
+ issuer: refreshConfig.issuer,
817
+ clientId: refreshConfig.clientId,
818
+ clientSecret: refreshConfig.clientSecret || "",
819
+ sessionId
820
+ }).catch((error) => {
821
+ console.error("[OAuth42 Middleware] Backend session resolve failed:", error);
822
+ return null;
823
+ });
824
+ if (!backendSession) {
825
+ const signInUrl = options.pages?.signIn || "/auth/signin";
826
+ const url = new URL(signInUrl, req.url);
827
+ url.searchParams.set("callbackUrl", pathname);
828
+ const response = NextResponse.redirect(url);
829
+ response.cookies.delete(cookieName);
830
+ return response;
831
+ }
832
+ const verified = await verifiedBackendSessionUserInfo(refreshConfig.issuer, backendSession, token.sub, options.dpop);
833
+ if (!await verifyAndAuthorize({ ...token, accessToken: backendSession.access_token }, verified)) return denied();
834
+ const requestHeaders = forwardedHeaders(req);
835
+ return NextResponse.next({
836
+ request: {
837
+ headers: requestHeaders
838
+ }
839
+ });
840
+ }
405
841
  const expiresAt = token.expiresAt;
406
842
  const now = Math.floor(Date.now() / 1e3);
407
843
  const bufferSeconds = 15;
@@ -411,26 +847,30 @@ function withOAuth42Auth(options = {}) {
411
847
  expiresAt,
412
848
  now,
413
849
  expiredAgo: expiresAt ? now - expiresAt : "n/a",
414
- accessTokenPrefix: token.accessToken?.substring(0, 20),
415
850
  path: pathname
416
851
  });
417
852
  }
418
- if (needsRefresh && token.refreshToken && clientId && clientSecret) {
419
- const userId = token.sub;
420
- let refreshPromise = pendingRefreshes.get(userId);
853
+ if (needsRefresh && token.refreshToken && refreshConfig.clientId && (refreshConfig.clientSecret || options.clientAssertionKey)) {
854
+ const grantKey = JSON.stringify([refreshConfig.issuer, refreshConfig.clientId, refreshConfig.clientSecret, token.sub, token.refreshToken, options.dpop?.thumbprint, options.clientAssertionKey?.thumbprint, options.clientAssertionKey?.kid]);
855
+ let refreshPromise = pendingRefreshes.get(grantKey);
421
856
  if (refreshPromise) {
422
857
  console.log("[OAuth42 Middleware] Waiting for in-flight refresh...");
423
858
  } else {
424
859
  console.log("[OAuth42 Middleware] Access token expired, refreshing...");
425
860
  refreshPromise = refreshTokens(
426
861
  token.refreshToken,
427
- clientId,
428
- clientSecret,
429
- issuer
862
+ refreshConfig.clientId,
863
+ refreshConfig.clientSecret || "",
864
+ refreshConfig.issuer,
865
+ options.dpop,
866
+ options.clientAssertionKey
430
867
  ).finally(() => {
431
- setTimeout(() => pendingRefreshes.delete(userId), 3e4);
868
+ const cleanupTimer = setTimeout(() => pendingRefreshes.delete(grantKey), 3e4);
869
+ if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
870
+ cleanupTimer.unref();
871
+ }
432
872
  });
433
- pendingRefreshes.set(userId, refreshPromise);
873
+ pendingRefreshes.set(grantKey, refreshPromise);
434
874
  }
435
875
  const refreshed = await refreshPromise;
436
876
  if (refreshed.success && refreshed.accessToken && refreshed.refreshToken) {
@@ -440,12 +880,13 @@ function withOAuth42Auth(options = {}) {
440
880
  refreshToken: refreshed.refreshToken,
441
881
  expiresAt: refreshed.expiresAt
442
882
  };
883
+ if (options.clientAssertionKey) delete updatedToken.clientSecret;
884
+ if (!await verifyAndAuthorize(updatedToken)) return denied();
443
885
  const newJwt = await encode({
444
886
  token: updatedToken,
445
887
  secret
446
888
  });
447
- const requestHeaders = new Headers(req.headers);
448
- requestHeaders.set("x-oauth42-refreshed-token", refreshed.accessToken);
889
+ const requestHeaders = forwardedHeaders(req, cookieName, newJwt);
449
890
  const response = NextResponse.next({
450
891
  request: {
451
892
  headers: requestHeaders
@@ -455,7 +896,8 @@ function withOAuth42Auth(options = {}) {
455
896
  httpOnly: true,
456
897
  sameSite: "lax",
457
898
  path: "/",
458
- secure: process.env.NODE_ENV === "production"
899
+ secure: process.env.NODE_ENV === "production",
900
+ maxAge: options.session?.maxAge ?? DEFAULT_SESSION_MAX_AGE_SECONDS
459
901
  };
460
902
  if (newJwt.length <= CHUNK_SIZE) {
461
903
  response.cookies.set(cookieName, newJwt, cookieOptions);
@@ -497,13 +939,12 @@ function withOAuth42Auth(options = {}) {
497
939
  chunked: newJwt.length > CHUNK_SIZE,
498
940
  existingBaseCooke: req.cookies.has(cookieName),
499
941
  existingChunks: existingChunks.length > 0 ? existingChunks : "none",
500
- newAccessTokenPrefix: refreshed.accessToken?.substring(0, 20),
501
942
  newExpiresAt: refreshed.expiresAt,
502
943
  cookieName
503
944
  });
504
945
  return response;
505
946
  } else {
506
- console.log("[OAuth42 Middleware] Refresh failed (likely token blacklisted after deploy), clearing session");
947
+ console.log("[OAuth42 Middleware] Refresh failed, clearing session");
507
948
  const signInUrl = options.pages?.signIn || "/auth/signin";
508
949
  const url = new URL(signInUrl, req.url);
509
950
  url.searchParams.set("callbackUrl", pathname);
@@ -520,16 +961,13 @@ function withOAuth42Auth(options = {}) {
520
961
  return response;
521
962
  }
522
963
  }
523
- if (options.callbacks?.authorized) {
524
- const isAuthorized = await options.callbacks.authorized({ token, req });
525
- if (!isAuthorized) {
526
- const signInUrl = options.pages?.signIn || "/auth/signin";
527
- const url = new URL(signInUrl, req.url);
528
- url.searchParams.set("callbackUrl", pathname);
529
- return NextResponse.redirect(url);
530
- }
964
+ if (!Number.isFinite(expiresAt) || now >= expiresAt || !token.accessToken) {
965
+ const url = new URL(options.pages?.signIn || "/auth/signin", req.url);
966
+ url.searchParams.set("callbackUrl", pathname);
967
+ return NextResponse.redirect(url);
531
968
  }
532
- return NextResponse.next();
969
+ if (!await verifyAndAuthorize(token)) return denied();
970
+ return NextResponse.next({ request: { headers: forwardedHeaders(req) } });
533
971
  };
534
972
  }
535
973
  function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = ["/auth", "/api/auth"]) {
@@ -548,15 +986,87 @@ function createMiddlewareConfig(protectedPaths = ["/protected"], publicPaths = [
548
986
  publicPaths
549
987
  };
550
988
  }
989
+
990
+ // src/server/dpop.ts
991
+ import { createHash as createHash2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2, randomBytes as randomBytes2, sign as sign2 } from "crypto";
992
+ var OAuth42DPoP = class _OAuth42DPoP {
993
+ constructor(key) {
994
+ this.nonces = /* @__PURE__ */ new Map();
995
+ if (key.type !== "private" || key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
996
+ throw new Error("DPoP requires a private P-256 key");
997
+ }
998
+ this.key = key;
999
+ const publicKey = createPublicKey2(key).export({ format: "jwk" });
1000
+ this.jwk = { crv: "P-256", kty: "EC", x: publicKey.x, y: publicKey.y };
1001
+ this.thumbprint = createHash2("sha256").update(JSON.stringify(this.jwk)).digest("base64url");
1002
+ }
1003
+ /** Persist keys in protected server storage; regeneration cannot recover a public refresh grant. */
1004
+ static generate() {
1005
+ return new _OAuth42DPoP(generateKeyPairSync2("ec", { namedCurve: "prime256v1" }).privateKey);
1006
+ }
1007
+ /** Passed directly to openid-client so its callback keeps OIDC issuer/nonce/signature validation. */
1008
+ get oidcKey() {
1009
+ return this.key;
1010
+ }
1011
+ proof(method, target, nonce, accessToken) {
1012
+ const url = new URL(target);
1013
+ if (!["http:", "https:"].includes(url.protocol) || url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) {
1014
+ throw new Error("DPoP requires HTTPS outside loopback development");
1015
+ }
1016
+ if (url.username || url.password) throw new Error("DPoP endpoint must not contain credentials");
1017
+ url.search = "";
1018
+ url.hash = "";
1019
+ const header = { typ: "dpop+jwt", alg: "ES256", jwk: this.jwk };
1020
+ const claims = {
1021
+ htm: method.toUpperCase(),
1022
+ htu: url.href,
1023
+ iat: Math.floor(Date.now() / 1e3),
1024
+ jti: randomBytes2(32).toString("base64url"),
1025
+ ...nonce ? { nonce } : {},
1026
+ ...accessToken !== void 0 ? { ath: createHash2("sha256").update(accessToken).digest("base64url") } : {}
1027
+ };
1028
+ const input = `${Buffer.from(JSON.stringify(header)).toString("base64url")}.${Buffer.from(JSON.stringify(claims)).toString("base64url")}`;
1029
+ return `${input}.${sign2("sha256", Buffer.from(input), { key: this.key, dsaEncoding: "ieee-p1363" }).toString("base64url")}`;
1030
+ }
1031
+ assertTokenResponse(tokens) {
1032
+ if (typeof tokens.token_type !== "string" || tokens.token_type.toLowerCase() !== "dpop") {
1033
+ throw new Error("Server did not issue the requested DPoP-bound token");
1034
+ }
1035
+ }
1036
+ async fetch(target, init = {}, accessToken, prepare) {
1037
+ if (init.body instanceof ReadableStream) throw new Error("DPoP nonce retries require a replayable request body");
1038
+ const cacheKey = `${new URL(target).origin}:${accessToken !== void 0 ? "resource" : "token"}`;
1039
+ for (let attempt = 0; attempt < 2; attempt++) {
1040
+ const request = prepare ? prepare() : init;
1041
+ const headers = new Headers(request.headers);
1042
+ headers.set("DPoP", this.proof(request.method || "GET", target, this.nonces.get(cacheKey), accessToken));
1043
+ if (accessToken !== void 0) headers.set("Authorization", `DPoP ${accessToken}`);
1044
+ const response = await fetch(target, { ...request, headers, redirect: "error", cache: "no-store", signal: request.signal || AbortSignal.timeout(3e4) });
1045
+ const nonce = response.headers.get("DPoP-Nonce");
1046
+ if (nonce && /^[\x21-\x7e]{1,128}$/.test(nonce)) {
1047
+ if (this.nonces.size >= 256) this.nonces.clear();
1048
+ this.nonces.set(cacheKey, nonce);
1049
+ } else return response;
1050
+ if (attempt || ![400, 401].includes(response.status)) return response;
1051
+ const error = await response.clone().json().catch(() => null);
1052
+ if (error?.error !== "use_dpop_nonce") return response;
1053
+ }
1054
+ throw new Error("DPoP nonce negotiation failed");
1055
+ }
1056
+ };
551
1057
  export {
1058
+ OAuth42ClientAssertion,
1059
+ OAuth42DPoP,
552
1060
  OAuth42Provider,
553
1061
  createAuth,
554
1062
  createMiddlewareConfig,
555
1063
  getOAuth42Session,
556
1064
  getServerSession,
1065
+ pushAuthorizationURL,
557
1066
  refreshAccessToken,
558
1067
  withOAuth42Auth,
559
1068
  withOAuth42ServerSideProps,
560
- withOAuth42Session
1069
+ withOAuth42Session,
1070
+ withPushedAuthorization
561
1071
  };
562
1072
  //# sourceMappingURL=index.mjs.map