@iqauth/sdk 2.2.0 → 2.5.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 (96) hide show
  1. package/README.md +134 -0
  2. package/dist/browser-session.d.mts +3 -3
  3. package/dist/browser-session.d.ts +3 -3
  4. package/dist/browser-session.js +89 -68
  5. package/dist/browser-session.mjs +2 -1
  6. package/dist/browser.d.mts +64 -29
  7. package/dist/browser.d.ts +64 -29
  8. package/dist/browser.js +794 -39
  9. package/dist/browser.mjs +44 -4
  10. package/dist/bundle-LUKDQYVQ.mjs +374 -0
  11. package/dist/chunk-3JULWS6F.mjs +106 -0
  12. package/dist/chunk-5T7GHBX6.mjs +1165 -0
  13. package/dist/{chunk-M4J6BPK7.mjs → chunk-6TDJJER7.mjs} +12 -3
  14. package/dist/{chunk-QZB745C2.mjs → chunk-76W5TLQQ.mjs} +264 -211
  15. package/dist/{chunk-D72UL5HL.mjs → chunk-BVV54LPI.mjs} +36 -4
  16. package/dist/chunk-LIZYFXH7.mjs +90 -0
  17. package/dist/chunk-MKKZULZR.mjs +241 -0
  18. package/dist/chunk-SL3KRS4W.mjs +54 -0
  19. package/dist/chunk-TKZTCPEK.mjs +232 -0
  20. package/dist/chunk-UKZLOHZG.mjs +83 -0
  21. package/dist/chunk-UNYDG2L4.mjs +209 -0
  22. package/dist/{chunk-MDUHPQMM.mjs → chunk-W3F4JYGP.mjs} +8 -180
  23. package/dist/{chunk-QEJB7WEQ.mjs → chunk-WQWBJSSS.mjs} +1 -1
  24. package/dist/cli/index.js +144 -36
  25. package/dist/cli/index.mjs +1 -1
  26. package/dist/{client-DXbHb2ul.d.ts → client-BNQe3AgF.d.ts} +3 -67
  27. package/dist/{client-Dv4v92Mj.d.mts → client-kYlJFgPv.d.mts} +3 -67
  28. package/dist/doctor-YYNHNMLD.mjs +198 -0
  29. package/dist/{express-BZmF1llh.d.mts → express-B6_1vBYZ.d.mts} +23 -2
  30. package/dist/{express-B4o3P8vK.d.ts → express-CHpfa7D_.d.ts} +23 -2
  31. package/dist/express.d.mts +77 -6
  32. package/dist/express.d.ts +77 -6
  33. package/dist/express.js +336 -74
  34. package/dist/express.mjs +209 -8
  35. package/dist/fastify.js +103 -72
  36. package/dist/fastify.mjs +6 -4
  37. package/dist/hono.js +102 -72
  38. package/dist/hono.mjs +5 -4
  39. package/dist/index.d.mts +8 -4
  40. package/dist/index.d.ts +8 -4
  41. package/dist/index.js +590 -73
  42. package/dist/index.mjs +30 -8
  43. package/dist/locales.d.mts +53 -0
  44. package/dist/locales.d.ts +53 -0
  45. package/dist/locales.js +1202 -0
  46. package/dist/locales.mjs +29 -0
  47. package/dist/mobile.d.mts +3 -3
  48. package/dist/mobile.d.ts +3 -3
  49. package/dist/mobile.js +89 -68
  50. package/dist/mobile.mjs +2 -1
  51. package/dist/next.d.mts +10 -1
  52. package/dist/next.d.ts +10 -1
  53. package/dist/next.js +101 -1618
  54. package/dist/next.mjs +9 -9
  55. package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
  56. package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
  57. package/dist/react.d.mts +1349 -10
  58. package/dist/react.d.ts +1349 -10
  59. package/dist/react.js +2998 -569
  60. package/dist/react.mjs +1518 -95
  61. package/dist/reverify-4UEJXUS6.mjs +16 -0
  62. package/dist/server/handlers.d.mts +12 -1
  63. package/dist/server/handlers.d.ts +12 -1
  64. package/dist/server/handlers.js +12 -3
  65. package/dist/server/handlers.mjs +2 -2
  66. package/dist/server.d.mts +5 -4
  67. package/dist/server.d.ts +5 -4
  68. package/dist/server.js +188 -73
  69. package/dist/server.mjs +13 -8
  70. package/dist/service.d.mts +3 -3
  71. package/dist/service.d.ts +3 -3
  72. package/dist/service.js +89 -68
  73. package/dist/service.mjs +2 -1
  74. package/dist/signIn-CCY4JE5G.mjs +15 -0
  75. package/dist/{signIn-D_kP3v-c.d.mts → signIn-CiIBTJIh.d.mts} +232 -4
  76. package/dist/{signIn-BVDTIA_t.d.ts → signIn-OCr88Zf8.d.ts} +232 -4
  77. package/dist/test.d.mts +86 -0
  78. package/dist/test.d.ts +86 -0
  79. package/dist/test.js +289 -0
  80. package/dist/test.mjs +9 -0
  81. package/dist/tokens-DCyzzn8L.d.mts +63 -0
  82. package/dist/tokens-aHiGFr_E.d.ts +63 -0
  83. package/dist/types-6bNdxesb.d.mts +196 -0
  84. package/dist/types-6bNdxesb.d.ts +196 -0
  85. package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.mts} +6 -0
  86. package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.ts} +6 -0
  87. package/dist/webhooks.d.mts +61 -0
  88. package/dist/webhooks.d.ts +61 -0
  89. package/dist/webhooks.js +119 -0
  90. package/dist/webhooks.mjs +11 -0
  91. package/dist/ws.d.mts +73 -0
  92. package/dist/ws.d.ts +73 -0
  93. package/dist/ws.js +397 -0
  94. package/dist/ws.mjs +12 -0
  95. package/package.json +24 -3
  96. package/dist/doctor-XCI77BQS.mjs +0 -90
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  assertPublishableKey
3
- } from "./chunk-QEJB7WEQ.mjs";
3
+ } from "./chunk-WQWBJSSS.mjs";
4
4
 
