@iqauth/sdk 2.3.0 → 2.6.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 (87) hide show
  1. package/README.md +110 -0
  2. package/dist/browser-session.d.mts +3 -2
  3. package/dist/browser-session.d.ts +3 -2
  4. package/dist/browser.d.mts +64 -29
  5. package/dist/browser.d.ts +64 -29
  6. package/dist/browser.js +782 -38
  7. package/dist/browser.mjs +43 -3
  8. package/dist/bundle-LUKDQYVQ.mjs +374 -0
  9. package/dist/chunk-3JULWS6F.mjs +106 -0
  10. package/dist/chunk-5T7GHBX6.mjs +1165 -0
  11. package/dist/{chunk-KGEPDXHU.mjs → chunk-6TDJJER7.mjs} +2 -2
  12. package/dist/{chunk-RACIPVLD.mjs → chunk-76W5TLQQ.mjs} +262 -220
  13. package/dist/{chunk-EKTNEZIH.mjs → chunk-BVV54LPI.mjs} +37 -5
  14. package/dist/chunk-LIZYFXH7.mjs +90 -0
  15. package/dist/chunk-MKKZULZR.mjs +241 -0
  16. package/dist/chunk-SL3KRS4W.mjs +54 -0
  17. package/dist/chunk-TKZTCPEK.mjs +232 -0
  18. package/dist/chunk-UKZLOHZG.mjs +83 -0
  19. package/dist/cli/index.js +144 -36
  20. package/dist/cli/index.mjs +1 -1
  21. package/dist/{client-DTX4hNdS.d.ts → client-BNQe3AgF.d.ts} +3 -62
  22. package/dist/{client-vdh2a9fJ.d.mts → client-kYlJFgPv.d.mts} +3 -62
  23. package/dist/doctor-YYNHNMLD.mjs +198 -0
  24. package/dist/{express-A0-dWEMy.d.mts → express-B6_1vBYZ.d.mts} +23 -2
  25. package/dist/{express-Bo_pJKHN.d.ts → express-CHpfa7D_.d.ts} +23 -2
  26. package/dist/express.d.mts +5 -4
  27. package/dist/express.d.ts +5 -4
  28. package/dist/express.js +36 -4
  29. package/dist/express.mjs +8 -8
  30. package/dist/fastify.js +2 -2
  31. package/dist/fastify.mjs +4 -4
  32. package/dist/hono.js +2 -2
  33. package/dist/hono.mjs +4 -4
  34. package/dist/index.d.mts +8 -3
  35. package/dist/index.d.ts +8 -3
  36. package/dist/index.js +500 -4
  37. package/dist/index.mjs +29 -9
  38. package/dist/locales.d.mts +53 -0
  39. package/dist/locales.d.ts +53 -0
  40. package/dist/locales.js +1202 -0
  41. package/dist/locales.mjs +29 -0
  42. package/dist/mobile.d.mts +3 -2
  43. package/dist/mobile.d.ts +3 -2
  44. package/dist/next.d.mts +1 -1
  45. package/dist/next.d.ts +1 -1
  46. package/dist/next.js +2 -2
  47. package/dist/next.mjs +1 -1
  48. package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
  49. package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
  50. package/dist/react.d.mts +1349 -10
  51. package/dist/react.d.ts +1349 -10
  52. package/dist/react.js +3006 -568
  53. package/dist/react.mjs +1540 -97
  54. package/dist/reverify-4UEJXUS6.mjs +16 -0
  55. package/dist/server/handlers.d.mts +10 -1
  56. package/dist/server/handlers.d.ts +10 -1
  57. package/dist/server/handlers.js +2 -2
  58. package/dist/server/handlers.mjs +1 -1
  59. package/dist/server.d.mts +5 -3
  60. package/dist/server.d.ts +5 -3
  61. package/dist/server.js +89 -4
  62. package/dist/server.mjs +12 -8
  63. package/dist/service.d.mts +3 -2
  64. package/dist/service.d.ts +3 -2
  65. package/dist/signIn-CCY4JE5G.mjs +15 -0
  66. package/dist/{signIn-Cd0P4y9d.d.mts → signIn-CiIBTJIh.d.mts} +224 -4
  67. package/dist/{signIn-DKakyzeu.d.ts → signIn-OCr88Zf8.d.ts} +224 -4
  68. package/dist/test.d.mts +86 -0
  69. package/dist/test.d.ts +86 -0
  70. package/dist/test.js +289 -0
  71. package/dist/test.mjs +9 -0
  72. package/dist/tokens-DCyzzn8L.d.mts +63 -0
  73. package/dist/tokens-aHiGFr_E.d.ts +63 -0
  74. package/dist/types-6bNdxesb.d.mts +196 -0
  75. package/dist/types-6bNdxesb.d.ts +196 -0
  76. package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.mts} +6 -0
  77. package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.ts} +6 -0
  78. package/dist/webhooks.d.mts +61 -0
  79. package/dist/webhooks.d.ts +61 -0
  80. package/dist/webhooks.js +119 -0
  81. package/dist/webhooks.mjs +11 -0
  82. package/dist/ws.d.mts +73 -0
  83. package/dist/ws.d.ts +73 -0
  84. package/dist/ws.js +397 -0
  85. package/dist/ws.mjs +12 -0
  86. package/package.json +22 -2
  87. package/dist/doctor-A5E7LSFW.mjs +0 -90
