@spfn/auth 0.3.0-beta.2 → 0.3.0-beta.20

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.
Files changed (43) hide show
  1. package/README.md +1382 -23
  2. package/dist/client-proof.d.ts +45 -15
  3. package/dist/client-proof.js +198 -4
  4. package/dist/client-proof.js.map +1 -1
  5. package/dist/client.d.ts +92 -1
  6. package/dist/client.js +58 -0
  7. package/dist/client.js.map +1 -1
  8. package/dist/config.d.ts +302 -0
  9. package/dist/config.js +134 -4
  10. package/dist/config.js.map +1 -1
  11. package/dist/errors.d.ts +370 -3
  12. package/dist/errors.js +245 -2
  13. package/dist/errors.js.map +1 -1
  14. package/dist/index.d.ts +185 -2
  15. package/dist/index.js +256 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/machine-principals-BD4tnASp.d.ts +2739 -0
  18. package/dist/nextjs/api.js +350 -12
  19. package/dist/nextjs/api.js.map +1 -1
  20. package/dist/nextjs/client.d.ts +28 -1
  21. package/dist/nextjs/client.js +24 -3
  22. package/dist/nextjs/client.js.map +1 -1
  23. package/dist/nextjs/server.d.ts +173 -3
  24. package/dist/nextjs/server.js +372 -10
  25. package/dist/nextjs/server.js.map +1 -1
  26. package/dist/server.d.ts +3761 -414
  27. package/dist/server.js +5865 -1043
  28. package/dist/server.js.map +1 -1
  29. package/dist/{session-DTHahDQ9.d.ts → session-Dfwu5g2W.d.ts} +28 -1
  30. package/migrations/20260810112144_colorful_tomorrow_man/migration.sql +18 -0
  31. package/migrations/20260810112144_colorful_tomorrow_man/snapshot.json +3576 -0
  32. package/migrations/20260901091716_fine_arclight/migration.sql +21 -0
  33. package/migrations/20260901091716_fine_arclight/snapshot.json +3849 -0
  34. package/migrations/20260906155957_natural_moonstone/migration.sql +33 -0
  35. package/migrations/20260906155957_natural_moonstone/snapshot.json +4275 -0
  36. package/migrations/20260907020904_giant_eternals/migration.sql +21 -0
  37. package/migrations/20260907020904_giant_eternals/snapshot.json +4561 -0
  38. package/migrations/20260907044807_eminent_angel/migration.sql +2 -0
  39. package/migrations/20260907044807_eminent_angel/snapshot.json +4561 -0
  40. package/migrations/20260918083158_foamy_roughhouse/migration.sql +55 -0
  41. package/migrations/20260918083158_foamy_roughhouse/snapshot.json +5271 -0
  42. package/package.json +9 -6
  43. package/dist/authenticate-55LeXHqZ.d.ts +0 -1447
@@ -96,7 +96,8 @@ var authLogger = {
96
96
  general: rootLogger.child("@spfn/auth:interceptor:general"),
97
97
  login: rootLogger.child("@spfn/auth:interceptor:login"),
98
98
  keyRotation: rootLogger.child("@spfn/auth:interceptor:key-rotation"),
99
- oauth: rootLogger.child("@spfn/auth:interceptor:oauth")
99
+ oauth: rootLogger.child("@spfn/auth:interceptor:oauth"),
100
+ csrf: rootLogger.child("@spfn/auth:interceptor:csrf")
100
101
  },
101
102
  session: rootLogger.child("@spfn/auth:session"),
102
103
  service: rootLogger.child("@spfn/auth:service"),
