@spfn/auth 0.2.0-beta.59 → 0.2.0-beta.60

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.
@@ -1,8 +1,253 @@
1
1
  // src/nextjs/api.ts
2
2
  import { registerInterceptors } from "@spfn/core/nextjs/server";
3
3
 
4
- // src/nextjs/interceptors/login-register.ts
5
- import { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from "@spfn/auth/server";
4
+ // src/server/lib/crypto.ts
5
+ import crypto2 from "crypto";
6
+ import jwt from "jsonwebtoken";
7
+ function generateKeyPairES256() {
8
+ const keyId = crypto2.randomUUID();
9
+ const { privateKey, publicKey } = crypto2.generateKeyPairSync("ec", {
10
+ namedCurve: "P-256",
11
+ // ES256
12
+ publicKeyEncoding: {
13
+ type: "spki",
14
+ format: "der"
15
+ },
16
+ privateKeyEncoding: {
17
+ type: "pkcs8",
18
+ format: "der"
19
+ }
20
+ });
21
+ const privateKeyB64 = privateKey.toString("base64");
22
+ const publicKeyB64 = publicKey.toString("base64");
23
+ const fingerprint = crypto2.createHash("sha256").update(publicKey).digest("hex");
24
+ return {
25
+ privateKey: privateKeyB64,
26
+ publicKey: publicKeyB64,
27
+ keyId,
28
+ fingerprint,
29
+ algorithm: "ES256"
30
+ };
31
+ }
32
+ function generateKeyPairRS256() {
33
+ const keyId = crypto2.randomUUID();
34
+ const { privateKey, publicKey } = crypto2.generateKeyPairSync("rsa", {
35
+ modulusLength: 2048,
36
+ publicKeyEncoding: {
37
+ type: "spki",
38
+ format: "der"
39
+ },
40
+ privateKeyEncoding: {
41
+ type: "pkcs8",
42
+ format: "der"
43
+ }
44
+ });
45
+ const privateKeyB64 = privateKey.toString("base64");
46
+ const publicKeyB64 = publicKey.toString("base64");
47
+ const fingerprint = crypto2.createHash("sha256").update(publicKey).digest("hex");
48
+ return {
49
+ privateKey: privateKeyB64,
50
+ publicKey: publicKeyB64,
51
+ keyId,
52
+ fingerprint,
53
+ algorithm: "RS256"
54
+ };
55
+ }
56
+ function generateKeyPair(algorithm = "ES256") {
57
+ return algorithm === "ES256" ? generateKeyPairES256() : generateKeyPairRS256();
58
+ }
59
+ function generateClientToken(payload, privateKeyB64, algorithm, options) {
60
+ try {
61
+ const privateKeyDER = Buffer.from(privateKeyB64, "base64");
62
+ const privateKeyObject = crypto2.createPrivateKey({
63
+ key: privateKeyDER,
64
+ format: "der",
65
+ type: "pkcs8"
66
+ });
67
+ const privateKeyPEM = privateKeyObject.export({
68
+ type: "pkcs8",
69
+ format: "pem"
70
+ });
71
+ const signOptions = {
72
+ algorithm,
73
+ issuer: options?.issuer || "spfn-client",
74
+ expiresIn: options?.expiresIn ?? "15m"
75
+ // Default to 15 minutes
76
+ };
77
+ return jwt.sign(payload, privateKeyPEM, signOptions);
78
+ } catch (error) {
79
+ throw new Error(
80
+ `Failed to generate client token: ${error instanceof Error ? error.message : "Unknown error"}`
81
+ );
82
+ }
83
+ }
84
+
85
+ // src/server/lib/session.ts
86
+ import * as jose from "jose";
87
+ import { env } from "@spfn/auth/config";
88
+ import { env as coreEnv } from "@spfn/core/config";
89
+
90
+ // src/server/logger.ts
91
+ import { logger as rootLogger } from "@spfn/core/logger";
92
+ var authLogger = {
93
+ plugin: rootLogger.child("@spfn/auth:plugin"),
94
+ middleware: rootLogger.child("@spfn/auth:middleware"),
95
+ interceptor: {
96
+ general: rootLogger.child("@spfn/auth:interceptor:general"),
97
+ login: rootLogger.child("@spfn/auth:interceptor:login"),
98
+ keyRotation: rootLogger.child("@spfn/auth:interceptor:key-rotation"),
99
+ oauth: rootLogger.child("@spfn/auth:interceptor:oauth")
100
+ },
101
+ session: rootLogger.child("@spfn/auth:session"),
102
+ service: rootLogger.child("@spfn/auth:service"),
103
+ setup: rootLogger.child("@spfn/auth:setup"),
104
+ email: rootLogger.child("@spfn/auth:email"),
105
+ sms: rootLogger.child("@spfn/auth:sms")
106
+ };
107
+
108
+ // src/server/lib/session.ts
109
+ async function getSessionSecretKey() {
110
+ const secret = env.SPFN_AUTH_SESSION_SECRET;
111
+ const encoder = new TextEncoder();
112
+ const data = encoder.encode(secret);
113
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
114
+ return new Uint8Array(hashBuffer);
115
+ }
116
+ async function getSecretFingerprint() {
117
+ const key = await getSessionSecretKey();
118
+ const hash = await crypto.subtle.digest("SHA-256", key.buffer);
119
+ const hex = Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
120
+ return hex.slice(0, 8);
121
+ }
122
+ async function sealSession(data, ttl = 60 * 60 * 24 * 7) {
123
+ const secret = await getSessionSecretKey();
124
+ const result = await new jose.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-client").encrypt(secret);
125
+ if (coreEnv.NODE_ENV !== "production") {
126
+ const fingerprint = await getSecretFingerprint();
127
+ authLogger.session.debug(`Sealed session`, {
128
+ secretFingerprint: fingerprint,
129
+ resultLength: result.length,
130
+ resultPrefix: result.slice(0, 20)
131
+ });
132
+ }
133
+ return result;
134
+ }
135
+ async function unsealSession(jwt2) {
136
+ try {
137
+ const secret = await getSessionSecretKey();
138
+ const { payload } = await jose.jwtDecrypt(jwt2, secret, {
139
+ issuer: "spfn-auth",
140
+ audience: "spfn-client"
141
+ });
142
+ return payload.data;
143
+ } catch (err) {
144
+ if (err instanceof jose.errors.JWTExpired) {
145
+ throw new Error("Session expired");
146
+ }
147
+ if (err instanceof jose.errors.JWEDecryptionFailed) {
148
+ if (coreEnv.NODE_ENV !== "production") {
149
+ const fingerprint = await getSecretFingerprint();
150
+ authLogger.session.warn(`JWE decryption failed`, {
151
+ secretFingerprint: fingerprint,
152
+ jwtLength: jwt2.length,
153
+ jwtPrefix: jwt2.slice(0, 20),
154
+ jwtSuffix: jwt2.slice(-10)
155
+ });
156
+ }
157
+ throw new Error("Invalid session");
158
+ }
159
+ if (err instanceof jose.errors.JWTClaimValidationFailed) {
160
+ throw new Error("Session validation failed");
161
+ }
162
+ throw new Error("Failed to unseal session");
163
+ }
164
+ }
165
+ async function getSessionInfo(jwt2) {
166
+ const secret = await getSessionSecretKey();
167
+ try {
168
+ const { payload } = await jose.jwtDecrypt(jwt2, secret);
169
+ return {
170
+ issuedAt: new Date(payload.iat * 1e3),
171
+ expiresAt: new Date(payload.exp * 1e3),
172
+ issuer: payload.iss || "",
173
+ audience: Array.isArray(payload.aud) ? payload.aud[0] : payload.aud || ""
174
+ };
175
+ } catch (err) {
176
+ if (coreEnv.NODE_ENV !== "production") {
177
+ authLogger.session.warn("Failed to get session info:", err instanceof Error ? err.message : "Unknown error");
178
+ }
179
+ return null;
180
+ }
181
+ }
182
+ async function shouldRefreshSession(jwt2, thresholdHours = 24) {
183
+ const info = await getSessionInfo(jwt2);
184
+ if (!info) {
185
+ return true;
186
+ }
187
+ const hoursRemaining = (info.expiresAt.getTime() - Date.now()) / (1e3 * 60 * 60);
188
+ return hoursRemaining < thresholdHours;
189
+ }
190
+
191
+ // src/server/lib/config.ts
192
+ import { env as env2 } from "@spfn/auth/config";
193
+ function getCookieSuffix() {
194
+ const port = process.env.PORT;
195
+ return port ? `_${port}` : "";
196
+ }
197
+ var COOKIE_NAMES = {
198
+ /** Encrypted session data (userId, privateKey, keyId, algorithm) */
199
+ get SESSION() {
200
+ return `spfn_session${getCookieSuffix()}`;
201
+ },
202
+ /** Current key ID (for key rotation) */
203
+ get SESSION_KEY_ID() {
204
+ return `spfn_session_key_id${getCookieSuffix()}`;
205
+ },
206
+ /** Pending OAuth session (privateKey, keyId, algorithm) - temporary during OAuth flow */
207
+ get OAUTH_PENDING() {
208
+ return `spfn_oauth_pending${getCookieSuffix()}`;
209
+ }
210
+ };
211
+ function parseDuration(duration) {
212
+ if (typeof duration === "number") {
213
+ return duration;
214
+ }
215
+ const match = duration.match(/^(\d+)([dhms]?)$/);
216
+ if (!match) {
217
+ throw new Error(`Invalid duration format: ${duration}. Use format like '30d', '12h', '45m', '3600s', or plain number.`);
218
+ }
219
+ const value = parseInt(match[1], 10);
220
+ const unit = match[2] || "s";
221
+ switch (unit) {
222
+ case "d":
223
+ return value * 24 * 60 * 60;
224
+ case "h":
225
+ return value * 60 * 60;
226
+ case "m":
227
+ return value * 60;
228
+ case "s":
229
+ return value;
230
+ default:
231
+ throw new Error(`Unknown duration unit: ${unit}`);
232
+ }
233
+ }
234
+ var globalConfig = {
235
+ sessionTtl: "7d"
236
+ // Default: 7 days
237
+ };
238
+ function getSessionTtl(override) {
239
+ if (override !== void 0) {
240
+ return parseDuration(override);
241
+ }
242
+ if (globalConfig.sessionTtl !== void 0) {
243
+ return parseDuration(globalConfig.sessionTtl);
244
+ }
245
+ const envTtl = env2.SPFN_AUTH_SESSION_TTL;
246
+ if (envTtl) {
247
+ return parseDuration(envTtl);
248
+ }
249
+ return 7 * 24 * 60 * 60;
250
+ }
6
251
 
7
252
  // src/nextjs/interceptors/cookie-options.ts
8
253
  function resolveSecure() {
@@ -91,7 +336,6 @@ var loginRegisterInterceptor = {
91
336
  };
92
337
 
93
338
  // src/nextjs/interceptors/general-auth.ts
94
- import { unsealSession, sealSession as sealSession2, shouldRefreshSession, generateClientToken, getSessionTtl as getSessionTtl2, COOKIE_NAMES as COOKIE_NAMES2, authLogger as authLogger2 } from "@spfn/auth/server";
95
339
  function requiresAuth(path) {
96
340
  const publicPaths = [
97
341
  /^\/_auth\/login$/,
@@ -111,18 +355,18 @@ var generalAuthInterceptor = {
111
355
  method: ["GET", "POST", "PUT", "PATCH", "DELETE"],
112
356
  request: async (ctx, next) => {
113
357
  if (!requiresAuth(ctx.path)) {
114
- authLogger2.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);
358
+ authLogger.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);
115
359
  await next();
116
360
  return;
117
361
  }
118
362
  const cookieNames = Array.from(ctx.cookies.keys());
119
- authLogger2.interceptor.general.debug("Available cookies:", {
363
+ authLogger.interceptor.general.debug("Available cookies:", {
120
364
  cookieNames,
121
365
  totalCount: cookieNames.length,
122
- lookingFor: COOKIE_NAMES2.SESSION
366
+ lookingFor: COOKIE_NAMES.SESSION
123
367
  });
124
- const sessionCookie = ctx.cookies.get(COOKIE_NAMES2.SESSION);
125
- authLogger2.interceptor.general.debug("Request", {
368
+ const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);
369
+ authLogger.interceptor.general.debug("Request", {
126
370
  method: ctx.method,
127
371
  path: ctx.path,
128
372
  hasSession: !!sessionCookie,
@@ -131,19 +375,19 @@ var generalAuthInterceptor = {
131
375
  sessionSuffix: sessionCookie?.slice(-10) ?? ""
132
376
  });
133
377
  if (!sessionCookie) {
134
- authLogger2.interceptor.general.debug("No session cookie, proceeding without auth");
378
+ authLogger.interceptor.general.debug("No session cookie, proceeding without auth");
135
379
  await next();
136
380
  return;
137
381
  }
138
382
  try {
139
383
  const session = await unsealSession(sessionCookie);
140
- authLogger2.interceptor.general.debug("Session valid", {
384
+ authLogger.interceptor.general.debug("Session valid", {
141
385
  userId: session.userId,
142
386
  keyId: session.keyId
143
387
  });
144
388
  const needsRefresh = await shouldRefreshSession(sessionCookie, 24);
145
389
  if (needsRefresh) {
146
- authLogger2.interceptor.general.debug("Session needs refresh (within 24h of expiry)");
390
+ authLogger.interceptor.general.debug("Session needs refresh (within 24h of expiry)");
147
391
  ctx.metadata.refreshSession = true;
148
392
  ctx.metadata.sessionData = session;
149
393
  }
@@ -157,7 +401,7 @@ var generalAuthInterceptor = {
157
401
  session.algorithm,
158
402
  { expiresIn: "15m" }
159
403
  );
160
- authLogger2.interceptor.general.debug("Generated JWT token (expires in 15m)");
404
+ authLogger.interceptor.general.debug("Generated JWT token (expires in 15m)");
161
405
  ctx.headers["Authorization"] = `Bearer ${token}`;
162
406
  ctx.headers["X-Key-Id"] = session.keyId;
163
407
  ctx.metadata.userId = session.userId;
@@ -166,31 +410,31 @@ var generalAuthInterceptor = {
166
410
  const err = error;
167
411
  const msg = err.message.toLowerCase();
168
412
  if (msg.includes("expired") || msg.includes("invalid")) {
169
- authLogger2.interceptor.general.warn("Session expired or invalid", {
413
+ authLogger.interceptor.general.warn("Session expired or invalid", {
170
414
  message: err.message,
171
415
  cookieLength: sessionCookie.length,
172
416
  cookiePrefix: sessionCookie.slice(0, 20),
173
417
  cookieSuffix: sessionCookie.slice(-10)
174
418
  });
175
- authLogger2.interceptor.general.debug("Marking session for cleanup");
419
+ authLogger.interceptor.general.debug("Marking session for cleanup");
176
420
  ctx.metadata.clearSession = true;
177
421
  ctx.metadata.sessionValid = false;
178
422
  } else {
179
- authLogger2.interceptor.general.error("Failed to process session", err);
423
+ authLogger.interceptor.general.error("Failed to process session", err);
180
424
  }
181
425
  }
182
426
  await next();
183
427
  },
184
428
  response: async (ctx, next) => {
185
429
  if (ctx.response.status === 401 && ctx.metadata.sessionValid) {
186
- authLogger2.interceptor.general.warn("Backend returned 401, clearing session");
430
+ authLogger.interceptor.general.warn("Backend returned 401, clearing session");
187
431
  ctx.setCookies.push({
188
- name: COOKIE_NAMES2.SESSION,
432
+ name: COOKIE_NAMES.SESSION,
189
433
  value: "",
190
434
  options: { maxAge: 0, path: "/" }
191
435
  });
192
436
  ctx.setCookies.push({
193
- name: COOKIE_NAMES2.SESSION_KEY_ID,
437
+ name: COOKIE_NAMES.SESSION_KEY_ID,
194
438
  value: "",
195
439
  options: { maxAge: 0, path: "/" }
196
440
  });
@@ -199,7 +443,7 @@ var generalAuthInterceptor = {
199
443
  }
200
444
  if (ctx.metadata.clearSession) {
201
445
  ctx.setCookies.push({
202
- name: COOKIE_NAMES2.SESSION,
446
+ name: COOKIE_NAMES.SESSION,
203
447
  value: "",
204
448
  options: {
205
449
  maxAge: 0,
@@ -207,7 +451,7 @@ var generalAuthInterceptor = {
207
451
  }
208
452
  });
209
453
  ctx.setCookies.push({
210
- name: COOKIE_NAMES2.SESSION_KEY_ID,
454
+ name: COOKIE_NAMES.SESSION_KEY_ID,
211
455
  value: "",
212
456
  options: {
213
457
  maxAge: 0,
@@ -217,10 +461,10 @@ var generalAuthInterceptor = {
217
461
  } else if (ctx.metadata.refreshSession && ctx.response.status === 200) {
218
462
  try {
219
463
  const sessionData = ctx.metadata.sessionData;
220
- const ttl = getSessionTtl2();
221
- const sealed = await sealSession2(sessionData, ttl);
464
+ const ttl = getSessionTtl();
465
+ const sealed = await sealSession(sessionData, ttl);
222
466
  ctx.setCookies.push({
223
- name: COOKIE_NAMES2.SESSION,
467
+ name: COOKIE_NAMES.SESSION,
224
468
  value: sealed,
225
469
  options: {
226
470
  httpOnly: true,
@@ -231,7 +475,7 @@ var generalAuthInterceptor = {
231
475
  }
232
476
  });
233
477
  ctx.setCookies.push({
234
- name: COOKIE_NAMES2.SESSION_KEY_ID,
478
+ name: COOKIE_NAMES.SESSION_KEY_ID,
235
479
  value: sessionData.keyId,
236
480
  options: {
237
481
  httpOnly: true,
@@ -241,18 +485,18 @@ var generalAuthInterceptor = {
241
485
  path: "/"
242
486
  }
243
487
  });
244
- authLogger2.interceptor.general.info("Session refreshed", {
488
+ authLogger.interceptor.general.info("Session refreshed", {
245
489
  userId: sessionData.userId,
246
490
  sealedLength: sealed.length,
247
491
  sealedPrefix: sealed.slice(0, 20)
248
492
  });
249
493
  } catch (error) {
250
494
  const err = error;
251
- authLogger2.interceptor.general.error("Failed to refresh session", err);
495
+ authLogger.interceptor.general.error("Failed to refresh session", err);
252
496
  }
253
497
  } else if (ctx.path === "/_auth/logout" && ctx.response.ok) {
254
498
  ctx.setCookies.push({
255
- name: COOKIE_NAMES2.SESSION,
499
+ name: COOKIE_NAMES.SESSION,
256
500
  value: "",
257
501
  options: {
258
502
  maxAge: 0,
@@ -260,7 +504,7 @@ var generalAuthInterceptor = {
260
504
  }
261
505
  });
262
506
  ctx.setCookies.push({
263
- name: COOKIE_NAMES2.SESSION_KEY_ID,
507
+ name: COOKIE_NAMES.SESSION_KEY_ID,
264
508
  value: "",
265
509
  options: {
266
510
  maxAge: 0,
@@ -273,19 +517,18 @@ var generalAuthInterceptor = {
273
517
  };
274
518
 
275
519
  // src/nextjs/interceptors/key-rotation.ts
276
- import { generateKeyPair as generateKeyPair2, unsealSession as unsealSession2, sealSession as sealSession3, generateClientToken as generateClientToken2, getSessionTtl as getSessionTtl3, COOKIE_NAMES as COOKIE_NAMES3, authLogger as authLogger3 } from "@spfn/auth/server";
277
520
  var keyRotationInterceptor = {
278
521
  pathPattern: "/_auth/keys/rotate",
279
522
  method: "POST",
280
523
  request: async (ctx, next) => {
281
- const sessionCookie = ctx.cookies.get(COOKIE_NAMES3.SESSION);
524
+ const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);
282
525
  if (!sessionCookie) {
283
526
  await next();
284
527
  return;
285
528
  }
286
529
  try {
287
- const currentSession = await unsealSession2(sessionCookie);
288
- const newKeyPair = generateKeyPair2("ES256");
530
+ const currentSession = await unsealSession(sessionCookie);
531
+ const newKeyPair = generateKeyPair("ES256");
289
532
  if (!ctx.body) {
290
533
  ctx.body = {};
291
534
  }
@@ -298,7 +541,7 @@ var keyRotationInterceptor = {
298
541
  console.log("publicKey:", newKeyPair.publicKey);
299
542
  console.log("keyId:", newKeyPair.keyId);
300
543
  console.log("fingerprint:", newKeyPair.fingerprint);
301
- const token = generateClientToken2(
544
+ const token = generateClientToken(
302
545
  {
303
546
  userId: currentSession.userId,
304
547
  keyId: currentSession.keyId,
@@ -317,7 +560,7 @@ var keyRotationInterceptor = {
317
560
  ctx.metadata.userId = currentSession.userId;
318
561
  } catch (error) {
319
562
  const err = error;
320
- authLogger3.interceptor.keyRotation.error("Failed to prepare key rotation", err);
563
+ authLogger.interceptor.keyRotation.error("Failed to prepare key rotation", err);
321
564
  }
322
565
  await next();
323
566
  },
@@ -327,21 +570,21 @@ var keyRotationInterceptor = {
327
570
  return;
328
571
  }
329
572
  if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId) {
330
- authLogger3.interceptor.keyRotation.error("Missing key rotation metadata");
573
+ authLogger.interceptor.keyRotation.error("Missing key rotation metadata");
331
574
  await next();
332
575
  return;
333
576
  }
334
577
  try {
335
- const ttl = getSessionTtl3();
578
+ const ttl = getSessionTtl();
336
579
  const newSessionData = {
337
580
  userId: ctx.metadata.userId,
338
581
  privateKey: ctx.metadata.newPrivateKey,
339
582
  keyId: ctx.metadata.newKeyId,
340
583
  algorithm: ctx.metadata.newAlgorithm
341
584
  };
342
- const sealed = await sealSession3(newSessionData, ttl);
585
+ const sealed = await sealSession(newSessionData, ttl);
343
586
  ctx.setCookies.push({
344
- name: COOKIE_NAMES3.SESSION,
587
+ name: COOKIE_NAMES.SESSION,
345
588
  value: sealed,
346
589
  options: {
347
590
  httpOnly: true,
@@ -352,7 +595,7 @@ var keyRotationInterceptor = {
352
595
  }
353
596
  });
354
597
  ctx.setCookies.push({
355
- name: COOKIE_NAMES3.SESSION_KEY_ID,
598
+ name: COOKIE_NAMES.SESSION_KEY_ID,
356
599
  value: ctx.metadata.newKeyId,
357
600
  options: {
358
601
  httpOnly: true,
@@ -364,36 +607,50 @@ var keyRotationInterceptor = {
364
607
  });
365
608
  } catch (error) {
366
609
  const err = error;
367
- authLogger3.interceptor.keyRotation.error("Failed to update session after rotation", err);
610
+ authLogger.interceptor.keyRotation.error("Failed to update session after rotation", err);
368
611
  }
369
612
  await next();
370
613
  }
371
614
  };
372
615
 
373
- // src/nextjs/interceptors/oauth.ts
374
- import {
375
- generateKeyPair as generateKeyPair3,
376
- createOAuthState,
377
- sealSession as sealSession5,
378
- COOKIE_NAMES as COOKIE_NAMES5,
379
- getSessionTtl as getSessionTtl5,
380
- authLogger as authLogger4
381
- } from "@spfn/auth/server";
616
+ // src/server/lib/oauth/state.ts
617
+ import * as jose2 from "jose";
618
+ import { env as env3 } from "@spfn/auth/config";
619
+ async function getStateKey() {
620
+ const secret = env3.SPFN_AUTH_SESSION_SECRET;
621
+ const encoder = new TextEncoder();
622
+ const data = encoder.encode(`oauth-state:${secret}`);
623
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
624
+ return new Uint8Array(hashBuffer);
625
+ }
626
+ function generateNonce() {
627
+ const array = new Uint8Array(16);
628
+ crypto.getRandomValues(array);
629
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
630
+ }
631
+ async function createOAuthState(params) {
632
+ const key = await getStateKey();
633
+ const state = {
634
+ returnUrl: params.returnUrl,
635
+ nonce: generateNonce(),
636
+ provider: params.provider,
637
+ publicKey: params.publicKey,
638
+ keyId: params.keyId,
639
+ fingerprint: params.fingerprint,
640
+ algorithm: params.algorithm,
641
+ metadata: params.metadata
642
+ };
643
+ const jwe = await new jose2.EncryptJWT({ state }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime("10m").encrypt(key);
644
+ return encodeURIComponent(jwe);
645
+ }
382
646
 
383
647
  // src/nextjs/session-helpers.ts
384
- import * as jose from "jose";
648
+ import * as jose3 from "jose";
385
649
  import { cookies } from "next/headers.js";
386
- import {
387
- sealSession as sealSession4,
388
- unsealSession as unsealSession3,
389
- COOKIE_NAMES as COOKIE_NAMES4,
390
- getSessionTtl as getSessionTtl4,
391
- parseDuration
392
- } from "@spfn/auth/server";
393
- import { env } from "@spfn/auth/config";
650
+ import { env as env4 } from "@spfn/auth/config";
394
651
  import { logger } from "@spfn/core/logger";
395
652
  async function getPendingSessionKey() {
396
- const secret = env.SPFN_AUTH_SESSION_SECRET;
653
+ const secret = env4.SPFN_AUTH_SESSION_SECRET;
397
654
  const encoder = new TextEncoder();
398
655
  const data = encoder.encode(`oauth-pending:${secret}`);
399
656
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -401,11 +658,11 @@ async function getPendingSessionKey() {
401
658
  }
402
659
  async function sealPendingSession(data, ttl = 600) {
403
660
  const key = await getPendingSessionKey();
404
- return await new jose.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-oauth").encrypt(key);
661
+ return await new jose3.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-oauth").encrypt(key);
405
662
  }
406
- async function unsealPendingSession(jwt) {
663
+ async function unsealPendingSession(jwt2) {
407
664
  const key = await getPendingSessionKey();
408
- const { payload } = await jose.jwtDecrypt(jwt, key, {
665
+ const { payload } = await jose3.jwtDecrypt(jwt2, key, {
409
666
  issuer: "spfn-auth",
410
667
  audience: "spfn-oauth"
411
668
  });
@@ -419,7 +676,7 @@ var oauthUrlInterceptor = {
419
676
  request: async (ctx, next) => {
420
677
  const provider = ctx.path.split("/")[3];
421
678
  const returnUrl = ctx.body?.returnUrl || "/";
422
- const keyPair = generateKeyPair3("ES256");
679
+ const keyPair = generateKeyPair("ES256");
423
680
  const state = await createOAuthState({
424
681
  provider,
425
682
  returnUrl,
@@ -437,7 +694,7 @@ var oauthUrlInterceptor = {
437
694
  keyId: keyPair.keyId,
438
695
  algorithm: keyPair.algorithm
439
696
  };
440
- authLogger4.interceptor.oauth?.debug?.("OAuth state created", {
697
+ authLogger.interceptor.oauth?.debug?.("OAuth state created", {
441
698
  provider,
442
699
  keyId: keyPair.keyId
443
700
  });
@@ -448,7 +705,7 @@ var oauthUrlInterceptor = {
448
705
  try {
449
706
  const sealed = await sealPendingSession(ctx.metadata.pendingSession);
450
707
  ctx.setCookies.push({
451
- name: COOKIE_NAMES5.OAUTH_PENDING,
708
+ name: COOKIE_NAMES.OAUTH_PENDING,
452
709
  value: sealed,
453
710
  options: {
454
711
  httpOnly: true,
@@ -460,12 +717,12 @@ var oauthUrlInterceptor = {
460
717
  path: "/"
461
718
  }
462
719
  });
463
- authLogger4.interceptor.oauth?.debug?.("Pending session cookie set", {
720
+ authLogger.interceptor.oauth?.debug?.("Pending session cookie set", {
464
721
  keyId: ctx.metadata.pendingSession.keyId
465
722
  });
466
723
  } catch (error) {
467
724
  const err = error;
468
- authLogger4.interceptor.oauth?.error?.("Failed to set pending session", err);
725
+ authLogger.interceptor.oauth?.error?.("Failed to set pending session", err);
469
726
  }
470
727
  }
471
728
  await next();
@@ -477,7 +734,7 @@ function setFinalizeError(ctx, message) {
477
734
  ctx.response.statusText = "Unauthorized";
478
735
  ctx.response.body = { success: false, message };
479
736
  ctx.setCookies.push({
480
- name: COOKIE_NAMES5.OAUTH_PENDING,
737
+ name: COOKIE_NAMES.OAUTH_PENDING,
481
738
  value: "",
482
739
  options: {
483
740
  httpOnly: true,
@@ -496,9 +753,9 @@ var oauthFinalizeInterceptor = {
496
753
  await next();
497
754
  return;
498
755
  }
499
- const pendingCookie = ctx.cookies.get(COOKIE_NAMES5.OAUTH_PENDING);
756
+ const pendingCookie = ctx.cookies.get(COOKIE_NAMES.OAUTH_PENDING);
500
757
  if (!pendingCookie) {
501
- authLogger4.interceptor.oauth?.warn?.("No pending session cookie found");
758
+ authLogger.interceptor.oauth?.warn?.("No pending session cookie found");
502
759
  setFinalizeError(ctx, "OAuth session expired. Please try again.");
503
760
  await next();
504
761
  return;
@@ -507,13 +764,13 @@ var oauthFinalizeInterceptor = {
507
764
  const pendingSession = await unsealPendingSession(pendingCookie);
508
765
  const { userId, keyId } = ctx.response.body || {};
509
766
  if (!userId || !keyId) {
510
- authLogger4.interceptor.oauth?.error?.("Missing userId or keyId in response");
767
+ authLogger.interceptor.oauth?.error?.("Missing userId or keyId in response");
511
768
  setFinalizeError(ctx, "OAuth finalize failed: missing credentials");
512
769
  await next();
513
770
  return;
514
771
  }
515
772
  if (pendingSession.keyId !== keyId) {
516
- authLogger4.interceptor.oauth?.error?.("KeyId mismatch", {
773
+ authLogger.interceptor.oauth?.error?.("KeyId mismatch", {
517
774
  expected: pendingSession.keyId,
518
775
  received: keyId
519
776
  });
@@ -521,15 +778,15 @@ var oauthFinalizeInterceptor = {
521
778
  await next();
522
779
  return;
523
780
  }
524
- const ttl = getSessionTtl5();
525
- const sessionToken = await sealSession5({
781
+ const ttl = getSessionTtl();
782
+ const sessionToken = await sealSession({
526
783
  userId,
527
784
  privateKey: pendingSession.privateKey,
528
785
  keyId: pendingSession.keyId,
529
786
  algorithm: pendingSession.algorithm
530
787
  }, ttl);
531
788
  ctx.setCookies.push({
532
- name: COOKIE_NAMES5.SESSION,
789
+ name: COOKIE_NAMES.SESSION,
533
790
  value: sessionToken,
534
791
  options: {
535
792
  httpOnly: true,
@@ -540,7 +797,7 @@ var oauthFinalizeInterceptor = {
540
797
  }
541
798
  });
542
799
  ctx.setCookies.push({
543
- name: COOKIE_NAMES5.SESSION_KEY_ID,
800
+ name: COOKIE_NAMES.SESSION_KEY_ID,
544
801
  value: keyId,
545
802
  options: {
546
803
  httpOnly: true,
@@ -551,7 +808,7 @@ var oauthFinalizeInterceptor = {
551
808
  }
552
809
  });
553
810
  ctx.setCookies.push({
554
- name: COOKIE_NAMES5.OAUTH_PENDING,
811
+ name: COOKIE_NAMES.OAUTH_PENDING,
555
812
  value: "",
556
813
  options: {
557
814
  httpOnly: true,
@@ -561,13 +818,13 @@ var oauthFinalizeInterceptor = {
561
818
  path: "/"
562
819
  }
563
820
  });
564
- authLogger4.interceptor.oauth?.debug?.("OAuth session finalized", {
821
+ authLogger.interceptor.oauth?.debug?.("OAuth session finalized", {
565
822
  userId,
566
823
  keyId
567
824
  });
568
825
  } catch (error) {
569
826
  const err = error;
570
- authLogger4.interceptor.oauth?.error?.("Failed to finalize OAuth session", err);
827
+ authLogger.interceptor.oauth?.error?.("Failed to finalize OAuth session", err);
571
828
  setFinalizeError(ctx, err.message);
572
829
  }
573
830
  await next();