@cimplify/sdk 0.64.1 → 0.66.0

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 (40) hide show
  1. package/dist/advanced.d.mts +1 -1
  2. package/dist/advanced.d.ts +1 -1
  3. package/dist/advanced.js +20 -20
  4. package/dist/advanced.mjs +1 -1
  5. package/dist/{chunk-SQ7U3BWY.mjs → chunk-227XTAEB.mjs} +52 -25
  6. package/dist/{chunk-2AU263DK.mjs → chunk-3Z3FQH7X.mjs} +1 -1
  7. package/dist/{chunk-XSOYJLMZ.mjs → chunk-7BKKBJQM.mjs} +10 -3
  8. package/dist/{chunk-JGBDWEPJ.js → chunk-AVQL3TSJ.js} +3 -0
  9. package/dist/{chunk-6DMHVQ42.js → chunk-GDAGM26J.js} +4 -4
  10. package/dist/{chunk-6W3AH4QW.js → chunk-H6DBPHMZ.js} +2 -2
  11. package/dist/{chunk-MW7ICTVK.js → chunk-J3MGY6AU.js} +86 -59
  12. package/dist/{chunk-DKT62WQW.js → chunk-KWYI2TTA.js} +10 -3
  13. package/dist/{chunk-F4WS3OIF.mjs → chunk-MWI5GAUF.mjs} +1 -1
  14. package/dist/{chunk-I4IXPQIX.mjs → chunk-R6363CC7.mjs} +3 -0
  15. package/dist/{client-Dfg_hmkP.d.ts → client-Bo5rujc2.d.ts} +8 -2
  16. package/dist/{client-Bhvlelij.d.mts → client-CUrA1uQ0.d.mts} +1 -1
  17. package/dist/{client-DlGJqSDd.d.ts → client-DasPHOJh.d.ts} +1 -1
  18. package/dist/{client-DKg-5OWu.d.mts → client-Dka2cGhu.d.mts} +8 -2
  19. package/dist/index.d.mts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +78 -78
  22. package/dist/index.mjs +5 -5
  23. package/dist/react.d.mts +1 -1
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +64 -64
  26. package/dist/react.mjs +5 -5
  27. package/dist/server.d.mts +18 -3
  28. package/dist/server.d.ts +18 -3
  29. package/dist/server.js +167 -28
  30. package/dist/server.mjs +159 -24
  31. package/dist/styles.css +1 -1
  32. package/dist/testing/suite.d.mts +2 -2
  33. package/dist/testing/suite.d.ts +2 -2
  34. package/dist/testing/suite.js +22 -22
  35. package/dist/testing/suite.mjs +3 -3
  36. package/dist/testing.d.mts +2 -2
  37. package/dist/testing.d.ts +2 -2
  38. package/dist/testing.js +78 -78
  39. package/dist/testing.mjs +4 -4
  40. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkDKT62WQW_js = require('./chunk-DKT62WQW.js');
4
- var chunkMW7ICTVK_js = require('./chunk-MW7ICTVK.js');
5
- require('./chunk-JGBDWEPJ.js');
3
+ var chunkKWYI2TTA_js = require('./chunk-KWYI2TTA.js');
4
+ var chunkJ3MGY6AU_js = require('./chunk-J3MGY6AU.js');
5
+ require('./chunk-AVQL3TSJ.js');
6
6
  require('./chunk-7Y2O3E4D.js');
7
7
  var chunkXA3ZNR75_js = require('./chunk-XA3ZNR75.js');
8
8
  require('./chunk-OWW5GUSB.js');