@@ -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,
@@ -1,3 +1,9 @@
1
+ import {
2
+ REFRESH_COOKIE,
3
+ clearCookie,
4
+ getCookie,
5
+ setCookie
6
+ } from "./chunk-TKZTCPEK.mjs";
1
7
  import {
2
8
  assertPublishableKey
3
9
  } from "./chunk-WQWBJSSS.mjs";
@@ -5,69 +11,23 @@ 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,174 +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_SSO_LOGOUT_PATH = "/oidc/sso-logout";
530
- var DEFAULT_TOKEN_PATH = "/oidc/token";
531
- var DEFAULT_CALLBACK_PATH = "/auth/callback";
532
- function defaultRedirectUri() {
533
- if (typeof window === "undefined") {
534
- throw new Error("redirectToSignIn requires a browser environment (window)");
535
- }
536
- 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));
537
512
  }
538
- function defaultReturnTo() {
539
- if (typeof window === "undefined") return "/";
540
- 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));
541
515
  }
542
- async function buildSignInUrl(manager, opts = {}) {
543
- const pkce = await createPkcePair();
544
- const redirectUri = opts.redirectUri ?? defaultRedirectUri();
545
- const returnTo = opts.returnTo ?? defaultReturnTo();
546
- savePkce({
547
- codeVerifier: pkce.codeVerifier,
548
- state: pkce.state,
549
- nonce: pkce.nonce,
550
- redirectUri,
551
- appKey: manager.publishableKey.raw,
552
- returnTo,
553
- createdAt: Date.now()
554
- });
555
- const url = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.issuerUrl);
556
- url.searchParams.set("response_type", "code");
557
- url.searchParams.set("app", manager.appKey);
558
- url.searchParams.set("publishable_key", manager.publishableKey.raw);
559
- url.searchParams.set("redirect_uri", redirectUri);
560
- url.searchParams.set("state", pkce.state);
561
- url.searchParams.set("nonce", pkce.nonce);
562
- url.searchParams.set("code_challenge", pkce.codeChallenge);
563
- url.searchParams.set("code_challenge_method", "S256");
564
- url.searchParams.set("scope", opts.scope ?? "openid profile email");
565
- url.searchParams.set("return_to", returnTo);
566
- 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);
567
524
  }
568
- async function redirectToSignIn(manager, opts = {}) {
569
- const url = await buildSignInUrl(manager, opts);
570
- if (typeof window === "undefined") {
571
- throw new Error("redirectToSignIn requires a browser environment");
572
- }
573
- window.location.assign(url);
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);
574
533
  }
575
- async function signIn(manager, opts = {}) {
576
- return redirectToSignIn(manager, opts);
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 ?? [];
577
539
  }
578
- async function handleAuthCallback(manager, options = {}) {
579
- const url = new URL(options.url ?? (typeof window !== "undefined" ? window.location.href : ""));
580
- const code = url.searchParams.get("code");
581
- const state = url.searchParams.get("state");
582
- const errorParam = url.searchParams.get("error");
583
- if (errorParam) {
584
- return { ok: false, returnTo: "/", error: errorParam };
585
- }
586
- if (!code || !state) {
587
- return { ok: false, returnTo: "/", error: "missing_code_or_state" };
588
- }
589
- const record = loadPkce(state);
590
- if (!record) {
591
- return { ok: false, returnTo: "/", error: "unknown_state" };
592
- }
593
- clearPkce(state);
594
- const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
595
- if (!fetchImpl) {
596
- return { ok: false, returnTo: record.returnTo, error: "no_fetch" };
597
- }
598
- const tokenUrl = `${manager.issuerUrl}${options.tokenPath ?? DEFAULT_TOKEN_PATH}`;
599
- const res = await fetchImpl(tokenUrl, {
600
- method: "POST",
601
- credentials: "include",
602
- headers: { "Content-Type": "application/json" },
603
- body: JSON.stringify({
604
- grant_type: "authorization_code",
605
- code,
606
- redirect_uri: record.redirectUri,
607
- client_id: manager.appKey,
608
- code_verifier: record.codeVerifier
609
- })
610
- });
611
- const body = await res.json().catch(() => ({}));
612
- if (!res.ok) {
613
- const desc = body.error_description ?? body.error ?? "token_exchange_failed";
614
- return { ok: false, returnTo: record.returnTo, error: desc };
615
- }
616
- const tokens = body;
617
- if (!tokens.access_token) {
618
- return { ok: false, returnTo: record.returnTo, error: "missing_access_token" };
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 };
619
571
  }