5
5
  // src/server/handlers.ts
6
6
  var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
@@ -28,8 +28,8 @@ function resolve(config) {
28
28
  publishableKey: config.publishableKey,
29
29
  secretKey: config.secretKey,
30
30
  issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
31
- accessCookieName: config.accessCookieName ?? "iqauth_at",
32
- refreshCookieName: config.refreshCookieName ?? "iqauth_rt",
31
+ accessCookieName: config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at",
32
+ refreshCookieName: config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt",
33
33
  cookieDomain: config.cookieDomain,
34
34
  sameSite: config.sameSite ?? "lax",
35
35
  secure: config.secure ?? true,
@@ -193,6 +193,15 @@ async function handleSignout(config, input) {
193
193
  } catch {
194
194
  }
195
195
  }
196
+ if (input.endSsoSession !== false && input.ssoCookieHeader) {
197
+ try {
198
+ await cfg.fetchImpl(`${cfg.issuer}/oidc/sso-logout`, {
199
+ method: "POST",
200
+ headers: { Cookie: input.ssoCookieHeader }
201
+ });
202
+ } catch {
203
+ }
204
+ }
196
205
  return {
197
206
  status: 200,
198
207
  body: { success: true, data: { signedOut: true } },
@@ -1,73 +1,33 @@
1
+ import {
2
+ REFRESH_COOKIE,
3
+ clearCookie,
4
+ getCookie,
5
+ setCookie
6
+ } from "./chunk-TKZTCPEK.mjs";
1
7
  import {
2
8
  assertPublishableKey
3
- } from "./chunk-QEJB7WEQ.mjs";
9
+ } from "./chunk-WQWBJSSS.mjs";
4
10
  import {
5
11
  IQAuthError
6
12
  } from "./chunk-6I6RM4MN.mjs";
7
13
 