@@ -27,7 +27,7 @@ function readEnv(...keys) {
27
27
  var getServerClient = react.cache((opts = {}) => {
28
28
  const baseUrl = opts.apiUrl ?? readEnv("CIMPLIFY_API_URL", "NEXT_PUBLIC_CIMPLIFY_API_URL") ?? defaultBaseUrl();
29
29
  const publicKey = opts.secretKey ?? readEnv("CIMPLIFY_SECRET_KEY", "NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY") ?? "mock-dev";
30
- const client = chunkMW7ICTVK_js.createCimplifyClient({
30
+ const client = chunkJ3MGY6AU_js.createCimplifyClient({
31
31
  baseUrl,
32
32
  publicKey,
33
33
  suppressPublicKeyWarning: true
@@ -240,6 +240,9 @@ async function refreshPage() {
240
240
  }
241
241
  var SESSION_COOKIE = "cimplify_session";
242
242
  var ACCESS_TOKEN_COOKIE = "cimplify_access";
243
+ var REFRESH_TOKEN_COOKIE = "cimplify_refresh";
244
+ var REFRESH_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
245
+ var ACCESS_REFRESH_SKEW_SECONDS = 30;
243
246
  var HEADER_LOCATION = "Location";
244
247
  var PARAM_CODE = "code";
245
248
  var PARAM_STATE = "state";
@@ -248,7 +251,7 @@ var QUERY_AUTH_ERROR = "cimplify_auth_error";
248
251
  var discoveryCache = /* @__PURE__ */ new Map();
249
252
  var jwksCache = /* @__PURE__ */ new Map();
250
253
  function issuerBase(cfg) {
251
- return (cfg.issuer ?? cfg.authUrl ?? chunkDKT62WQW_js.DEFAULT_ISSUER).replace(/\/$/, "");
254
+ return (cfg.issuer ?? cfg.authUrl ?? chunkKWYI2TTA_js.DEFAULT_ISSUER).replace(/\/$/, "");
252
255
  }
253
256
  function cookieName(cfg) {
254
257
  return cfg.cookieName ?? SESSION_COOKIE;
@@ -256,6 +259,9 @@ function cookieName(cfg) {
256
259
  function accessTokenCookieName(cfg) {
257
260
  return cfg.accessTokenCookieName ?? ACCESS_TOKEN_COOKIE;
258
261
  }
262
+ function refreshTokenCookieName(cfg) {
263
+ return cfg.refreshTokenCookieName ?? REFRESH_TOKEN_COOKIE;
264
+ }
259
265
  async function discovery(cfg) {
260
266
  const base = issuerBase(cfg);
261
267
  let p = discoveryCache.get(base);
@@ -356,28 +362,38 @@ function buildAccessTokenCookie(cfg, accessToken, maxAgeSeconds) {
356
362
  if (cfg.cookieDomain) parts.push(`Domain=${cfg.cookieDomain}`);
357
363
  return parts.join("; ");
358
364
  }
359
- function buildSignoutCookies(cfg) {
360
- const idTokenParts = [
361
- `${cookieName(cfg)}=`,
365
+ function buildRefreshTokenCookie(cfg, refreshToken, maxAgeSeconds) {
366
+ const parts = [
367
+ `${refreshTokenCookieName(cfg)}=${refreshToken}`,
362
368
  "Path=/",
363
369
  "HttpOnly",
364
370
  "Secure",
365
371
  "SameSite=Lax",
366
- "Max-Age=0"
372
+ `Max-Age=${maxAgeSeconds}`
367
373
  ];
368
- const accessParts = [
369
- `${accessTokenCookieName(cfg)}=`,
370
- "Path=/",
371
- "HttpOnly",
372
- "Secure",
373
- "SameSite=Lax",
374
- "Max-Age=0"
374
+ if (cfg.cookieDomain) parts.push(`Domain=${cfg.cookieDomain}`);
375
+ return parts.join("; ");
376
+ }
377
+ function buildSignoutCookies(cfg) {
378
+ const clear = (name) => {
379
+ const parts = [`${name}=`, "Path=/", "HttpOnly", "Secure", "SameSite=Lax", "Max-Age=0"];
380
+ if (cfg.cookieDomain) parts.push(`Domain=${cfg.cookieDomain}`);
381
+ return parts.join("; ");
382
+ };
383
+ return [
384
+ clear(cookieName(cfg)),
385
+ clear(accessTokenCookieName(cfg)),
386
+ clear(refreshTokenCookieName(cfg))
375
387
  ];
376
- if (cfg.cookieDomain) {
377
- idTokenParts.push(`Domain=${cfg.cookieDomain}`);
378
- accessParts.push(`Domain=${cfg.cookieDomain}`);
379
- }
380
- return [idTokenParts.join("; "), accessParts.join("; ")];
388
+ }
389
+ function readCookie(name, cookieHeader) {
390
+ if (!cookieHeader) return null;
391
+ const match = cookieHeader.split(/;\s*/).find((p) => p.startsWith(`${name}=`));
392
+ if (!match) return null;
393
+ return match.slice(name.length + 1) || null;
394
+ }
395
+ function getRefreshTokenFromCookieHeader(cfg, cookieHeader) {
396
+ return readCookie(refreshTokenCookieName(cfg), cookieHeader);
381
397
  }
382
398
  function getAccessTokenFromCookieHeader(cfg, cookieHeader) {
383
399
  if (!cookieHeader) return null;
@@ -387,7 +403,21 @@ function getAccessTokenFromCookieHeader(cfg, cookieHeader) {
387
403
  const token = match.slice(name.length + 1);
388
404
  return token || null;
389
405
  }
406
+ function isSameOriginCallback(req, redirectUri) {
407
+ const site = req.headers.get("sec-fetch-site");
408
+ if (site) return site === "same-origin";
409
+ const origin = req.headers.get("origin");
410
+ if (!origin) return false;
411
+ try {
412
+ return origin === new URL(redirectUri).origin;
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
390
417
  async function handleOidcCallback(req, opts) {
418
+ if (!isSameOriginCallback(req, opts.redirectUri)) {
419
+ return Response.json({ error: "forbidden_origin" }, { status: 403 });
420
+ }
391
421
  let body;
392
422
  try {
393
423
  body = await req.json();
@@ -404,30 +434,36 @@ async function handleOidcCallback(req, opts) {
404
434
  code: body.code,
405
435
  codeVerifier: body.codeVerifier
406
436
  });
407
- } catch (e) {
408
- return Response.json({ error: "exchange_failed", detail: String(e) }, { status: 400 });
437
+ } catch {
438
+ return Response.json({ error: "exchange_failed" }, { status: 400 });
409
439
  }
410
440
  if (!tokens.idToken) {
411
441
  return Response.json({ error: "missing_id_token" }, { status: 400 });
412
442
  }
413
443
  try {
414
444
  await verifyIdToken(opts, tokens.idToken);
415
- } catch (e) {
416
- return Response.json({ error: "id_token_invalid", detail: String(e) }, { status: 401 });
445
+ } catch {
446
+ return Response.json({ error: "id_token_invalid" }, { status: 401 });
417
447
  }
418
448
  const headers = new Headers({ "Content-Type": "application/json" });
419
449
  headers.append("Set-Cookie", buildSessionCookie(opts, tokens.idToken, tokens.expiresIn));
420
450
  headers.append("Set-Cookie", buildAccessTokenCookie(opts, tokens.accessToken, tokens.expiresIn));
451
+ if (tokens.refreshToken) {
452
+ headers.append(
453
+ "Set-Cookie",
454
+ buildRefreshTokenCookie(opts, tokens.refreshToken, REFRESH_COOKIE_MAX_AGE_SECONDS)
455
+ );
456
+ }
421
457
  return new Response(JSON.stringify({ ok: true }), { status: 200, headers });
422
458
  }
423
459
  function readPkceCookie(cookieHeader) {
424
460
  if (!cookieHeader) return null;
425
- const match = cookieHeader.split(/;\s*/).find((p) => p.startsWith(`${chunkDKT62WQW_js.PKCE_COOKIE}=`));
461
+ const match = cookieHeader.split(/;\s*/).find((p) => p.startsWith(`${chunkKWYI2TTA_js.PKCE_COOKIE}=`));
426
462
  if (!match) return null;
427
- return chunkDKT62WQW_js.decodePkceState(match.slice(chunkDKT62WQW_js.PKCE_COOKIE.length + 1));
463
+ return chunkKWYI2TTA_js.decodePkceState(match.slice(chunkKWYI2TTA_js.PKCE_COOKIE.length + 1));
428
464
  }
429
465
  function buildPkceClearCookie() {
430
- return `${chunkDKT62WQW_js.PKCE_COOKIE}=; Path=${chunkDKT62WQW_js.PKCE_COOKIE_PATH}; Max-Age=0; Secure; SameSite=Lax`;
466
+ return `${chunkKWYI2TTA_js.PKCE_COOKIE}=; Path=${chunkKWYI2TTA_js.PKCE_COOKIE_PATH}; Max-Age=0; Secure; SameSite=Lax`;
431
467
  }
432
468
  function safeReturnPath(returnTo, fallback) {
433
469
  if (returnTo && returnTo.startsWith("/") && !returnTo.startsWith("//")) return returnTo;
@@ -467,6 +503,12 @@ async function handleRedirectCallback(req, opts) {
467
503
  const headers = new Headers({ [HEADER_LOCATION]: returnTo });
468
504
  headers.append("Set-Cookie", buildSessionCookie(opts, tokens.idToken, tokens.expiresIn));
469
505
  headers.append("Set-Cookie", buildAccessTokenCookie(opts, tokens.accessToken, tokens.expiresIn));
506
+ if (tokens.refreshToken) {
507
+ headers.append(
508
+ "Set-Cookie",
509
+ buildRefreshTokenCookie(opts, tokens.refreshToken, REFRESH_COOKIE_MAX_AGE_SECONDS)
510
+ );
511
+ }
470
512
  headers.append("Set-Cookie", buildPkceClearCookie());
471
513
  return new Response(null, { status: 302, headers });
472
514
  }
@@ -497,6 +539,99 @@ async function getSessionFromCookieHeader(cfg, cookieHeader) {
497
539
  return null;
498
540
  }
499
541
  }
542
+ async function refreshTokens(cfg, refreshToken) {
543
+ const doc = await discovery(cfg);
544
+ const body = new URLSearchParams({
545
+ grant_type: "refresh_token",
546
+ refresh_token: refreshToken,
547
+ client_id: cfg.clientId
548
+ });
549
+ const res = await fetch(doc.token_endpoint, {
550
+ method: "POST",
551
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
552
+ body: body.toString()
553
+ });
554
+ if (!res.ok) {
555
+ const text = await res.text().catch(() => "");
556
+ throw new Error(`token refresh failed (${res.status}): ${text}`);
557
+ }
558
+ const payload = await res.json();
559
+ return {
560
+ accessToken: payload.access_token,
561
+ idToken: payload.id_token,
562
+ refreshToken: payload.refresh_token,
563
+ expiresIn: payload.expires_in,
564
+ scope: payload.scope
565
+ };
566
+ }
567
+ function decodeBase64Url(segment) {
568
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
569
+ const pad = b64.length % 4 ? "=".repeat(4 - b64.length % 4) : "";
570
+ return atob(b64 + pad);
571
+ }
572
+ function accessTokenExpiry(token) {
573
+ if (!token) return null;
574
+ const parts = token.split(".");
575
+ if (parts.length < 2) return null;
576
+ try {
577
+ const payload = JSON.parse(decodeBase64Url(parts[1]));
578
+ return typeof payload.exp === "number" ? payload.exp : null;
579
+ } catch {
580
+ return null;
581
+ }
582
+ }
583
+ var refreshInflight = /* @__PURE__ */ new Map();
584
+ function dedupedRefresh(cfg, refreshToken) {
585
+ const existing = refreshInflight.get(refreshToken);
586
+ if (existing) return existing;
587
+ const p = refreshTokens(cfg, refreshToken).finally(() => refreshInflight.delete(refreshToken));
588
+ refreshInflight.set(refreshToken, p);
589
+ return p;
590
+ }
591
+ function clearedResult(cfg) {
592
+ return {
593
+ outcome: "cleared",
594
+ setCookies: buildSignoutCookies(cfg),
595
+ cookies: [
596
+ { name: cookieName(cfg), value: "" },
597
+ { name: accessTokenCookieName(cfg), value: "" },
598
+ { name: refreshTokenCookieName(cfg), value: "" }
599
+ ]
600
+ };
601
+ }
602
+ async function handleTokenRefresh(req, cfg) {
603
+ const cookieHeader = req.headers.get("cookie");
604
+ const refreshToken = getRefreshTokenFromCookieHeader(cfg, cookieHeader);
605
+ if (!refreshToken) return { outcome: "noop", setCookies: [], cookies: [] };
606
+ const access = getAccessTokenFromCookieHeader(cfg, cookieHeader);
607
+ const exp = accessTokenExpiry(access);
608
+ const now = Math.floor(Date.now() / 1e3);
609
+ if (access && exp !== null && exp - now > ACCESS_REFRESH_SKEW_SECONDS) {
610
+ return { outcome: "noop", setCookies: [], cookies: [] };
611
+ }
612
+ let tokens;
613
+ try {
614
+ tokens = await dedupedRefresh(cfg, refreshToken);
615
+ } catch {
616
+ return clearedResult(cfg);
617
+ }
618
+ if (!tokens.idToken) return clearedResult(cfg);
619
+ const setCookies = [
620
+ buildSessionCookie(cfg, tokens.idToken, tokens.expiresIn),
621
+ buildAccessTokenCookie(cfg, tokens.accessToken, tokens.expiresIn)
622
+ ];
623
+ const cookies = [
624
+ { name: cookieName(cfg), value: tokens.idToken },
625
+ { name: accessTokenCookieName(cfg), value: tokens.accessToken }
626
+ ];
627
+ if (tokens.refreshToken) {
628
+ setCookies.push(
629
+ buildRefreshTokenCookie(cfg, tokens.refreshToken, REFRESH_COOKIE_MAX_AGE_SECONDS)
630
+ );
631
+ cookies.push({ name: refreshTokenCookieName(cfg), value: tokens.refreshToken });
632
+ }
633
+ return { outcome: "refreshed", setCookies, cookies };
634
+ }
500
635
 
501
636
  Object.defineProperty(exports, "CimplifyError", {
502
637
  enumerable: true,
@@ -505,17 +640,21 @@ Object.defineProperty(exports, "CimplifyError", {
505
640
  exports.CACHE_LIFE_DEFAULT = CACHE_LIFE_DEFAULT;
506
641
  exports.CACHE_LIFE_PROBE = CACHE_LIFE_PROBE;
507
642
  exports.buildAccessTokenCookie = buildAccessTokenCookie;
643
+ exports.buildRefreshTokenCookie = buildRefreshTokenCookie;
508
644
  exports.buildSessionCookie = buildSessionCookie;
509
645
  exports.buildSignoutCookie = buildSignoutCookie;
510
646
  exports.buildSignoutCookies = buildSignoutCookies;
511
647
  exports.exchangeCode = exchangeCode;
512
648
  exports.getAccessTokenFromCookieHeader = getAccessTokenFromCookieHeader;
649
+ exports.getRefreshTokenFromCookieHeader = getRefreshTokenFromCookieHeader;
513
650
  exports.getServerClient = getServerClient;
514
651
  exports.getSessionFromCookieHeader = getSessionFromCookieHeader;
515
652
  exports.handleOidcCallback = handleOidcCallback;
516
653
  exports.handleRedirectCallback = handleRedirectCallback;
517
654
  exports.handleSessionRequest = handleSessionRequest;
655
+ exports.handleTokenRefresh = handleTokenRefresh;
518
656
  exports.refreshPage = refreshPage;
657
+ exports.refreshTokens = refreshTokens;
519
658
  exports.revalidateAddOn = revalidateAddOn;
520
659
  exports.revalidateAddOns = revalidateAddOns;
521
660
  exports.revalidateBrand = revalidateBrand;
package/dist/server.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { PKCE_COOKIE, decodePkceState, PKCE_COOKIE_PATH, DEFAULT_ISSUER } from './chunk-XSOYJLMZ.mjs';
2
- import { createCimplifyClient } from './chunk-SQ7U3BWY.mjs';
3
- import './chunk-I4IXPQIX.mjs';
1
+ import { PKCE_COOKIE, decodePkceState, PKCE_COOKIE_PATH, DEFAULT_ISSUER } from './chunk-7BKKBJQM.mjs';
2
+ import { createCimplifyClient } from './chunk-227XTAEB.mjs';
3
+ import './chunk-R6363CC7.mjs';
4
4
  import './chunk-TD3AY34U.mjs';
5
5
  export { CimplifyError } from './chunk-AMZXALF6.mjs';
6
6
  import './chunk-3G6RQLXK.mjs';
@@ -238,6 +238,9 @@ async function refreshPage() {
238
238
  }
239
239
  var SESSION_COOKIE = "cimplify_session";
240
240
  var ACCESS_TOKEN_COOKIE = "cimplify_access";
241
+ var REFRESH_TOKEN_COOKIE = "cimplify_refresh";
242
+ var REFRESH_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
243
+ var ACCESS_REFRESH_SKEW_SECONDS = 30;
241
244
  var HEADER_LOCATION = "Location";
242
245
  var PARAM_CODE = "code";
243
246
  var PARAM_STATE = "state";
@@ -254,6 +257,9 @@ function cookieName(cfg) {
254
257
  function accessTokenCookieName(cfg) {
255
258
  return cfg.accessTokenCookieName ?? ACCESS_TOKEN_COOKIE;
256
259
  }
260
+ function refreshTokenCookieName(cfg) {
261
+ return cfg.refreshTokenCookieName ?? REFRESH_TOKEN_COOKIE;
262
+ }
257
263
  async function discovery(cfg) {
258
264
  const base = issuerBase(cfg);
259
265
  let p = discoveryCache.get(base);
@@ -354,28 +360,38 @@ function buildAccessTokenCookie(cfg, accessToken, maxAgeSeconds) {
354
360
  if (cfg.cookieDomain) parts.push(`Domain=${cfg.cookieDomain}`);
355
361
  return parts.join("; ");
356
362
  }
357
- function buildSignoutCookies(cfg) {
358
- const idTokenParts = [
359
- `${cookieName(cfg)}=`,
363
+ function buildRefreshTokenCookie(cfg, refreshToken, maxAgeSeconds) {
364
+ const parts = [
365
+ `${refreshTokenCookieName(cfg)}=${refreshToken}`,
360
366
  "Path=/",
361
367
  "HttpOnly",
362
368
  "Secure",
363
369
  "SameSite=Lax",
364
- "Max-Age=0"
370
+ `Max-Age=${maxAgeSeconds}`
365
371
  ];
366
- const accessParts = [
367
- `${accessTokenCookieName(cfg)}=`,
368
- "Path=/",
369
- "HttpOnly",
370
- "Secure",
371
- "SameSite=Lax",
372
- "Max-Age=0"
372
+ if (cfg.cookieDomain) parts.push(`Domain=${cfg.cookieDomain}`);
373
+ return parts.join("; ");
374
+ }
375
+ function buildSignoutCookies(cfg) {
376
+ const clear = (name) => {
377
+ const parts = [`${name}=`, "Path=/", "HttpOnly", "Secure", "SameSite=Lax", "Max-Age=0"];
378
+ if (cfg.cookieDomain) parts.push(`Domain=${cfg.cookieDomain}`);
379
+ return parts.join("; ");
380
+ };
381
+ return [
382
+ clear(cookieName(cfg)),
383
+ clear(accessTokenCookieName(cfg)),
384
+ clear(refreshTokenCookieName(cfg))
373
385
  ];
374
- if (cfg.cookieDomain) {
375
- idTokenParts.push(`Domain=${cfg.cookieDomain}`);
376
- accessParts.push(`Domain=${cfg.cookieDomain}`);
377
- }
378
- return [idTokenParts.join("; "), accessParts.join("; ")];
386
+ }
387
+ function readCookie(name, cookieHeader) {
388
+ if (!cookieHeader) return null;
389
+ const match = cookieHeader.split(/;\s*/).find((p) => p.startsWith(`${name}=`));
390
+ if (!match) return null;
391
+ return match.slice(name.length + 1) || null;
392
+ }
393
+ function getRefreshTokenFromCookieHeader(cfg, cookieHeader) {
394
+ return readCookie(refreshTokenCookieName(cfg), cookieHeader);
379
395
  }
380
396
  function getAccessTokenFromCookieHeader(cfg, cookieHeader) {
381
397
  if (!cookieHeader) return null;
@@ -385,7 +401,21 @@ function getAccessTokenFromCookieHeader(cfg, cookieHeader) {
385
401
  const token = match.slice(name.length + 1);
386
402
  return token || null;
387
403
  }
404
+ function isSameOriginCallback(req, redirectUri) {
405
+ const site = req.headers.get("sec-fetch-site");
406
+ if (site) return site === "same-origin";
407
+ const origin = req.headers.get("origin");
408
+ if (!origin) return false;
409
+ try {
410
+ return origin === new URL(redirectUri).origin;
411
+ } catch {
412
+ return false;
413
+ }
414
+ }
388
415
  async function handleOidcCallback(req, opts) {
416
+ if (!isSameOriginCallback(req, opts.redirectUri)) {
417
+ return Response.json({ error: "forbidden_origin" }, { status: 403 });
418
+ }
389
419
  let body;
390
420
  try {
391
421
  body = await req.json();
@@ -402,20 +432,26 @@ async function handleOidcCallback(req, opts) {
402
432
  code: body.code,
403
433
  codeVerifier: body.codeVerifier
404
434
  });
405
- } catch (e) {
406
- return Response.json({ error: "exchange_failed", detail: String(e) }, { status: 400 });
435
+ } catch {
436
+ return Response.json({ error: "exchange_failed" }, { status: 400 });
407
437
  }
408
438
  if (!tokens.idToken) {
409
439
  return Response.json({ error: "missing_id_token" }, { status: 400 });
410
440
  }
411
441
  try {
412
442
  await verifyIdToken(opts, tokens.idToken);
413
- } catch (e) {
414
- return Response.json({ error: "id_token_invalid", detail: String(e) }, { status: 401 });
443
+ } catch {
444
+ return Response.json({ error: "id_token_invalid" }, { status: 401 });
415
445
  }
416
446
  const headers = new Headers({ "Content-Type": "application/json" });
417
447
  headers.append("Set-Cookie", buildSessionCookie(opts, tokens.idToken, tokens.expiresIn));
418
448
  headers.append("Set-Cookie", buildAccessTokenCookie(opts, tokens.accessToken, tokens.expiresIn));
449
+ if (tokens.refreshToken) {
450
+ headers.append(
451
+ "Set-Cookie",
452
+ buildRefreshTokenCookie(opts, tokens.refreshToken, REFRESH_COOKIE_MAX_AGE_SECONDS)
453
+ );
454
+ }
419
455
  return new Response(JSON.stringify({ ok: true }), { status: 200, headers });
420
456
  }
421
457
  function readPkceCookie(cookieHeader) {
@@ -465,6 +501,12 @@ async function handleRedirectCallback(req, opts) {
465
501
  const headers = new Headers({ [HEADER_LOCATION]: returnTo });
466
502
  headers.append("Set-Cookie", buildSessionCookie(opts, tokens.idToken, tokens.expiresIn));
467
503
  headers.append("Set-Cookie", buildAccessTokenCookie(opts, tokens.accessToken, tokens.expiresIn));
504
+ if (tokens.refreshToken) {
505
+ headers.append(
506
+ "Set-Cookie",
507
+ buildRefreshTokenCookie(opts, tokens.refreshToken, REFRESH_COOKIE_MAX_AGE_SECONDS)
508
+ );
509
+ }
468
510
  headers.append("Set-Cookie", buildPkceClearCookie());
469
511
  return new Response(null, { status: 302, headers });
470
512
  }
@@ -495,5 +537,98 @@ async function getSessionFromCookieHeader(cfg, cookieHeader) {
495
537
  return null;
496
538
  }
497
539
  }
540
+ async function refreshTokens(cfg, refreshToken) {
541
+ const doc = await discovery(cfg);
542
+ const body = new URLSearchParams({
543
+ grant_type: "refresh_token",
544
+ refresh_token: refreshToken,
545
+ client_id: cfg.clientId
546
+ });
547
+ const res = await fetch(doc.token_endpoint, {
548
+ method: "POST",
549
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
550
+ body: body.toString()
551
+ });
552
+ if (!res.ok) {
553
+ const text = await res.text().catch(() => "");
554
+ throw new Error(`token refresh failed (${res.status}): ${text}`);
555
+ }
556
+ const payload = await res.json();
557
+ return {
558
+ accessToken: payload.access_token,
559
+ idToken: payload.id_token,
560
+ refreshToken: payload.refresh_token,
561
+ expiresIn: payload.expires_in,
562
+ scope: payload.scope
563
+ };
564
+ }
565
+ function decodeBase64Url(segment) {
566
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
567
+ const pad = b64.length % 4 ? "=".repeat(4 - b64.length % 4) : "";
568
+ return atob(b64 + pad);
569
+ }
570
+ function accessTokenExpiry(token) {
571
+ if (!token) return null;
572
+ const parts = token.split(".");
573
+ if (parts.length < 2) return null;
574
+ try {
575
+ const payload = JSON.parse(decodeBase64Url(parts[1]));
576
+ return typeof payload.exp === "number" ? payload.exp : null;
577
+ } catch {
578
+ return null;
579
+ }
580
+ }
581
+ var refreshInflight = /* @__PURE__ */ new Map();
582
+ function dedupedRefresh(cfg, refreshToken) {
583
+ const existing = refreshInflight.get(refreshToken);
584
+ if (existing) return existing;
585
+ const p = refreshTokens(cfg, refreshToken).finally(() => refreshInflight.delete(refreshToken));
586
+ refreshInflight.set(refreshToken, p);
587
+ return p;
588
+ }
589
+ function clearedResult(cfg) {
590
+ return {
591
+ outcome: "cleared",
592
+ setCookies: buildSignoutCookies(cfg),
593
+ cookies: [
594
+ { name: cookieName(cfg), value: "" },
595
+ { name: accessTokenCookieName(cfg), value: "" },
596
+ { name: refreshTokenCookieName(cfg), value: "" }
597
+ ]
598
+ };
599
+ }
600
+ async function handleTokenRefresh(req, cfg) {
601
+ const cookieHeader = req.headers.get("cookie");
602
+ const refreshToken = getRefreshTokenFromCookieHeader(cfg, cookieHeader);
603
+ if (!refreshToken) return { outcome: "noop", setCookies: [], cookies: [] };
604
+ const access = getAccessTokenFromCookieHeader(cfg, cookieHeader);
605
+ const exp = accessTokenExpiry(access);
606
+ const now = Math.floor(Date.now() / 1e3);
607
+ if (access && exp !== null && exp - now > ACCESS_REFRESH_SKEW_SECONDS) {
608
+ return { outcome: "noop", setCookies: [], cookies: [] };
609
+ }
610
+ let tokens;
611
+ try {
612
+ tokens = await dedupedRefresh(cfg, refreshToken);
613
+ } catch {
614
+ return clearedResult(cfg);
615
+ }
616
+ if (!tokens.idToken) return clearedResult(cfg);
617
+ const setCookies = [
618
+ buildSessionCookie(cfg, tokens.idToken, tokens.expiresIn),
619
+ buildAccessTokenCookie(cfg, tokens.accessToken, tokens.expiresIn)
620
+ ];
621
+ const cookies = [
622
+ { name: cookieName(cfg), value: tokens.idToken },
623
+ { name: accessTokenCookieName(cfg), value: tokens.accessToken }
624
+ ];
625
+ if (tokens.refreshToken) {
626
+ setCookies.push(
627
+ buildRefreshTokenCookie(cfg, tokens.refreshToken, REFRESH_COOKIE_MAX_AGE_SECONDS)
628
+ );
629
+ cookies.push({ name: refreshTokenCookieName(cfg), value: tokens.refreshToken });
630
+ }
631
+ return { outcome: "refreshed", setCookies, cookies };
632
+ }
498
633
 
499
- export { CACHE_LIFE_DEFAULT, CACHE_LIFE_PROBE, buildAccessTokenCookie, buildSessionCookie, buildSignoutCookie, buildSignoutCookies, exchangeCode, getAccessTokenFromCookieHeader, getServerClient, getSessionFromCookieHeader, handleOidcCallback, handleRedirectCallback, handleSessionRequest, refreshPage, revalidateAddOn, revalidateAddOns, revalidateBrand, revalidateBusiness, revalidateByTag, revalidateCategories, revalidateCategory, revalidateCollection, revalidateCollections, revalidateLocation, revalidateLocations, revalidatePricing, revalidateProduct, revalidateProducts, revalidateStock, revalidateSubscription, revalidateSubscriptions, tags, updateAddOn, updateAddOns, updateBrand, updateBusiness, updateByTag, updateCategories, updateCategory, updateCollection, updateCollections, updateLocation, updateLocations, updatePricing, updateProduct, updateProducts, updateStock, updateSubscription, updateSubscriptions, verifyIdToken };
634
+ export { CACHE_LIFE_DEFAULT, CACHE_LIFE_PROBE, buildAccessTokenCookie, buildRefreshTokenCookie, buildSessionCookie, buildSignoutCookie, buildSignoutCookies, exchangeCode, getAccessTokenFromCookieHeader, getRefreshTokenFromCookieHeader, getServerClient, getSessionFromCookieHeader, handleOidcCallback, handleRedirectCallback, handleSessionRequest, handleTokenRefresh, refreshPage, refreshTokens, revalidateAddOn, revalidateAddOns, revalidateBrand, revalidateBusiness, revalidateByTag, revalidateCategories, revalidateCategory, revalidateCollection, revalidateCollections, revalidateLocation, revalidateLocations, revalidatePricing, revalidateProduct, revalidateProducts, revalidateStock, revalidateSubscription, revalidateSubscriptions, tags, updateAddOn, updateAddOns, updateBrand, updateBusiness, updateByTag, updateCategories, updateCategory, updateCollection, updateCollections, updateLocation, updateLocations, updatePricing, updateProduct, updateProducts, updateStock, updateSubscription, updateSubscriptions, verifyIdToken };