@broberg/sso 0.18.3 → 0.19.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/README.md CHANGED
@@ -240,6 +240,31 @@ try {
240
240
  - **Needs `SSO_CLIENT_SECRET`** — a public client cannot introspect. Since 0.18.0 (F084.160) that is `SsoConfigError`, thrown before any request (0.17.0 called it `SsoIntrospectionUnavailableError`, which sent callers down the 503-and-retry path for a fault that never goes away). Answer 500 and fix the setup; never retry. A wrong secret is reported as unavailable with "client id/secret was refused" — measured against id.broberg.ai.
241
241
  - **Cache** (optional, `cacheMs`): an active answer is reused up to that long and never past the token's `exp`; an inactive answer is never cached. The token itself never appears in an error message.
242
242
 
243
+ ## Login and signup in YOUR design — `client.embedded` (0.19.0, F084.165)
244
+
245
+ For an app BID has allowed to draw its own login (BID F084.166 v1.1 + F084.167 — owner's exception,
246
+ Jam first). Runs on YOUR SERVER as a confidential client (`clientSecret` required — BID gets
247
+ `Authorization: Basic client_id:client_secret`). Ends in the SAME `LoginResult` as `completeLogin`,
248
+ so your `signSession` step does not change.
249
+
250
+ ```ts
251
+ const s = await client.embedded.start({ email, endUserIp, intent: "signup", name, termsVersion }); // intent/name/termsVersion only for signup
252
+ if (s.ok) setCookie("bid_flow", await sealEmbeddedFlow(s.flow, cookieSecret), { httpOnly: true, maxAge: EMBEDDED_FLOW_MAX_AGE });
253
+ // later, with the code from the mail:
254
+ const f = await openEmbeddedFlow(getCookie("bid_flow"), cookieSecret); // { ok, flow } | { ok:false, reason: absent|expired|unreadable }
255
+ const step = f.ok ? await client.embedded.verify(f.flow, code) : null; // done → step.result is a LoginResult
256
+ // second_factor → client.embedded.sendSms(flow) / verifySecondFactor(flow, "totp"|"sms", code)
257
+ const terms = await client.embedded.terms("da"); // show terms.text + links VERBATIM at the signup button
258
+ ```
259
+
260
+ - The PKCE verifier and nonce stay server-side in the sealed flow cookie (10 min, same signing as the login
261
+ transaction); the id_token's `nonce` must match the flow's, or `verify` throws.
262
+ - Errors are data: `invalid_code {attemptsLeft}`, `expired`, `locked {retryAfter}`, `too_many_requests {retryAfter}`,
263
+ `not_embedded` (403/404 — fall back to the normal redirect login), `signup_not_enabled`, `terms_outdated {version}`
264
+ (re-fetch `terms()`), `invalid_request`, `unavailable`.
265
+ - A 401 from BID is YOUR configuration (wrong client id/secret) and THROWS `SsoError` — it is never shown to the
266
+ user as a wrong code.
267
+
243
268
  ## What it will never do
244
269
 
245
270
  No passwords. No passkey registration. No social-provider keys. No email
@@ -73,6 +73,127 @@ function loadSsoConfig(env = process.env) {
73
73
  clientSecret: env.SSO_CLIENT_SECRET?.trim() || void 0
74
74
  };
75
75
  }