@@ -190,8 +191,9 @@ async function shouldRefreshSession(jwt2, thresholdHours = 24) {
190
191
 
191
192
  // src/server/lib/config.ts
192
193
  import { env as env2 } from "@spfn/auth/config";
194
+ import { PasskeyConfigError } from "@spfn/auth/errors";
193
195
  function getCookieSuffix() {
194
- const port = process.env.PORT;
196
+ const port = process.env.SPFN_PORT;
195
197
  return port ? `_${port}` : "";
196
198
  }
197
199
  var COOKIE_NAMES = {
@@ -210,6 +212,18 @@ var COOKIE_NAMES = {
210
212
  /** OAuth CSRF nonce — double-submit against the (encrypted) state.nonce at callback */
211
213
  get OAUTH_CSRF() {
212
214
  return `spfn_oauth_csrf${getCookieSuffix()}`;
215
+ },
216
+ /** Password-setup session for verified-email signup — temporary, single-purpose */
217
+ get SIGNUP_SETUP() {
218
+ return `spfn_signup_setup${getCookieSuffix()}`;
219
+ },
220
+ /** Password-setup session for a password reset — temporary, single-purpose */
221
+ get PASSWORD_RESET_SETUP() {
222
+ return `spfn_password_reset_setup${getCookieSuffix()}`;
223
+ },
224
+ /** CSRF token — the only cookie here the browser can read */
225
+ get CSRF() {
226
+ return `spfn_csrf${getCookieSuffix()}`;
213
227
  }
214
228
  };
215
229
  function parseDuration(duration) {
@@ -252,6 +266,33 @@ function getSessionTtl(override) {
252
266
  }
253
267
  return 7 * 24 * 60 * 60;
254
268
  }
269
+ var CSRF_MODES = ["off", "warn", "enforce"];
270
+ var unrecognizedCsrfModeReported = false;
271
+ function getCsrfMode() {
272
+ const configured = globalConfig.csrf?.mode ?? env2.SPFN_AUTH_CSRF;
273
+ if (!configured) {
274
+ return "warn";
275
+ }
276
+ const normalized = String(configured).trim().toLowerCase();
277
+ if (!CSRF_MODES.includes(normalized)) {
278
+ if (!unrecognizedCsrfModeReported) {
279
+ unrecognizedCsrfModeReported = true;
280
+ authLogger.interceptor.csrf.error(
281
+ `Unrecognized CSRF mode "${configured}" \u2014 expected off | warn | enforce. Enforcing.`
282
+ );
283
+ }
284
+ return "enforce";
285
+ }
286
+ return normalized;
287
+ }
288
+ var PACKAGE_CSRF_EXEMPT_PATHS = [
289
+ "/_auth/oauth2/register",
290
+ "/_auth/oauth2/token",
291
+ "/_auth/oauth2/revoke"
292
+ ];
293
+ function getCsrfExemptPaths() {
294
+ return [...PACKAGE_CSRF_EXEMPT_PATHS, ...globalConfig.csrf?.exemptPaths ?? []];
295
+ }
255
296
 
256
297
  // src/nextjs/interceptors/cookie-options.ts
257
298
  function resolveSecure() {
@@ -263,9 +304,144 @@ function resolveSecure() {
263
304
  }
264
305
  var cookieSecure = resolveSecure();
265
306
 
307
+ // src/server/lib/csrf.ts
308
+ import { env as env3 } from "@spfn/auth/config";
309
+ var CSRF_HEADER = "x-spfn-csrf";
310
+ var CSRF_SUBKEY_LABEL = "spfn-auth-csrf-token-v1";
311
+ var MAX_CANDIDATES = 32;
312
+ function sessionSecret() {
313
+ const secret = env3.SPFN_AUTH_SESSION_SECRET;
314
+ if (!secret) {
315
+ throw new Error(
316
+ "SPFN_AUTH_SESSION_SECRET is required for CSRF protection. Set it (sessions need it anyway), or set SPFN_AUTH_CSRF=off."
317
+ );
318
+ }
319
+ return secret;
320
+ }
321
+ async function hmacSha256(key, message) {
322
+ const cryptoKey = await crypto.subtle.importKey(
323
+ "raw",
324
+ key.buffer,
325
+ { name: "HMAC", hash: "SHA-256" },
326
+ false,
327
+ ["sign"]
328
+ );
329
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(message));
330
+ return new Uint8Array(signature);
331
+ }
332
+ function toHex(bytes) {
333
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
334
+ }
335
+ async function deriveCsrfToken(keyId) {
336
+ const subkey = await hmacSha256(new TextEncoder().encode(sessionSecret()), CSRF_SUBKEY_LABEL);
337
+ return toHex(await hmacSha256(subkey, keyId));
338
+ }
339
+ function timingSafeEqualString(a, b) {
340
+ if (a.length !== b.length) {
341
+ return false;
342
+ }
343
+ let difference = 0;
344
+ for (let i = 0; i < a.length; i++) {
345
+ difference |= a.charCodeAt(i) ^ b.charCodeAt(i);
346
+ }
347
+ return difference === 0;
348
+ }
349
+ function matchesCsrfToken(expected, presented) {
350
+ if (!presented) {
351
+ return false;
352
+ }
353
+ return presented.split(",", MAX_CANDIDATES).some((candidate) => timingSafeEqualString(expected, candidate.trim()));
354
+ }
355
+
356
+ // src/nextjs/interceptors/csrf.ts
357
+ var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
358
+ function csrfCookie(token, ttl) {
359
+ return {
360
+ name: COOKIE_NAMES.CSRF,
361
+ value: token,
362
+ options: {
363
+ httpOnly: false,
364
+ secure: cookieSecure,
365
+ sameSite: "lax",
366
+ maxAge: ttl,
367
+ path: "/"
368
+ }
369
+ };
370
+ }
371
+ function refusal() {
372
+ return {
373
+ status: 403,
374
+ body: {
375
+ error: "Forbidden",
376
+ message: "CSRF token missing or invalid"
377
+ },
378
+ setCookies: []
379
+ };
380
+ }
381
+ async function refuseInvalidCsrf(ctx, keyId) {
382
+ const mode = getCsrfMode();
383
+ if (mode === "off" || SAFE_METHODS.has(ctx.method.toUpperCase())) {
384
+ return false;
385
+ }
386
+ if (getCsrfExemptPaths().includes(ctx.path)) {
387
+ authLogger.interceptor.csrf.debug("Path is CSRF-exempt", { path: ctx.path });
388
+ return false;
389
+ }
390
+ let expected;
391
+ try {
392
+ expected = await deriveCsrfToken(keyId);
393
+ } catch (error) {
394
+ authLogger.interceptor.csrf.error(
395
+ "Cannot derive the CSRF token \u2014 refusing regardless of mode",
396
+ error
397
+ );
398
+ ctx.abort = refusal();
399
+ return true;
400
+ }
401
+ const presented = ctx.request.headers.get(CSRF_HEADER);
402
+ if (matchesCsrfToken(expected, presented)) {
403
+ return false;
404
+ }
405
+ const detail = {
406
+ method: ctx.method,
407
+ path: ctx.path,
408
+ headerPresent: !!presented
409
+ };
410
+ if (mode === "warn") {
411
+ authLogger.interceptor.csrf.warn("CSRF check would refuse this request (mode=warn)", detail);
412
+ return false;
413
+ }
414
+ authLogger.interceptor.csrf.warn("CSRF check refused this request", detail);
415
+ ctx.abort = refusal();
416
+ ctx.abort.setCookies = [csrfCookie(expected, getSessionTtl())];
417
+ return true;
418
+ }
419
+ async function pushCsrfCookie(setCookies, keyId, ttl) {
420
+ setCookies.push(csrfCookie(await deriveCsrfToken(keyId), ttl));
421
+ }
422
+ async function pushCsrfCookieIfStale(setCookies, presented, keyId) {
423
+ try {
424
+ const token = await deriveCsrfToken(keyId);
425
+ if (presented && timingSafeEqualString(token, presented)) {
426
+ return;
427
+ }
428
+ setCookies.push(csrfCookie(token, getSessionTtl()));
429
+ } catch (error) {
430
+ authLogger.interceptor.csrf.error("Cannot reissue the CSRF cookie", error);
431
+ }
432
+ }
433
+ function pushCsrfCookieRemoval(setCookies) {
434
+ setCookies.push({
435
+ name: COOKIE_NAMES.CSRF,
436
+ value: "",
437
+ options: { maxAge: 0, path: "/" }
438
+ });
439
+ }
440
+
266
441
  // src/nextjs/interceptors/login-register.ts
442
+ var ROTATING_SIGN_IN_PATHS = /* @__PURE__ */ new Set(["/_auth/login", "/_auth/passkeys/login/verify"]);
267
443
  var loginRegisterInterceptor = {
268
- pathPattern: /^\/_auth\/(login|register|invitations\/accept)$/,
444
+ pathPattern: /^\/_auth\/(login|register|invitations\/accept|signup\/password|password\/reset\/complete|passkeys\/login\/verify)$/,
269
445
  method: "POST",
270
446
  request: async (ctx, next) => {
271
447
  const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);
@@ -279,7 +455,7 @@ var loginRegisterInterceptor = {
279
455
  ctx.body.fingerprint = keyPair.fingerprint;
280
456
  ctx.body.algorithm = keyPair.algorithm;
281
457
  ctx.body.keySize = Buffer.from(keyPair.publicKey, "base64").length;
282
- if (ctx.path === "/_auth/login" && oldKeyId) {
458
+ if (ROTATING_SIGN_IN_PATHS.has(ctx.path) && oldKeyId) {
283
459
  ctx.body.oldKeyId = oldKeyId;
284
460
  }
285
461
  delete ctx.body.remember;
@@ -331,6 +507,7 @@ var loginRegisterInterceptor = {
331
507
  path: "/"
332
508
  }
333
509
  });
510
+ await pushCsrfCookie(ctx.setCookies, ctx.metadata.keyId, ttl);
334
511
  } catch (error) {
335
512
  const err = error;
336
513
  authLogger.interceptor.login.error("Failed to save session", err);
@@ -389,6 +566,9 @@ var generalAuthInterceptor = {
389
566
  userId: session.userId,
390
567
  keyId: session.keyId
391
568
  });
569
+ if (await refuseInvalidCsrf(ctx, session.keyId)) {
570
+ return;
571
+ }
392
572
  const needsRefresh = await shouldRefreshSession(sessionCookie, 24);
393
573
  if (needsRefresh) {
394
574
  authLogger.interceptor.general.debug("Session needs refresh (within 24h of expiry)");
@@ -409,6 +589,7 @@ var generalAuthInterceptor = {
409
589
  ctx.headers["Authorization"] = `Bearer ${token}`;
410
590
  ctx.headers["X-Key-Id"] = session.keyId;
411
591
  ctx.metadata.userId = session.userId;
592
+ ctx.metadata.keyId = session.keyId;
412
593
  ctx.metadata.sessionValid = true;
413
594
  } catch (error) {
414
595
  const err = error;
@@ -442,6 +623,7 @@ var generalAuthInterceptor = {
442
623
  value: "",
443
624
  options: { maxAge: 0, path: "/" }
444
625
  });
626
+ pushCsrfCookieRemoval(ctx.setCookies);
445
627
  await next();
446
628
  return;
447
629
  }
@@ -462,6 +644,7 @@ var generalAuthInterceptor = {
462
644
  path: "/"
463
645
  }
464
646
  });
