@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.
- package/dist/advanced.d.mts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +20 -20
- package/dist/advanced.mjs +1 -1
- package/dist/{chunk-SQ7U3BWY.mjs → chunk-227XTAEB.mjs} +52 -25
- package/dist/{chunk-2AU263DK.mjs → chunk-3Z3FQH7X.mjs} +1 -1
- package/dist/{chunk-XSOYJLMZ.mjs → chunk-7BKKBJQM.mjs} +10 -3
- package/dist/{chunk-JGBDWEPJ.js → chunk-AVQL3TSJ.js} +3 -0
- package/dist/{chunk-6DMHVQ42.js → chunk-GDAGM26J.js} +4 -4
- package/dist/{chunk-6W3AH4QW.js → chunk-H6DBPHMZ.js} +2 -2
- package/dist/{chunk-MW7ICTVK.js → chunk-J3MGY6AU.js} +86 -59
- package/dist/{chunk-DKT62WQW.js → chunk-KWYI2TTA.js} +10 -3
- package/dist/{chunk-F4WS3OIF.mjs → chunk-MWI5GAUF.mjs} +1 -1
- package/dist/{chunk-I4IXPQIX.mjs → chunk-R6363CC7.mjs} +3 -0
- package/dist/{client-Dfg_hmkP.d.ts → client-Bo5rujc2.d.ts} +8 -2
- package/dist/{client-Bhvlelij.d.mts → client-CUrA1uQ0.d.mts} +1 -1
- package/dist/{client-DlGJqSDd.d.ts → client-DasPHOJh.d.ts} +1 -1
- package/dist/{client-DKg-5OWu.d.mts → client-Dka2cGhu.d.mts} +8 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +78 -78
- package/dist/index.mjs +5 -5
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +64 -64
- package/dist/react.mjs +5 -5
- package/dist/server.d.mts +18 -3
- package/dist/server.d.ts +18 -3
- package/dist/server.js +167 -28
- package/dist/server.mjs +159 -24
- package/dist/styles.css +1 -1
- package/dist/testing/suite.d.mts +2 -2
- package/dist/testing/suite.d.ts +2 -2
- package/dist/testing/suite.js +22 -22
- package/dist/testing/suite.mjs +3 -3
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +78 -78
- package/dist/testing.mjs +4 -4
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
require('./chunk-
|
|
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 =
|
|
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 ??
|
|
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
|
|
360
|
-
const
|
|
361
|
-
`${
|
|
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
|
-
|
|
372
|
+
`Max-Age=${maxAgeSeconds}`
|
|
367
373
|
];
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
"SameSite=Lax",
|
|
374
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
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
|
|
408
|
-
return Response.json({ error: "exchange_failed"
|
|
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
|
|
416
|
-
return Response.json({ error: "id_token_invalid"
|
|
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(`${
|
|
461
|
+
const match = cookieHeader.split(/;\s*/).find((p) => p.startsWith(`${chunkKWYI2TTA_js.PKCE_COOKIE}=`));
|
|
426
462
|
if (!match) return null;
|
|
427
|
-
return
|
|
463
|
+
return chunkKWYI2TTA_js.decodePkceState(match.slice(chunkKWYI2TTA_js.PKCE_COOKIE.length + 1));
|
|
428
464
|
}
|
|
429
465
|
function buildPkceClearCookie() {
|
|
430
|
-
return `${
|
|
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-
|
|
2
|
-
import { createCimplifyClient } from './chunk-
|
|
3
|
-
import './chunk-
|
|
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
|
|
358
|
-
const
|
|
359
|
-
`${
|
|
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
|
-
|
|
370
|
+
`Max-Age=${maxAgeSeconds}`
|
|
365
371
|
];
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"SameSite=Lax",
|
|
372
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
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
|
|
406
|
-
return Response.json({ error: "exchange_failed"
|
|
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
|
|
414
|
-
return Response.json({ error: "id_token_invalid"
|
|
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 };
|