8
- // src/browser/storage.ts
9
- var REFRESH_COOKIE = "iqauth_rt";
10
- var PKCE_STORAGE_PREFIX = "iqauth.pkce.";
11
- function isBrowser() {
12
- return typeof window !== "undefined" && typeof document !== "undefined";
13
- }
14
- function setCookie(name, value, opts = {}) {
15
- if (!isBrowser()) return;
16
- const parts = [`${name}=${encodeURIComponent(value)}`];
17
- parts.push(`Path=${opts.path ?? "/"}`);
18
- if (opts.maxAgeSeconds !== void 0) parts.push(`Max-Age=${opts.maxAgeSeconds}`);
19
- if (opts.domain) parts.push(`Domain=${opts.domain}`);
20
- if (opts.secure ?? location.protocol === "https:") parts.push("Secure");
21
- parts.push(`SameSite=${opts.sameSite ?? "lax"}`);
22
- document.cookie = parts.join("; ");
23
- }
24
- function getCookie(name) {
25
- if (!isBrowser()) return null;
26
- const target = `${name}=`;
27
- const segments = document.cookie ? document.cookie.split(";") : [];
28
- for (const seg of segments) {
29
- const trimmed = seg.trim();
30
- if (trimmed.startsWith(target)) {
31
- try {
32
- return decodeURIComponent(trimmed.slice(target.length));
33
- } catch {
34
- return null;
35
- }
36
- }
37
- }
38
- return null;
39
- }
40
- function clearCookie(name, opts = {}) {
41
- setCookie(name, "", { ...opts, maxAgeSeconds: 0 });
42
- }
43
- function savePkce(record) {
44
- if (!isBrowser()) return;
45
- try {
46
- sessionStorage.setItem(PKCE_STORAGE_PREFIX + record.state, JSON.stringify(record));
47
- } catch {
48
- }
49
- }
50
- function loadPkce(state) {
51
- if (!isBrowser()) return null;
14
+ // src/browser/sessionManager.ts
15
+ var DEFAULT_REFRESH_PATH = "/api/v1/auth/refresh";
16
+ var DEFAULT_USERINFO_PATH = "/api/v1/auth/me";
17
+ async function readAuthErrorCode(res) {
52
18
  try {
53
- const raw = sessionStorage.getItem(PKCE_STORAGE_PREFIX + state);
54
- if (!raw) return null;
55
- return JSON.parse(raw);
56
- } catch {
19
+ const cloned = res.clone();
20
+ const data = await cloned.json().catch(() => null);
21
+ if (!data || typeof data !== "object") return null;
22
+ const err = data.error;
23
+ if (err && typeof err.code === "string") {
24
+ return { code: err.code, message: typeof err.message === "string" ? err.message : void 0 };
25
+ }
57
26
  return null;
58
- }
59
- }
60
- function clearPkce(state) {
61
- if (!isBrowser()) return;
62
- try {
63
- sessionStorage.removeItem(PKCE_STORAGE_PREFIX + state);
64
27
  } catch {
28
+ return null;
65
29
  }
66
30
  }
67
-
68
- // src/browser/sessionManager.ts
69
- var DEFAULT_REFRESH_PATH = "/api/v1/auth/refresh";
70
- var DEFAULT_USERINFO_PATH = "/api/v1/auth/me";
71
31
  function decodeClaims(token) {
72
32
  try {
73
33
  const parts = token.split(".");
@@ -101,11 +61,11 @@ var EMPTY = {
101
61
  error: null,
102
62
  version: 0
103
63
  };
104
- function defaultCookieStore() {
64
+ function defaultCookieStore(name = REFRESH_COOKIE) {
105
65
  return {
106
- read: () => getCookie(REFRESH_COOKIE),
107
- write: (token) => setCookie(REFRESH_COOKIE, token, { maxAgeSeconds: 60 * 60 * 24 * 30 }),
108
- clear: () => clearCookie(REFRESH_COOKIE)
66
+ read: () => getCookie(name),
67
+ write: (token) => setCookie(name, token, { maxAgeSeconds: 60 * 60 * 24 * 30 }),
68
+ clear: () => clearCookie(name)
109
69
  };
110
70
  }
111
71
  var NO_OP_STORE = {
@@ -134,7 +94,8 @@ var SessionManager = class {
134
94
  this.useCookies = options.useCookies ?? true;
135
95
  this.serverManagedSession = options.serverManagedSession ?? false;
136
96
  this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
137
- this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
97
+ this.refreshCookieName = options.cookieNames?.refresh ?? REFRESH_COOKIE;
98
+ this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore(this.refreshCookieName) : NO_OP_STORE);
138
99
  this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
139
100
  this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
140
101
  throw new Error("global fetch is not available; pass fetchImpl");
@@ -162,6 +123,10 @@ var SessionManager = class {
162
123
  get issuerUrl() {
163
124
  return this.issuer;
164
125
  }
126
+ /** Cookie name the SDK uses for the refresh token (overridable via `cookieNames.refresh`). */
127
+ get refreshCookie() {
128
+ return this.refreshCookieName;
129
+ }
165
130
  getSnapshot() {
166
131
  return this.snapshot;
167
132
  }
@@ -268,7 +233,7 @@ var SessionManager = class {
268
233
  return false;
269
234
  }
270
235
  if (data.refreshToken) {
271
- await Promise.resolve(this.tokenStore.write(data.refreshToken));
236
+ await Promise.resolve(this.tokenStore.write(data.refreshToken, { claims: decodeClaims(data.accessToken) }));
272
237
  }
273
238
  this.applyAccessToken(data.accessToken);
274
239
  this.broadcast("session:refresh");
@@ -312,7 +277,7 @@ var SessionManager = class {
312
277
  const claims = decodeClaims(accessToken);
313
278
  const user = claimsToSessionUser(claims);
314
279
  if (refreshToken) {
315
- void Promise.resolve(this.tokenStore.write(refreshToken));
280
+ void Promise.resolve(this.tokenStore.write(refreshToken, { claims }));
316
281
  }
317
282
  this.update({
318
283
  status: user ? "authenticated" : "unauthenticated",
@@ -346,33 +311,39 @@ var SessionManager = class {
346
311
  */
347
312
  async fetch(input, init = {}) {
348
313
  const exec = async (token2) => {
349
- const headers = new Headers(init.headers || {});
350
- if (token2) headers.set("Authorization", `Bearer ${token2}`);
314
+ const headers2 = new Headers(init.headers || {});
315
+ if (token2) headers2.set("Authorization", `Bearer ${token2}`);
351
316
  return this.fetchImpl(input, {
352
317
  ...init,
353
- headers,
318
+ headers: headers2,
354
319
  credentials: init.credentials ?? "include"
355
320
  });
356
321
  };
357
322
  let token = await this.getToken();
358
323
  let res = await exec(token);
359
324
  if (res.status !== 401) return res;
325
+ const initialErr = await readAuthErrorCode(res);
326
+ if (initialErr && (initialErr.code === "SESSION_EXPIRED_INACTIVITY" || initialErr.code === "SESSION_EXPIRED_MAX_DURATION" || initialErr.code === "SESSION_INVALID")) {
327
+ this.signOutLocal("unauthenticated");
328
+ throw new IQAuthError(initialErr.code, initialErr.message || "Session expired", 401);
329
+ }
360
330
  const refreshed = await this.refresh();
361
331
  if (!refreshed) {
362
332
  this.signOutLocal("unauthenticated");
363
333
  throw new IQAuthError(
364
- "TOKEN_EXPIRED",
365
- "Session refresh failed; user must sign in again",
334
+ initialErr?.code || "TOKEN_EXPIRED",
335
+ initialErr?.message || "Session refresh failed; user must sign in again",
366
336
  401
367
337
  );
368
338
  }
369
339
  token = this.snapshot.accessToken;
370
340
  res = await exec(token);
371
341
  if (res.status === 401) {
342
+ const secondErr = await readAuthErrorCode(res);
372
343
  this.signOutLocal("unauthenticated");
373
344
  throw new IQAuthError(
374
- "TOKEN_EXPIRED",
375
- "Authenticated request failed twice with 401; aborting",
345
+ secondErr?.code || "TOKEN_EXPIRED",
346
+ secondErr?.message || "Authenticated request failed twice with 401; aborting",
376
347
  401
377
348
  );
378
349
  }
@@ -400,6 +371,14 @@ var SessionManager = class {
400
371
  });
401
372
  this.broadcast("session:signout");
402
373
  }
374
+ /**
375
+ * Replace the refresh-token store at runtime. Used by the F22
376
+ * `<MultisessionAppSupport>` wrapper to swap in a `MultiAccountTokenStore`
377
+ * after the manager has already been constructed by `<IQAuthProvider>`.
378
+ */
379
+ setTokenStore(store) {
380
+ this.tokenStore = store;
381
+ }
403
382
  destroy() {
404
383
  if (this.proactiveTimer) clearTimeout(this.proactiveTimer);
405
384
  this.proactiveTimer = null;
@@ -492,163 +471,237 @@ var SessionManager = class {
492
471
  }
493
472
  };
494
473
 
495
- // src/browser/pkce.ts
496
- function getCrypto() {
497
- if (typeof globalThis !== "undefined" && globalThis.crypto) {
498
- return globalThis.crypto;
474
+ // src/browser/passwordless.ts
475
+ function url(base, path) {
476
+ return `${base.replace(/\/+$/, "")}${path}`;
477
+ }
478
+ function headers(o) {
479
+ const h = { "Content-Type": "application/json" };
480
+ if (o.cookieSession !== false) h["x-iqauth-session"] = "cookie";
481
+ return h;
482
+ }
483
+ async function postJson(u, body, h) {
484
+ const r = await fetch(u, {
485
+ method: "POST",
486
+ credentials: "include",
487
+ headers: h,
488
+ body: JSON.stringify(body)
489
+ });
490
+ const p = await r.json().catch(() => ({}));
491
+ if (!r.ok) {
492
+ const msg = p?.error?.message || `Request failed (${r.status})`;
493
+ throw new Error(msg);
499
494
  }
500
- throw new Error("WebCrypto is not available in this environment");
495
+ return p.data;
501
496
  }
502
- function base64UrlEncode(bytes) {
503
- let bin = "";
504
- for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
505
- const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bin, "binary").toString("base64");
506
- return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
497
+ async function requestMagicLink(opts, input) {
498
+ await postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/magic-link/request"), input, headers(opts));
499
+ return { ok: true };
507
500
  }
508
- function randomUrlSafe(byteLength = 32) {
509
- const bytes = new Uint8Array(byteLength);
510
- getCrypto().getRandomValues(bytes);
511
- return base64UrlEncode(bytes);
501
+ async function verifyMagicLink(opts, token) {
502
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/magic-link/verify"), { token }, headers(opts));
512
503
  }
513
- async function s256Challenge(verifier) {
514
- const data = new TextEncoder().encode(verifier);
515
- const digest = await getCrypto().subtle.digest("SHA-256", data);
516
- return base64UrlEncode(new Uint8Array(digest));
504
+ async function beginPasskeyAuthentication(opts, input = {}) {
505
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/authentication/options"), input, headers(opts));
517
506
  }
518
- async function createPkcePair() {
519
- const codeVerifier = randomUrlSafe(32);
520
- const codeChallenge = await s256Challenge(codeVerifier);
521
- const state = randomUrlSafe(16);
522
- const nonce = randomUrlSafe(16);
523
- return { codeVerifier, codeChallenge, state, nonce };
507
+ async function finishPasskeyAuthentication(opts, response) {
508
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/authentication/verify"), { response }, headers(opts));
524
509
  }
525
-
526
- // src/browser/signIn.ts
527
- var DEFAULT_SIGN_IN_PATH = "/sign-in";
528
- var DEFAULT_LOGOUT_PATH = "/api/v1/auth/logout";
529
- var DEFAULT_TOKEN_PATH = "/oidc/token";
530
- var DEFAULT_CALLBACK_PATH = "/auth/callback";
531
- function defaultRedirectUri() {
532
- if (typeof window === "undefined") {
533
- throw new Error("redirectToSignIn requires a browser environment (window)");
534
- }
535
- return `${window.location.origin}${DEFAULT_CALLBACK_PATH}`;
510
+ async function beginPasskeyRegistration(opts) {
511
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/registration/options"), {}, headers(opts));
536
512
  }
537
- function defaultReturnTo() {
538
- if (typeof window === "undefined") return "/";
539
- return window.location.href;
513
+ async function finishPasskeyRegistration(opts, response, name) {
514
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/registration/verify"), { response, name }, headers(opts));
540
515
  }
541
- async function buildSignInUrl(manager, opts = {}) {
542
- const pkce = await createPkcePair();
543
- const redirectUri = opts.redirectUri ?? defaultRedirectUri();
544
- const returnTo = opts.returnTo ?? defaultReturnTo();
545
- savePkce({
546
- codeVerifier: pkce.codeVerifier,
547
- state: pkce.state,
548
- nonce: pkce.nonce,
549
- redirectUri,
550
- appKey: manager.publishableKey.raw,
551
- returnTo,
552
- createdAt: Date.now()
553
- });
554
- const url = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.issuerUrl);
555
- url.searchParams.set("response_type", "code");
556
- url.searchParams.set("app", manager.appKey);
557
- url.searchParams.set("publishable_key", manager.publishableKey.raw);
558
- url.searchParams.set("redirect_uri", redirectUri);
559
- url.searchParams.set("state", pkce.state);
560
- url.searchParams.set("nonce", pkce.nonce);
561
- url.searchParams.set("code_challenge", pkce.codeChallenge);
562
- url.searchParams.set("code_challenge_method", "S256");
563
- url.searchParams.set("scope", opts.scope ?? "openid profile email");
564
- url.searchParams.set("return_to", returnTo);
565
- return url.toString();
516
+ async function signInWithPasskey(opts, input = {}) {
517
+ const mod = await import(
518
+ /* @vite-ignore */
519
+ "./bundle-LUKDQYVQ.mjs"
520
+ );
521
+ const options = await beginPasskeyAuthentication(opts, input);
522
+ const response = await mod.startAuthentication({ optionsJSON: options });
523
+ return finishPasskeyAuthentication(opts, response);
524
+ }
525
+ async function enrollPasskey(opts, name) {
526
+ const mod = await import(
527
+ /* @vite-ignore */
528
+ "./bundle-LUKDQYVQ.mjs"
529
+ );
530
+ const options = await beginPasskeyRegistration(opts);
531
+ const response = await mod.startRegistration({ optionsJSON: options });
532
+ return finishPasskeyRegistration(opts, response, name);
533
+ }
534
+ async function listLinkedIdentities(opts) {
535
+ const r = await fetch(url(opts.iqAuthBaseUrl, "/api/v1/auth/identities"), { credentials: "include" });
536
+ const p = await r.json().catch(() => ({}));
537
+ if (!r.ok) throw new Error(p?.error?.message || `Request failed (${r.status})`);
538
+ return p?.data?.identities ?? [];
566
539
  }
567
- async function redirectToSignIn(manager, opts = {}) {
568
- const url = await buildSignInUrl(manager, opts);
569
- if (typeof window === "undefined") {
570
- throw new Error("redirectToSignIn requires a browser environment");
540
+ async function linkProvider(opts, input) {
541
+ await postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/identities/link"), input, headers(opts));
542
+ return { ok: true };
543
+ }
544
+ async function unlinkProvider(opts, input) {
545
+ await postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/identities/unlink"), input, headers(opts));
546
+ return { ok: true };
547
+ }
548
+
549
+ // src/browser/accountRegistry.ts
550
+ var STORAGE_PREFIX = "iqauth.accounts.";
551
+ var COOKIE_PREFIX = "iqauth_rt_";
552
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
553
+ function isBrowser() {
554
+ return typeof window !== "undefined" && typeof localStorage !== "undefined";
555
+ }
556
+ function storageKey(appId) {
557
+ return STORAGE_PREFIX + appId;
558
+ }
559
+ function readState(appId) {
560
+ if (!isBrowser()) return { accounts: [], activeAccountId: null };
561
+ try {
562
+ const raw = localStorage.getItem(storageKey(appId));
563
+ if (!raw) return { accounts: [], activeAccountId: null };
564
+ const parsed = JSON.parse(raw);
565
+ return {
566
+ accounts: Array.isArray(parsed.accounts) ? parsed.accounts : [],
567
+ activeAccountId: parsed.activeAccountId ?? null
568
+ };
569
+ } catch {
570
+ return { accounts: [], activeAccountId: null };
571
571
  }
572
- window.location.assign(url);
573
572
  }
574
- async function signIn(manager, opts = {}) {
575
- return redirectToSignIn(manager, opts);
573
+ function writeState(appId, state) {
574
+ if (!isBrowser()) return;
575
+ try {
576
+ localStorage.setItem(storageKey(appId), JSON.stringify(state));
577
+ } catch {
578
+ }
576
579
  }
577
- async function handleAuthCallback(manager, options = {}) {
578
- const url = new URL(options.url ?? (typeof window !== "undefined" ? window.location.href : ""));
579
- const code = url.searchParams.get("code");
580
- const state = url.searchParams.get("state");
581
- const errorParam = url.searchParams.get("error");
582
- if (errorParam) {
583
- return { ok: false, returnTo: "/", error: errorParam };
584
- }
585
- if (!code || !state) {
586
- return { ok: false, returnTo: "/", error: "missing_code_or_state" };
587
- }
588
- const record = loadPkce(state);
589
- if (!record) {
590
- return { ok: false, returnTo: "/", error: "unknown_state" };
591
- }
592
- clearPkce(state);
593
- const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
594
- if (!fetchImpl) {
595
- return { ok: false, returnTo: record.returnTo, error: "no_fetch" };
596
- }
597
- const tokenUrl = `${manager.issuerUrl}${options.tokenPath ?? DEFAULT_TOKEN_PATH}`;
598
- const res = await fetchImpl(tokenUrl, {
599
- method: "POST",
600
- credentials: "include",
601
- headers: { "Content-Type": "application/json" },
602
- body: JSON.stringify({
603
- grant_type: "authorization_code",
604
- code,
605
- redirect_uri: record.redirectUri,
606
- client_id: manager.appKey,
607
- code_verifier: record.codeVerifier
608
- })
609
- });
610
- const body = await res.json().catch(() => ({}));
611
- if (!res.ok) {
612
- const desc = body.error_description ?? body.error ?? "token_exchange_failed";
613
- return { ok: false, returnTo: record.returnTo, error: desc };
580
+ var AccountRegistry = class {
581
+ constructor(appId) {
582
+ this.appId = appId;
583
+ this.listeners = /* @__PURE__ */ new Set();
584
+ this.storageHandler = null;
585
+ if (isBrowser()) {
586
+ this.storageHandler = (ev) => {
587
+ if (ev.key === storageKey(this.appId)) {
588
+ for (const l of this.listeners) l();
589
+ }
590
+ };
591
+ window.addEventListener("storage", this.storageHandler);
592
+ }
614
593
  }
615
- const tokens = body;
616
- if (!tokens.access_token) {
617
- return { ok: false, returnTo: record.returnTo, error: "missing_access_token" };
594
+ destroy() {
595
+ if (this.storageHandler && isBrowser()) {
596
+ window.removeEventListener("storage", this.storageHandler);
597
+ }
598
+ this.listeners.clear();
618
599
  }
619
- if (tokens.refresh_token) {
620
- setCookie(REFRESH_COOKIE, tokens.refresh_token, { maxAgeSeconds: 60 * 60 * 24 * 30 });
600
+ list() {
601
+ return readState(this.appId).accounts.slice();
602
+ }
603
+ active() {
604
+ return readState(this.appId).activeAccountId;
605
+ }
606
+ get(accountId) {
607
+ return readState(this.appId).accounts.find((a) => a.accountId === accountId) ?? null;
608
+ }
609
+ upsert(rec) {
610
+ const state = readState(this.appId);
611
+ const idx = state.accounts.findIndex((a) => a.accountId === rec.accountId);
612
+ if (idx >= 0) state.accounts[idx] = { ...state.accounts[idx], ...rec };
613
+ else state.accounts.push(rec);
614
+ writeState(this.appId, state);
615
+ this.notify();
616
+ }
617
+ setActive(accountId) {
618
+ const state = readState(this.appId);
619
+ state.activeAccountId = accountId;
620
+ writeState(this.appId, state);
621
+ this.notify();
622
+ }
623
+ remove(accountId) {
624
+ const state = readState(this.appId);
625
+ state.accounts = state.accounts.filter((a) => a.accountId !== accountId);
626
+ if (state.activeAccountId === accountId) state.activeAccountId = state.accounts[0]?.accountId ?? null;
627
+ writeState(this.appId, state);
628
+ clearCookie(COOKIE_PREFIX + accountId);
629
+ this.notify();
621
630
  }
622
- manager.applyAccessToken(tokens.access_token, tokens.refresh_token);
623
- return { ok: true, returnTo: record.returnTo };
624
- }
625
- async function signOut(manager, opts = {}) {
626
- if (!opts.localOnly) {
627
- try {
628
- const url = `${manager.issuerUrl}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
629
- await manager.fetch(url, { method: "POST" }).catch(() => void 0);
630
- } catch {
631
+ subscribe(listener) {
632
+ this.listeners.add(listener);
633
+ return () => this.listeners.delete(listener);
634
+ }
635
+ /**
636
+ * Read the refresh token for a specific account from its per-account
637
+ * cookie. Used by `MultiAccountTokenStore.read()`.
638
+ */
639
+ readRefreshToken(accountId) {
640
+ return getCookie(COOKIE_PREFIX + accountId);
641
+ }
642
+ /**
643
+ * Persist a refresh token to the per-account cookie. Caller is the
644
+ * SessionManager via `MultiAccountTokenStore.write()`.
645
+ */
646
+ writeRefreshToken(accountId, token, opts = {}) {
647
+ setCookie(COOKIE_PREFIX + accountId, token, { maxAgeSeconds: COOKIE_MAX_AGE, ...opts });
648
+ }
649
+ clearRefreshToken(accountId) {
650
+ clearCookie(COOKIE_PREFIX + accountId);
651
+ }
652
+ notify() {
653
+ for (const l of this.listeners) l();
654
+ }
655
+ };
656
+ var MultiAccountTokenStore = class {
657
+ constructor(registry, fallback) {
658
+ this.registry = registry;
659
+ this.fallback = fallback;
660
+ }
661
+ read() {
662
+ const id = this.registry.active();
663
+ if (id) return this.registry.readRefreshToken(id);
664
+ return this.fallback.read();
665
+ }
666
+ write(token, ctx) {
667
+ let id = this.registry.active();
668
+ if (!id) {
669
+ const sub = ctx?.claims?.sub;
670
+ if (sub) {
671
+ id = sub;
672
+ this.registry.setActive(sub);
673
+ }
631
674
  }
675
+ if (id) {
676
+ this.registry.writeRefreshToken(id, token);
677
+ return;
678
+ }
679
+ return this.fallback.write(token);
632
680
  }
633
- clearCookie(REFRESH_COOKIE);
634
- manager.signOutLocal();
635
- if (opts.returnTo && typeof window !== "undefined") {
636
- window.location.assign(opts.returnTo);
681
+ clear() {
682
+ const id = this.registry.active();
683
+ if (id) {
684
+ this.registry.clearRefreshToken(id);
685
+ return;
686
+ }
687
+ return this.fallback.clear();
637
688
  }
638
- }
689
+ };
639
690
 
640
691
  export {
641
- REFRESH_COOKIE,
642
- setCookie,
643
- getCookie,
644
- clearCookie,
692
+ defaultCookieStore,
645
693
  SessionManager,
646
- randomUrlSafe,
647
- s256Challenge,
648
- createPkcePair,
649
- buildSignInUrl,
650
- redirectToSignIn,
651
- signIn,
652
- handleAuthCallback,
653
- signOut
694
+ requestMagicLink,
695
+ verifyMagicLink,
696
+ beginPasskeyAuthentication,
697
+ finishPasskeyAuthentication,
698
+ beginPasskeyRegistration,
699
+ finishPasskeyRegistration,
700
+ signInWithPasskey,
701
+ enrollPasskey,
702
+ listLinkedIdentities,
703
+ linkProvider,
704
+ unlinkProvider,
705
+ AccountRegistry,
706
+ MultiAccountTokenStore
654
707
  };