647
+ pushCsrfCookieRemoval(ctx.setCookies);
465
648
  } else if (ctx.metadata.refreshSession && ctx.response.status === 200) {
466
649
  try {
467
650
  const sessionData = ctx.metadata.sessionData;
@@ -489,6 +672,7 @@ var generalAuthInterceptor = {
489
672
  path: "/"
490
673
  }
491
674
  });
675
+ await pushCsrfCookie(ctx.setCookies, sessionData.keyId, ttl);
492
676
  authLogger.interceptor.general.info("Session refreshed", {
493
677
  userId: sessionData.userId,
494
678
  sealedLength: sealed.length,
@@ -520,6 +704,15 @@ var generalAuthInterceptor = {
520
704
  value: "",
521
705
  options: { ...base, sameSite: "lax" }
522
706
  });
707
+ pushCsrfCookieRemoval(ctx.setCookies);
708
+ }
709
+ const csrfQueued = ctx.setCookies.some((cookie) => cookie.name === COOKIE_NAMES.CSRF);
710
+ if (ctx.metadata.sessionValid && !csrfQueued) {
711
+ await pushCsrfCookieIfStale(
712
+ ctx.setCookies,
713
+ ctx.cookies.get(COOKIE_NAMES.CSRF),
714
+ ctx.metadata.keyId
715
+ );
523
716
  }