620
- if (tokens.refresh_token) {
621
- setCookie(REFRESH_COOKIE, tokens.refresh_token, { maxAgeSeconds: 60 * 60 * 24 * 30 });
572
+ }
573
+ function writeState(appId, state) {
574
+ if (!isBrowser()) return;
575
+ try {
576
+ localStorage.setItem(storageKey(appId), JSON.stringify(state));
577
+ } catch {
622
578
  }
623
- manager.applyAccessToken(tokens.access_token, tokens.refresh_token);
624
- return { ok: true, returnTo: record.returnTo };
625
579
  }
626
- async function signOut(manager, opts = {}) {
627
- if (!opts.localOnly) {
628
- const issuer = manager.issuerUrl.replace(/\/$/, "");
629
- try {
630
- const url = `${issuer}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
631
- await manager.fetch(url, { method: "POST" }).catch(() => void 0);
632
- } catch {
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);
633
592
  }
634
- if (opts.endSsoSession !== false) {
635
- try {
636
- await fetch(`${issuer}${DEFAULT_SSO_LOGOUT_PATH}`, {
637
- method: "POST",
638
- credentials: "include"
639
- }).catch(() => void 0);
640
- } catch {
593
+ }
594
+ destroy() {
595
+ if (this.storageHandler && isBrowser()) {
596
+ window.removeEventListener("storage", this.storageHandler);
597
+ }
598
+ this.listeners.clear();
599
+ }
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();
630
+ }
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);
641
673
  }
642
674
  }
675
+ if (id) {
676
+ this.registry.writeRefreshToken(id, token);
677
+ return;
678
+ }
679
+ return this.fallback.write(token);
643
680
  }
644
- clearCookie(REFRESH_COOKIE);
645
- manager.signOutLocal();
646
- if (opts.returnTo && typeof window !== "undefined") {
647
- 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();
648
688
  }
649
- }
689
+ };
650
690
 
651
691
  export {
652
- REFRESH_COOKIE,
653
- setCookie,
654
- getCookie,
655
- clearCookie,
692
+ defaultCookieStore,
656
693
  SessionManager,
657
- randomUrlSafe,
658
- s256Challenge,
659
- createPkcePair,
660
- buildSignInUrl,
661
- redirectToSignIn,
662
- signIn,
663
- handleAuthCallback,
664
- 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
665
707
  };
@@ -1,9 +1,9 @@
1
- import {
2
- IQAuthClient
3
- } from "./chunk-W3F4JYGP.mjs";
4
1
  import {
5
2
  assertPublishableKey
6
3
  } from "./chunk-WQWBJSSS.mjs";
4
+ import {
5
+ IQAuthClient
6
+ } from "./chunk-W3F4JYGP.mjs";
7
7
  import {
8
8
  IQAuthError
9
9
  } from "./chunk-6I6RM4MN.mjs";
@@ -43,6 +43,22 @@ function readCookie(req, name) {
43
43
  }
44
44
  return void 0;
45
45
  }
46
+ function compileMatcher(pat) {
47
+ if (pat instanceof RegExp) return (p) => pat.test(p);
48
+ const re = new RegExp(
49
+ "^" + pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLE::").replace(/\*/g, "[^/]*").replace(/::DOUBLE::/g, ".*") + "$"
50
+ );
51
+ return (p) => re.test(p);
52
+ }
53
+ function compileMatchers(pats) {
54
+ return (pats ?? []).map(compileMatcher);
55
+ }
56
+ function pathOf(req) {
57
+ const r = req;
58
+ const raw = r.path || r.originalUrl || r.url || "/";
59
+ const q = raw.indexOf("?");
60
+ return q >= 0 ? raw.slice(0, q) : raw;
61
+ }
46
62
  function clientFromPublishableKey(opts) {
47
63
  const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
48
64
  const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
@@ -70,10 +86,26 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
70
86
  onUnauthorized,
71
87
  onForbidden,
72
88
  onError,
73
- accessCookieName = DEFAULT_ACCESS_COOKIE,
74
- cookieAware = true
89
+ cookieAware = true,
90
+ cookieNames,
91
+ protect,
92
+ publicRoutes
75
93
  } = resolvedOptions;
94
+ const accessCookieName = resolvedOptions.accessCookieName ?? cookieNames?.access ?? DEFAULT_ACCESS_COOKIE;
95
+ const protectMatchers = compileMatchers(protect);
96
+ const publicMatchers = compileMatchers(publicRoutes);
97
+ const hasProtect = protectMatchers.length > 0;
98
+ const hasPublic = publicMatchers.length > 0;
76
99
  return async (req, res, next) => {
100
+ if (hasProtect || hasPublic) {
101
+ const path = pathOf(req);
102
+ if (hasPublic && publicMatchers.some((m) => m(path))) {
103
+ return next();
104
+ }
105
+ if (hasProtect && !protectMatchers.some((m) => m(path))) {
106
+ return next();
107
+ }
108
+ }
77
109
  let token;
78
110
  const authHeader = getAuthorizationHeader(req);
79
111
  if (authHeader && authHeader.startsWith("Bearer ")) {