76
+
77
+ // src/session.ts
78
+ var enc = new TextEncoder();
79
+ var b64url = (bytes) => {
80
+ let s = "";
81
+ for (const b of bytes) s += String.fromCharCode(b);
82
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
83
+ };
84
+ var fromB64url = (s) => {
85
+ const pad = s.replace(/-/g, "+").replace(/_/g, "/");
86
+ const bin = atob(pad + "=".repeat((4 - pad.length % 4) % 4));
87
+ return Uint8Array.from(bin, (c) => c.charCodeAt(0));
88
+ };
89
+ async function hmacKey(secret) {
90
+ return crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [
91
+ "sign",
92
+ "verify"
93
+ ]);
94
+ }
95
+ async function signValue(value, secret, options = {}) {
96
+ const payload = b64url(enc.encode(value));
97
+ const body = options.maxAgeSeconds === void 0 ? payload : `t${Math.floor((options.now ?? Date.now)() / 1e3)}~${payload}`;
98
+ const sig = new Uint8Array(await crypto.subtle.sign("HMAC", await hmacKey(secret), enc.encode(body)));
99
+ return `${body}.${b64url(sig)}`;
100
+ }
101
+ var STAMPED = /^t(\d+)~(.*)$/s;
102
+ async function verifyValue(token, secret, options = {}) {
103
+ if (!token) return null;
104
+ const dot = token.lastIndexOf(".");
105
+ if (dot <= 0) return null;
106
+ const body = token.slice(0, dot);
107
+ try {
108
+ const ok = await crypto.subtle.verify(
109
+ "HMAC",
110
+ await hmacKey(secret),
111
+ fromB64url(token.slice(dot + 1)),
112
+ enc.encode(body)
113
+ );
114
+ if (!ok) return null;
115
+ const stamped = STAMPED.exec(body);
116
+ if (options.maxAgeSeconds !== void 0) {
117
+ if (!stamped) return null;
118
+ const issuedAt = Number(stamped[1]);
119
+ if ((options.now ?? Date.now)() / 1e3 - issuedAt > options.maxAgeSeconds) return null;
120
+ }
121
+ return new TextDecoder().decode(fromB64url(stamped?.[2] ?? body));
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+ async function signSession(payload, secret) {
127
+ return signValue(JSON.stringify(payload), secret);
128
+ }
129
+ async function verifySession(token, secret, now = Date.now) {
130
+ const body = await verifyValue(token, secret);
131
+ if (body === null) return null;
132
+ try {
133
+ const payload = JSON.parse(body);
134
+ if (typeof payload.sub !== "string" || payload.sub === "") return null;
135
+ if (typeof payload.exp !== "number" || payload.exp * 1e3 <= now()) return null;
136
+ return payload;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+ function cookieHeader(name, value, opts) {
142
+ const parts = [
143
+ `${name}=${value}`,
144
+ `Path=${opts.path ?? "/"}`,
145
+ `Max-Age=${opts.maxAge}`,
146
+ "HttpOnly",
147
+ // Lax, NOT Strict, and this is load-bearing: the callback from BrobergID
148
+ // is a top-level GET navigation from another site. Strict withholds the
149
+ // cookie on exactly that navigation, so the login transaction cookie would
150
+ // be missing when it is needed and every sign-in would fail with "state
151
+ // does not match" — a message that sends you looking in the wrong place.
152
+ `SameSite=${opts.sameSite ?? "Lax"}`
153
+ ];
154
+ if (opts.secure) parts.push("Secure");
155
+ return parts.join("; ");
156
+ }
157
+ function readCookie(header, name) {
158
+ if (!header) return void 0;
159
+ for (const part of header.split(";")) {
160
+ const eq = part.indexOf("=");
161
+ if (eq === -1) continue;
162
+ if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim();
163
+ }
164
+ return void 0;
165
+ }
166
+
167
+ // src/embedded.ts
168
+ var EMBEDDED_FLOW_MAX_AGE = 600;
169
+ function sealEmbeddedFlow(flow, secret, options = {}) {
170
+ return signValue(JSON.stringify(flow), secret, { maxAgeSeconds: EMBEDDED_FLOW_MAX_AGE, ...options });
171
+ }
172
+ async function openEmbeddedFlow(raw, secret, options = {}) {
173
+ if (!raw) return { ok: false, reason: "absent" };
174
+ if (await verifyValue(raw, secret) === null) return { ok: false, reason: "unreadable" };
175
+ const fresh = await verifyValue(raw, secret, { maxAgeSeconds: EMBEDDED_FLOW_MAX_AGE, ...options });
176
+ if (fresh === null) return { ok: false, reason: "expired" };
177
+ try {
178
+ const f = JSON.parse(fresh);
179
+ if (typeof f.flowId !== "string" || typeof f.codeVerifier !== "string" || typeof f.nonce !== "string" || typeof f.email !== "string") return { ok: false, reason: "unreadable" };
180
+ return { ok: true, flow: f };
181
+ } catch {
182
+ return { ok: false, reason: "unreadable" };
183
+ }
184
+ }
185
+ function embeddedError(status, body) {
186
+ const code = typeof body.error === "string" ? body.error : "";
187
+ const num = (v) => typeof v === "number" ? v : void 0;
188
+ if (status === 400 && code === "invalid_code") return { kind: "invalid_code", ...num(body.attempts_left) !== void 0 ? { attemptsLeft: num(body.attempts_left) } : {} };
189
+ if (status === 400 && code === "expired") return { kind: "expired" };
190
+ if (status === 400 && code === "terms_outdated") return { kind: "terms_outdated", ...typeof body.version === "string" ? { version: body.version } : {} };
191
+ if (status === 400) return { kind: "invalid_request" };
192
+ if (status === 403 && code === "signup_not_enabled") return { kind: "signup_not_enabled" };
193
+ if (status === 403 || status === 404 || status === 405) return { kind: "not_embedded" };
194
+ if (status === 429) return { kind: code === "locked" ? "locked" : "too_many_requests", ...num(body.retry_after) !== void 0 ? { retryAfter: num(body.retry_after) } : {} };
195
+ return { kind: "unavailable", message: `BID answered ${status}${code ? ` (${code})` : ""}` };
196
+ }
76
197
  var JwksError = class extends Error {
77
198
  constructor(message) {
78
199
  super(message);
@@ -226,15 +347,15 @@ var AVATAR_TYPES = ["image/png", "image/jpeg", "image/webp"];
226
347
  function mediaType(contentType) {
227
348
  return (contentType ?? "").split(";")[0].trim().toLowerCase();
228
349
  }
229
- var b64url = (bytes) => {
350
+ var b64url2 = (bytes) => {
230
351
  const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
231
352
  let s = "";
232
353
  for (const b of view) s += String.fromCharCode(b);
233
354
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
234
355
  };
235
- var randomToken = (bytes = 32) => b64url(crypto.getRandomValues(new Uint8Array(bytes)));
356
+ var randomToken = (bytes = 32) => b64url2(crypto.getRandomValues(new Uint8Array(bytes)));
236
357
  async function challengeFor(verifier) {
237
- return b64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
358
+ return b64url2(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
238
359
  }
239
360
  var ADDRESS_OWNERSHIP = /* @__PURE__ */ new Set(["verified", "unverified", "not_on_account"]);
240
361
  var INVITATION_OUTCOMES = /* @__PURE__ */ new Set([
@@ -554,9 +675,133 @@ function createSsoClient(config, options = {}) {
554
675
  if (options2.cacheMs && options2.cacheMs > 0) introspectCache.set(key, { result, until: Math.min(now() + options2.cacheMs, exp * 1e3) });
555
676
  return result;
556
677
  }
678
+ async function exchangeCode(code, codeVerifier, nonce) {
679
+ const { token_endpoint } = await discovery();
680
+ const res = await fetchImpl(token_endpoint, {
681
+ method: "POST",
682
+ headers: { "content-type": "application/x-www-form-urlencoded" },
683
+ body: new URLSearchParams({
684
+ grant_type: "authorization_code",
685
+ code,
686
+ redirect_uri: config.redirectUri,
687
+ client_id: config.clientId,
688
+ // ALWAYS sent, secret or not. See SsoConfig.clientSecret: the two
689
+ // prove different things, so a confidential client keeps PKCE.
690
+ code_verifier: codeVerifier,
691
+ ...config.clientSecret ? { client_secret: config.clientSecret } : {}
692
+ })
693
+ });
694
+ const raw = await res.text();
695
+ let body;
696
+ try {
697
+ body = raw.trim() === "" ? {} : JSON.parse(raw);
698
+ } catch {
699
+ throw new SsoError(
700
+ `token exchange failed (${res.status}): the response body is not JSON${res.headers.get("content-type") ? ` (content-type: ${res.headers.get("content-type")})` : ""} \u2014 ${excerptForLog(raw, config.clientSecret)}`
701
+ );
702
+ }
703
+ if (!res.ok || !body.id_token) {
704
+ throw new SsoError(
705
+ `token exchange failed (${res.status}): ${body.error ?? "no id_token in response"}` + (body.error_description ? ` \u2014 ${body.error_description}` : "") + invalidClientHint(body.error, config.clientSecret !== void 0)
706
+ );
707
+ }
708
+ const claims = await verifyIdToken(body.id_token, { nonce });
709
+ const merged = body.access_token ? await withUserInfo(claims, body.access_token) : { claims, unverifiedClaims: [] };
710
+ return {
711
+ claims: merged.claims,
712
+ unverifiedClaims: merged.unverifiedClaims,
713
+ idToken: body.id_token,
714
+ ...body.access_token ? { accessToken: body.access_token } : {},
715
+ ...body.refresh_token ? { refreshToken: body.refresh_token } : {},
716
+ ...typeof body.expires_in === "number" ? { expiresIn: body.expires_in } : {}
717
+ };
718
+ }
719
+ async function embeddedCall(method, path, body) {
720
+ if (!config.clientSecret) throw new SsoError("the embedded login runs from the server as a confidential client \u2014 set clientSecret (SSO_CLIENT_SECRET)");
721
+ const url = new URL(`/embedded${path}`, config.issuer).toString();
722
+ let res;
723
+ try {
724
+ res = await fetchImpl(url, {
725
+ method,
726
+ headers: {
727
+ authorization: `Basic ${btoa(`${config.clientId}:${config.clientSecret}`)}`,
728
+ accept: "application/json",
729
+ ...body ? { "content-type": "application/json" } : {}
730
+ },
731
+ ...body ? { body: JSON.stringify({ client_id: config.clientId, ...body }) } : {}
732
+ });
733
+ } catch (e) {
734
+ return { ok: false, status: 0, body: { error: "network", message: e instanceof Error ? e.message : String(e) } };
735
+ }
736
+ const raw = await res.text();
737
+ let json = {};
738
+ try {
739
+ json = raw.trim() ? JSON.parse(raw) : {};
740
+ } catch {
741
+ }
742
+ if (res.status === 401) {
743
+ throw new SsoError(`BID refused this app's client credentials on /embedded${path} (401 ${typeof json.error === "string" ? json.error : "invalid_client"}) \u2014 check SSO_CLIENT_ID / SSO_CLIENT_SECRET`);
744
+ }
745
+ return res.ok ? { ok: true, body: json } : { ok: false, status: res.status, body: json };
746
+ }
747
+ const toError = (r) => r.status === 0 ? { kind: "unavailable", message: `BID did not answer: ${String(r.body.message ?? "")}` } : embeddedError(r.status, r.body);
748
+ async function embeddedNext(r, flow) {
749
+ if (!r.ok) return { kind: "error", error: toError(r) };
750
+ if (r.body.next === "done" && typeof r.body.code === "string") {
751
+ return { kind: "done", result: await exchangeCode(r.body.code, flow.codeVerifier, flow.nonce) };
752
+ }
753
+ if (r.body.next === "second_factor") {
754
+ const methods = (Array.isArray(r.body.methods) ? r.body.methods : []).filter((m) => m === "totp" || m === "sms");
755
+ return { kind: "second_factor", methods };
756
+ }
757
+ return { kind: "error", error: { kind: "unavailable", message: "BID's answer names no next step" } };
758
+ }
759
+ const embedded = {
760
+ async start(input) {
761
+ const codeVerifier = randomToken();
762
+ const nonce = randomToken(16);
763
+ const r = await embeddedCall("POST", "/start", {
764
+ email: input.email,
765
+ code_challenge: await challengeFor(codeVerifier),
766
+ code_challenge_method: "S256",
767
+ nonce,
768
+ ...input.intent === "signup" ? { intent: "signup", name: input.name, terms_version: input.termsVersion } : {},
769
+ ...input.endUserIp ? { end_user_ip: input.endUserIp } : {}
770
+ });
771
+ if (!r.ok) return { ok: false, error: toError(r) };
772
+ if (typeof r.body.flow_id !== "string") return { ok: false, error: { kind: "unavailable", message: "BID's answer has no flow_id" } };
773
+ return {
774
+ ok: true,
775
+ flow: { flowId: r.body.flow_id, codeVerifier, nonce, email: input.email },
776
+ expiresIn: typeof r.body.expires_in === "number" ? r.body.expires_in : 600
777
+ };
778
+ },
779
+ async verify(flow, code) {
780
+ return embeddedNext(await embeddedCall("POST", "/verify", { flow_id: flow.flowId, code }), flow);
781
+ },
782
+ async sendSms(flow) {
783
+ const r = await embeddedCall("POST", "/second-factor/sms", { flow_id: flow.flowId });
784
+ return r.ok ? { ok: true } : { ok: false, error: toError(r) };
785
+ },
786
+ async verifySecondFactor(flow, method, code) {
787
+ return embeddedNext(await embeddedCall("POST", "/second-factor", { flow_id: flow.flowId, method, code }), flow);
788
+ },
789
+ async terms(lang) {
790
+ const r = await embeddedCall("GET", `/terms?lang=${lang}`);
791
+ const b = r.body;
792
+ if (!r.ok || typeof b.version !== "string" || typeof b.text !== "string") {
793
+ throw new SsoError(`BID did not answer the signup terms (${r.ok ? "no version/text" : r.status})`);
794
+ }
795
+ const links = (Array.isArray(b.links) ? b.links : []).filter(
796
+ (l) => typeof l === "object" && l !== null && typeof l.label === "string" && typeof l.url === "string"
797
+ );
798
+ return { version: b.version, text: b.text, links: links.map((l) => ({ label: l.label, url: l.url })) };
799
+ }
800
+ };
557
801
  return {
558
802
  discovery,
559
803
  introspectToken,
804
+ embedded,
560
805
  get jwks() {
561
806
  if (!jwksCache) throw new SsoError("the key cache is created on first verification");
562
807
  return jwksCache;
@@ -595,45 +840,7 @@ function createSsoClient(config, options = {}) {
595
840
  }
596
841
  const code = params.get("code");
597
842
  if (!code) throw new SsoError("callback carried neither an error nor a code");
598
- const { token_endpoint } = await discovery();
599
- const res = await fetchImpl(token_endpoint, {
600
- method: "POST",
601
- headers: { "content-type": "application/x-www-form-urlencoded" },
602
- body: new URLSearchParams({
603
- grant_type: "authorization_code",
604
- code,
605
- redirect_uri: config.redirectUri,
606
- client_id: config.clientId,
607
- // ALWAYS sent, secret or not. See SsoConfig.clientSecret: the two
608
- // prove different things, so a confidential client keeps PKCE.
609
- code_verifier: codeVerifier,
610
- ...config.clientSecret ? { client_secret: config.clientSecret } : {}
611
- })
612
- });
613
- const raw = await res.text();
614
- let body;
615
- try {
616
- body = raw.trim() === "" ? {} : JSON.parse(raw);
617
- } catch {
618
- throw new SsoError(
619
- `token exchange failed (${res.status}): the response body is not JSON${res.headers.get("content-type") ? ` (content-type: ${res.headers.get("content-type")})` : ""} \u2014 ${excerptForLog(raw, config.clientSecret)}`
620
- );
621
- }
622
- if (!res.ok || !body.id_token) {
623
- throw new SsoError(
624
- `token exchange failed (${res.status}): ${body.error ?? "no id_token in response"}` + (body.error_description ? ` \u2014 ${body.error_description}` : "") + invalidClientHint(body.error, config.clientSecret !== void 0)
625
- );
626
- }
627
- const claims = await verifyIdToken(body.id_token, { nonce });
628
- const merged = body.access_token ? await withUserInfo(claims, body.access_token) : { claims, unverifiedClaims: [] };
629
- return {
630
- claims: merged.claims,
631
- unverifiedClaims: merged.unverifiedClaims,
632
- idToken: body.id_token,
633
- ...body.access_token ? { accessToken: body.access_token } : {},
634
- ...body.refresh_token ? { refreshToken: body.refresh_token } : {},
635
- ...typeof body.expires_in === "number" ? { expiresIn: body.expires_in } : {}
636
- };
843
+ return exchangeCode(code, codeVerifier, nonce);
637
844
  },
638
845
  async refreshTokens(refreshToken) {
639
846
  const { token_endpoint } = await discovery();
@@ -750,96 +957,6 @@ function createSsoClient(config, options = {}) {
750
957
  };
751
958
  }
752
959
 
753
- // src/session.ts
754
- var enc = new TextEncoder();
755
- var b64url2 = (bytes) => {
756
- let s = "";
757
- for (const b of bytes) s += String.fromCharCode(b);
758
- return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
759
- };
760
- var fromB64url = (s) => {
761
- const pad = s.replace(/-/g, "+").replace(/_/g, "/");
762
- const bin = atob(pad + "=".repeat((4 - pad.length % 4) % 4));
763
- return Uint8Array.from(bin, (c) => c.charCodeAt(0));
764
- };
765
- async function hmacKey(secret) {
766
- return crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [
767
- "sign",
768
- "verify"
769
- ]);
770
- }
771
- async function signValue(value, secret, options = {}) {
772
- const payload = b64url2(enc.encode(value));
773
- const body = options.maxAgeSeconds === void 0 ? payload : `t${Math.floor((options.now ?? Date.now)() / 1e3)}~${payload}`;
774
- const sig = new Uint8Array(await crypto.subtle.sign("HMAC", await hmacKey(secret), enc.encode(body)));
775
- return `${body}.${b64url2(sig)}`;
776
- }
777
- var STAMPED = /^t(\d+)~(.*)$/s;
778
- async function verifyValue(token, secret, options = {}) {
779
- if (!token) return null;
780
- const dot = token.lastIndexOf(".");
781
- if (dot <= 0) return null;
782
- const body = token.slice(0, dot);
783
- try {
784
- const ok = await crypto.subtle.verify(
785
- "HMAC",
786
- await hmacKey(secret),
787
- fromB64url(token.slice(dot + 1)),
788
- enc.encode(body)
789
- );
790
- if (!ok) return null;
791
- const stamped = STAMPED.exec(body);
792
- if (options.maxAgeSeconds !== void 0) {
793
- if (!stamped) return null;
794
- const issuedAt = Number(stamped[1]);
795
- if ((options.now ?? Date.now)() / 1e3 - issuedAt > options.maxAgeSeconds) return null;
796
- }
797
- return new TextDecoder().decode(fromB64url(stamped?.[2] ?? body));
798
- } catch {
799
- return null;
800
- }
801
- }
802
- async function signSession(payload, secret) {
803
- return signValue(JSON.stringify(payload), secret);
804
- }
805
- async function verifySession(token, secret, now = Date.now) {
806
- const body = await verifyValue(token, secret);
807
- if (body === null) return null;
808
- try {
809
- const payload = JSON.parse(body);
810
- if (typeof payload.sub !== "string" || payload.sub === "") return null;
811
- if (typeof payload.exp !== "number" || payload.exp * 1e3 <= now()) return null;
812
- return payload;
813
- } catch {
814
- return null;
815
- }
816
- }
817
- function cookieHeader(name, value, opts) {
818
- const parts = [
819
- `${name}=${value}`,
820
- `Path=${opts.path ?? "/"}`,
821
- `Max-Age=${opts.maxAge}`,
822
- "HttpOnly",
823
- // Lax, NOT Strict, and this is load-bearing: the callback from BrobergID
824
- // is a top-level GET navigation from another site. Strict withholds the
825
- // cookie on exactly that navigation, so the login transaction cookie would
826
- // be missing when it is needed and every sign-in would fail with "state
827
- // does not match" — a message that sends you looking in the wrong place.
828
- `SameSite=${opts.sameSite ?? "Lax"}`
829
- ];
830
- if (opts.secure) parts.push("Secure");
831
- return parts.join("; ");
832
- }
833
- function readCookie(header, name) {
834
- if (!header) return void 0;
835
- for (const part of header.split(";")) {
836
- const eq = part.indexOf("=");
837
- if (eq === -1) continue;
838
- if (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim();
839
- }
840
- return void 0;
841
- }
842
-
843
960
  // src/tokens.ts
844
961
  var warned = false;
845
962
  function memoryTokenStore(options = {}) {
@@ -890,6 +1007,7 @@ function newSid() {
890
1007
  exports.ALLOWED_ALGS = ALLOWED_ALGS;
891
1008
  exports.AVATAR_TYPES = AVATAR_TYPES;
892
1009
  exports.DEFAULT_SESSION_MAX_AGE = DEFAULT_SESSION_MAX_AGE;
1010
+ exports.EMBEDDED_FLOW_MAX_AGE = EMBEDDED_FLOW_MAX_AGE;
893
1011
  exports.JwksError = JwksError;
894
1012
  exports.JwksUnavailableError = JwksUnavailableError;
895
1013
  exports.JwksUnknownKeyError = JwksUnknownKeyError;
@@ -911,10 +1029,12 @@ exports.loadSsoConfig = loadSsoConfig;
911
1029
  exports.mediaType = mediaType;
912
1030
  exports.memoryTokenStore = memoryTokenStore;
913
1031
  exports.newSid = newSid;
1032
+ exports.openEmbeddedFlow = openEmbeddedFlow;
914
1033
  exports.readCookie = readCookie;
1034
+ exports.sealEmbeddedFlow = sealEmbeddedFlow;
915
1035
  exports.signSession = signSession;
916
1036
  exports.signValue = signValue;
917
1037
  exports.verifySession = verifySession;
918
1038
  exports.verifyValue = verifyValue;
919
- //# sourceMappingURL=chunk-CHRCHUZ3.cjs.map
920
- //# sourceMappingURL=chunk-CHRCHUZ3.cjs.map
1039
+ //# sourceMappingURL=chunk-MZE4TPFU.cjs.map
1040
+ //# sourceMappingURL=chunk-MZE4TPFU.cjs.map