524
717
  await next();
525
718
  }
@@ -546,10 +739,11 @@ var keyRotationInterceptor = {
546
739
  ctx.body.fingerprint = newKeyPair.fingerprint;
547
740
  ctx.body.algorithm = newKeyPair.algorithm;
548
741
  ctx.body.keySize = Buffer.from(newKeyPair.publicKey, "base64").length;
549
- console.log("New key generated:", newKeyPair);
550
- console.log("publicKey:", newKeyPair.publicKey);
551
- console.log("keyId:", newKeyPair.keyId);
552
- console.log("fingerprint:", newKeyPair.fingerprint);
742
+ authLogger.interceptor.keyRotation.debug("Generated a new key pair", {
743
+ keyId: newKeyPair.keyId,
744
+ fingerprint: newKeyPair.fingerprint,
745
+ algorithm: newKeyPair.algorithm
746
+ });
553
747
  const token = generateClientToken(
554
748
  {
555
749
  userId: currentSession.userId,
@@ -614,6 +808,7 @@ var keyRotationInterceptor = {
614
808
  path: "/"
615
809
  }
616
810
  });
811
+ await pushCsrfCookie(ctx.setCookies, ctx.metadata.newKeyId, ttl);
617
812
  } catch (error) {
618
813
  const err = error;
619
814
  authLogger.interceptor.keyRotation.error("Failed to update session after rotation", err);
@@ -624,9 +819,9 @@ var keyRotationInterceptor = {
624
819
 
625
820
  // src/server/lib/oauth/state.ts
626
821
  import * as jose2 from "jose";
627
- import { env as env3 } from "@spfn/auth/config";
822
+ import { env as env4 } from "@spfn/auth/config";
628
823
  async function getStateKey() {
629
- const secret = env3.SPFN_AUTH_SESSION_SECRET;
824
+ const secret = env4.SPFN_AUTH_SESSION_SECRET;
630
825
  const encoder = new TextEncoder();
631
826
  const data = encoder.encode(`oauth-state:${secret}`);
632
827
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -656,13 +851,28 @@ async function createOAuthState(params) {
656
851
  return encodeURIComponent(jwe);
657
852
  }
658
853
 
854
+ // src/lib/return-path.ts
855
+ var URL_STRIPPED_CHARACTER = /[\t\n\r]/;
856
+ function isSafeReturnPath(returnPath) {
857
+ if (!returnPath.startsWith("/")) {
858
+ return false;
859
+ }
860
+ if (returnPath.startsWith("//") || returnPath.includes("\\")) {
861
+ return false;
862
+ }
863
+ if (returnPath.includes("..") || URL_STRIPPED_CHARACTER.test(returnPath)) {
864
+ return false;
865
+ }
866
+ return !/^\/[^/?#]*:/.test(returnPath);
867
+ }
868
+
659
869
  // src/nextjs/session-helpers.ts
660
870
  import * as jose3 from "jose";
661
871
  import { cookies } from "next/headers.js";
662
- import { env as env4 } from "@spfn/auth/config";
872
+ import { env as env5 } from "@spfn/auth/config";
663
873
  import { logger } from "@spfn/core/logger";
664
874
  async function getPendingSessionKey() {
665
- const secret = env4.SPFN_AUTH_SESSION_SECRET;
875
+ const secret = env5.SPFN_AUTH_SESSION_SECRET;
666
876
  const encoder = new TextEncoder();
667
877
  const data = encoder.encode(`oauth-pending:${secret}`);
668
878
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -682,6 +892,20 @@ async function unsealPendingSession(jwt2) {
682
892
  }
683
893
 
684
894
  // src/nextjs/interceptors/oauth.ts
895
+ var UNSAFE_RETURN_URL_MESSAGE = "returnUrl must be a relative path within the app";
896
+ function refuseUnsafeReturnUrl() {
897
+ return {
898
+ status: 400,
899
+ body: {
900
+ __type: "ValidationError",
901
+ message: UNSAFE_RETURN_URL_MESSAGE,
902
+ error: {
903
+ code: "ValidationError",
904
+ message: UNSAFE_RETURN_URL_MESSAGE
905
+ }
906
+ }
907
+ };
908
+ }
685
909
  var oauthUrlInterceptor = {
686
910
  pathPattern: /^\/_auth\/oauth\/\w+\/url$/,
687
911
  method: "POST",
@@ -689,6 +913,13 @@ var oauthUrlInterceptor = {
689
913
  const provider = ctx.path.split("/")[3];
690
914
  const returnUrl = ctx.body?.returnUrl || "/";
691
915
  const metadata = ctx.body?.metadata;
916
+ if (typeof returnUrl !== "string" || !isSafeReturnPath(returnUrl)) {
917
+ authLogger.interceptor.oauth?.warn?.("OAuth start refused: returnUrl is not a path within the app", {
918
+ provider
919
+ });
920
+ ctx.abort = refuseUnsafeReturnUrl();
921
+ return;
922
+ }
692
923
  const keyPair = generateKeyPair("ES256");
693
924
  const csrfNonce = generateOAuthNonce();
694
925
  const state = await createOAuthState({
@@ -837,6 +1068,7 @@ var oauthFinalizeInterceptor = {
837
1068
  path: "/"
838
1069
  }
839
1070
  });
1071
+ await pushCsrfCookie(ctx.setCookies, keyId, ttl);
840
1072
  ctx.setCookies.push({
841
1073
  name: COOKIE_NAMES.OAUTH_PENDING,
842
1074
  value: "",
@@ -861,8 +1093,114 @@ var oauthFinalizeInterceptor = {
861
1093
  }
862
1094
  };
863
1095
 
1096
+ // src/nextjs/interceptors/signup-link.ts
1097
+ var SETUP_COOKIE_TTL_SECONDS = 60 * 60;
1098
+ function setupCookie(value, maxAge) {
1099
+ return {
1100
+ name: COOKIE_NAMES.SIGNUP_SETUP,
1101
+ value,
1102
+ options: {
1103
+ httpOnly: true,
1104
+ secure: cookieSecure,
1105
+ sameSite: "lax",
1106
+ maxAge,
1107
+ path: "/"
1108
+ }
1109
+ };
1110
+ }
1111
+ var signupLinkInterceptor = {
1112
+ pathPattern: /^\/_auth\/signup\/(email\/confirm|password)$/,
1113
+ method: "POST",
1114
+ request: async (ctx, next) => {
1115
+ if (ctx.path === "/_auth/signup/password") {
1116
+ const cookie = ctx.cookies.get(COOKIE_NAMES.SIGNUP_SETUP);
1117
+ if (cookie) {
1118
+ if (!ctx.body) {
1119
+ ctx.body = {};
1120
+ }
1121
+ ctx.body.setupSecret = cookie;
1122
+ }
1123
+ }
1124
+ await next();
1125
+ },
1126
+ response: async (ctx, next) => {
1127
+ if (!ctx.response.ok) {
1128
+ await next();
1129
+ return;
1130
+ }
1131
+ if (ctx.path === "/_auth/signup/email/confirm") {
1132
+ const secret = ctx.response.body?.setupSecret;
1133
+ if (!secret) {
1134
+ authLogger.interceptor.oauth?.error?.("Signup confirm response carried no setup secret");
1135
+ await next();
1136
+ return;
1137
+ }
1138
+ ctx.setCookies.push(setupCookie(secret, SETUP_COOKIE_TTL_SECONDS));
1139
+ delete ctx.response.body.setupSecret;
1140
+ }
1141
+ if (ctx.path === "/_auth/signup/password") {
1142
+ ctx.setCookies.push(setupCookie("", 0));
1143
+ }
1144
+ await next();
1145
+ }
1146
+ };
1147
+
1148
+ // src/nextjs/interceptors/password-reset.ts
1149
+ var SETUP_COOKIE_TTL_SECONDS2 = 60 * 60;
1150
+ function setupCookie2(value, maxAge) {
1151
+ return {
1152
+ name: COOKIE_NAMES.PASSWORD_RESET_SETUP,
1153
+ value,
1154
+ options: {
1155
+ httpOnly: true,
1156
+ secure: cookieSecure,
1157
+ sameSite: "lax",
1158
+ maxAge,
1159
+ path: "/"
1160
+ }
1161
+ };
1162
+ }
1163
+ var passwordResetInterceptor = {
1164
+ pathPattern: /^\/_auth\/password\/reset\/(confirm|complete)$/,
1165
+ method: "POST",
1166
+ request: async (ctx, next) => {
1167
+ if (ctx.path === "/_auth/password/reset/complete") {
1168
+ const cookie = ctx.cookies.get(COOKIE_NAMES.PASSWORD_RESET_SETUP);
1169
+ if (cookie) {
1170
+ if (!ctx.body) {
1171
+ ctx.body = {};
1172
+ }
1173
+ ctx.body.setupSecret = cookie;
1174
+ }
1175
+ }
1176
+ await next();
1177
+ },
1178
+ response: async (ctx, next) => {
1179
+ if (!ctx.response.ok) {
1180
+ await next();
1181
+ return;
1182
+ }
1183
+ if (ctx.path === "/_auth/password/reset/confirm") {
1184
+ const secret = ctx.response.body?.setupSecret;
1185
+ if (!secret) {
1186
+ authLogger.interceptor.oauth?.error?.("Password reset confirm response carried no setup secret");
1187
+ await next();
1188
+ return;
1189
+ }
1190
+ ctx.setCookies.push(setupCookie2(secret, SETUP_COOKIE_TTL_SECONDS2));
1191
+ delete ctx.response.body.setupSecret;
1192
+ }
1193
+ if (ctx.path === "/_auth/password/reset/complete") {
1194
+ ctx.setCookies.push(setupCookie2("", 0));
1195
+ }
1196
+ await next();
1197
+ }
1198
+ };
1199
+
864
1200
  // src/nextjs/interceptors/index.ts
865
1201
  var authInterceptors = [
1202
+ signupLinkInterceptor,
1203
+ passwordResetInterceptor,
866
1204
  loginRegisterInterceptor,
867
1205
  keyRotationInterceptor,
868
1206
  oauthUrlInterceptor,