@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
package/dist/react.js CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,120 +20,24 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
20
- // src/react.ts
21
- var react_exports = {};
22
- __export(react_exports, {
23
- AuthCallback: () => AuthCallback,
24
- IQAuthLoaded: () => IQAuthLoaded,
25
- IQAuthLoading: () => IQAuthLoading,
26
- IQAuthProvider: () => IQAuthProvider,
27
- OrganizationSwitcher: () => OrganizationSwitcher,
28
- RedirectToSignIn: () => RedirectToSignIn,
29
- SignIn: () => SignIn,
30
- SignUp: () => SignUp,
31
- SignedIn: () => SignedIn,
32
- SignedOut: () => SignedOut,
33
- UserButton: () => UserButton,
34
- UserProfile: () => UserProfile,
35
- __version__: () => __version__,
36
- isSilentSsoEligible: () => isSilentSsoEligible,
37
- sanitizeBrandCss: () => sanitizeBrandCss,
38
- useAuth: () => useAuth,
39
- useAuthFetch: () => useAuthFetch,
40
- useIQAuthSignInContext: () => useIQAuthSignInContext,
41
- useOrganization: () => useOrganization,
42
- useResolvedSdkBranding: () => useResolvedSdkBranding,
43
- useSession: () => useSession,
44
- useUser: () => useUser
45
- });
46
- module.exports = __toCommonJS(react_exports);
47
-
48
- // src/react/index.tsx
49
- var import_react = require("react");
50
-
51
23
  // src/errors.ts
52
- var IQAuthError = class extends Error {
53
- constructor(code, message, status, raw) {
54
- super(message);
55
- this.name = "IQAuthError";
56
- this.code = code;
57
- this.status = status;
58
- this.raw = raw;
59
- }
60
- };
61
-
62
- // src/publishableKey.ts
63
- function b64urlDecode(input) {
64
- const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
65
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
66
- if (typeof atob === "function") {
67
- const bin = atob(normalized);
68
- const bytes = new Uint8Array(bin.length);
69
- for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
70
- return new TextDecoder().decode(bytes);
71
- }
72
- const { Buffer: Buffer2 } = require("buffer");
73
- return Buffer2.from(normalized, "base64").toString("utf8");
74
- }
75
- function isValidIssuerUrl(iss) {
76
- if (typeof iss !== "string" || iss.length === 0) return false;
77
- if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
78
- try {
79
- const u = new URL(iss);
80
- if (u.protocol !== "http:" && u.protocol !== "https:") return false;
81
- if (!u.hostname) return false;
82
- return true;
83
- } catch {
84
- return false;
85
- }
86
- }
87
- function assertPublishableKey(raw, opts) {
88
- const ctx = opts?.context ? `${opts.context}: ` : "";
89
- if (typeof raw !== "string" || raw.length === 0) {
90
- throw new IQAuthError(
91
- "CONFIG_INVALID",
92
- `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
93
- );
94
- }
95
- const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
96
- if (!shapeMatch) {
97
- throw new IQAuthError(
98
- "CONFIG_INVALID",
99
- `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
100
- );
101
- }
102
- let decoded;
103
- try {
104
- decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
105
- } catch {
106
- throw new IQAuthError(
107
- "CONFIG_INVALID",
108
- `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
109
- );
110
- }
111
- if (!isPublishableKeyPayload(decoded)) {
112
- throw new IQAuthError(
113
- "CONFIG_INVALID",
114
- `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
115
- );
116
- }
117
- if (!isValidIssuerUrl(decoded.iss)) {
118
- throw new IQAuthError(
119
- "CONFIG_INVALID",
120
- `${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console \u2014 the new key will encode a valid issuer URL.`
121
- );
24
+ var IQAuthError;
25
+ var init_errors = __esm({
26
+ "src/errors.ts"() {
27
+ "use strict";
28
+ IQAuthError = class extends Error {
29
+ constructor(code, message, status, raw) {
30
+ super(message);
31
+ this.name = "IQAuthError";
32
+ this.code = code;
33
+ this.status = status;
34
+ this.raw = raw;
35
+ }
36
+ };
122
37
  }
123
- return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
124
- }
125
- function isPublishableKeyPayload(value) {
126
- if (!value || typeof value !== "object") return false;
127
- const v = value;
128
- return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
129
- }
38
+ });
130
39
 
131
40
  // src/browser/storage.ts
132
- var REFRESH_COOKIE = "iqauth_rt";
133
- var PKCE_STORAGE_PREFIX = "iqauth.pkce.";
134
41
  function isBrowser() {
135
42
  return typeof window !== "undefined" && typeof document !== "undefined";
136
43
  }
@@ -187,105 +94,918 @@ function clearPkce(state) {
187
94
  } catch {
188
95
  }
189
96
  }
97
+ var REFRESH_COOKIE, PKCE_STORAGE_PREFIX;
98
+ var init_storage = __esm({
99
+ "src/browser/storage.ts"() {
100
+ "use strict";
101
+ REFRESH_COOKIE = "iqauth_rt";
102
+ PKCE_STORAGE_PREFIX = "iqauth.pkce.";
103
+ }
104
+ });
190
105
 
191
- // src/browser/sessionManager.ts
192
- var DEFAULT_REFRESH_PATH = "/api/v1/auth/refresh";
193
- var DEFAULT_USERINFO_PATH = "/api/v1/auth/me";
194
- function decodeClaims(token) {
195
- try {
196
- const parts = token.split(".");
197
- if (parts.length !== 3) return null;
198
- const json = typeof atob === "function" ? decodeURIComponent(
199
- atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
200
- ) : Buffer.from(parts[1], "base64url").toString("utf8");
201
- return JSON.parse(json);
202
- } catch {
203
- return null;
106
+ // src/browser/pkce.ts
107
+ function getCrypto() {
108
+ if (typeof globalThis !== "undefined" && globalThis.crypto) {
109
+ return globalThis.crypto;
204
110
  }
111
+ throw new Error("WebCrypto is not available in this environment");
205
112
  }
206
- function claimsToSessionUser(claims) {
207
- if (!claims) return null;
208
- return {
209
- sub: claims.sub,
210
- email: claims.email,
211
- name: claims.name,
212
- tenantId: claims.tenantId,
213
- vendorId: claims.vendorId,
214
- roles: claims.roles ?? [],
215
- entitlements: claims.entitlements ?? []
216
- };
113
+ function base64UrlEncode(bytes) {
114
+ let bin = "";
115
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
116
+ const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bin, "binary").toString("base64");
117
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
217
118
  }
218
- var EMPTY = {
219
- status: "loading",
220
- accessToken: null,
221
- user: null,
222
- claims: null,
223
- tenantId: null,
224
- error: null,
225
- version: 0
226
- };
227
- function defaultCookieStore() {
228
- return {
229
- read: () => getCookie(REFRESH_COOKIE),
230
- write: (token) => setCookie(REFRESH_COOKIE, token, { maxAgeSeconds: 60 * 60 * 24 * 30 }),
231
- clear: () => clearCookie(REFRESH_COOKIE)
232
- };
119
+ function randomUrlSafe(byteLength = 32) {
120
+ const bytes = new Uint8Array(byteLength);
121
+ getCrypto().getRandomValues(bytes);
122
+ return base64UrlEncode(bytes);
233
123
  }
234
- var NO_OP_STORE = {
235
- read: () => null,
236
- write: () => void 0,
237
- clear: () => void 0
238
- };
239
- var SessionManager = class {
240
- constructor(options) {
241
- this.snapshot = { ...EMPTY };
242
- this.listeners = /* @__PURE__ */ new Set();
243
- this.refreshPromise = null;
244
- this.channel = null;
245
- this.proactiveTimer = null;
246
- this.bootstrapped = false;
247
- /** Pending refresh awaited by other tabs after a `refresh:claim` from us. */
248
- this.remoteRefreshWaiters = [];
249
- /** Active claims by other tabs (keyed by source tabId). */
250
- this.foreignClaim = null;
251
- const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
252
- this.key = parsed;
253
- const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
254
- this.issuer = inferred.replace(/\/+$/, "");
255
- this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
256
- this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
257
- this.useCookies = options.useCookies ?? true;
258
- this.serverManagedSession = options.serverManagedSession ?? false;
259
- this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
260
- this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
261
- this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
262
- this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
263
- throw new Error("global fetch is not available; pass fetchImpl");
264
- }));
265
- this.tabId = Math.random().toString(36).slice(2);
266
- if (typeof BroadcastChannel !== "undefined") {
267
- const name = options.channelName ?? `iqauth.${parsed.appId}`;
268
- try {
269
- this.channel = new BroadcastChannel(name);
270
- this.channel.onmessage = (ev) => this.onBroadcast(ev.data);
271
- } catch {
272
- this.channel = null;
273
- }
274
- }
124
+ async function s256Challenge(verifier) {
125
+ const data = new TextEncoder().encode(verifier);
126
+ const digest = await getCrypto().subtle.digest("SHA-256", data);
127
+ return base64UrlEncode(new Uint8Array(digest));
128
+ }
129
+ async function createPkcePair() {
130
+ const codeVerifier = randomUrlSafe(32);
131
+ const codeChallenge = await s256Challenge(codeVerifier);
132
+ const state = randomUrlSafe(16);
133
+ const nonce = randomUrlSafe(16);
134
+ return { codeVerifier, codeChallenge, state, nonce };
135
+ }
136
+ var init_pkce = __esm({
137
+ "src/browser/pkce.ts"() {
138
+ "use strict";
275
139
  }
276
- get publishableKey() {
277
- return this.key;
140
+ });
141
+
142
+ // src/browser/signIn.ts
143
+ var signIn_exports = {};
144
+ __export(signIn_exports, {
145
+ buildSignInUrl: () => buildSignInUrl,
146
+ handleAuthCallback: () => handleAuthCallback,
147
+ redirectToSignIn: () => redirectToSignIn,
148
+ signIn: () => signIn,
149
+ signOut: () => signOut
150
+ });
151
+ function defaultRedirectUri() {
152
+ if (typeof window === "undefined") {
153
+ throw new Error("redirectToSignIn requires a browser environment (window)");
278
154
  }
279
- get appKey() {
280
- return this.key.appId;
155
+ return `${window.location.origin}${DEFAULT_CALLBACK_PATH}`;
156
+ }
157
+ function defaultReturnTo() {
158
+ if (typeof window === "undefined") return "/";
159
+ return window.location.href;
160
+ }
161
+ async function buildSignInUrl(manager, opts = {}) {
162
+ const pkce = await createPkcePair();
163
+ const redirectUri = opts.redirectUri ?? defaultRedirectUri();
164
+ const returnTo = opts.returnTo ?? defaultReturnTo();
165
+ savePkce({
166
+ codeVerifier: pkce.codeVerifier,
167
+ state: pkce.state,
168
+ nonce: pkce.nonce,
169
+ redirectUri,
170
+ appKey: manager.publishableKey.raw,
171
+ returnTo,
172
+ createdAt: Date.now()
173
+ });
174
+ const url2 = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.issuerUrl);
175
+ url2.searchParams.set("response_type", "code");
176
+ url2.searchParams.set("app", manager.appKey);
177
+ url2.searchParams.set("publishable_key", manager.publishableKey.raw);
178
+ url2.searchParams.set("redirect_uri", redirectUri);
179
+ url2.searchParams.set("state", pkce.state);
180
+ url2.searchParams.set("nonce", pkce.nonce);
181
+ url2.searchParams.set("code_challenge", pkce.codeChallenge);
182
+ url2.searchParams.set("code_challenge_method", "S256");
183
+ url2.searchParams.set("scope", opts.scope ?? "openid profile email");
184
+ url2.searchParams.set("return_to", returnTo);
185
+ if (opts.prompt) url2.searchParams.set("prompt", opts.prompt);
186
+ return url2.toString();
187
+ }
188
+ async function redirectToSignIn(manager, opts = {}) {
189
+ const url2 = await buildSignInUrl(manager, opts);
190
+ if (typeof window === "undefined") {
191
+ throw new Error("redirectToSignIn requires a browser environment");
281
192
  }
282
- get tenantIdFromKey() {
283
- return this.key.tenantId;
193
+ window.location.assign(url2);
194
+ }
195
+ async function signIn(manager, opts = {}) {
196
+ return redirectToSignIn(manager, opts);
197
+ }
198
+ async function handleAuthCallback(manager, options = {}) {
199
+ const url2 = new URL(options.url ?? (typeof window !== "undefined" ? window.location.href : ""));
200
+ const code = url2.searchParams.get("code");
201
+ const state = url2.searchParams.get("state");
202
+ const errorParam = url2.searchParams.get("error");
203
+ if (errorParam) {
204
+ return { ok: false, returnTo: "/", error: errorParam };
284
205
  }
285
- get issuerUrl() {
286
- return this.issuer;
206
+ if (!code || !state) {
207
+ return { ok: false, returnTo: "/", error: "missing_code_or_state" };
287
208
  }
288
- getSnapshot() {
209
+ const record = loadPkce(state);
210
+ if (!record) {
211
+ return { ok: false, returnTo: "/", error: "unknown_state" };
212
+ }
213
+ clearPkce(state);
214
+ const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
215
+ if (!fetchImpl) {
216
+ return { ok: false, returnTo: record.returnTo, error: "no_fetch" };
217
+ }
218
+ const tokenUrl = `${manager.issuerUrl}${options.tokenPath ?? DEFAULT_TOKEN_PATH}`;
219
+ const res = await fetchImpl(tokenUrl, {
220
+ method: "POST",
221
+ credentials: "include",
222
+ headers: { "Content-Type": "application/json" },
223
+ body: JSON.stringify({
224
+ grant_type: "authorization_code",
225
+ code,
226
+ redirect_uri: record.redirectUri,
227
+ client_id: manager.appKey,
228
+ code_verifier: record.codeVerifier
229
+ })
230
+ });
231
+ const body = await res.json().catch(() => ({}));
232
+ if (!res.ok) {
233
+ const desc = body.error_description ?? body.error ?? "token_exchange_failed";
234
+ return { ok: false, returnTo: record.returnTo, error: desc };
235
+ }
236
+ const tokens = body;
237
+ if (!tokens.access_token) {
238
+ return { ok: false, returnTo: record.returnTo, error: "missing_access_token" };
239
+ }
240
+ if (tokens.refresh_token) {
241
+ const cookieName = options.cookieNames?.refresh ?? manager.refreshCookie ?? REFRESH_COOKIE;
242
+ setCookie(cookieName, tokens.refresh_token, { maxAgeSeconds: 60 * 60 * 24 * 30 });
243
+ }
244
+ manager.applyAccessToken(tokens.access_token, tokens.refresh_token);
245
+ return { ok: true, returnTo: record.returnTo };
246
+ }
247
+ async function signOut(manager, opts = {}) {
248
+ if (!opts.localOnly) {
249
+ const issuer = manager.issuerUrl.replace(/\/$/, "");
250
+ try {
251
+ const url2 = `${issuer}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
252
+ await manager.fetch(url2, { method: "POST" }).catch(() => void 0);
253
+ } catch {
254
+ }
255
+ if (opts.endSsoSession !== false) {
256
+ try {
257
+ await fetch(`${issuer}${DEFAULT_SSO_LOGOUT_PATH}`, {
258
+ method: "POST",
259
+ credentials: "include"
260
+ }).catch(() => void 0);
261
+ } catch {
262
+ }
263
+ }
264
+ }
265
+ clearCookie(manager.refreshCookie ?? REFRESH_COOKIE);
266
+ manager.signOutLocal();
267
+ if (opts.returnTo && typeof window !== "undefined") {
268
+ window.location.assign(opts.returnTo);
269
+ }
270
+ }
271
+ var DEFAULT_SIGN_IN_PATH, DEFAULT_LOGOUT_PATH, DEFAULT_SSO_LOGOUT_PATH, DEFAULT_TOKEN_PATH, DEFAULT_CALLBACK_PATH;
272
+ var init_signIn = __esm({
273
+ "src/browser/signIn.ts"() {
274
+ "use strict";
275
+ init_pkce();
276
+ init_storage();
277
+ DEFAULT_SIGN_IN_PATH = "/sign-in";
278
+ DEFAULT_LOGOUT_PATH = "/api/v1/auth/logout";
279
+ DEFAULT_SSO_LOGOUT_PATH = "/oidc/sso-logout";
280
+ DEFAULT_TOKEN_PATH = "/oidc/token";
281
+ DEFAULT_CALLBACK_PATH = "/auth/callback";
282
+ }
283
+ });
284
+
285
+ // ../../node_modules/@simplewebauthn/browser/dist/bundle/index.js
286
+ var bundle_exports = {};
287
+ __export(bundle_exports, {
288
+ WebAuthnAbortService: () => WebAuthnAbortService,
289
+ WebAuthnError: () => WebAuthnError,
290
+ base64URLStringToBuffer: () => base64URLStringToBuffer,
291
+ browserSupportsWebAuthn: () => browserSupportsWebAuthn,
292
+ browserSupportsWebAuthnAutofill: () => browserSupportsWebAuthnAutofill,
293
+ bufferToBase64URLString: () => bufferToBase64URLString,
294
+ platformAuthenticatorIsAvailable: () => platformAuthenticatorIsAvailable,
295
+ startAuthentication: () => startAuthentication,
296
+ startRegistration: () => startRegistration
297
+ });
298
+ function bufferToBase64URLString(buffer) {
299
+ const bytes = new Uint8Array(buffer);
300
+ let str = "";
301
+ for (const charCode of bytes) {
302
+ str += String.fromCharCode(charCode);
303
+ }
304
+ const base64String = btoa(str);
305
+ return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
306
+ }
307
+ function base64URLStringToBuffer(base64URLString) {
308
+ const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/");
309
+ const padLength = (4 - base64.length % 4) % 4;
310
+ const padded = base64.padEnd(base64.length + padLength, "=");
311
+ const binary = atob(padded);
312
+ const buffer = new ArrayBuffer(binary.length);
313
+ const bytes = new Uint8Array(buffer);
314
+ for (let i = 0; i < binary.length; i++) {
315
+ bytes[i] = binary.charCodeAt(i);
316
+ }
317
+ return buffer;
318
+ }
319
+ function browserSupportsWebAuthn() {
320
+ return window?.PublicKeyCredential !== void 0 && typeof window.PublicKeyCredential === "function";
321
+ }
322
+ function toPublicKeyCredentialDescriptor(descriptor) {
323
+ const { id } = descriptor;
324
+ return {
325
+ ...descriptor,
326
+ id: base64URLStringToBuffer(id),
327
+ transports: descriptor.transports
328
+ };
329
+ }
330
+ function isValidDomain(hostname) {
331
+ return hostname === "localhost" || /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname);
332
+ }
333
+ function identifyRegistrationError({ error, options }) {
334
+ const { publicKey } = options;
335
+ if (!publicKey) {
336
+ throw Error("options was missing required publicKey property");
337
+ }
338
+ if (error.name === "AbortError") {
339
+ if (options.signal instanceof AbortSignal) {
340
+ return new WebAuthnError({
341
+ message: "Registration ceremony was sent an abort signal",
342
+ code: "ERROR_CEREMONY_ABORTED",
343
+ cause: error
344
+ });
345
+ }
346
+ } else if (error.name === "ConstraintError") {
347
+ if (publicKey.authenticatorSelection?.requireResidentKey === true) {
348
+ return new WebAuthnError({
349
+ message: "Discoverable credentials were required but no available authenticator supported it",
350
+ code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",
351
+ cause: error
352
+ });
353
+ } else if (options.mediation === "conditional" && publicKey.authenticatorSelection?.userVerification === "required") {
354
+ return new WebAuthnError({
355
+ message: "User verification was required during automatic registration but it could not be performed",
356
+ code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE",
357
+ cause: error
358
+ });
359
+ } else if (publicKey.authenticatorSelection?.userVerification === "required") {
360
+ return new WebAuthnError({
361
+ message: "User verification was required but no available authenticator supported it",
362
+ code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",
363
+ cause: error
364
+ });
365
+ }
366
+ } else if (error.name === "InvalidStateError") {
367
+ return new WebAuthnError({
368
+ message: "The authenticator was previously registered",
369
+ code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",
370
+ cause: error
371
+ });
372
+ } else if (error.name === "NotAllowedError") {
373
+ return new WebAuthnError({
374
+ message: error.message,
375
+ code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
376
+ cause: error
377
+ });
378
+ } else if (error.name === "NotSupportedError") {
379
+ const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === "public-key");
380
+ if (validPubKeyCredParams.length === 0) {
381
+ return new WebAuthnError({
382
+ message: 'No entry in pubKeyCredParams was of type "public-key"',
383
+ code: "ERROR_MALFORMED_PUBKEYCREDPARAMS",
384
+ cause: error
385
+ });
386
+ }
387
+ return new WebAuthnError({
388
+ message: "No available authenticator supported any of the specified pubKeyCredParams algorithms",
389
+ code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",
390
+ cause: error
391
+ });
392
+ } else if (error.name === "SecurityError") {
393
+ const effectiveDomain = window.location.hostname;
394
+ if (!isValidDomain(effectiveDomain)) {
395
+ return new WebAuthnError({
396
+ message: `${window.location.hostname} is an invalid domain`,
397
+ code: "ERROR_INVALID_DOMAIN",
398
+ cause: error
399
+ });
400
+ } else if (publicKey.rp.id !== effectiveDomain) {
401
+ return new WebAuthnError({
402
+ message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
403
+ code: "ERROR_INVALID_RP_ID",
404
+ cause: error
405
+ });
406
+ }
407
+ } else if (error.name === "TypeError") {
408
+ if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
409
+ return new WebAuthnError({
410
+ message: "User ID was not between 1 and 64 characters",
411
+ code: "ERROR_INVALID_USER_ID_LENGTH",
412
+ cause: error
413
+ });
414
+ }
415
+ } else if (error.name === "UnknownError") {
416
+ return new WebAuthnError({
417
+ message: "The authenticator was unable to process the specified options, or could not create a new credential",
418
+ code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
419
+ cause: error
420
+ });
421
+ }
422
+ return error;
423
+ }
424
+ function toAuthenticatorAttachment(attachment) {
425
+ if (!attachment) {
426
+ return;
427
+ }
428
+ if (attachments.indexOf(attachment) < 0) {
429
+ return;
430
+ }
431
+ return attachment;
432
+ }
433
+ async function startRegistration(options) {
434
+ const { optionsJSON, useAutoRegister = false } = options;
435
+ if (!browserSupportsWebAuthn()) {
436
+ throw new Error("WebAuthn is not supported in this browser");
437
+ }
438
+ const publicKey = {
439
+ ...optionsJSON,
440
+ challenge: base64URLStringToBuffer(optionsJSON.challenge),
441
+ user: {
442
+ ...optionsJSON.user,
443
+ id: base64URLStringToBuffer(optionsJSON.user.id)
444
+ },
445
+ excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor)
446
+ };
447
+ const createOptions = {};
448
+ if (useAutoRegister) {
449
+ createOptions.mediation = "conditional";
450
+ }
451
+ createOptions.publicKey = publicKey;
452
+ createOptions.signal = WebAuthnAbortService.createNewAbortSignal();
453
+ let credential;
454
+ try {
455
+ credential = await navigator.credentials.create(createOptions);
456
+ } catch (err) {
457
+ throw identifyRegistrationError({ error: err, options: createOptions });
458
+ }
459
+ if (!credential) {
460
+ throw new Error("Registration was not completed");
461
+ }
462
+ const { id, rawId, response, type } = credential;
463
+ let transports = void 0;
464
+ if (typeof response.getTransports === "function") {
465
+ transports = response.getTransports();
466
+ }
467
+ let responsePublicKeyAlgorithm = void 0;
468
+ if (typeof response.getPublicKeyAlgorithm === "function") {
469
+ try {
470
+ responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
471
+ } catch (error) {
472
+ warnOnBrokenImplementation("getPublicKeyAlgorithm()", error);
473
+ }
474
+ }
475
+ let responsePublicKey = void 0;
476
+ if (typeof response.getPublicKey === "function") {
477
+ try {
478
+ const _publicKey = response.getPublicKey();
479
+ if (_publicKey !== null) {
480
+ responsePublicKey = bufferToBase64URLString(_publicKey);
481
+ }
482
+ } catch (error) {
483
+ warnOnBrokenImplementation("getPublicKey()", error);
484
+ }
485
+ }
486
+ let responseAuthenticatorData;
487
+ if (typeof response.getAuthenticatorData === "function") {
488
+ try {
489
+ responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
490
+ } catch (error) {
491
+ warnOnBrokenImplementation("getAuthenticatorData()", error);
492
+ }
493
+ }
494
+ return {
495
+ id,
496
+ rawId: bufferToBase64URLString(rawId),
497
+ response: {
498
+ attestationObject: bufferToBase64URLString(response.attestationObject),
499
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
500
+ transports,
501
+ publicKeyAlgorithm: responsePublicKeyAlgorithm,
502
+ publicKey: responsePublicKey,
503
+ authenticatorData: responseAuthenticatorData
504
+ },
505
+ type,
506
+ clientExtensionResults: credential.getClientExtensionResults(),
507
+ authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
508
+ };
509
+ }
510
+ function warnOnBrokenImplementation(methodName, cause) {
511
+ console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.
512
+ `, cause);
513
+ }
514
+ function browserSupportsWebAuthnAutofill() {
515
+ if (!browserSupportsWebAuthn()) {
516
+ return new Promise((resolve) => resolve(false));
517
+ }
518
+ const globalPublicKeyCredential = window.PublicKeyCredential;
519
+ if (globalPublicKeyCredential.isConditionalMediationAvailable === void 0) {
520
+ return new Promise((resolve) => resolve(false));
521
+ }
522
+ return globalPublicKeyCredential.isConditionalMediationAvailable();
523
+ }
524
+ function identifyAuthenticationError({ error, options }) {
525
+ const { publicKey } = options;
526
+ if (!publicKey) {
527
+ throw Error("options was missing required publicKey property");
528
+ }
529
+ if (error.name === "AbortError") {
530
+ if (options.signal instanceof AbortSignal) {
531
+ return new WebAuthnError({
532
+ message: "Authentication ceremony was sent an abort signal",
533
+ code: "ERROR_CEREMONY_ABORTED",
534
+ cause: error
535
+ });
536
+ }
537
+ } else if (error.name === "NotAllowedError") {
538
+ return new WebAuthnError({
539
+ message: error.message,
540
+ code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
541
+ cause: error
542
+ });
543
+ } else if (error.name === "SecurityError") {
544
+ const effectiveDomain = window.location.hostname;
545
+ if (!isValidDomain(effectiveDomain)) {
546
+ return new WebAuthnError({
547
+ message: `${window.location.hostname} is an invalid domain`,
548
+ code: "ERROR_INVALID_DOMAIN",
549
+ cause: error
550
+ });
551
+ } else if (publicKey.rpId !== effectiveDomain) {
552
+ return new WebAuthnError({
553
+ message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
554
+ code: "ERROR_INVALID_RP_ID",
555
+ cause: error
556
+ });
557
+ }
558
+ } else if (error.name === "UnknownError") {
559
+ return new WebAuthnError({
560
+ message: "The authenticator was unable to process the specified options, or could not create a new assertion signature",
561
+ code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
562
+ cause: error
563
+ });
564
+ }
565
+ return error;
566
+ }
567
+ async function startAuthentication(options) {
568
+ const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true } = options;
569
+ if (!browserSupportsWebAuthn()) {
570
+ throw new Error("WebAuthn is not supported in this browser");
571
+ }
572
+ let allowCredentials;
573
+ if (optionsJSON.allowCredentials?.length !== 0) {
574
+ allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
575
+ }
576
+ const publicKey = {
577
+ ...optionsJSON,
578
+ challenge: base64URLStringToBuffer(optionsJSON.challenge),
579
+ allowCredentials
580
+ };
581
+ const getOptions = {};
582
+ if (useBrowserAutofill) {
583
+ if (!await browserSupportsWebAuthnAutofill()) {
584
+ throw Error("Browser does not support WebAuthn autofill");
585
+ }
586
+ const eligibleInputs = document.querySelectorAll("input[autocomplete$='webauthn']");
587
+ if (eligibleInputs.length < 1 && verifyBrowserAutofillInput) {
588
+ throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected');
589
+ }
590
+ getOptions.mediation = "conditional";
591
+ publicKey.allowCredentials = [];
592
+ }
593
+ getOptions.publicKey = publicKey;
594
+ getOptions.signal = WebAuthnAbortService.createNewAbortSignal();
595
+ let credential;
596
+ try {
597
+ credential = await navigator.credentials.get(getOptions);
598
+ } catch (err) {
599
+ throw identifyAuthenticationError({ error: err, options: getOptions });
600
+ }
601
+ if (!credential) {
602
+ throw new Error("Authentication was not completed");
603
+ }
604
+ const { id, rawId, response, type } = credential;
605
+ let userHandle = void 0;
606
+ if (response.userHandle) {
607
+ userHandle = bufferToBase64URLString(response.userHandle);
608
+ }
609
+ return {
610
+ id,
611
+ rawId: bufferToBase64URLString(rawId),
612
+ response: {
613
+ authenticatorData: bufferToBase64URLString(response.authenticatorData),
614
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
615
+ signature: bufferToBase64URLString(response.signature),
616
+ userHandle
617
+ },
618
+ type,
619
+ clientExtensionResults: credential.getClientExtensionResults(),
620
+ authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
621
+ };
622
+ }
623
+ function platformAuthenticatorIsAvailable() {
624
+ if (!browserSupportsWebAuthn()) {
625
+ return new Promise((resolve) => resolve(false));
626
+ }
627
+ return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
628
+ }
629
+ var WebAuthnError, BaseWebAuthnAbortService, WebAuthnAbortService, attachments;
630
+ var init_bundle = __esm({
631
+ "../../node_modules/@simplewebauthn/browser/dist/bundle/index.js"() {
632
+ "use strict";
633
+ WebAuthnError = class extends Error {
634
+ constructor({ message, code, cause, name }) {
635
+ super(message, { cause });
636
+ this.name = name ?? cause.name;
637
+ this.code = code;
638
+ }
639
+ };
640
+ BaseWebAuthnAbortService = class {
641
+ createNewAbortSignal() {
642
+ if (this.controller) {
643
+ const abortError = new Error("Cancelling existing WebAuthn API call for new one");
644
+ abortError.name = "AbortError";
645
+ this.controller.abort(abortError);
646
+ }
647
+ const newController = new AbortController();
648
+ this.controller = newController;
649
+ return newController.signal;
650
+ }
651
+ cancelCeremony() {
652
+ if (this.controller) {
653
+ const abortError = new Error("Manually cancelling existing WebAuthn API call");
654
+ abortError.name = "AbortError";
655
+ this.controller.abort(abortError);
656
+ this.controller = void 0;
657
+ }
658
+ }
659
+ };
660
+ WebAuthnAbortService = new BaseWebAuthnAbortService();
661
+ attachments = ["cross-platform", "platform"];
662
+ }
663
+ });
664
+
665
+ // src/browser/reverify.ts
666
+ var reverify_exports = {};
667
+ __export(reverify_exports, {
668
+ PRIOR_SESSION_STORAGE_KEY: () => PRIOR_SESSION_STORAGE_KEY,
669
+ enterImpersonation: () => enterImpersonation,
670
+ exitImpersonation: () => exitImpersonation,
671
+ reverify: () => reverify,
672
+ withReverification: () => withReverification
673
+ });
674
+ function enterImpersonation(manager, actorAccessToken) {
675
+ if (typeof window === "undefined") return;
676
+ const current = manager.getSnapshot().accessToken;
677
+ if (current) {
678
+ const envelope = { accessToken: current, savedAt: Date.now() };
679
+ try {
680
+ window.sessionStorage.setItem(PRIOR_SESSION_STORAGE_KEY, JSON.stringify(envelope));
681
+ } catch {
682
+ }
683
+ }
684
+ manager.applyAccessToken(actorAccessToken);
685
+ }
686
+ function exitImpersonation(manager) {
687
+ if (typeof window === "undefined") return false;
688
+ let raw = null;
689
+ try {
690
+ raw = window.sessionStorage.getItem(PRIOR_SESSION_STORAGE_KEY);
691
+ } catch {
692
+ return false;
693
+ }
694
+ if (!raw) return false;
695
+ try {
696
+ const envelope = JSON.parse(raw);
697
+ if (!envelope?.accessToken) return false;
698
+ manager.applyAccessToken(envelope.accessToken);
699
+ return true;
700
+ } catch {
701
+ return false;
702
+ } finally {
703
+ try {
704
+ window.sessionStorage.removeItem(PRIOR_SESSION_STORAGE_KEY);
705
+ } catch {
706
+ }
707
+ }
708
+ }
709
+ async function reverify(manager, input, options = {}) {
710
+ if (input.level === "password" && !input.password) {
711
+ throw new IQAuthError("MISSING_PASSWORD", "password is required for level=password");
712
+ }
713
+ if (input.level === "mfa" && !input.totp) {
714
+ throw new IQAuthError("MISSING_CODE", "totp code is required for level=mfa");
715
+ }
716
+ const issuer = manager.issuerUrl.replace(/\/$/, "");
717
+ const url2 = `${issuer}${options.path ?? DEFAULT_REVERIFY_PATH}`;
718
+ const res = await manager.fetch(url2, {
719
+ method: "POST",
720
+ headers: { "Content-Type": "application/json" },
721
+ body: JSON.stringify(input)
722
+ });
723
+ let payload = null;
724
+ try {
725
+ payload = await res.json();
726
+ } catch {
727
+ }
728
+ if (!res.ok || !payload?.success) {
729
+ const code = payload?.error?.code ?? "REVERIFICATION_FAILED";
730
+ const message = payload?.error?.message ?? `reverification failed (${res.status})`;
731
+ throw new IQAuthError(code, message);
732
+ }
733
+ return {
734
+ token: payload.data.reverificationToken,
735
+ level: payload.data.level,
736
+ expiresAt: new Date(payload.data.expiresAt)
737
+ };
738
+ }
739
+ function withReverification(manager, token) {
740
+ let used = false;
741
+ return async (input, init = {}) => {
742
+ if (used) throw new IQAuthError("REVERIFICATION_USED", "Reverification token already consumed");
743
+ used = true;
744
+ const headers2 = new Headers(init.headers);
745
+ headers2.set("X-Reverification-Token", token);
746
+ return manager.fetch(input, { ...init, headers: headers2 });
747
+ };
748
+ }
749
+ var PRIOR_SESSION_STORAGE_KEY, DEFAULT_REVERIFY_PATH;
750
+ var init_reverify = __esm({
751
+ "src/browser/reverify.ts"() {
752
+ "use strict";
753
+ init_errors();
754
+ PRIOR_SESSION_STORAGE_KEY = "iqauth_prior_admin_session";
755
+ DEFAULT_REVERIFY_PATH = "/api/v1/auth/reverify";
756
+ }
757
+ });
758
+
759
+ // src/react.ts
760
+ var react_exports = {};
761
+ __export(react_exports, {
762
+ AuthCallback: () => AuthCallback,
763
+ CreateOrganization: () => CreateOrganization,
764
+ IQAuthLoaded: () => IQAuthLoaded,
765
+ IQAuthLoading: () => IQAuthLoading,
766
+ IQAuthProvider: () => IQAuthProvider,
767
+ IQAuthReturnToBouncer: () => IQAuthReturnToBouncer,
768
+ ImpersonationBanner: () => ImpersonationBanner,
769
+ LinkedAccounts: () => LinkedAccounts,
770
+ MagicLinkSignInForm: () => MagicLinkSignInForm,
771
+ MultisessionAppSupport: () => MultisessionAppSupport,
772
+ OrganizationList: () => OrganizationList,
773
+ OrganizationProfile: () => OrganizationProfile,
774
+ OrganizationSwitcher: () => OrganizationSwitcher,
775
+ PasskeySignInButton: () => PasskeySignInButton,
776
+ Protect: () => Protect,
777
+ RedirectToSignIn: () => RedirectToSignIn,
778
+ RedirectToSignedIn: () => RedirectToSignedIn,
779
+ SignIn: () => SignIn,
780
+ SignUp: () => SignUp,
781
+ SignedIn: () => SignedIn,
782
+ SignedOut: () => SignedOut,
783
+ UserButton: () => UserButton,
784
+ UserProfile: () => UserProfile,
785
+ Waitlist: () => Waitlist,
786
+ __version__: () => __version__,
787
+ isReturnToAllowed: () => isReturnToAllowed,
788
+ isSilentSsoEligible: () => isSilentSsoEligible,
789
+ preflightReturnTo: () => preflightReturnTo,
790
+ revokeSession: () => revokeSession,
791
+ sanitizeBrandCss: () => sanitizeBrandCss,
792
+ sanitizeReturnTo: () => sanitizeReturnTo,
793
+ slugify: () => slugify,
794
+ useAccountList: () => useAccountList,
795
+ useAccountSwitcher: () => useAccountSwitcher,
796
+ useAuth: () => useAuth,
797
+ useAuthFetch: () => useAuthFetch,
798
+ useIQAuthSignInContext: () => useIQAuthSignInContext,
799
+ useImpersonation: () => useImpersonation,
800
+ useLinkedIdentities: () => useLinkedIdentities,
801
+ useLocale: () => useLocale,
802
+ useMagicLink: () => useMagicLink,
803
+ useOrganization: () => useOrganization,
804
+ usePasskey: () => usePasskey,
805
+ useResolvedSdkBranding: () => useResolvedSdkBranding,
806
+ useReturnTo: () => useReturnTo,
807
+ useReverification: () => useReverification,
808
+ useSession: () => useSession,
809
+ useSessionList: () => useSessionList,
810
+ useT: () => useT,
811
+ useUser: () => useUser
812
+ });
813
+ module.exports = __toCommonJS(react_exports);
814
+
815
+ // src/react/index.tsx
816
+ var import_react = require("react");
817
+
818
+ // src/browser/sessionManager.ts
819
+ init_errors();
820
+
821
+ // src/publishableKey.ts
822
+ init_errors();
823
+ function b64urlDecode(input) {
824
+ const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
825
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
826
+ if (typeof atob === "function") {
827
+ const bin = atob(normalized);
828
+ const bytes = new Uint8Array(bin.length);
829
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
830
+ return new TextDecoder().decode(bytes);
831
+ }
832
+ const { Buffer: Buffer2 } = require("buffer");
833
+ return Buffer2.from(normalized, "base64").toString("utf8");
834
+ }
835
+ function isValidIssuerUrl(iss) {
836
+ if (typeof iss !== "string" || iss.length === 0) return false;
837
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
838
+ try {
839
+ const u = new URL(iss);
840
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
841
+ if (!u.hostname) return false;
842
+ return true;
843
+ } catch {
844
+ return false;
845
+ }
846
+ }
847
+ function assertPublishableKey(raw, opts) {
848
+ const ctx = opts?.context ? `${opts.context}: ` : "";
849
+ if (typeof raw !== "string" || raw.length === 0) {
850
+ throw new IQAuthError(
851
+ "CONFIG_INVALID",
852
+ `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
853
+ );
854
+ }
855
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
856
+ if (!shapeMatch) {
857
+ throw new IQAuthError(
858
+ "CONFIG_INVALID",
859
+ `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
860
+ );
861
+ }
862
+ let decoded;
863
+ try {
864
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
865
+ } catch {
866
+ throw new IQAuthError(
867
+ "CONFIG_INVALID",
868
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
869
+ );
870
+ }
871
+ if (!isPublishableKeyPayload(decoded)) {
872
+ throw new IQAuthError(
873
+ "CONFIG_INVALID",
874
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
875
+ );
876
+ }
877
+ if (!isValidIssuerUrl(decoded.iss)) {
878
+ throw new IQAuthError(
879
+ "CONFIG_INVALID",
880
+ `${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console \u2014 the new key will encode a valid issuer URL.`
881
+ );
882
+ }
883
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
884
+ }
885
+ function isPublishableKeyPayload(value) {
886
+ if (!value || typeof value !== "object") return false;
887
+ const v = value;
888
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
889
+ }
890
+
891
+ // src/browser/sessionManager.ts
892
+ init_storage();
893
+ var DEFAULT_REFRESH_PATH = "/api/v1/auth/refresh";
894
+ var DEFAULT_USERINFO_PATH = "/api/v1/auth/me";
895
+ async function readAuthErrorCode(res) {
896
+ try {
897
+ const cloned = res.clone();
898
+ const data = await cloned.json().catch(() => null);
899
+ if (!data || typeof data !== "object") return null;
900
+ const err = data.error;
901
+ if (err && typeof err.code === "string") {
902
+ return { code: err.code, message: typeof err.message === "string" ? err.message : void 0 };
903
+ }
904
+ return null;
905
+ } catch {
906
+ return null;
907
+ }
908
+ }
909
+ function decodeClaims(token) {
910
+ try {
911
+ const parts = token.split(".");
912
+ if (parts.length !== 3) return null;
913
+ const json = typeof atob === "function" ? decodeURIComponent(
914
+ atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
915
+ ) : Buffer.from(parts[1], "base64url").toString("utf8");
916
+ return JSON.parse(json);
917
+ } catch {
918
+ return null;
919
+ }
920
+ }
921
+ function claimsToSessionUser(claims) {
922
+ if (!claims) return null;
923
+ return {
924
+ sub: claims.sub,
925
+ email: claims.email,
926
+ name: claims.name,
927
+ tenantId: claims.tenantId,
928
+ vendorId: claims.vendorId,
929
+ roles: claims.roles ?? [],
930
+ entitlements: claims.entitlements ?? []
931
+ };
932
+ }
933
+ var EMPTY = {
934
+ status: "loading",
935
+ accessToken: null,
936
+ user: null,
937
+ claims: null,
938
+ tenantId: null,
939
+ error: null,
940
+ version: 0
941
+ };
942
+ function defaultCookieStore(name = REFRESH_COOKIE) {
943
+ return {
944
+ read: () => getCookie(name),
945
+ write: (token) => setCookie(name, token, { maxAgeSeconds: 60 * 60 * 24 * 30 }),
946
+ clear: () => clearCookie(name)
947
+ };
948
+ }
949
+ var NO_OP_STORE = {
950
+ read: () => null,
951
+ write: () => void 0,
952
+ clear: () => void 0
953
+ };
954
+ var SessionManager = class {
955
+ constructor(options) {
956
+ this.snapshot = { ...EMPTY };
957
+ this.listeners = /* @__PURE__ */ new Set();
958
+ this.refreshPromise = null;
959
+ this.channel = null;
960
+ this.proactiveTimer = null;
961
+ this.bootstrapped = false;
962
+ /** Pending refresh awaited by other tabs after a `refresh:claim` from us. */
963
+ this.remoteRefreshWaiters = [];
964
+ /** Active claims by other tabs (keyed by source tabId). */
965
+ this.foreignClaim = null;
966
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
967
+ this.key = parsed;
968
+ const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
969
+ this.issuer = inferred.replace(/\/+$/, "");
970
+ this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
971
+ this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
972
+ this.useCookies = options.useCookies ?? true;
973
+ this.serverManagedSession = options.serverManagedSession ?? false;
974
+ this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
975
+ this.refreshCookieName = options.cookieNames?.refresh ?? REFRESH_COOKIE;
976
+ this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore(this.refreshCookieName) : NO_OP_STORE);
977
+ this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
978
+ this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
979
+ throw new Error("global fetch is not available; pass fetchImpl");
980
+ }));
981
+ this.tabId = Math.random().toString(36).slice(2);
982
+ if (typeof BroadcastChannel !== "undefined") {
983
+ const name = options.channelName ?? `iqauth.${parsed.appId}`;
984
+ try {
985
+ this.channel = new BroadcastChannel(name);
986
+ this.channel.onmessage = (ev) => this.onBroadcast(ev.data);
987
+ } catch {
988
+ this.channel = null;
989
+ }
990
+ }
991
+ }
992
+ get publishableKey() {
993
+ return this.key;
994
+ }
995
+ get appKey() {
996
+ return this.key.appId;
997
+ }
998
+ get tenantIdFromKey() {
999
+ return this.key.tenantId;
1000
+ }
1001
+ get issuerUrl() {
1002
+ return this.issuer;
1003
+ }
1004
+ /** Cookie name the SDK uses for the refresh token (overridable via `cookieNames.refresh`). */
1005
+ get refreshCookie() {
1006
+ return this.refreshCookieName;
1007
+ }
1008
+ getSnapshot() {
289
1009
  return this.snapshot;
290
1010
  }
291
1011
  subscribe(listener) {
@@ -391,7 +1111,7 @@ var SessionManager = class {
391
1111
  return false;
392
1112
  }
393
1113
  if (data.refreshToken) {
394
- await Promise.resolve(this.tokenStore.write(data.refreshToken));
1114
+ await Promise.resolve(this.tokenStore.write(data.refreshToken, { claims: decodeClaims(data.accessToken) }));
395
1115
  }
396
1116
  this.applyAccessToken(data.accessToken);
397
1117
  this.broadcast("session:refresh");
@@ -435,7 +1155,7 @@ var SessionManager = class {
435
1155
  const claims = decodeClaims(accessToken);
436
1156
  const user = claimsToSessionUser(claims);
437
1157
  if (refreshToken) {
438
- void Promise.resolve(this.tokenStore.write(refreshToken));
1158
+ void Promise.resolve(this.tokenStore.write(refreshToken, { claims }));
439
1159
  }
440
1160
  this.update({
441
1161
  status: user ? "authenticated" : "unauthenticated",
@@ -469,33 +1189,39 @@ var SessionManager = class {
469
1189
  */
470
1190
  async fetch(input, init = {}) {
471
1191
  const exec = async (token2) => {
472
- const headers = new Headers(init.headers || {});
473
- if (token2) headers.set("Authorization", `Bearer ${token2}`);
1192
+ const headers2 = new Headers(init.headers || {});
1193
+ if (token2) headers2.set("Authorization", `Bearer ${token2}`);
474
1194
  return this.fetchImpl(input, {
475
1195
  ...init,
476
- headers,
1196
+ headers: headers2,
477
1197
  credentials: init.credentials ?? "include"
478
1198
  });
479
1199
  };
480
1200
  let token = await this.getToken();
481
1201
  let res = await exec(token);
482
1202
  if (res.status !== 401) return res;
1203
+ const initialErr = await readAuthErrorCode(res);
1204
+ if (initialErr && (initialErr.code === "SESSION_EXPIRED_INACTIVITY" || initialErr.code === "SESSION_EXPIRED_MAX_DURATION" || initialErr.code === "SESSION_INVALID")) {
1205
+ this.signOutLocal("unauthenticated");
1206
+ throw new IQAuthError(initialErr.code, initialErr.message || "Session expired", 401);
1207
+ }
483
1208
  const refreshed = await this.refresh();
484
1209
  if (!refreshed) {
485
1210
  this.signOutLocal("unauthenticated");
486
1211
  throw new IQAuthError(
487
- "TOKEN_EXPIRED",
488
- "Session refresh failed; user must sign in again",
1212
+ initialErr?.code || "TOKEN_EXPIRED",
1213
+ initialErr?.message || "Session refresh failed; user must sign in again",
489
1214
  401
490
1215
  );
491
1216
  }
492
1217
  token = this.snapshot.accessToken;
493
1218
  res = await exec(token);
494
1219
  if (res.status === 401) {
1220
+ const secondErr = await readAuthErrorCode(res);
495
1221
  this.signOutLocal("unauthenticated");
496
1222
  throw new IQAuthError(
497
- "TOKEN_EXPIRED",
498
- "Authenticated request failed twice with 401; aborting",
1223
+ secondErr?.code || "TOKEN_EXPIRED",
1224
+ secondErr?.message || "Authenticated request failed twice with 401; aborting",
499
1225
  401
500
1226
  );
501
1227
  }
@@ -523,6 +1249,14 @@ var SessionManager = class {
523
1249
  });
524
1250
  this.broadcast("session:signout");
525
1251
  }
1252
+ /**
1253
+ * Replace the refresh-token store at runtime. Used by the F22
1254
+ * `<MultisessionAppSupport>` wrapper to swap in a `MultiAccountTokenStore`
1255
+ * after the manager has already been constructed by `<IQAuthProvider>`.
1256
+ */
1257
+ setTokenStore(store) {
1258
+ this.tokenStore = store;
1259
+ }
526
1260
  destroy() {
527
1261
  if (this.proactiveTimer) clearTimeout(this.proactiveTimer);
528
1262
  this.proactiveTimer = null;
@@ -589,190 +1323,518 @@ var SessionManager = class {
589
1323
  this.foreignClaim = null;
590
1324
  return;
591
1325
  }
592
- if (env.type === "session:signout") {
593
- this.update({
594
- status: "unauthenticated",
595
- accessToken: null,
596
- user: null,
597
- claims: null,
598
- tenantId: null,
599
- error: null,
600
- version: this.snapshot.version + 1
601
- });
1326
+ if (env.type === "session:signout") {
1327
+ this.update({
1328
+ status: "unauthenticated",
1329
+ accessToken: null,
1330
+ user: null,
1331
+ claims: null,
1332
+ tenantId: null,
1333
+ error: null,
1334
+ version: this.snapshot.version + 1
1335
+ });
1336
+ return;
1337
+ }
1338
+ if ((env.type === "session:update" || env.type === "session:refresh") && env.payload) {
1339
+ this.update({
1340
+ ...env.payload,
1341
+ version: Math.max(this.snapshot.version, env.payload.version) + 1
1342
+ });
1343
+ if (this.remoteRefreshWaiters.length) {
1344
+ const waiters = this.remoteRefreshWaiters;
1345
+ this.remoteRefreshWaiters = [];
1346
+ for (const w of waiters) w(true);
1347
+ }
1348
+ }
1349
+ }
1350
+ };
1351
+
1352
+ // src/react/index.tsx
1353
+ init_signIn();
1354
+
1355
+ // src/browser/accountRegistry.ts
1356
+ init_storage();
1357
+ var STORAGE_PREFIX = "iqauth.accounts.";
1358
+ var COOKIE_PREFIX = "iqauth_rt_";
1359
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
1360
+ function isBrowser2() {
1361
+ return typeof window !== "undefined" && typeof localStorage !== "undefined";
1362
+ }
1363
+ function storageKey(appId) {
1364
+ return STORAGE_PREFIX + appId;
1365
+ }
1366
+ function readState(appId) {
1367
+ if (!isBrowser2()) return { accounts: [], activeAccountId: null };
1368
+ try {
1369
+ const raw = localStorage.getItem(storageKey(appId));
1370
+ if (!raw) return { accounts: [], activeAccountId: null };
1371
+ const parsed = JSON.parse(raw);
1372
+ return {
1373
+ accounts: Array.isArray(parsed.accounts) ? parsed.accounts : [],
1374
+ activeAccountId: parsed.activeAccountId ?? null
1375
+ };
1376
+ } catch {
1377
+ return { accounts: [], activeAccountId: null };
1378
+ }
1379
+ }
1380
+ function writeState(appId, state) {
1381
+ if (!isBrowser2()) return;
1382
+ try {
1383
+ localStorage.setItem(storageKey(appId), JSON.stringify(state));
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ var AccountRegistry = class {
1388
+ constructor(appId) {
1389
+ this.appId = appId;
1390
+ this.listeners = /* @__PURE__ */ new Set();
1391
+ this.storageHandler = null;
1392
+ if (isBrowser2()) {
1393
+ this.storageHandler = (ev) => {
1394
+ if (ev.key === storageKey(this.appId)) {
1395
+ for (const l of this.listeners) l();
1396
+ }
1397
+ };
1398
+ window.addEventListener("storage", this.storageHandler);
1399
+ }
1400
+ }
1401
+ destroy() {
1402
+ if (this.storageHandler && isBrowser2()) {
1403
+ window.removeEventListener("storage", this.storageHandler);
1404
+ }
1405
+ this.listeners.clear();
1406
+ }
1407
+ list() {
1408
+ return readState(this.appId).accounts.slice();
1409
+ }
1410
+ active() {
1411
+ return readState(this.appId).activeAccountId;
1412
+ }
1413
+ get(accountId) {
1414
+ return readState(this.appId).accounts.find((a) => a.accountId === accountId) ?? null;
1415
+ }
1416
+ upsert(rec) {
1417
+ const state = readState(this.appId);
1418
+ const idx = state.accounts.findIndex((a) => a.accountId === rec.accountId);
1419
+ if (idx >= 0) state.accounts[idx] = { ...state.accounts[idx], ...rec };
1420
+ else state.accounts.push(rec);
1421
+ writeState(this.appId, state);
1422
+ this.notify();
1423
+ }
1424
+ setActive(accountId) {
1425
+ const state = readState(this.appId);
1426
+ state.activeAccountId = accountId;
1427
+ writeState(this.appId, state);
1428
+ this.notify();
1429
+ }
1430
+ remove(accountId) {
1431
+ const state = readState(this.appId);
1432
+ state.accounts = state.accounts.filter((a) => a.accountId !== accountId);
1433
+ if (state.activeAccountId === accountId) state.activeAccountId = state.accounts[0]?.accountId ?? null;
1434
+ writeState(this.appId, state);
1435
+ clearCookie(COOKIE_PREFIX + accountId);
1436
+ this.notify();
1437
+ }
1438
+ subscribe(listener) {
1439
+ this.listeners.add(listener);
1440
+ return () => this.listeners.delete(listener);
1441
+ }
1442
+ /**
1443
+ * Read the refresh token for a specific account from its per-account
1444
+ * cookie. Used by `MultiAccountTokenStore.read()`.
1445
+ */
1446
+ readRefreshToken(accountId) {
1447
+ return getCookie(COOKIE_PREFIX + accountId);
1448
+ }
1449
+ /**
1450
+ * Persist a refresh token to the per-account cookie. Caller is the
1451
+ * SessionManager via `MultiAccountTokenStore.write()`.
1452
+ */
1453
+ writeRefreshToken(accountId, token, opts = {}) {
1454
+ setCookie(COOKIE_PREFIX + accountId, token, { maxAgeSeconds: COOKIE_MAX_AGE, ...opts });
1455
+ }
1456
+ clearRefreshToken(accountId) {
1457
+ clearCookie(COOKIE_PREFIX + accountId);
1458
+ }
1459
+ notify() {
1460
+ for (const l of this.listeners) l();
1461
+ }
1462
+ };
1463
+ var MultiAccountTokenStore = class {
1464
+ constructor(registry, fallback) {
1465
+ this.registry = registry;
1466
+ this.fallback = fallback;
1467
+ }
1468
+ read() {
1469
+ const id = this.registry.active();
1470
+ if (id) return this.registry.readRefreshToken(id);
1471
+ return this.fallback.read();
1472
+ }
1473
+ write(token, ctx) {
1474
+ let id = this.registry.active();
1475
+ if (!id) {
1476
+ const sub = ctx?.claims?.sub;
1477
+ if (sub) {
1478
+ id = sub;
1479
+ this.registry.setActive(sub);
1480
+ }
1481
+ }
1482
+ if (id) {
1483
+ this.registry.writeRefreshToken(id, token);
602
1484
  return;
603
1485
  }
604
- if ((env.type === "session:update" || env.type === "session:refresh") && env.payload) {
605
- this.update({
606
- ...env.payload,
607
- version: Math.max(this.snapshot.version, env.payload.version) + 1
608
- });
609
- if (this.remoteRefreshWaiters.length) {
610
- const waiters = this.remoteRefreshWaiters;
611
- this.remoteRefreshWaiters = [];
612
- for (const w of waiters) w(true);
613
- }
1486
+ return this.fallback.write(token);
1487
+ }
1488
+ clear() {
1489
+ const id = this.registry.active();
1490
+ if (id) {
1491
+ this.registry.clearRefreshToken(id);
1492
+ return;
614
1493
  }
1494
+ return this.fallback.clear();
615
1495
  }
616
1496
  };
617
1497
 
618
- // src/browser/pkce.ts
619
- function getCrypto() {
620
- if (typeof globalThis !== "undefined" && globalThis.crypto) {
621
- return globalThis.crypto;
622
- }
623
- throw new Error("WebCrypto is not available in this environment");
624
- }
625
- function base64UrlEncode(bytes) {
626
- let bin = "";
627
- for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
628
- const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bin, "binary").toString("base64");
629
- return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
630
- }
631
- function randomUrlSafe(byteLength = 32) {
632
- const bytes = new Uint8Array(byteLength);
633
- getCrypto().getRandomValues(bytes);
634
- return base64UrlEncode(bytes);
1498
+ // src/locales/enUS.ts
1499
+ var enUS = {
1500
+ locale: "en-US",
1501
+ "common.loading": "Loading\u2026",
1502
+ "common.submitting": "Submitting\u2026",
1503
+ "common.cancel": "Cancel",
1504
+ "common.continue": "Continue",
1505
+ "common.back": "Back",
1506
+ "common.close": "Close",
1507
+ "common.save": "Save",
1508
+ "common.saving": "Saving\u2026",
1509
+ "common.delete": "Delete",
1510
+ "common.confirm": "Confirm",
1511
+ "common.email": "Email",
1512
+ "common.password": "Password",
1513
+ "common.name": "Name",
1514
+ "common.or": "or",
1515
+ "common.required": "Required",
1516
+ "common.optional": "Optional",
1517
+ "common.retry": "Try again",
1518
+ "signIn.title": "Sign in",
1519
+ "signIn.subtitle": "Welcome back",
1520
+ "signIn.titleWithApp": "Sign in to {appName}",
1521
+ "signIn.emailLabel": "Email",
1522
+ "signIn.emailPlaceholder": "you@company.com",
1523
+ "signIn.passwordLabel": "Password",
1524
+ "signIn.passwordPlaceholder": "Your password",
1525
+ "signIn.submit": "Sign in",
1526
+ "signIn.submitting": "Signing in\u2026",
1527
+ "signIn.continueWithGoogle": "Continue with Google",
1528
+ "signIn.continueWithMagicLink": "Email me a sign-in link",
1529
+ "signIn.continueWithPasskey": "Sign in with passkey",
1530
+ "signIn.forgotPassword": "Forgot password?",
1531
+ "signIn.noAccount": "Don't have an account?",
1532
+ "signIn.signUp": "Sign up",
1533
+ "signIn.resumingSession": "Signing you in\u2026",
1534
+ "signIn.useDifferentAccount": "Use a different account",
1535
+ "signIn.selectTenant": "Choose an organization",
1536
+ "signIn.selectTenantSubtitle": "You belong to multiple organizations. Pick one to continue.",
1537
+ "signIn.dividerOr": "or",
1538
+ "signIn.preparingExperience": "Preparing your sign-in experience.",
1539
+ "signIn.applicationUnavailable": "Application unavailable",
1540
+ "signIn.applicationNotFound": "Application not found",
1541
+ "signIn.invalidRedirect": "Invalid redirect",
1542
+ "signIn.returnUrlNotRegistered": "The return URL is not registered for {appName}. Add the origin to the app's allowed origins.",
1543
+ "signIn.welcomeBackName": "Welcome back, {name}.",
1544
+ "signIn.oneMomentResume": "One moment while we resume your session.",
1545
+ "signIn.notYouUseDifferent": "Not you? Use a different account",
1546
+ "signIn.chooseWorkspace": "Choose a workspace",
1547
+ "signIn.pickTenantToSignIn": "Pick the tenant to sign in to.",
1548
+ "signIn.subtitleHosted": "Use your work email and password to continue.",
1549
+ "signIn.createAccount": "Create account",
1550
+ "signIn.couldntResume": "Couldn't resume your session.",
1551
+ "signUp.title": "Create your account",
1552
+ "signUp.subtitle": "Start in seconds",
1553
+ "signUp.nameLabel": "Full name",
1554
+ "signUp.namePlaceholder": "Jane Doe",
1555
+ "signUp.emailLabel": "Work email",
1556
+ "signUp.passwordLabel": "Password",
1557
+ "signUp.passwordHint": "At least 8 characters.",
1558
+ "signUp.submit": "Create account",
1559
+ "signUp.submitting": "Creating account\u2026",
1560
+ "signUp.haveAccount": "Already have an account?",
1561
+ "signUp.signIn": "Sign in",
1562
+ "signUp.tenantNameLabel": "Organization name",
1563
+ "signUp.tenantNamePlaceholder": "Acme, Inc.",
1564
+ "signUp.legal": "By creating an account you agree to the Terms of Service and Privacy Policy.",
1565
+ "forgotPassword.title": "Reset your password",
1566
+ "forgotPassword.subtitle": "We'll email you a reset link.",
1567
+ "forgotPassword.submit": "Send reset link",
1568
+ "forgotPassword.submitting": "Sending\u2026",
1569
+ "forgotPassword.sent": "Check your inbox for a reset link.",
1570
+ "forgotPassword.backToSignIn": "Back to sign in",
1571
+ "resetPassword.title": "Choose a new password",
1572
+ "resetPassword.newPasswordLabel": "New password",
1573
+ "resetPassword.confirmPasswordLabel": "Confirm new password",
1574
+ "resetPassword.submit": "Update password",
1575
+ "resetPassword.submitting": "Updating\u2026",
1576
+ "resetPassword.success": "Password updated. You can now sign in.",
1577
+ "resetPassword.mismatch": "Passwords do not match.",
1578
+ "mfa.title": "Verify it's you",
1579
+ "mfa.subtitle": "Enter the verification code to continue.",
1580
+ "mfa.totpLabel": "Authenticator code",
1581
+ "mfa.totpPlaceholder": "123456",
1582
+ "mfa.smsLabel": "Text message code",
1583
+ "mfa.emailLabel": "Email code",
1584
+ "mfa.submit": "Verify",
1585
+ "mfa.submitting": "Verifying\u2026",
1586
+ "mfa.useBackupCode": "Use a backup code",
1587
+ "mfa.useAuthenticator": "Use authenticator app",
1588
+ "mfa.useSms": "Use text message",
1589
+ "mfa.useEmail": "Use email",
1590
+ "mfa.resend": "Resend code",
1591
+ "mfa.resent": "Code sent.",
1592
+ "mfa.backupCodeLabel": "Backup code",
1593
+ "userButton.signedInAs": "Signed in as",
1594
+ "userButton.manageAccount": "Manage account",
1595
+ "userButton.switchOrg": "Switch organization",
1596
+ "userButton.signOut": "Sign out",
1597
+ "userProfile.title": "Your account",
1598
+ "userProfile.profileTab": "Profile",
1599
+ "userProfile.securityTab": "Security",
1600
+ "userProfile.sessionsTab": "Devices & sessions",
1601
+ "userProfile.linkedAccountsTab": "Linked accounts",
1602
+ "userProfile.changeName": "Update name",
1603
+ "userProfile.changeEmail": "Change email",
1604
+ "userProfile.changePassword": "Change password",
1605
+ "userProfile.currentPassword": "Current password",
1606
+ "userProfile.newPassword": "New password",
1607
+ "userProfile.confirmPassword": "Confirm new password",
1608
+ "userProfile.passwordUpdated": "Password updated.",
1609
+ "userProfile.mfaSection": "Two-factor authentication",
1610
+ "userProfile.mfaEnable": "Enable two-factor",
1611
+ "userProfile.mfaDisable": "Disable two-factor",
1612
+ "userProfile.sessionsSection": "Active sessions",
1613
+ "userProfile.revokeSession": "Revoke",
1614
+ "userProfile.revokeAllOthers": "Sign out of all other sessions",
1615
+ "userProfile.thisDevice": "This device",
1616
+ "userProfile.sessionsEmpty": "No active sessions.",
1617
+ "userProfile.linkedAccountsEmpty": "No linked accounts.",
1618
+ "userProfile.connectGoogle": "Connect Google",
1619
+ "userProfile.disconnect": "Disconnect",
1620
+ "orgSwitcher.label": "Organization",
1621
+ "orgSwitcher.personal": "Personal account",
1622
+ "orgSwitcher.createNew": "Create organization",
1623
+ "orgSwitcher.manage": "Manage organization",
1624
+ "orgSwitcher.noOrgs": "You don't belong to any organizations yet.",
1625
+ "orgProfile.title": "Organization settings",
1626
+ "orgProfile.generalTab": "General",
1627
+ "orgProfile.membersTab": "Members",
1628
+ "orgProfile.invitationsTab": "Invitations",
1629
+ "orgProfile.dangerTab": "Danger zone",
1630
+ "orgProfile.invite": "Invite member",
1631
+ "orgProfile.inviteEmailLabel": "Email address",
1632
+ "orgProfile.inviteRoleLabel": "Role",
1633
+ "orgProfile.inviteSend": "Send invitation",
1634
+ "orgProfile.inviteSent": "Invitation sent.",
1635
+ "orgProfile.removeMember": "Remove",
1636
+ "orgProfile.deleteOrg": "Permanently delete organization",
1637
+ "orgProfile.deleteOrgConfirm": "Type the organization name to confirm.",
1638
+ "createOrg.title": "Create an organization",
1639
+ "createOrg.nameLabel": "Organization name",
1640
+ "createOrg.submit": "Create",
1641
+ "createOrg.submitting": "Creating\u2026",
1642
+ "waitlist.title": "Join the waitlist",
1643
+ "waitlist.subtitle": "We'll notify you when access opens up.",
1644
+ "waitlist.submit": "Join waitlist",
1645
+ "waitlist.submitting": "Joining\u2026",
1646
+ "waitlist.success": "You're on the list. We'll be in touch.",
1647
+ "impersonation.banner": "Impersonation active \u2014 you are signed in as {targetEmail}.",
1648
+ "impersonation.exit": "Exit impersonation",
1649
+ "magicLink.title": "Check your email",
1650
+ "magicLink.subtitle": "We sent a sign-in link to {email}.",
1651
+ "magicLink.resend": "Resend link",
1652
+ "magicLink.changeEmail": "Use a different email",
1653
+ "errors.generic": "Something went wrong. Please try again.",
1654
+ "errors.network": "Network error. Check your connection and try again.",
1655
+ "errors.invalidCredentials": "Incorrect email or password.",
1656
+ "errors.userNotFound": "No account found for that email.",
1657
+ "errors.emailInUse": "An account already exists for that email.",
1658
+ "errors.weakPassword": "Password is too weak.",
1659
+ "errors.mfaInvalid": "That code didn't work. Try again.",
1660
+ "errors.mfaExpired": "That code has expired. Request a new one.",
1661
+ "errors.tooManyAttempts": "Too many attempts. Please wait and try again.",
1662
+ "errors.sessionExpired": "Your session expired. Please sign in again.",
1663
+ "errors.permissionDenied": "You don't have permission to do that.",
1664
+ "errors.notFound": "Not found.",
1665
+ "errors.serverError": "Server error. Please try again shortly.",
1666
+ "errors.invalidEmail": "Enter a valid email address.",
1667
+ "errors.passwordTooShort": "Password must be at least {min} characters.",
1668
+ "errors.required": "This field is required.",
1669
+ "errors.invitationInvalid": "This invitation link is invalid or expired.",
1670
+ "validation.emailRequired": "Email is required.",
1671
+ "validation.emailInvalid": "Enter a valid email address.",
1672
+ "validation.passwordRequired": "Password is required.",
1673
+ "validation.nameRequired": "Name is required.",
1674
+ "validation.codeRequired": "Verification code is required.",
1675
+ "validation.codeInvalid": "Code must be 6 digits."
1676
+ };
1677
+
1678
+ // src/locales/index.ts
1679
+ var defaultBundle = enUS;
1680
+ function t(bundle, key, vars) {
1681
+ const fromBundle = bundle && bundle[key];
1682
+ const raw = typeof fromBundle === "string" && fromBundle || defaultBundle[key];
1683
+ if (!vars) return raw;
1684
+ return raw.replace(/\{(\w+)\}/g, (_match, name) => {
1685
+ const v = vars[name];
1686
+ return v === void 0 || v === null ? `{${name}}` : String(v);
1687
+ });
635
1688
  }
636
- async function s256Challenge(verifier) {
637
- const data = new TextEncoder().encode(verifier);
638
- const digest = await getCrypto().subtle.digest("SHA-256", data);
639
- return base64UrlEncode(new Uint8Array(digest));
1689
+ function resolveBundle(override) {
1690
+ if (!override) return defaultBundle;
1691
+ return { ...defaultBundle, ...override };
640
1692
  }
641
- async function createPkcePair() {
642
- const codeVerifier = randomUrlSafe(32);
643
- const codeChallenge = await s256Challenge(codeVerifier);
644
- const state = randomUrlSafe(16);
645
- const nonce = randomUrlSafe(16);
646
- return { codeVerifier, codeChallenge, state, nonce };
1693
+ var ERROR_CODE_MAP = {
1694
+ INVALID_CREDENTIALS: "errors.invalidCredentials",
1695
+ USER_NOT_FOUND: "errors.userNotFound",
1696
+ EMAIL_IN_USE: "errors.emailInUse",
1697
+ EMAIL_ALREADY_EXISTS: "errors.emailInUse",
1698
+ WEAK_PASSWORD: "errors.weakPassword",
1699
+ MFA_INVALID: "errors.mfaInvalid",
1700
+ MFA_CODE_INVALID: "errors.mfaInvalid",
1701
+ MFA_EXPIRED: "errors.mfaExpired",
1702
+ MFA_CODE_EXPIRED: "errors.mfaExpired",
1703
+ RATE_LIMITED: "errors.tooManyAttempts",
1704
+ TOO_MANY_REQUESTS: "errors.tooManyAttempts",
1705
+ SESSION_EXPIRED: "errors.sessionExpired",
1706
+ TOKEN_EXPIRED: "errors.sessionExpired",
1707
+ FORBIDDEN: "errors.permissionDenied",
1708
+ PERMISSION_DENIED: "errors.permissionDenied",
1709
+ NOT_FOUND: "errors.notFound",
1710
+ INTERNAL_ERROR: "errors.serverError",
1711
+ SERVER_ERROR: "errors.serverError",
1712
+ NETWORK_ERROR: "errors.network",
1713
+ INVITATION_INVALID: "errors.invitationInvalid",
1714
+ INVITATION_EXPIRED: "errors.invitationInvalid"
1715
+ };
1716
+ function localizeErrorCode(bundle, code, vars) {
1717
+ if (!code) return t(bundle, "errors.generic", vars);
1718
+ const key = ERROR_CODE_MAP[code.toUpperCase()];
1719
+ return key ? t(bundle, key, vars) : t(bundle, "errors.generic", vars);
647
1720
  }
648
1721
 
649
- // src/browser/signIn.ts
650
- var DEFAULT_SIGN_IN_PATH = "/sign-in";
651
- var DEFAULT_LOGOUT_PATH = "/api/v1/auth/logout";
652
- var DEFAULT_SSO_LOGOUT_PATH = "/oidc/sso-logout";
653
- var DEFAULT_TOKEN_PATH = "/oidc/token";
654
- var DEFAULT_CALLBACK_PATH = "/auth/callback";
655
- function defaultRedirectUri() {
656
- if (typeof window === "undefined") {
657
- throw new Error("redirectToSignIn requires a browser environment (window)");
1722
+ // src/browser/returnTo.ts
1723
+ function normalizeOrigin(o) {
1724
+ try {
1725
+ return new URL(o).origin;
1726
+ } catch {
1727
+ return o.replace(/\/+$/, "");
658
1728
  }
659
- return `${window.location.origin}${DEFAULT_CALLBACK_PATH}`;
660
1729
  }
661
- function defaultReturnTo() {
662
- if (typeof window === "undefined") return "/";
663
- return window.location.href;
1730
+ function sanitizeReturnTo(input, options = {}) {
1731
+ const fallback = options.fallback ?? "/";
1732
+ if (!input || typeof input !== "string") return fallback;
1733
+ const trimmed = input.trim();
1734
+ if (!trimmed) return fallback;
1735
+ if (trimmed.startsWith("//")) return fallback;
1736
+ if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?")) {
1737
+ return trimmed;
1738
+ }
1739
+ if (!/^[a-z][a-z0-9+\-.]*:/i.test(trimmed)) {
1740
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
1741
+ }
1742
+ let parsed;
1743
+ try {
1744
+ parsed = new URL(trimmed);
1745
+ } catch {
1746
+ return fallback;
1747
+ }
1748
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return fallback;
1749
+ const currentOrigin = options.currentOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
1750
+ const allowed = /* @__PURE__ */ new Set();
1751
+ if (currentOrigin) allowed.add(normalizeOrigin(currentOrigin));
1752
+ for (const o of options.allowedOrigins ?? []) allowed.add(normalizeOrigin(o));
1753
+ if (allowed.has(parsed.origin)) return parsed.toString();
1754
+ return fallback;
664
1755
  }
665
- async function buildSignInUrl(manager, opts = {}) {
666
- const pkce = await createPkcePair();
667
- const redirectUri = opts.redirectUri ?? defaultRedirectUri();
668
- const returnTo = opts.returnTo ?? defaultReturnTo();
669
- savePkce({
670
- codeVerifier: pkce.codeVerifier,
671
- state: pkce.state,
672
- nonce: pkce.nonce,
673
- redirectUri,
674
- appKey: manager.publishableKey.raw,
675
- returnTo,
676
- createdAt: Date.now()
677
- });
678
- const url = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.issuerUrl);
679
- url.searchParams.set("response_type", "code");
680
- url.searchParams.set("app", manager.appKey);
681
- url.searchParams.set("publishable_key", manager.publishableKey.raw);
682
- url.searchParams.set("redirect_uri", redirectUri);
683
- url.searchParams.set("state", pkce.state);
684
- url.searchParams.set("nonce", pkce.nonce);
685
- url.searchParams.set("code_challenge", pkce.codeChallenge);
686
- url.searchParams.set("code_challenge_method", "S256");
687
- url.searchParams.set("scope", opts.scope ?? "openid profile email");
688
- url.searchParams.set("return_to", returnTo);
689
- return url.toString();
1756
+ function isReturnToAllowed(input, options = {}) {
1757
+ const fallback = options.fallback ?? "/";
1758
+ const out = sanitizeReturnTo(input, options);
1759
+ if (!input) return false;
1760
+ return out !== fallback || input === fallback;
690
1761
  }
691
- async function redirectToSignIn(manager, opts = {}) {
692
- const url = await buildSignInUrl(manager, opts);
693
- if (typeof window === "undefined") {
694
- throw new Error("redirectToSignIn requires a browser environment");
695
- }
696
- window.location.assign(url);
1762
+
1763
+ // src/browser/passwordless.ts
1764
+ function url(base, path) {
1765
+ return `${base.replace(/\/+$/, "")}${path}`;
697
1766
  }
698
- async function signIn(manager, opts = {}) {
699
- return redirectToSignIn(manager, opts);
1767
+ function headers(o) {
1768
+ const h = { "Content-Type": "application/json" };
1769
+ if (o.cookieSession !== false) h["x-iqauth-session"] = "cookie";
1770
+ return h;
700
1771
  }
701
- async function handleAuthCallback(manager, options = {}) {
702
- const url = new URL(options.url ?? (typeof window !== "undefined" ? window.location.href : ""));
703
- const code = url.searchParams.get("code");
704
- const state = url.searchParams.get("state");
705
- const errorParam = url.searchParams.get("error");
706
- if (errorParam) {
707
- return { ok: false, returnTo: "/", error: errorParam };
708
- }
709
- if (!code || !state) {
710
- return { ok: false, returnTo: "/", error: "missing_code_or_state" };
711
- }
712
- const record = loadPkce(state);
713
- if (!record) {
714
- return { ok: false, returnTo: "/", error: "unknown_state" };
715
- }
716
- clearPkce(state);
717
- const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
718
- if (!fetchImpl) {
719
- return { ok: false, returnTo: record.returnTo, error: "no_fetch" };
720
- }
721
- const tokenUrl = `${manager.issuerUrl}${options.tokenPath ?? DEFAULT_TOKEN_PATH}`;
722
- const res = await fetchImpl(tokenUrl, {
1772
+ async function postJson(u, body, h) {
1773
+ const r = await fetch(u, {
723
1774
  method: "POST",
724
1775
  credentials: "include",
725
- headers: { "Content-Type": "application/json" },
726
- body: JSON.stringify({
727
- grant_type: "authorization_code",
728
- code,
729
- redirect_uri: record.redirectUri,
730
- client_id: manager.appKey,
731
- code_verifier: record.codeVerifier
732
- })
1776
+ headers: h,
1777
+ body: JSON.stringify(body)
733
1778
  });
734
- const body = await res.json().catch(() => ({}));
735
- if (!res.ok) {
736
- const desc = body.error_description ?? body.error ?? "token_exchange_failed";
737
- return { ok: false, returnTo: record.returnTo, error: desc };
738
- }
739
- const tokens = body;
740
- if (!tokens.access_token) {
741
- return { ok: false, returnTo: record.returnTo, error: "missing_access_token" };
742
- }
743
- if (tokens.refresh_token) {
744
- setCookie(REFRESH_COOKIE, tokens.refresh_token, { maxAgeSeconds: 60 * 60 * 24 * 30 });
1779
+ const p = await r.json().catch(() => ({}));
1780
+ if (!r.ok) {
1781
+ const msg = p?.error?.message || `Request failed (${r.status})`;
1782
+ throw new Error(msg);
745
1783
  }
746
- manager.applyAccessToken(tokens.access_token, tokens.refresh_token);
747
- return { ok: true, returnTo: record.returnTo };
1784
+ return p.data;
748
1785
  }
749
- async function signOut(manager, opts = {}) {
750
- if (!opts.localOnly) {
751
- const issuer = manager.issuerUrl.replace(/\/$/, "");
752
- try {
753
- const url = `${issuer}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
754
- await manager.fetch(url, { method: "POST" }).catch(() => void 0);
755
- } catch {
756
- }
757
- if (opts.endSsoSession !== false) {
758
- try {
759
- await fetch(`${issuer}${DEFAULT_SSO_LOGOUT_PATH}`, {
760
- method: "POST",
761
- credentials: "include"
762
- }).catch(() => void 0);
763
- } catch {
764
- }
765
- }
766
- }
767
- clearCookie(REFRESH_COOKIE);
768
- manager.signOutLocal();
769
- if (opts.returnTo && typeof window !== "undefined") {
770
- window.location.assign(opts.returnTo);
771
- }
1786
+ async function requestMagicLink(opts, input) {
1787
+ await postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/magic-link/request"), input, headers(opts));
1788
+ return { ok: true };
1789
+ }
1790
+ async function beginPasskeyAuthentication(opts, input = {}) {
1791
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/authentication/options"), input, headers(opts));
1792
+ }
1793
+ async function finishPasskeyAuthentication(opts, response) {
1794
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/authentication/verify"), { response }, headers(opts));
1795
+ }
1796
+ async function beginPasskeyRegistration(opts) {
1797
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/registration/options"), {}, headers(opts));
1798
+ }
1799
+ async function finishPasskeyRegistration(opts, response, name) {
1800
+ return postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/passkeys/registration/verify"), { response, name }, headers(opts));
1801
+ }
1802
+ async function signInWithPasskey(opts, input = {}) {
1803
+ const mod = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
1804
+ const options = await beginPasskeyAuthentication(opts, input);
1805
+ const response = await mod.startAuthentication({ optionsJSON: options });
1806
+ return finishPasskeyAuthentication(opts, response);
1807
+ }
1808
+ async function enrollPasskey(opts, name) {
1809
+ const mod = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
1810
+ const options = await beginPasskeyRegistration(opts);
1811
+ const response = await mod.startRegistration({ optionsJSON: options });
1812
+ return finishPasskeyRegistration(opts, response, name);
1813
+ }
1814
+ async function listLinkedIdentities(opts) {
1815
+ const r = await fetch(url(opts.iqAuthBaseUrl, "/api/v1/auth/identities"), { credentials: "include" });
1816
+ const p = await r.json().catch(() => ({}));
1817
+ if (!r.ok) throw new Error(p?.error?.message || `Request failed (${r.status})`);
1818
+ return p?.data?.identities ?? [];
1819
+ }
1820
+ async function linkProvider(opts, input) {
1821
+ await postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/identities/link"), input, headers(opts));
1822
+ return { ok: true };
1823
+ }
1824
+ async function unlinkProvider(opts, input) {
1825
+ await postJson(url(opts.iqAuthBaseUrl, "/api/v1/auth/identities/unlink"), input, headers(opts));
1826
+ return { ok: true };
772
1827
  }
773
1828
 
774
1829
  // src/react/index.tsx
775
1830
  var import_jsx_runtime = require("react/jsx-runtime");
1831
+ var globalManager = null;
1832
+ function setGlobalManager(m) {
1833
+ globalManager = m;
1834
+ }
1835
+ function getGlobalManager() {
1836
+ return globalManager;
1837
+ }
776
1838
  var IQAuthContext = (0, import_react.createContext)(null);
777
1839
  function IQAuthProvider({
778
1840
  publishableKey,
@@ -780,6 +1842,11 @@ function IQAuthProvider({
780
1842
  channelName,
781
1843
  proactiveRefresh,
782
1844
  manager: externalManager,
1845
+ allowedReturnOrigins,
1846
+ appearance,
1847
+ roleMapper,
1848
+ cookieNames,
1849
+ localization,
783
1850
  children
784
1851
  }) {
785
1852
  const managerRef = (0, import_react.useRef)(null);
@@ -788,8 +1855,10 @@ function IQAuthProvider({
788
1855
  publishableKey,
789
1856
  issuer,
790
1857
  channelName,
791
- proactiveRefresh
1858
+ proactiveRefresh,
1859
+ cookieNames
792
1860
  });
1861
+ setGlobalManager(managerRef.current);
793
1862
  }
794
1863
  const manager = managerRef.current;
795
1864
  const subscribe = (0, import_react.useCallback)(
@@ -815,7 +1884,21 @@ function IQAuthProvider({
815
1884
  }
816
1885
  };
817
1886
  }, [manager]);
818
- const value = (0, import_react.useMemo)(() => ({ manager, snapshot }), [manager, snapshot]);
1887
+ const resolvedLocalization = (0, import_react.useMemo)(
1888
+ () => resolveBundle(localization),
1889
+ [localization]
1890
+ );
1891
+ const value = (0, import_react.useMemo)(
1892
+ () => ({
1893
+ manager,
1894
+ snapshot,
1895
+ allowedReturnOrigins: allowedReturnOrigins ?? [],
1896
+ appearance: appearance ?? null,
1897
+ roleMapper: roleMapper ?? null,
1898
+ localization: resolvedLocalization
1899
+ }),
1900
+ [manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization]
1901
+ );
819
1902
  return (0, import_react.createElement)(IQAuthContext.Provider, { value }, children);
820
1903
  }
821
1904
  function useCtx() {
@@ -823,16 +1906,56 @@ function useCtx() {
823
1906
  if (!ctx) throw new Error("IQAuth hooks must be used inside <IQAuthProvider>");
824
1907
  return ctx;
825
1908
  }
1909
+ function useLocale() {
1910
+ const ctx = (0, import_react.useContext)(IQAuthContext);
1911
+ return ctx?.localization ?? defaultBundle;
1912
+ }
1913
+ function useT() {
1914
+ const bundle = useLocale();
1915
+ return (0, import_react.useMemo)(
1916
+ () => (key, vars) => t(bundle, key, vars),
1917
+ [bundle]
1918
+ );
1919
+ }
1920
+ function localizeError(bundle, raw) {
1921
+ if (!raw) return t(bundle, "errors.generic");
1922
+ if (typeof raw === "string") {
1923
+ if (/^[A-Z][A-Z0-9_]+$/.test(raw)) return localizeErrorCode(bundle, raw);
1924
+ return raw;
1925
+ }
1926
+ if (raw.code) return localizeErrorCode(bundle, raw.code);
1927
+ return raw.message || t(bundle, "errors.generic");
1928
+ }
826
1929
  function useUser() {
827
- const { snapshot } = useCtx();
1930
+ const { snapshot, roleMapper } = useCtx();
828
1931
  return (0, import_react.useMemo)(
829
- () => ({
830
- isLoaded: snapshot.status !== "loading",
831
- isSignedIn: snapshot.status === "authenticated" && !!snapshot.user,
832
- user: snapshot.user,
833
- error: snapshot.error
834
- }),
835
- [snapshot.status, snapshot.user, snapshot.error, snapshot.version]
1932
+ () => {
1933
+ const user = snapshot.user ? {
1934
+ ...snapshot.user,
1935
+ // F13 — derive `role` via roleMapper, falling back to a sensible default
1936
+ role: (() => {
1937
+ if (roleMapper) {
1938
+ try {
1939
+ return roleMapper(snapshot.claims);
1940
+ } catch {
1941
+ return null;
1942
+ }
1943
+ }
1944
+ const c = snapshot.claims;
1945
+ if (!c) return null;
1946
+ if (typeof c.role === "string" && c.role) return c.role;
1947
+ if (Array.isArray(c.roles) && c.roles.length > 0 && typeof c.roles[0] === "string") return c.roles[0];
1948
+ return null;
1949
+ })()
1950
+ } : null;
1951
+ return {
1952
+ isLoaded: snapshot.status !== "loading",
1953
+ isSignedIn: snapshot.status === "authenticated" && !!snapshot.user,
1954
+ user,
1955
+ error: snapshot.error
1956
+ };
1957
+ },
1958
+ [snapshot.status, snapshot.user, snapshot.claims, snapshot.error, snapshot.version, roleMapper]
836
1959
  );
837
1960
  }
838
1961
  function useSession() {
@@ -884,6 +2007,148 @@ function useAuthFetch() {
884
2007
  [manager]
885
2008
  );
886
2009
  }
2010
+ function useSessionList() {
2011
+ const { manager } = useCtx();
2012
+ const [sessions, setSessions] = (0, import_react.useState)([]);
2013
+ const [loading, setLoading] = (0, import_react.useState)(true);
2014
+ const [error, setError] = (0, import_react.useState)(null);
2015
+ const base = manager.issuerUrl.replace(/\/$/, "");
2016
+ const refresh = (0, import_react.useCallback)(async () => {
2017
+ setLoading(true);
2018
+ setError(null);
2019
+ try {
2020
+ const res = await manager.fetch(`${base}/api/v1/users/me/sessions`);
2021
+ const json = await res.json().catch(() => ({}));
2022
+ if (!res.ok) throw new Error(json?.error?.message || `HTTP ${res.status}`);
2023
+ setSessions(json?.data?.sessions || []);
2024
+ } catch (err) {
2025
+ setError(err.message);
2026
+ } finally {
2027
+ setLoading(false);
2028
+ }
2029
+ }, [manager, base]);
2030
+ (0, import_react.useEffect)(() => {
2031
+ void refresh();
2032
+ }, [refresh]);
2033
+ const revoke = (0, import_react.useCallback)(async (sessionId) => {
2034
+ const res = await manager.fetch(`${base}/api/v1/users/me/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
2035
+ if (!res.ok) {
2036
+ const j = await res.json().catch(() => ({}));
2037
+ throw new Error(j?.error?.message || `HTTP ${res.status}`);
2038
+ }
2039
+ setSessions((prev) => prev.filter((s) => s.id !== sessionId));
2040
+ }, [manager, base]);
2041
+ const revokeAllOthers = (0, import_react.useCallback)(async () => {
2042
+ const res = await manager.fetch(`${base}/api/v1/users/me/sessions/revoke-all-others`, { method: "POST" });
2043
+ const json = await res.json().catch(() => ({}));
2044
+ if (!res.ok) throw new Error(json?.error?.message || `HTTP ${res.status}`);
2045
+ setSessions((prev) => prev.filter((s) => s.isCurrent));
2046
+ return { terminatedCount: json?.data?.terminatedCount ?? 0 };
2047
+ }, [manager, base]);
2048
+ return { sessions, loading, error, refresh, revoke, revokeAllOthers };
2049
+ }
2050
+ async function revokeSession(sessionId) {
2051
+ const mgr = getGlobalManager();
2052
+ if (!mgr) throw new Error("revokeSession() requires <IQAuthProvider> mounted");
2053
+ const base = mgr.issuerUrl.replace(/\/$/, "");
2054
+ const res = await mgr.fetch(`${base}/api/v1/users/me/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
2055
+ if (!res.ok) {
2056
+ const j = await res.json().catch(() => ({}));
2057
+ throw new Error(j?.error?.message || `HTTP ${res.status}`);
2058
+ }
2059
+ }
2060
+ var MultisessionContext = (0, import_react.createContext)(null);
2061
+ function MultisessionAppSupport({ children }) {
2062
+ const { manager, snapshot } = useCtx();
2063
+ const registryRef = (0, import_react.useRef)(null);
2064
+ if (!registryRef.current) {
2065
+ registryRef.current = new AccountRegistry(manager.appKey);
2066
+ }
2067
+ const registry = registryRef.current;
2068
+ const [version, setVersion] = (0, import_react.useState)(0);
2069
+ (0, import_react.useEffect)(() => {
2070
+ const store = new MultiAccountTokenStore(registry, defaultCookieStore());
2071
+ manager.setTokenStore(store);
2072
+ const unsub = registry.subscribe(() => setVersion((v) => v + 1));
2073
+ return () => {
2074
+ unsub();
2075
+ registry.destroy();
2076
+ };
2077
+ }, [manager]);
2078
+ (0, import_react.useEffect)(() => {
2079
+ if (snapshot.status !== "authenticated" || !snapshot.user) return;
2080
+ const claims = snapshot.claims;
2081
+ const rec = {
2082
+ accountId: snapshot.user.sub,
2083
+ userId: snapshot.user.sub,
2084
+ email: snapshot.user.email,
2085
+ name: snapshot.user.name,
2086
+ tenantId: snapshot.tenantId ?? claims?.tenantId ?? null,
2087
+ addedAt: Date.now()
2088
+ };
2089
+ registry.upsert(rec);
2090
+ if (registry.active() !== rec.accountId) {
2091
+ registry.setActive(rec.accountId);
2092
+ }
2093
+ }, [snapshot.user?.sub, snapshot.tenantId, snapshot.status]);
2094
+ const value = (0, import_react.useMemo)(
2095
+ () => ({ registry, version }),
2096
+ [registry, version]
2097
+ );
2098
+ return (0, import_react.createElement)(MultisessionContext.Provider, { value }, children);
2099
+ }
2100
+ function useMultiCtx() {
2101
+ const ctx = (0, import_react.useContext)(MultisessionContext);
2102
+ if (!ctx) throw new Error("F22 hooks must be used inside <MultisessionAppSupport>");
2103
+ return ctx;
2104
+ }
2105
+ function useAccountList() {
2106
+ const { registry, version } = useMultiCtx();
2107
+ const { isLoaded } = useUser();
2108
+ const accounts = (0, import_react.useMemo)(() => {
2109
+ const active = registry.active();
2110
+ return registry.list().map((a) => ({
2111
+ accountId: a.accountId,
2112
+ userId: a.userId,
2113
+ email: a.email,
2114
+ name: a.name,
2115
+ tenantId: a.tenantId,
2116
+ isActive: a.accountId === active
2117
+ }));
2118
+ }, [registry, version]);
2119
+ return { accounts, loading: !isLoaded };
2120
+ }
2121
+ function useAccountSwitcher() {
2122
+ const { manager } = useCtx();
2123
+ const { registry } = useMultiCtx();
2124
+ const addAccount = (0, import_react.useCallback)(
2125
+ async (opts = {}) => {
2126
+ registry.setActive(null);
2127
+ await signIn(manager, { ...opts, prompt: "login" });
2128
+ },
2129
+ [manager, registry]
2130
+ );
2131
+ const switchTo = (0, import_react.useCallback)(
2132
+ async (accountId) => {
2133
+ const target = registry.get(accountId);
2134
+ if (!target) throw new Error(`Unknown accountId: ${accountId}`);
2135
+ registry.setActive(accountId);
2136
+ const ok = await manager.refresh();
2137
+ if (!ok) {
2138
+ registry.remove(accountId);
2139
+ throw new Error("Could not switch account; session may have been revoked");
2140
+ }
2141
+ },
2142
+ [manager, registry]
2143
+ );
2144
+ const removeAccount = (0, import_react.useCallback)(
2145
+ (accountId) => {
2146
+ registry.remove(accountId);
2147
+ },
2148
+ [registry]
2149
+ );
2150
+ return { addAccount, switchTo, removeAccount };
2151
+ }
887
2152
  function SignedIn({ children }) {
888
2153
  const { isSignedIn, isLoaded } = useUser();
889
2154
  if (!isLoaded || !isSignedIn) return null;
@@ -947,6 +2212,113 @@ function RedirectToSignIn(props = {}) {
947
2212
  }
948
2213
  return null;
949
2214
  }
2215
+ function asArray(v) {
2216
+ if (v == null) return [];
2217
+ return Array.isArray(v) ? v : [v];
2218
+ }
2219
+ function claimRoles(c) {
2220
+ if (!c) return [];
2221
+ const x = c;
2222
+ if (Array.isArray(x.roles)) return x.roles.filter((r) => typeof r === "string");
2223
+ if (typeof x.role === "string") return [x.role];
2224
+ return [];
2225
+ }
2226
+ function claimPermissions(c) {
2227
+ if (!c) return [];
2228
+ const x = c;
2229
+ const out = /* @__PURE__ */ new Set();
2230
+ if (Array.isArray(x.permissions)) {
2231
+ for (const p of x.permissions) if (typeof p === "string") out.add(p);
2232
+ }
2233
+ if (Array.isArray(x.entitlements)) {
2234
+ for (const p of x.entitlements) if (typeof p === "string") out.add(p);
2235
+ }
2236
+ if (typeof x.scope === "string") {
2237
+ for (const s of x.scope.split(/\s+/)) if (s) out.add(s);
2238
+ }
2239
+ return Array.from(out);
2240
+ }
2241
+ function Protect({ role, permission, condition, fallback = null, children }) {
2242
+ const { snapshot } = useCtx();
2243
+ if (snapshot.status !== "authenticated") return (0, import_react.createElement)(import_react.Fragment, null, fallback);
2244
+ const wantedRoles = asArray(role);
2245
+ const wantedPerms = asArray(permission);
2246
+ if (wantedRoles.length) {
2247
+ const have = new Set(claimRoles(snapshot.claims));
2248
+ if (!wantedRoles.some((r) => have.has(r))) return (0, import_react.createElement)(import_react.Fragment, null, fallback);
2249
+ }
2250
+ if (wantedPerms.length) {
2251
+ const have = new Set(claimPermissions(snapshot.claims));
2252
+ if (!wantedPerms.some((p) => have.has(p))) return (0, import_react.createElement)(import_react.Fragment, null, fallback);
2253
+ }
2254
+ if (condition && !condition(snapshot.claims)) return (0, import_react.createElement)(import_react.Fragment, null, fallback);
2255
+ return (0, import_react.createElement)(import_react.Fragment, null, children);
2256
+ }
2257
+ function RedirectToSignedIn({ to = "/", replace = true } = {}) {
2258
+ const { snapshot, allowedReturnOrigins } = useCtx();
2259
+ (0, import_react.useEffect)(() => {
2260
+ if (snapshot.status !== "authenticated") return;
2261
+ if (typeof window === "undefined") return;
2262
+ const safe = sanitizeReturnTo(to, { allowedOrigins: allowedReturnOrigins, fallback: "/" });
2263
+ if (replace) window.location.replace(safe);
2264
+ else window.location.assign(safe);
2265
+ }, [snapshot.status, to, replace, allowedReturnOrigins]);
2266
+ return null;
2267
+ }
2268
+ function useReturnTo(options = {}) {
2269
+ const { allowedReturnOrigins } = useCtx();
2270
+ const paramName = options.paramName ?? "return_to";
2271
+ const storageKey2 = options.storageKey ?? "iqauth_return_to";
2272
+ const fallback = options.fallback ?? "/";
2273
+ return (0, import_react.useMemo)(() => {
2274
+ if (typeof window === "undefined") return fallback;
2275
+ let raw = null;
2276
+ try {
2277
+ const params = new URLSearchParams(window.location.search);
2278
+ raw = params.get(paramName) || params.get("next");
2279
+ } catch {
2280
+ }
2281
+ if (!raw) {
2282
+ try {
2283
+ raw = window.sessionStorage.getItem(storageKey2);
2284
+ } catch {
2285
+ }
2286
+ }
2287
+ const safe = sanitizeReturnTo(raw, { allowedOrigins: allowedReturnOrigins, fallback });
2288
+ if (safe !== fallback) {
2289
+ try {
2290
+ window.sessionStorage.setItem(storageKey2, safe);
2291
+ } catch {
2292
+ }
2293
+ }
2294
+ return safe;
2295
+ }, [paramName, storageKey2, fallback, allowedReturnOrigins]);
2296
+ }
2297
+ function IQAuthReturnToBouncer({ children, ...opts }) {
2298
+ const { snapshot } = useCtx();
2299
+ const returnTo = useReturnTo(opts);
2300
+ (0, import_react.useEffect)(() => {
2301
+ if (snapshot.status !== "authenticated") return;
2302
+ if (typeof window === "undefined") return;
2303
+ window.location.replace(returnTo);
2304
+ }, [snapshot.status, returnTo]);
2305
+ if (snapshot.status === "authenticated") return null;
2306
+ return (0, import_react.createElement)(import_react.Fragment, null, children);
2307
+ }
2308
+ async function preflightReturnTo(args) {
2309
+ const f = args.fetchImpl ?? fetch;
2310
+ const url2 = `${args.iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(args.appKey)}/sign-in-context?return_to=${encodeURIComponent(args.returnTo)}`;
2311
+ try {
2312
+ const r = await f(url2, { credentials: "include" });
2313
+ const body = await r.json().catch(() => null);
2314
+ if (!r.ok || !body?.success || !body.data) {
2315
+ return { ok: false, allowedOrigins: [], reason: body?.error?.message || `HTTP ${r.status}` };
2316
+ }
2317
+ return { ok: !!body.data.returnAllowed, allowedOrigins: body.data.allowedOrigins ?? [], reason: body.data.returnAllowed ? void 0 : "returnTo not in app allowedOrigins" };
2318
+ } catch (err) {
2319
+ return { ok: false, allowedOrigins: [], reason: err.message };
2320
+ }
2321
+ }
950
2322
  function AuthCallback({ onComplete, fallback } = {}) {
951
2323
  const { manager } = useCtx();
952
2324
  (0, import_react.useEffect)(() => {
@@ -980,8 +2352,8 @@ function brandStyle(branding) {
980
2352
  if (branding.fontFamilyHeading) s["--brand-font-heading"] = branding.fontFamilyHeading;
981
2353
  return s;
982
2354
  }
983
- async function jsonFetch(url, init) {
984
- const res = await fetch(url, { ...init, credentials: init?.credentials || "include" });
2355
+ async function jsonFetch(url2, init) {
2356
+ const res = await fetch(url2, { ...init, credentials: init?.credentials || "include" });
985
2357
  const payload = await res.json().catch(() => ({}));
986
2358
  if (!res.ok && payload?.success !== true) {
987
2359
  const message = payload?.error?.message || payload?.error_description || payload?.error || `HTTP ${res.status}`;
@@ -1001,8 +2373,20 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
1001
2373
  }
1002
2374
  let cancelled = false;
1003
2375
  setLoading(true);
1004
- const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
1005
- fetch(url, { credentials: "include" }).then((r) => r.json()).then((payload) => {
2376
+ const url2 = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
2377
+ fetch(url2, { credentials: "include" }).then(async (r) => {
2378
+ const contentType = r.headers.get("content-type") || "";
2379
+ if (!r.ok || !contentType.includes("json")) {
2380
+ const bodyPreview = await r.text().then((t2) => t2.slice(0, 160)).catch(() => "");
2381
+ console.error(
2382
+ `[IQAuth] sign-in-context request failed: ${r.status} ${r.statusText} (content-type: ${contentType || "\u2014"}). URL: ${url2}. Common causes: (1) iqAuthBaseUrl points at the wrong host (it should be your IQAuth issuer, e.g. https://auth.dispositioniq.com \u2014 NOT your own app's domain); (2) the app key "${appKey}" is wrong or revoked; (3) CORS preflight is blocked because this origin isn't in the app's allowed-origins list. Response body preview: ${bodyPreview}`
2383
+ );
2384
+ throw new Error(
2385
+ r.status >= 500 ? "Failed to load sign-in context (server error)" : `Failed to load sign-in context (HTTP ${r.status})`
2386
+ );
2387
+ }
2388
+ return r.json();
2389
+ }).then((payload) => {
1006
2390
  if (cancelled) return;
1007
2391
  if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
1008
2392
  setCtx(payload.data);
@@ -1025,6 +2409,14 @@ var SHELL_CSS = `
1025
2409
  grid-template-columns: 1fr;
1026
2410
  background: var(--brand-bg, #f7f7f6);
1027
2411
  color: var(--brand-text, #0f172a);
2412
+ /* Container queries so the two-pane layout responds to the SDK's
2413
+ RENDERED width, not the viewport. This keeps the form usable when
2414
+ a host app embeds <SignIn/> inside a narrower card or sidebar
2415
+ instead of full-screen \u2014 the previous viewport @media query would
2416
+ happily render the side-by-side hero+form into a 200px container
2417
+ and wrap text one character per line. */
2418
+ container-type: inline-size;
2419
+ container-name: iqauth-sdk;
1028
2420
  }
1029
2421
  .iqauth-sdk-hero { display: none; }
1030
2422
  .iqauth-sdk-pane {
@@ -1074,7 +2466,7 @@ var SHELL_CSS = `
1074
2466
  .iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
1075
2467
  .iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
1076
2468
 
1077
- @media (min-width: 768px) {
2469
+ @container iqauth-sdk (min-width: 768px) {
1078
2470
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
1079
2471
  .iqauth-sdk-hero {
1080
2472
  display: flex; flex-direction: column; justify-content: space-between;
@@ -1095,7 +2487,7 @@ var SHELL_CSS = `
1095
2487
  .iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
1096
2488
  .iqauth-sdk-mobile-brand { display: none; }
1097
2489
  }
1098
- @media (min-width: 1280px) {
2490
+ @container iqauth-sdk (min-width: 1280px) {
1099
2491
  .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
1100
2492
  }
1101
2493
  `;
@@ -1164,34 +2556,34 @@ function flattenBrandingPayload(data) {
1164
2556
  function useResolvedSdkBranding(iqAuthBaseUrl, appId) {
1165
2557
  const ctx = (0, import_react.useContext)(IQAuthContext);
1166
2558
  const resolvedAppId = appId ?? ctx?.manager?.appKey ?? null;
1167
- const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/branding${resolvedAppId ? `?appId=${encodeURIComponent(resolvedAppId)}` : ""}`;
1168
- const cached = sdkBrandingCache.get(url);
2559
+ const url2 = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/branding${resolvedAppId ? `?appId=${encodeURIComponent(resolvedAppId)}` : ""}`;
2560
+ const cached = sdkBrandingCache.get(url2);
1169
2561
  const fresh = cached && Date.now() - cached.ts < SDK_BRANDING_TTL_MS ? cached.data : null;
1170
2562
  const [b, setB] = (0, import_react.useState)(fresh);
1171
2563
  (0, import_react.useEffect)(() => {
1172
2564
  let cancelled = false;
1173
- const entry = sdkBrandingCache.get(url);
1174
- const headers = {};
1175
- if (entry?.rev) headers["If-None-Match"] = `W/"brand-${entry.rev}"`;
2565
+ const entry = sdkBrandingCache.get(url2);
2566
+ const headers2 = {};
2567
+ if (entry?.rev) headers2["If-None-Match"] = `W/"brand-${entry.rev}"`;
1176
2568
  if (entry) setB(entry.data);
1177
- fetch(url, { credentials: "include", headers }).then(async (r) => {
2569
+ fetch(url2, { credentials: "include", headers: headers2 }).then(async (r) => {
1178
2570
  if (cancelled) return;
1179
2571
  if (r.status === 304 && entry) {
1180
- sdkBrandingCache.set(url, { ...entry, ts: Date.now() });
2572
+ sdkBrandingCache.set(url2, { ...entry, ts: Date.now() });
1181
2573
  return;
1182
2574
  }
1183
2575
  if (!r.ok) return;
1184
2576
  const p = await r.json().catch(() => null);
1185
2577
  if (!p?.data) return;
1186
2578
  const flat = flattenBrandingPayload(p.data);
1187
- sdkBrandingCache.set(url, { ts: Date.now(), rev: flat.brandingRev || "", data: flat });
2579
+ sdkBrandingCache.set(url2, { ts: Date.now(), rev: flat.brandingRev || "", data: flat });
1188
2580
  setB(flat);
1189
2581
  }).catch(() => {
1190
2582
  });
1191
2583
  return () => {
1192
2584
  cancelled = true;
1193
2585
  };
1194
- }, [url]);
2586
+ }, [url2]);
1195
2587
  return b;
1196
2588
  }
1197
2589
  function ensureSdkShellStyles() {
@@ -1232,10 +2624,12 @@ function Shell({
1232
2624
  className,
1233
2625
  children,
1234
2626
  title,
1235
- subtitle
2627
+ subtitle,
2628
+ appearance
1236
2629
  }) {
1237
2630
  ensureSdkShellStyles();
1238
- useDocumentBranding(branding, title || "Sign in");
2631
+ const t2 = useT();
2632
+ useDocumentBranding(branding, title || t2("signIn.title"));
1239
2633
  const brandVars = brandStyle(branding);
1240
2634
  const brandName = branding?.brandName || "IQAuth";
1241
2635
  const heroImage = branding?.heroImageUrl || null;
@@ -1246,14 +2640,16 @@ function Shell({
1246
2640
  const heroStyle = heroImage ? { ["--iqauth-sdk-hero-image"]: `url("${heroImage.replace(/"/g, '\\"')}")` } : {};
1247
2641
  const shellStyle = {
1248
2642
  ...brandVars,
1249
- ...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {}
2643
+ ...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {},
2644
+ ...appearance?.elements?.rootBox?.style || {}
1250
2645
  };
2646
+ const ap = appearance?.elements;
1251
2647
  const supportLink = branding?.supportUrl ? branding.supportUrl : branding?.supportEmail ? `mailto:${branding.supportEmail}` : null;
1252
2648
  const hasFooterLinks = !!(branding?.termsUrl || branding?.privacyUrl || supportLink);
1253
2649
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1254
2650
  "div",
1255
2651
  {
1256
- className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`,
2652
+ className: `iqauth-sdk-shell${className ? ` ${className}` : ""}${ap?.rootBox?.className ? ` ${ap.rootBox.className}` : ""}`,
1257
2653
  "data-layout": layout,
1258
2654
  "data-social-style": socialStyle || void 0,
1259
2655
  style: shellStyle,
@@ -1274,13 +2670,34 @@ function Shell({
1274
2670
  ] }),
1275
2671
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { style: { width: "100%", maxWidth: 420 }, children: [
1276
2672
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-mobile-brand", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SdkBrandLogo, { branding, alt: `${brandName} logo`, fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: brandName }) }) }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "iqauth-sdk-card", children: [
1278
- title || subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-card-header", children: [
1279
- title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: title }) : null,
1280
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: subtitle }) : null
1281
- ] }) : null,
1282
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-card-body", children })
1283
- ] }),
2673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2674
+ "section",
2675
+ {
2676
+ className: `iqauth-sdk-card${ap?.card?.className ? ` ${ap.card.className}` : ""}`,
2677
+ style: ap?.card?.style,
2678
+ children: [
2679
+ title || subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2680
+ "div",
2681
+ {
2682
+ className: `iqauth-sdk-card-header${ap?.cardHeader?.className ? ` ${ap.cardHeader.className}` : ""}`,
2683
+ style: ap?.cardHeader?.style,
2684
+ children: [
2685
+ title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: ap?.headerTitle?.className, style: ap?.headerTitle?.style, children: title }) : null,
2686
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: ap?.headerSubtitle?.className, style: ap?.headerSubtitle?.style, children: subtitle }) : null
2687
+ ]
2688
+ }
2689
+ ) : null,
2690
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2691
+ "div",
2692
+ {
2693
+ className: `iqauth-sdk-card-body${ap?.cardBody?.className ? ` ${ap.cardBody.className}` : ""}`,
2694
+ style: ap?.cardBody?.style,
2695
+ children
2696
+ }
2697
+ )
2698
+ ]
2699
+ }
2700
+ ),
1284
2701
  hasFooterLinks || branding?.footerText ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "iqauth-sdk-footer", children: [
1285
2702
  branding?.footerText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "text-brand-footer-sdk", style: { fontSize: 12, opacity: 0.75 }, children: branding.footerText }) : null,
1286
2703
  hasFooterLinks ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-footer-links", children: [
@@ -1371,8 +2788,30 @@ function isSilentSsoEligible(ctx, effectivePrompt) {
1371
2788
  if (!ctx.returnAllowed) return false;
1372
2789
  return true;
1373
2790
  }
1374
- function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt }) {
2791
+ function SignIn(props) {
2792
+ const providerCtx = (0, import_react.useContext)(IQAuthContext);
2793
+ const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
2794
+ const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
2795
+ const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
2796
+ const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
2797
+ const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
2798
+ if (!iqAuthBaseUrl || !appKey) {
2799
+ console.error(
2800
+ "[IQAuth] <SignIn /> could not determine iqAuthBaseUrl/appKey. Either pass them explicitly OR wrap the component in <IQAuthProvider publishableKey=\u2026/>."
2801
+ );
2802
+ }
2803
+ const t2 = useT();
2804
+ const localeBundle = useLocale();
1375
2805
  const { ctx, loading, error } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo);
2806
+ const preflightLoggedRef = (0, import_react.useRef)(false);
2807
+ (0, import_react.useEffect)(() => {
2808
+ if (!ctx || preflightLoggedRef.current) return;
2809
+ if (ctx.returnAllowed) return;
2810
+ preflightLoggedRef.current = true;
2811
+ console.error(
2812
+ `[IQAuth] returnTo "${returnTo}" is NOT in the app's allowed origins. Add it via the IQAuth admin console: Apps \u2192 ${ctx.app.key} \u2192 Allowed Origins. Currently allowed: [${ctx.allowedOrigins.join(", ") || "\u2014"}].`
2813
+ );
2814
+ }, [ctx, returnTo]);
1376
2815
  const [email, setEmail] = (0, import_react.useState)("");
1377
2816
  const [password, setPassword] = (0, import_react.useState)("");
1378
2817
  const [submitting, setSubmitting] = (0, import_react.useState)(false);
@@ -1431,9 +2870,9 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
1431
2870
  body: JSON.stringify({ email, password, ...oidcPayload() })
1432
2871
  });
1433
2872
  const payload = await r.json().catch(() => ({}));
1434
- if (!handlePayload(payload)) setFormError(payload.error_description || payload.error || "Sign-in failed");
2873
+ if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
1435
2874
  } catch (err) {
1436
- setFormError(err.message || "Network error");
2875
+ setFormError(err.message || t(localeBundle, "errors.network"));
1437
2876
  }
1438
2877
  setSubmitting(false);
1439
2878
  };
@@ -1455,7 +2894,7 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
1455
2894
  })
1456
2895
  });
1457
2896
  const payload = await r.json().catch(() => ({}));
1458
- if (!handlePayload(payload)) setFormError(payload.error_description || payload.error || "MFA verification failed");
2897
+ if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
1459
2898
  setSubmitting(false);
1460
2899
  };
1461
2900
  const submitTenant = async (tenantId) => {
@@ -1469,7 +2908,7 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
1469
2908
  body: JSON.stringify({ tenantSelectionToken: tenantSel.token, tenantId, ...oidcPayload() })
1470
2909
  });
1471
2910
  const payload = await r.json().catch(() => ({}));
1472
- if (!handlePayload(payload)) setFormError(payload.error_description || payload.error || "Tenant selection failed");
2911
+ if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
1473
2912
  setSubmitting(false);
1474
2913
  };
1475
2914
  const startGoogleLogin = () => {
@@ -1478,8 +2917,8 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
1478
2917
  return;
1479
2918
  }
1480
2919
  const bridgeUrl = window.location.href;
1481
- const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/google?redirect_uri=${encodeURIComponent(bridgeUrl)}&client_id=${encodeURIComponent(ctx.app.defaultClientId)}`;
1482
- window.location.href = url;
2920
+ const url2 = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/google?redirect_uri=${encodeURIComponent(bridgeUrl)}&client_id=${encodeURIComponent(ctx.app.defaultClientId)}`;
2921
+ window.location.href = url2;
1483
2922
  };
1484
2923
  (0, import_react.useEffect)(() => {
1485
2924
  if (loading || error || !ctx) return;
@@ -1567,36 +3006,36 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
1567
3006
  setOauthExchanging(false);
1568
3007
  })();
1569
3008
  }, [ctx?.app.defaultClientId]);
1570
- if (loading || oauthExchanging) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: ctx?.branding || null, className, title: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026" }) });
1571
- if (error || !ctx) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: null, className, title: "Application unavailable", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error || "Failed to load app context" }) });
1572
- if (!ctx.returnAllowed) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: ctx.branding, className, title: "Invalid redirect", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: `returnTo "${returnTo}" is not in this app's allowed origins.` }) });
3009
+ if (loading || oauthExchanging) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding: ctx?.branding || null, className, title: oauthExchanging ? t2("signIn.submitting") : t2("common.loading"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: oauthExchanging ? t2("signIn.submitting") : t2("common.loading") }) });
3010
+ if (error || !ctx) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding: null, className, title: t2("errors.serverError"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error || t2("errors.serverError") }) });
3011
+ if (!ctx.returnAllowed) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding: ctx.branding, className, title: t2("errors.generic"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: `returnTo "${returnTo}" is not in this app's allowed origins.` }) });
1573
3012
  const silentEligible = isSilentSsoEligible(ctx, effectivePrompt);
1574
3013
  if (silentEligible && silent !== "failed") {
1575
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding: ctx.branding, className, title: "Signing you in\u2026", subtitle: ctx.session ? `Welcome back, ${ctx.session.name || ctx.session.email}.` : void 0, children: [
1576
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: "Resuming your session." }),
1577
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: "Not you? Use a different account" })
3014
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { appearance, branding: ctx.branding, className, title: t2("signIn.resumingSession"), subtitle: ctx.session ? `${t2("signIn.subtitle")}, ${ctx.session.name || ctx.session.email}.` : void 0, children: [
3015
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: t2("signIn.resumingSession") }),
3016
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: t2("signIn.useDifferentAccount") })
1578
3017
  ] });
1579
3018
  }
1580
- const cardTitle = ctx.branding?.loginHeadline || `Sign in to ${ctx.app.name}`;
3019
+ const cardTitle = ctx.branding?.loginHeadline || t2("signIn.titleWithApp", { appName: ctx.app.name });
1581
3020
  const cardSubtitle = ctx.branding?.loginSubheadline || void 0;
1582
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
3021
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { appearance, branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
1583
3022
  formError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: formError }) : null,
1584
- tenantSel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "radiogroup", "aria-label": "Choose tenant", style: { display: "flex", flexDirection: "column", gap: 8 }, children: tenantSel.tenants.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
3023
+ tenantSel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "radiogroup", "aria-label": t2("signIn.selectTenant"), style: { display: "flex", flexDirection: "column", gap: 8 }, children: tenantSel.tenants.map((tn) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1585
3024
  "button",
1586
3025
  {
1587
3026
  type: "button",
1588
- "data-iqauth-tenant": t.tenantId,
1589
- onClick: () => submitTenant(t.tenantId),
3027
+ "data-iqauth-tenant": tn.tenantId,
3028
+ onClick: () => submitTenant(tn.tenantId),
1590
3029
  style: { textAlign: "left", padding: "10px 14px", border: "1px solid rgba(15,23,42,0.15)", borderRadius: 8, background: "transparent", color: "inherit", cursor: "pointer" },
1591
3030
  children: [
1592
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontWeight: 500 }, children: t.tenantName || t.tenantSlug || t.tenantId }),
1593
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: 12, opacity: 0.6 }, children: t.roles.join(", ") })
3031
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontWeight: 500 }, children: tn.tenantName || tn.tenantSlug || tn.tenantId }),
3032
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: 12, opacity: 0.6 }, children: tn.roles.join(", ") })
1594
3033
  ]
1595
3034
  },
1596
- t.tenantId
1597
- )) }) : mfa ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submitMfa, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": "MFA verification", children: [
1598
- !mfa.backup && mfa.methods.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Method", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { style: inputStyle(), value: mfa.selected, onChange: (e) => setMfa({ ...mfa, selected: e.target.value }), children: mfa.methods.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: m, children: m.toUpperCase() }, m)) }) }) : null,
1599
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: mfa.backup ? "Backup code" : "Verification code", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3035
+ tn.tenantId
3036
+ )) }) : mfa ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submitMfa, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": t2("mfa.title"), children: [
3037
+ !mfa.backup && mfa.methods.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("mfa.title"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { style: inputStyle(), value: mfa.selected, onChange: (e) => setMfa({ ...mfa, selected: e.target.value }), children: mfa.methods.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: m, children: m.toUpperCase() }, m)) }) }) : null,
3038
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: mfa.backup ? t2("mfa.backupCodeLabel") : t2("mfa.totpLabel"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1600
3039
  "input",
1601
3040
  {
1602
3041
  style: { ...inputStyle(), fontFamily: "monospace", textAlign: mfa.backup ? "left" : "center", letterSpacing: mfa.backup ? "0.04em" : "0.3em" },
@@ -1606,34 +3045,33 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt
1606
3045
  inputMode: mfa.backup ? "text" : "numeric"
1607
3046
  }
1608
3047
  ) }),
1609
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !mfa.code, children: submitting ? "Verifying\u2026" : "Verify" }),
1610
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GhostButton, { type: "button", onClick: () => setMfa({ ...mfa, backup: !mfa.backup, code: "" }), children: mfa.backup ? "Use verification code" : "Use backup code" })
3048
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !mfa.code, children: submitting ? t2("mfa.submitting") : t2("mfa.submit") }),
3049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GhostButton, { type: "button", onClick: () => setMfa({ ...mfa, backup: !mfa.backup, code: "" }), children: mfa.backup ? t2("mfa.useAuthenticator") : t2("mfa.useBackupCode") })
1611
3050
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1612
3051
  ctx.providers?.google ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1613
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "iqauth-sdk-google-btn", onClick: startGoogleLogin, disabled: submitting, "aria-label": ctx.branding?.googleButtonLabel || "Continue with Google", children: [
3052
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "iqauth-sdk-google-btn", onClick: startGoogleLogin, disabled: submitting, "aria-label": ctx.branding?.googleButtonLabel || t2("signIn.continueWithGoogle"), children: [
1614
3053
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
1615
3054
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "#4285F4", d: "M17.64 9.2c0-.64-.06-1.25-.17-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.71v2.26h2.92a8.78 8.78 0 0 0 2.68-6.61z" }),
1616
3055
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "#34A853", d: "M9 18c2.43 0 4.47-.81 5.96-2.18l-2.92-2.26a5.4 5.4 0 0 1-8.04-2.83H.96v2.33A9 9 0 0 0 9 18z" }),
1617
3056
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "#FBBC05", d: "M3.96 10.71A5.41 5.41 0 0 1 3.68 9c0-.59.1-1.17.29-1.71V4.96H.96a9 9 0 0 0 0 8.08l3-2.33z" }),
1618
3057
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "#EA4335", d: "M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.59A9 9 0 0 0 .96 4.96l3 2.33A5.4 5.4 0 0 1 9 3.58z" })
1619
3058
  ] }),
1620
- ctx.branding?.googleButtonLabel || "Continue with Google"
3059
+ ctx.branding?.googleButtonLabel || t2("signIn.continueWithGoogle")
1621
3060
  ] }),
1622
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "separator", "aria-label": "or", className: "iqauth-sdk-divider", children: "OR" })
3061
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "separator", "aria-label": t2("common.or"), className: "iqauth-sdk-divider", children: t2("signIn.dividerOr").toUpperCase() })
1623
3062
  ] }) : null,
1624
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submitLogin, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": `Sign in to ${ctx.app.name}`, children: [
1625
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Email", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "email", autoComplete: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) }) }),
1626
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Password", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value) }) }),
1627
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? "Signing in\u2026" : "Sign in" })
3063
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submitLogin, style: { display: "flex", flexDirection: "column", gap: 12 }, "aria-label": t2("signIn.titleWithApp", { appName: ctx.app.name }), children: [
3064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("signIn.emailLabel"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "email", autoComplete: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) }) }),
3065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("signIn.passwordLabel"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value) }) }),
3066
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? t2("signIn.submitting") : t2("signIn.submit") })
1628
3067
  ] })
1629
3068
  ] }),
1630
- (silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { marginTop: 12, fontSize: 13, opacity: 0.75 }, children: [
1631
- silent === "failed" && ctx.session ? "Couldn't resume your session. " : null,
1632
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", children: "Use a different account" })
1633
- ] }) : null
3069
+ (silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { marginTop: 12, fontSize: 13, opacity: 0.75 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", children: t2("signIn.useDifferentAccount") }) }) : null
1634
3070
  ] });
1635
3071
  }
1636
3072
  function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
3073
+ const t2 = useT();
3074
+ const localeBundle = useLocale();
1637
3075
  const { ctx, loading } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo || "");
1638
3076
  const [name, setName] = (0, import_react.useState)("");
1639
3077
  const [email, setEmail] = (0, import_react.useState)("");
@@ -1655,22 +3093,19 @@ function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
1655
3093
  setDone(true);
1656
3094
  onSuccess?.();
1657
3095
  } catch (err) {
1658
- setError(err.message);
3096
+ setError(localizeError(localeBundle, err.message));
1659
3097
  }
1660
3098
  setSubmitting(false);
1661
3099
  };
1662
- if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: null, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Loading\u2026" }) });
1663
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding: ctx?.branding || null, className, children: [
1664
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { fontSize: 20, fontWeight: 600, margin: "0 0 12px" }, children: "Create your account" }),
1665
- done ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Account created. Check your email for verification." }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1666
- error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error }) : null,
1667
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Full name", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true }) }),
1668
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Email", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "email", autoComplete: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true }) }),
1669
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Organization (optional)", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
1670
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Password", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: password, onChange: (e) => setPassword(e.target.value), required: true }) }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !email || !password || !name, children: submitting ? "Creating\u2026" : "Create account" })
1672
- ] })
1673
- ] });
3100
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: null, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t2("common.loading") }) });
3101
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: ctx?.branding || null, className, title: t2("signUp.title"), children: done ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t2("magicLink.subtitle", { email }) }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
3102
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error }) : null,
3103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("signUp.nameLabel"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true }) }),
3104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("signUp.emailLabel"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "email", autoComplete: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true }) }),
3105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: `${t2("signUp.tenantNameLabel")} (${t2("common.optional")})`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
3106
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("signUp.passwordLabel"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: password, onChange: (e) => setPassword(e.target.value), required: true }) }),
3107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !email || !password || !name, children: submitting ? t2("signUp.submitting") : t2("signUp.submit") })
3108
+ ] }) });
1674
3109
  }
1675
3110
  function initialsOf(name, email) {
1676
3111
  const src = name || email || "?";
@@ -1679,6 +3114,7 @@ function initialsOf(name, email) {
1679
3114
  return src.substring(0, 2).toUpperCase();
1680
3115
  }
1681
3116
  function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
3117
+ const t2 = useT();
1682
3118
  const [user, setUser] = (0, import_react.useState)(null);
1683
3119
  const [open, setOpen] = (0, import_react.useState)(false);
1684
3120
  const branding = useResolvedSdkBranding(iqAuthBaseUrl);
@@ -1749,7 +3185,7 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1749
3185
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
1750
3186
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: user.email })
1751
3187
  ] }),
1752
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: "Account" }),
3188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: t2("userButton.manageAccount") }),
1753
3189
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1754
3190
  "button",
1755
3191
  {
@@ -1757,7 +3193,7 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1757
3193
  type: "button",
1758
3194
  onClick: signOut2,
1759
3195
  style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
1760
- children: "Sign out"
3196
+ children: t2("userButton.signOut")
1761
3197
  }
1762
3198
  )
1763
3199
  ] }) : null
@@ -1766,185 +3202,1187 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1766
3202
  );
1767
3203
  }
1768
3204
  function UserProfile({ iqAuthBaseUrl, className }) {
3205
+ const t2 = useT();
3206
+ const localeBundle = useLocale();
1769
3207
  const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1770
3208
  const [user, setUser] = (0, import_react.useState)(null);
1771
3209
  const [oldPassword, setOldPassword] = (0, import_react.useState)("");
1772
3210
  const [newPassword, setNewPassword] = (0, import_react.useState)("");
1773
3211
  const [pwState, setPwState] = (0, import_react.useState)({ submitting: false, message: "", error: "" });
1774
3212
  const [sessions, setSessions] = (0, import_react.useState)([]);
3213
+ const [revokeAllBusy, setRevokeAllBusy] = (0, import_react.useState)(false);
3214
+ const loadSessions = () => {
3215
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions`, { credentials: "include" }).then((r) => r.ok ? r.json() : Promise.reject(r)).then((p) => setSessions(p?.data?.sessions || [])).catch(() => {
3216
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions`, { credentials: "include" }).then((r) => r.json()).then((p) => setSessions(p?.data?.sessions || p?.data || [])).catch(() => {
3217
+ });
3218
+ });
3219
+ };
3220
+ (0, import_react.useEffect)(() => {
3221
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
3222
+ if (p?.data) setUser(p.data);
3223
+ }).catch(() => {
3224
+ });
3225
+ loadSessions();
3226
+ }, [iqAuthBaseUrl]);
3227
+ const changePassword = async (e) => {
3228
+ e.preventDefault();
3229
+ setPwState({ submitting: true, message: "", error: "" });
3230
+ try {
3231
+ await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/password/change`, {
3232
+ method: "POST",
3233
+ headers: { "Content-Type": "application/json" },
3234
+ body: JSON.stringify({ oldPassword, newPassword })
3235
+ });
3236
+ setPwState({ submitting: false, message: t(localeBundle, "userProfile.passwordUpdated"), error: "" });
3237
+ setOldPassword("");
3238
+ setNewPassword("");
3239
+ } catch (err) {
3240
+ setPwState({ submitting: false, message: "", error: localizeError(localeBundle, err.message) });
3241
+ }
3242
+ };
3243
+ const revoke = async (sessionId) => {
3244
+ const newRes = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
3245
+ if (!newRes.ok && newRes.status === 404) {
3246
+ await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
3247
+ }
3248
+ setSessions((prev) => prev.filter((s) => s.id !== sessionId));
3249
+ };
3250
+ const revokeAllOthers = async () => {
3251
+ setRevokeAllBusy(true);
3252
+ try {
3253
+ await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/users/me/sessions/revoke-all-others`, { method: "POST", credentials: "include" });
3254
+ setSessions((prev) => prev.filter((s) => s.isCurrent));
3255
+ } finally {
3256
+ setRevokeAllBusy(false);
3257
+ }
3258
+ };
3259
+ if (!user) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t2("common.loading") }) });
3260
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding, className, title: t2("userProfile.title"), children: [
3261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-profile", style: { marginBottom: 20 }, children: [
3262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: t2("userProfile.profileTab") }),
3263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
3264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
3265
+ t2("common.name"),
3266
+ ":"
3267
+ ] }),
3268
+ " ",
3269
+ user.name
3270
+ ] }),
3271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
3272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
3273
+ t2("common.email"),
3274
+ ":"
3275
+ ] }),
3276
+ " ",
3277
+ user.email
3278
+ ] })
3279
+ ] }),
3280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-pw", style: { marginBottom: 20 }, children: [
3281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-pw", style: { fontSize: 14, fontWeight: 600 }, children: t2("userProfile.changePassword") }),
3282
+ pwState.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: pwState.error }) : null,
3283
+ pwState.message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", style: { fontSize: 13, color: "#047857" }, children: pwState.message }) : null,
3284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: changePassword, style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
3285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("userProfile.currentPassword"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "current-password", value: oldPassword, onChange: (e) => setOldPassword(e.target.value), required: true }) }),
3286
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: t2("userProfile.newPassword"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: newPassword, onChange: (e) => setNewPassword(e.target.value), required: true }) }),
3287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: pwState.submitting || !oldPassword || !newPassword, children: pwState.submitting ? t2("common.saving") : t2("userProfile.changePassword") })
3288
+ ] })
3289
+ ] }),
3290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-sessions", children: [
3291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
3292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-sessions", style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: t2("userProfile.sessionsTab") }),
3293
+ sessions.some((s) => !s.isCurrent) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3294
+ "button",
3295
+ {
3296
+ type: "button",
3297
+ disabled: revokeAllBusy,
3298
+ onClick: revokeAllOthers,
3299
+ style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "4px 10px", cursor: "pointer" },
3300
+ children: revokeAllBusy ? t2("common.submitting") : t2("userProfile.revokeAllOthers")
3301
+ }
3302
+ )
3303
+ ] }),
3304
+ sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6 }, children: t2("userProfile.sessionsEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", padding: 0, margin: "8px 0 0", display: "flex", flexDirection: "column", gap: 6 }, children: sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 13, padding: "8px 10px", background: s.isCurrent ? "#ecfdf5" : "#f8fafc", borderRadius: 6, border: s.isCurrent ? "1px solid #a7f3d0" : "1px solid transparent" }, children: [
3305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
3306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontWeight: 500 }, children: [
3307
+ s.device || s.userAgent || s.deviceName || "\u2014",
3308
+ s.isCurrent && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { marginLeft: 8, fontSize: 11, color: "#047857" }, children: [
3309
+ "(",
3310
+ t2("userProfile.thisDevice"),
3311
+ ")"
3312
+ ] })
3313
+ ] }),
3314
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 11, opacity: 0.65 }, children: [
3315
+ s.ip || "\u2014",
3316
+ s.lastActiveAt ? ` \xB7 ${new Date(s.lastActiveAt).toLocaleString()}` : ""
3317
+ ] })
3318
+ ] }),
3319
+ !s.isCurrent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => revoke(s.id), style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "2px 8px", cursor: "pointer" }, children: t2("userProfile.revokeSession") })
3320
+ ] }, s.id)) })
3321
+ ] })
3322
+ ] });
3323
+ }
3324
+ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, appearance: _appearance, className }) {
3325
+ const t2 = useT();
3326
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
3327
+ const accent = branding?.accentColor || "#6366f1";
3328
+ const [memberships, setMemberships] = (0, import_react.useState)([]);
3329
+ const [activeTenantId, setActiveTenantId] = (0, import_react.useState)(null);
3330
+ const [open, setOpen] = (0, import_react.useState)(false);
3331
+ (0, import_react.useEffect)(() => {
3332
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
3333
+ if (p?.data?.tenantId) setActiveTenantId(p.data.tenantId);
3334
+ }).catch(() => {
3335
+ });
3336
+ fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants/memberships`, { credentials: "include" }).then((r) => r.json()).then((p) => setMemberships(p?.data?.memberships || p?.data || [])).catch(() => {
3337
+ });
3338
+ }, [iqAuthBaseUrl]);
3339
+ const switchTo = async (tenantId) => {
3340
+ try {
3341
+ await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/select-tenant`, {
3342
+ method: "POST",
3343
+ headers: { "Content-Type": "application/json" },
3344
+ body: JSON.stringify({ tenantId })
3345
+ });
3346
+ setActiveTenantId(tenantId);
3347
+ setOpen(false);
3348
+ onSwitched?.(tenantId);
3349
+ } catch {
3350
+ }
3351
+ };
3352
+ const active = memberships.find((m) => m.tenantId === activeTenantId);
3353
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
3354
+ "div",
3355
+ {
3356
+ className,
3357
+ style: { position: "relative", display: "inline-block", ...brandStyle(branding) },
3358
+ "data-iqauth-sdk-orgswitcher": "",
3359
+ "data-branding-rev": branding?.brandingRev || "",
3360
+ children: [
3361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3362
+ "button",
3363
+ {
3364
+ type: "button",
3365
+ "aria-haspopup": "menu",
3366
+ "aria-expanded": open,
3367
+ onClick: () => setOpen((o) => !o),
3368
+ style: { background: "transparent", border: `1px solid ${accent}55`, color: branding?.primaryColor || "#0f172a", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
3369
+ children: active?.tenantName || active?.tenantSlug || t2("orgSwitcher.label")
3370
+ }
3371
+ ),
3372
+ open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "menu", style: {
3373
+ position: "absolute",
3374
+ left: 0,
3375
+ top: 36,
3376
+ minWidth: 220,
3377
+ background: "#fff",
3378
+ border: "1px solid rgba(15,23,42,0.12)",
3379
+ borderRadius: 8,
3380
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
3381
+ padding: 8,
3382
+ zIndex: 100
3383
+ }, children: memberships.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: t2("orgSwitcher.noOrgs") }) : memberships.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
3384
+ "button",
3385
+ {
3386
+ role: "menuitem",
3387
+ type: "button",
3388
+ onClick: () => switchTo(m.tenantId),
3389
+ style: {
3390
+ display: "block",
3391
+ width: "100%",
3392
+ textAlign: "left",
3393
+ padding: "8px 10px",
3394
+ background: m.tenantId === activeTenantId ? `${accent}14` : "transparent",
3395
+ border: "none",
3396
+ borderRadius: 4,
3397
+ cursor: "pointer",
3398
+ fontSize: 13,
3399
+ color: "#0f172a"
3400
+ },
3401
+ children: [
3402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
3403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, opacity: 0.6 }, children: m.roles.join(", ") })
3404
+ ]
3405
+ },
3406
+ m.tenantId
3407
+ )) }) : null
3408
+ ]
3409
+ }
3410
+ );
3411
+ }
3412
+ function useImpersonation() {
3413
+ const { snapshot } = useCtx();
3414
+ return (0, import_react.useMemo)(() => {
3415
+ const claims = snapshot.claims;
3416
+ const isImpersonating = claims?.purpose === "impersonation" && !!claims?.act?.sub;
3417
+ return {
3418
+ isImpersonating,
3419
+ actor: isImpersonating ? claims.act : null,
3420
+ target: isImpersonating ? snapshot.user : null
3421
+ };
3422
+ }, [snapshot]);
3423
+ }
3424
+ function ImpersonationBanner({ render, onExit, className, style } = {}) {
3425
+ const t2 = useT();
3426
+ const info = useImpersonation();
3427
+ const { manager } = useCtx();
3428
+ const exit = (0, import_react.useCallback)(async () => {
3429
+ if (onExit) return void onExit();
3430
+ const { exitImpersonation: exitImpersonation2 } = await Promise.resolve().then(() => (init_reverify(), reverify_exports));
3431
+ const restored = exitImpersonation2(manager);
3432
+ if (restored) return;
3433
+ const { signOut: signOut2 } = await Promise.resolve().then(() => (init_signIn(), signIn_exports));
3434
+ await signOut2(manager);
3435
+ }, [manager, onExit]);
3436
+ if (!info.isImpersonating) return null;
3437
+ if (render) return (0, import_react.createElement)(import_react.Fragment, null, render({ ...info, exit }));
3438
+ const targetLabel = info.target?.email || info.target?.name || info.target?.sub || "user";
3439
+ const _actorLabel = info.actor?.email || info.actor?.name || info.actor?.sub || "admin";
3440
+ void _actorLabel;
3441
+ return (0, import_react.createElement)(
3442
+ "div",
3443
+ {
3444
+ role: "alert",
3445
+ className,
3446
+ style: {
3447
+ position: "sticky",
3448
+ top: 0,
3449
+ left: 0,
3450
+ right: 0,
3451
+ zIndex: 9999,
3452
+ background: "#b91c1c",
3453
+ color: "#fff",
3454
+ padding: "8px 16px",
3455
+ display: "flex",
3456
+ alignItems: "center",
3457
+ justifyContent: "space-between",
3458
+ fontSize: 13,
3459
+ fontFamily: "system-ui, sans-serif",
3460
+ ...style
3461
+ }
3462
+ },
3463
+ (0, import_react.createElement)(
3464
+ "span",
3465
+ null,
3466
+ t2("impersonation.banner", { targetEmail: targetLabel })
3467
+ ),
3468
+ (0, import_react.createElement)(
3469
+ "button",
3470
+ {
3471
+ type: "button",
3472
+ onClick: exit,
3473
+ style: {
3474
+ background: "rgba(255,255,255,0.18)",
3475
+ color: "#fff",
3476
+ border: "1px solid rgba(255,255,255,0.4)",
3477
+ borderRadius: 4,
3478
+ padding: "4px 10px",
3479
+ cursor: "pointer",
3480
+ fontSize: 12
3481
+ }
3482
+ },
3483
+ t2("impersonation.exit")
3484
+ )
3485
+ );
3486
+ }
3487
+ function useReverification(fn, opts = {}) {
3488
+ const { manager } = useCtx();
3489
+ const level = opts.level ?? "password";
3490
+ return (async (...args) => {
3491
+ let token = null;
3492
+ let res = await fn(token)(...args);
3493
+ const code = await peekErrorCode(res);
3494
+ const isReverifyError = res.status === 401 && code && (code === "REVERIFICATION_REQUIRED" || code === "REVERIFICATION_EXPIRED" || code === "REVERIFICATION_INVALID" || code === "REVERIFICATION_USED" || code === "REVERIFICATION_LEVEL_INSUFFICIENT");
3495
+ if (!isReverifyError) return res;
3496
+ const prompt = opts.prompt ?? defaultReverifyPrompt;
3497
+ const credentials = await prompt(level);
3498
+ if (!credentials) {
3499
+ throw new Error("Reverification cancelled");
3500
+ }
3501
+ const { reverify: reverify2 } = await Promise.resolve().then(() => (init_reverify(), reverify_exports));
3502
+ const minted = await reverify2(manager, { level, ...credentials });
3503
+ token = minted.token;
3504
+ res = await fn(token)(...args);
3505
+ return res;
3506
+ });
3507
+ }
3508
+ async function peekErrorCode(res) {
3509
+ try {
3510
+ const cloned = res.clone();
3511
+ const body = await cloned.json();
3512
+ return body?.error?.code ?? null;
3513
+ } catch {
3514
+ return null;
3515
+ }
3516
+ }
3517
+ async function defaultReverifyPrompt(level) {
3518
+ if (typeof window === "undefined") return null;
3519
+ if (level === "password") {
3520
+ const password = window.prompt("Confirm your password to continue");
3521
+ if (!password) return null;
3522
+ return { password };
3523
+ }
3524
+ const totp = window.prompt("Enter your MFA code to continue");
3525
+ if (!totp) return null;
3526
+ return { totp, method: "totp" };
3527
+ }
3528
+ function slugify(input) {
3529
+ return input.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
3530
+ }
3531
+ function CreateOrganization({ iqAuthBaseUrl, onCreated, redirectUrl, unstyled, appearance, className }) {
3532
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
3533
+ const [name, setName] = (0, import_react.useState)("");
3534
+ const [slug, setSlug] = (0, import_react.useState)("");
3535
+ const [slugTouched, setSlugTouched] = (0, import_react.useState)(false);
3536
+ const [submitting, setSubmitting] = (0, import_react.useState)(false);
3537
+ const [error, setError] = (0, import_react.useState)(null);
3538
+ const [created, setCreated] = (0, import_react.useState)(null);
3539
+ const [slugCheck, setSlugCheck] = (0, import_react.useState)({ status: "idle" });
3540
+ (0, import_react.useEffect)(() => {
3541
+ if (!slugTouched) setSlug(slugify(name));
3542
+ }, [name, slugTouched]);
3543
+ (0, import_react.useEffect)(() => {
3544
+ const s = slug.trim().toLowerCase();
3545
+ if (!s) {
3546
+ setSlugCheck({ status: "idle" });
3547
+ return;
3548
+ }
3549
+ if (!/^[a-z0-9-]{2,64}$/.test(s)) {
3550
+ setSlugCheck({ status: "invalid", checked: s });
3551
+ return;
3552
+ }
3553
+ setSlugCheck({ status: "checking", checked: s });
3554
+ const handle = setTimeout(async () => {
3555
+ try {
3556
+ const res = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants/check-slug?slug=${encodeURIComponent(s)}`, {
3557
+ credentials: "include",
3558
+ headers: { Accept: "application/json" }
3559
+ });
3560
+ const body = await res.json().catch(() => ({}));
3561
+ const data = body.data;
3562
+ if (!data) {
3563
+ setSlugCheck({ status: "idle", checked: s });
3564
+ return;
3565
+ }
3566
+ if (data.reason === "INVALID_FORMAT") {
3567
+ setSlugCheck({ status: "invalid", checked: s });
3568
+ return;
3569
+ }
3570
+ setSlugCheck({ status: data.available ? "available" : "taken", checked: s });
3571
+ } catch {
3572
+ setSlugCheck({ status: "idle", checked: s });
3573
+ }
3574
+ }, 350);
3575
+ return () => clearTimeout(handle);
3576
+ }, [slug, iqAuthBaseUrl]);
3577
+ const submit = async (e) => {
3578
+ e.preventDefault();
3579
+ setSubmitting(true);
3580
+ setError(null);
3581
+ try {
3582
+ const res = await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants`, {
3583
+ method: "POST",
3584
+ headers: { "Content-Type": "application/json" },
3585
+ credentials: "include",
3586
+ body: JSON.stringify({ name: name.trim(), slug: slug.trim() })
3587
+ });
3588
+ const payload = res;
3589
+ const tenant = payload.data ?? payload;
3590
+ const next = { id: tenant.id, name: tenant.name, slug: tenant.slug };
3591
+ setCreated(next);
3592
+ onCreated?.(next);
3593
+ setName("");
3594
+ setSlug("");
3595
+ setSlugTouched(false);
3596
+ if (redirectUrl && typeof window !== "undefined") {
3597
+ const url2 = typeof redirectUrl === "function" ? redirectUrl(next) : redirectUrl;
3598
+ if (url2) window.location.assign(url2);
3599
+ }
3600
+ } catch (err) {
3601
+ setError(err instanceof Error ? err.message : "Failed to create organization");
3602
+ } finally {
3603
+ setSubmitting(false);
3604
+ }
3605
+ };
3606
+ const form = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, "data-iqauth-sdk-create-org": "", "aria-labelledby": "iqauth-create-org-heading", children: [
3607
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error }) : null,
3608
+ created ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "status", style: { fontSize: 13, color: "#047857" }, children: [
3609
+ "Organization \u201C",
3610
+ created.name,
3611
+ "\u201D created."
3612
+ ] }) : null,
3613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Organization name", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-create-org-name", autoFocus: true, style: inputStyle(), value: name, onChange: (e) => setName(e.target.value), required: true, minLength: 2, "aria-required": "true" }) }),
3614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Field, { label: "Organization slug", children: [
3615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3616
+ "input",
3617
+ {
3618
+ "data-testid": "input-create-org-slug",
3619
+ style: { ...inputStyle(), fontFamily: "monospace" },
3620
+ value: slug,
3621
+ onChange: (e) => {
3622
+ setSlugTouched(true);
3623
+ setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "-"));
3624
+ },
3625
+ required: true,
3626
+ pattern: "[a-z0-9-]+",
3627
+ minLength: 2,
3628
+ "aria-required": "true",
3629
+ "aria-describedby": "iqauth-create-org-slug-hint",
3630
+ "aria-invalid": slugCheck.status === "taken" || slugCheck.status === "invalid"
3631
+ }
3632
+ ),
3633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3634
+ "p",
3635
+ {
3636
+ id: "iqauth-create-org-slug-hint",
3637
+ "data-testid": "text-create-org-slug-status",
3638
+ role: "status",
3639
+ "aria-live": "polite",
3640
+ style: { fontSize: 12, marginTop: 4, color: slugCheck.status === "taken" || slugCheck.status === "invalid" ? "#b91c1c" : slugCheck.status === "available" ? "#047857" : "inherit", opacity: slugCheck.status === "checking" ? 0.6 : 1 },
3641
+ children: slugCheck.status === "checking" ? "Checking availability\u2026" : slugCheck.status === "available" ? "Slug is available." : slugCheck.status === "taken" ? "That slug is taken." : slugCheck.status === "invalid" ? "Slugs must be 2\u201364 chars, lowercase letters/numbers/hyphens." : "We'll auto-generate a slug from the name; you can override it."
3642
+ }
3643
+ )
3644
+ ] }),
3645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3646
+ PrimaryButton,
3647
+ {
3648
+ "data-testid": "button-create-org-submit",
3649
+ type: "submit",
3650
+ disabled: submitting || !name.trim() || !slug.trim() || slugCheck.status === "taken" || slugCheck.status === "invalid" || slugCheck.status === "checking",
3651
+ children: submitting ? "Creating\u2026" : "Create organization"
3652
+ }
3653
+ )
3654
+ ] });
3655
+ if (unstyled) {
3656
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, "data-iqauth-sdk-create-org-bare": "", children: [
3657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-create-org-heading", style: { fontSize: 14, fontWeight: 600, margin: "0 0 12px" }, children: "Create organization" }),
3658
+ form
3659
+ ] });
3660
+ }
3661
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding, className, title: "Create organization", subtitle: "Spin up a new tenant for this app.", children: form });
3662
+ }
3663
+ function OrganizationProfile({ iqAuthBaseUrl, tenantId: tenantIdProp, tabs, onDeleted, appearance, className }) {
3664
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
3665
+ const baseUrl = iqAuthBaseUrl.replace(/\/$/, "");
3666
+ const visibleTabs = tabs && tabs.length > 0 ? tabs : ["general", "members", "invitations", "danger"];
3667
+ const [activeTab, setActiveTab] = (0, import_react.useState)(visibleTabs[0]);
3668
+ const [tenantId, setTenantId] = (0, import_react.useState)(tenantIdProp || null);
3669
+ const [tenant, setTenant] = (0, import_react.useState)(null);
3670
+ const [members, setMembers] = (0, import_react.useState)([]);
3671
+ const [pendingInvites, setPendingInvites] = (0, import_react.useState)([]);
3672
+ const [loading, setLoading] = (0, import_react.useState)(true);
3673
+ const [invitesLoading, setInvitesLoading] = (0, import_react.useState)(false);
3674
+ const [error, setError] = (0, import_react.useState)(null);
3675
+ const [renameValue, setRenameValue] = (0, import_react.useState)("");
3676
+ const [slugValue, setSlugValue] = (0, import_react.useState)("");
3677
+ const [renameSubmitting, setRenameSubmitting] = (0, import_react.useState)(false);
3678
+ const [inviteEmail, setInviteEmail] = (0, import_react.useState)("");
3679
+ const [inviteRole, setInviteRole] = (0, import_react.useState)("tenant_member");
3680
+ const [inviteSubmitting, setInviteSubmitting] = (0, import_react.useState)(false);
3681
+ const [actionMessage, setActionMessage] = (0, import_react.useState)(null);
3682
+ const [confirmDeleteText, setConfirmDeleteText] = (0, import_react.useState)("");
3683
+ const [confirmDeletePassword, setConfirmDeletePassword] = (0, import_react.useState)("");
3684
+ const [deleteSubmitting, setDeleteSubmitting] = (0, import_react.useState)(false);
3685
+ const { user } = useUser();
3686
+ const callerRole = user?.role || null;
3687
+ const callerIsAdmin = callerRole === "tenant_admin" || callerRole === "platform_admin";
3688
+ const visibleTabsFiltered = visibleTabs.filter((t2) => t2 !== "danger" || callerIsAdmin);
3689
+ (0, import_react.useEffect)(() => {
3690
+ let cancelled = false;
3691
+ (async () => {
3692
+ try {
3693
+ let tid = tenantIdProp || null;
3694
+ if (!tid) {
3695
+ const me = await fetch(`${baseUrl}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json());
3696
+ tid = me?.data?.tenantId || null;
3697
+ }
3698
+ if (!tid) {
3699
+ if (!cancelled) {
3700
+ setError("No active tenant");
3701
+ setLoading(false);
3702
+ }
3703
+ return;
3704
+ }
3705
+ const t2 = await fetch(`${baseUrl}/api/v1/tenants/${tid}`, { credentials: "include" }).then((r) => r.json());
3706
+ const m = await fetch(`${baseUrl}/api/v1/tenants/${tid}/users`, { credentials: "include" }).then((r) => r.json());
3707
+ if (cancelled) return;
3708
+ setTenantId(tid);
3709
+ setTenant(t2?.data ? { id: t2.data.id, name: t2.data.name, slug: t2.data.slug } : null);
3710
+ setRenameValue(t2?.data?.name || "");
3711
+ setSlugValue(t2?.data?.slug || "");
3712
+ const rows = m?.data || [];
3713
+ setMembers(rows.map((row) => ({
3714
+ userId: String(row.userId ?? row.user?.id ?? row.id ?? ""),
3715
+ email: String(row.email ?? row.user?.email ?? ""),
3716
+ name: String(row.name ?? row.user?.name ?? ""),
3717
+ role: String(row.role ?? row.roles?.[0] ?? "tenant_member"),
3718
+ joinedAt: row.joinedAt ?? row.createdAt
3719
+ })));
3720
+ } catch (err) {
3721
+ if (!cancelled) setError(err instanceof Error ? err.message : "Failed to load organization");
3722
+ } finally {
3723
+ if (!cancelled) setLoading(false);
3724
+ }
3725
+ })();
3726
+ return () => {
3727
+ cancelled = true;
3728
+ };
3729
+ }, [baseUrl, tenantIdProp]);
3730
+ const loadInvites = async (tid) => {
3731
+ setInvitesLoading(true);
3732
+ try {
3733
+ const r = await fetch(`${baseUrl}/api/v1/invites?tenantId=${encodeURIComponent(tid)}&status=pending`, { credentials: "include" }).then((res) => res.json());
3734
+ const rows = r?.data || [];
3735
+ setPendingInvites(rows.map((inv) => ({
3736
+ id: String(inv.id),
3737
+ email: String(inv.email),
3738
+ role: String(inv.role),
3739
+ status: String(inv.status),
3740
+ expiresAt: inv.expiresAt,
3741
+ createdAt: inv.createdAt
3742
+ })));
3743
+ } catch (err) {
3744
+ setError(err instanceof Error ? err.message : "Failed to load invitations");
3745
+ } finally {
3746
+ setInvitesLoading(false);
3747
+ }
3748
+ };
1775
3749
  (0, import_react.useEffect)(() => {
1776
- fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
1777
- if (p?.data) setUser(p.data);
1778
- }).catch(() => {
1779
- });
1780
- fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions`, { credentials: "include" }).then((r) => r.json()).then((p) => setSessions(p?.data?.sessions || p?.data || [])).catch(() => {
1781
- });
1782
- }, [iqAuthBaseUrl]);
1783
- const changePassword = async (e) => {
3750
+ if (activeTab === "invitations" && tenantId) loadInvites(tenantId);
3751
+ }, [activeTab, tenantId]);
3752
+ const reloadMembers = async () => {
3753
+ if (!tenantId) return;
3754
+ const m = await fetch(`${baseUrl}/api/v1/tenants/${tenantId}/users`, { credentials: "include" }).then((r) => r.json());
3755
+ const rows = m?.data || [];
3756
+ setMembers(rows.map((row) => ({
3757
+ userId: String(row.userId ?? row.user?.id ?? row.id ?? ""),
3758
+ email: String(row.email ?? row.user?.email ?? ""),
3759
+ name: String(row.name ?? row.user?.name ?? ""),
3760
+ role: String(row.role ?? row.roles?.[0] ?? "tenant_member"),
3761
+ joinedAt: row.joinedAt ?? row.createdAt
3762
+ })));
3763
+ };
3764
+ const submitRename = async (e) => {
1784
3765
  e.preventDefault();
1785
- setPwState({ submitting: true, message: "", error: "" });
3766
+ if (!tenantId) return;
3767
+ setRenameSubmitting(true);
3768
+ setError(null);
1786
3769
  try {
1787
- await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/password/change`, {
3770
+ const body = {};
3771
+ if (renameValue.trim() && renameValue.trim() !== tenant?.name) body.name = renameValue.trim();
3772
+ if (slugValue.trim() && slugValue.trim() !== tenant?.slug) body.slug = slugValue.trim();
3773
+ if (Object.keys(body).length === 0) {
3774
+ setRenameSubmitting(false);
3775
+ return;
3776
+ }
3777
+ const res = await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}`, {
3778
+ method: "PATCH",
3779
+ headers: { "Content-Type": "application/json" },
3780
+ credentials: "include",
3781
+ body: JSON.stringify(body)
3782
+ });
3783
+ const t2 = res?.data ?? res;
3784
+ setTenant({ id: t2.id, name: t2.name, slug: t2.slug });
3785
+ setActionMessage("Organization saved.");
3786
+ } catch (err) {
3787
+ setError(err instanceof Error ? err.message : "Failed to save");
3788
+ } finally {
3789
+ setRenameSubmitting(false);
3790
+ }
3791
+ };
3792
+ const submitInvite = async (e) => {
3793
+ e.preventDefault();
3794
+ if (!tenantId) return;
3795
+ setInviteSubmitting(true);
3796
+ setError(null);
3797
+ try {
3798
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/invite`, {
1788
3799
  method: "POST",
1789
3800
  headers: { "Content-Type": "application/json" },
1790
- body: JSON.stringify({ oldPassword, newPassword })
3801
+ credentials: "include",
3802
+ body: JSON.stringify({ email: inviteEmail.trim(), role: inviteRole })
1791
3803
  });
1792
- setPwState({ submitting: false, message: "Password updated.", error: "" });
1793
- setOldPassword("");
1794
- setNewPassword("");
3804
+ setActionMessage(`Invitation sent to ${inviteEmail.trim()}.`);
3805
+ setInviteEmail("");
3806
+ if (activeTab === "invitations") await loadInvites(tenantId);
1795
3807
  } catch (err) {
1796
- setPwState({ submitting: false, message: "", error: err.message });
3808
+ setError(err instanceof Error ? err.message : "Failed to invite");
3809
+ } finally {
3810
+ setInviteSubmitting(false);
1797
3811
  }
1798
3812
  };
1799
- const revoke = async (sessionId) => {
1800
- await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
1801
- setSessions((prev) => prev.filter((s) => s.id !== sessionId));
3813
+ const changeRole = async (userId, role) => {
3814
+ if (!tenantId) return;
3815
+ try {
3816
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/${userId}/role`, {
3817
+ method: "PATCH",
3818
+ headers: { "Content-Type": "application/json" },
3819
+ credentials: "include",
3820
+ body: JSON.stringify({ role })
3821
+ });
3822
+ await reloadMembers();
3823
+ setActionMessage("Role updated.");
3824
+ } catch (err) {
3825
+ setError(err instanceof Error ? err.message : "Failed to update role");
3826
+ }
1802
3827
  };
1803
- if (!user) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Loading account\u2026" }) });
1804
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding, className, children: [
1805
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { fontSize: 20, fontWeight: 600, margin: "0 0 12px" }, children: "Your account" }),
1806
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-profile", style: { marginBottom: 20 }, children: [
1807
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: "Profile" }),
1808
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
1809
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Name:" }),
1810
- " ",
1811
- user.name
1812
- ] }),
1813
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: 13, margin: "4px 0" }, children: [
1814
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Email:" }),
1815
- " ",
1816
- user.email
1817
- ] })
1818
- ] }),
1819
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-pw", style: { marginBottom: 20 }, children: [
1820
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-pw", style: { fontSize: 14, fontWeight: 600 }, children: "Change password" }),
1821
- pwState.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: pwState.error }) : null,
1822
- pwState.message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", style: { fontSize: 13, color: "#047857" }, children: pwState.message }) : null,
1823
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: changePassword, style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
1824
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Current password", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "current-password", value: oldPassword, onChange: (e) => setOldPassword(e.target.value), required: true }) }),
1825
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "New password", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "new-password", minLength: 8, value: newPassword, onChange: (e) => setNewPassword(e.target.value), required: true }) }),
1826
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: pwState.submitting || !oldPassword || !newPassword, children: pwState.submitting ? "Updating\u2026" : "Change password" })
3828
+ const removeMember = async (userId) => {
3829
+ if (!tenantId) return;
3830
+ try {
3831
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}/users/${userId}`, { method: "DELETE", credentials: "include" });
3832
+ await reloadMembers();
3833
+ setActionMessage("Member removed.");
3834
+ } catch (err) {
3835
+ setError(err instanceof Error ? err.message : "Failed to remove member");
3836
+ }
3837
+ };
3838
+ const resendInvite = async (id) => {
3839
+ if (!tenantId) return;
3840
+ try {
3841
+ await jsonFetch(`${baseUrl}/api/v1/invites/${id}/resend`, { method: "POST", credentials: "include" });
3842
+ setActionMessage("Invitation resent.");
3843
+ await loadInvites(tenantId);
3844
+ } catch (err) {
3845
+ setError(err instanceof Error ? err.message : "Failed to resend invitation");
3846
+ }
3847
+ };
3848
+ const revokeInvite = async (id) => {
3849
+ if (!tenantId) return;
3850
+ try {
3851
+ await jsonFetch(`${baseUrl}/api/v1/invites/${id}/revoke`, { method: "POST", credentials: "include" });
3852
+ setActionMessage("Invitation revoked.");
3853
+ await loadInvites(tenantId);
3854
+ } catch (err) {
3855
+ setError(err instanceof Error ? err.message : "Failed to revoke invitation");
3856
+ }
3857
+ };
3858
+ const submitDelete = async () => {
3859
+ if (!tenantId || !tenant) return;
3860
+ if (confirmDeleteText !== tenant.slug) {
3861
+ setError("Type the organization slug to confirm deletion.");
3862
+ return;
3863
+ }
3864
+ if (!confirmDeletePassword) {
3865
+ setError("Re-enter your password to confirm deletion.");
3866
+ return;
3867
+ }
3868
+ setDeleteSubmitting(true);
3869
+ setError(null);
3870
+ try {
3871
+ await jsonFetch(`${baseUrl}/api/v1/tenants/${tenantId}`, {
3872
+ method: "DELETE",
3873
+ credentials: "include",
3874
+ headers: { "Content-Type": "application/json" },
3875
+ body: JSON.stringify({ confirmPassword: confirmDeletePassword })
3876
+ });
3877
+ setActionMessage("Organization deleted.");
3878
+ setConfirmDeletePassword("");
3879
+ onDeleted?.(tenantId);
3880
+ } catch (err) {
3881
+ setError(err instanceof Error ? err.message : "Failed to delete organization");
3882
+ } finally {
3883
+ setDeleteSubmitting(false);
3884
+ }
3885
+ };
3886
+ if (loading) {
3887
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding, className, title: "Organization", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", "aria-live": "polite", style: { fontSize: 13 }, children: "Loading\u2026" }) });
3888
+ }
3889
+ const tabBtnStyle = (key) => ({
3890
+ background: activeTab === key ? `${branding?.accentColor || "#6366f1"}1a` : "transparent",
3891
+ border: `1px solid ${activeTab === key ? branding?.accentColor || "#6366f1" : "rgba(15,23,42,0.12)"}`,
3892
+ color: branding?.primaryColor || "#0f172a",
3893
+ padding: "6px 12px",
3894
+ borderRadius: 6,
3895
+ cursor: "pointer",
3896
+ fontSize: 12,
3897
+ fontWeight: 500
3898
+ });
3899
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding, className, title: tenant?.name || "Organization", subtitle: tenant?.slug ? `slug: ${tenant.slug}` : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, "data-iqauth-sdk-org-profile": "", children: [
3900
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error }) : null,
3901
+ actionMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", "aria-live": "polite", style: { fontSize: 13, color: "#047857", margin: 0 }, children: actionMessage }) : null,
3902
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "tablist", "aria-label": "Organization sections", style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: visibleTabsFiltered.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3903
+ "button",
3904
+ {
3905
+ role: "tab",
3906
+ "aria-selected": activeTab === t2,
3907
+ "aria-controls": `iqauth-org-tab-${t2}`,
3908
+ "data-testid": `tab-org-${t2}`,
3909
+ type: "button",
3910
+ onClick: () => setActiveTab(t2),
3911
+ style: tabBtnStyle(t2),
3912
+ children: t2 === "general" ? "General" : t2 === "members" ? `Members (${members.length})` : t2 === "invitations" ? "Invitations" : "Danger zone"
3913
+ },
3914
+ t2
3915
+ )) }),
3916
+ activeTab === "general" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { role: "tabpanel", id: "iqauth-org-tab-general", "aria-labelledby": "tab-org-general", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
3917
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: "Settings" }),
3918
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submitRename, style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
3919
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Organization name", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-org-rename", style: inputStyle(), value: renameValue, onChange: (e) => setRenameValue(e.target.value), required: true, minLength: 2 }) }),
3920
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Slug", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-org-slug", style: { ...inputStyle(), fontFamily: "monospace" }, value: slugValue, onChange: (e) => setSlugValue(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "-")), required: true, pattern: "[a-z0-9-]+", minLength: 2 }) }),
3921
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { "data-testid": "button-org-rename", type: "submit", disabled: renameSubmitting || renameValue.trim() === tenant?.name && slugValue.trim() === tenant?.slug, children: renameSubmitting ? "Saving\u2026" : "Save changes" })
1827
3922
  ] })
1828
- ] }),
1829
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-sessions", children: [
1830
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-sessions", style: { fontSize: 14, fontWeight: 600 }, children: "Sessions" }),
1831
- sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6 }, children: "No active sessions." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", justifyContent: "space-between", fontSize: 13, padding: "6px 10px", background: "#f8fafc", borderRadius: 6 }, children: [
1832
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: s.userAgent || s.deviceName || "Unknown" }),
1833
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => revoke(s.id), style: { fontSize: 12, color: "#b91c1c", background: "transparent", border: "1px solid #fecaca", borderRadius: 4, padding: "2px 8px", cursor: "pointer" }, children: "Revoke" })
1834
- ] }, s.id)) })
1835
- ] })
1836
- ] });
3923
+ ] }) : null,
3924
+ activeTab === "members" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { role: "tabpanel", id: "iqauth-org-tab-members", "aria-labelledby": "tab-org-members", style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
3925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submitInvite, style: { display: "flex", gap: 8, flexWrap: "wrap" }, "aria-label": "Invite a new member", children: [
3926
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-org-invite-email", type: "email", placeholder: "email@company.com", "aria-label": "Email", style: { ...inputStyle(), flex: 1, minWidth: 180 }, value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), required: true }),
3927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { "data-testid": "select-org-invite-role", "aria-label": "Role", style: { ...inputStyle(), width: "auto" }, value: inviteRole, onChange: (e) => setInviteRole(e.target.value), children: [
3928
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "tenant_member", children: "tenant_member" }),
3929
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "tenant_admin", children: "tenant_admin" })
3930
+ ] }),
3931
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { "data-testid": "button-org-invite", type: "submit", disabled: inviteSubmitting || !inviteEmail.trim(), children: inviteSubmitting ? "Sending\u2026" : "Send invite" })
3932
+ ] }),
3933
+ members.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6 }, children: "No members yet." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { "aria-label": "Members", style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { "data-testid": `row-org-member-${m.userId}`, style: { display: "grid", gridTemplateColumns: "1fr auto auto", gap: 8, alignItems: "center", padding: "8px 10px", background: "rgba(15,23,42,0.04)", borderRadius: 6, fontSize: 13 }, children: [
3934
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3935
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: m.name || m.email }),
3936
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, opacity: 0.7 }, children: m.email })
3937
+ ] }),
3938
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { "data-testid": `select-org-member-role-${m.userId}`, "aria-label": `Role for ${m.email}`, value: m.role, onChange: (e) => changeRole(m.userId, e.target.value), style: { ...inputStyle(), width: "auto", padding: "4px 8px", fontSize: 12 }, children: [
3939
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "tenant_member", children: "tenant_member" }),
3940
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "tenant_admin", children: "tenant_admin" })
3941
+ ] }),
3942
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3943
+ "button",
3944
+ {
3945
+ type: "button",
3946
+ "data-testid": `button-org-member-remove-${m.userId}`,
3947
+ "aria-label": `Remove ${m.email}`,
3948
+ onClick: () => removeMember(m.userId),
3949
+ style: { background: "transparent", border: "1px solid rgba(220,38,38,0.4)", color: "#b91c1c", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
3950
+ children: "Remove"
3951
+ }
3952
+ )
3953
+ ] }, m.userId)) })
3954
+ ] }) : null,
3955
+ activeTab === "invitations" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { role: "tabpanel", id: "iqauth-org-tab-invitations", "aria-labelledby": "tab-org-invitations", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
3956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0 }, children: "Pending invitations" }),
3957
+ invitesLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", "aria-live": "polite", style: { fontSize: 13 }, children: "Loading\u2026" }) : pendingInvites.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6 }, children: "No pending invitations." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { "aria-label": "Pending invitations", style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: pendingInvites.map((inv) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { "data-testid": `row-org-invite-${inv.id}`, style: { display: "grid", gridTemplateColumns: "1fr auto auto", gap: 8, alignItems: "center", padding: "8px 10px", background: "rgba(15,23,42,0.04)", borderRadius: 6, fontSize: 13 }, children: [
3958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
3959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: inv.email }),
3960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, opacity: 0.7 }, children: [
3961
+ "role: ",
3962
+ inv.role,
3963
+ inv.expiresAt ? ` \u2022 expires ${new Date(inv.expiresAt).toLocaleDateString()}` : ""
3964
+ ] })
3965
+ ] }),
3966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3967
+ "button",
3968
+ {
3969
+ type: "button",
3970
+ "data-testid": `button-org-invite-resend-${inv.id}`,
3971
+ onClick: () => resendInvite(inv.id),
3972
+ style: { background: "transparent", border: "1px solid rgba(15,23,42,0.18)", color: "#0f172a", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
3973
+ children: "Resend"
3974
+ }
3975
+ ),
3976
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3977
+ "button",
3978
+ {
3979
+ type: "button",
3980
+ "data-testid": `button-org-invite-revoke-${inv.id}`,
3981
+ onClick: () => revokeInvite(inv.id),
3982
+ style: { background: "transparent", border: "1px solid rgba(220,38,38,0.4)", color: "#b91c1c", borderRadius: 4, padding: "4px 10px", cursor: "pointer", fontSize: 12 },
3983
+ children: "Revoke"
3984
+ }
3985
+ )
3986
+ ] }, inv.id)) })
3987
+ ] }) : null,
3988
+ activeTab === "danger" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { role: "tabpanel", id: "iqauth-org-tab-danger", "aria-labelledby": "tab-org-danger", style: { display: "flex", flexDirection: "column", gap: 10, border: "1px solid rgba(220,38,38,0.3)", padding: 12, borderRadius: 8, background: "rgba(220,38,38,0.04)" }, children: [
3989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: 14, fontWeight: 600, margin: 0, color: "#b91c1c" }, children: "Delete organization" }),
3990
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: 12, opacity: 0.85, margin: 0 }, children: [
3991
+ "This permanently deletes ",
3992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: tenant?.name }),
3993
+ " and all of its members, roles, and audit history. To confirm, type the slug ",
3994
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: tenant?.slug }),
3995
+ " below."
3996
+ ] }),
3997
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Type the organization slug to confirm", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-org-delete-confirm", autoComplete: "off", placeholder: tenant?.slug || "", "aria-label": "Type slug to confirm", style: { ...inputStyle(), fontFamily: "monospace" }, value: confirmDeleteText, onChange: (e) => setConfirmDeleteText(e.target.value) }) }),
3998
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Re-enter your password", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-org-delete-password", type: "password", autoComplete: "current-password", "aria-label": "Password to confirm deletion", style: inputStyle(), value: confirmDeletePassword, onChange: (e) => setConfirmDeletePassword(e.target.value) }) }),
3999
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4000
+ "button",
4001
+ {
4002
+ type: "button",
4003
+ "data-testid": "button-org-delete",
4004
+ onClick: submitDelete,
4005
+ disabled: deleteSubmitting || confirmDeleteText !== tenant?.slug || !confirmDeletePassword,
4006
+ style: { background: "#b91c1c", color: "#fff", border: "none", padding: "8px 14px", borderRadius: 6, cursor: confirmDeleteText === tenant?.slug && confirmDeletePassword ? "pointer" : "not-allowed", fontSize: 13, opacity: confirmDeleteText === tenant?.slug && confirmDeletePassword ? 1 : 0.5 },
4007
+ children: deleteSubmitting ? "Deleting\u2026" : "Permanently delete organization"
4008
+ }
4009
+ )
4010
+ ] }) : null
4011
+ ] }) });
1837
4012
  }
1838
- function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
4013
+ function OrganizationList({ iqAuthBaseUrl, onSelect, showCreate = true, createRedirectUrl, appearance, className }) {
4014
+ const [showCreateForm, setShowCreateForm] = (0, import_react.useState)(false);
4015
+ const reloadList = () => {
4016
+ setShowCreateForm(false);
4017
+ if (typeof window !== "undefined") setTimeout(() => window.location.reload(), 50);
4018
+ };
1839
4019
  const branding = useResolvedSdkBranding(iqAuthBaseUrl);
4020
+ const baseUrl = iqAuthBaseUrl.replace(/\/$/, "");
1840
4021
  const accent = branding?.accentColor || "#6366f1";
1841
4022
  const [memberships, setMemberships] = (0, import_react.useState)([]);
1842
4023
  const [activeTenantId, setActiveTenantId] = (0, import_react.useState)(null);
1843
- const [open, setOpen] = (0, import_react.useState)(false);
4024
+ const [loading, setLoading] = (0, import_react.useState)(true);
4025
+ const [error, setError] = (0, import_react.useState)(null);
1844
4026
  (0, import_react.useEffect)(() => {
1845
- fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
1846
- if (p?.data?.tenantId) setActiveTenantId(p.data.tenantId);
1847
- }).catch(() => {
1848
- });
1849
- fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/tenants/memberships`, { credentials: "include" }).then((r) => r.json()).then((p) => setMemberships(p?.data?.memberships || p?.data || [])).catch(() => {
4027
+ let cancelled = false;
4028
+ Promise.all([
4029
+ fetch(`${baseUrl}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()),
4030
+ fetch(`${baseUrl}/api/v1/tenants/memberships`, { credentials: "include" }).then((r) => r.json())
4031
+ ]).then(([me, mems]) => {
4032
+ if (cancelled) return;
4033
+ setActiveTenantId(me?.data?.tenantId || null);
4034
+ setMemberships(mems?.data?.memberships || mems?.data || []);
4035
+ }).catch((err) => {
4036
+ if (!cancelled) setError(err instanceof Error ? err.message : "Failed to load organizations");
4037
+ }).finally(() => {
4038
+ if (!cancelled) setLoading(false);
1850
4039
  });
1851
- }, [iqAuthBaseUrl]);
1852
- const switchTo = async (tenantId) => {
4040
+ return () => {
4041
+ cancelled = true;
4042
+ };
4043
+ }, [baseUrl]);
4044
+ const select = async (tenantId) => {
4045
+ if (tenantId === activeTenantId) {
4046
+ onSelect?.(tenantId);
4047
+ return;
4048
+ }
1853
4049
  try {
1854
- await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/select-tenant`, {
4050
+ await jsonFetch(`${baseUrl}/api/v1/auth/select-tenant`, {
1855
4051
  method: "POST",
1856
4052
  headers: { "Content-Type": "application/json" },
4053
+ credentials: "include",
1857
4054
  body: JSON.stringify({ tenantId })
1858
4055
  });
1859
4056
  setActiveTenantId(tenantId);
1860
- setOpen(false);
1861
- onSwitched?.(tenantId);
1862
- } catch {
4057
+ onSelect?.(tenantId);
4058
+ } catch (err) {
4059
+ setError(err instanceof Error ? err.message : "Failed to switch organization");
1863
4060
  }
1864
4061
  };
1865
- const active = memberships.find((m) => m.tenantId === activeTenantId);
4062
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding, className, title: "Your organizations", subtitle: "Select an organization to make it active.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-iqauth-sdk-org-list": "", style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
4063
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error }) : null,
4064
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13 }, children: "Loading\u2026" }) : memberships.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6 }, children: "You don\u2019t belong to any organizations yet." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 6 }, children: memberships.map((m) => {
4065
+ const active = m.tenantId === activeTenantId;
4066
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
4067
+ "button",
4068
+ {
4069
+ type: "button",
4070
+ "data-testid": `button-org-list-${m.tenantId}`,
4071
+ onClick: () => select(m.tenantId),
4072
+ style: {
4073
+ display: "block",
4074
+ width: "100%",
4075
+ textAlign: "left",
4076
+ padding: "10px 12px",
4077
+ borderRadius: 6,
4078
+ cursor: "pointer",
4079
+ fontSize: 13,
4080
+ background: active ? `${accent}1a` : "transparent",
4081
+ border: `1px solid ${active ? accent : "rgba(15,23,42,0.12)"}`,
4082
+ color: branding?.primaryColor || "#0f172a"
4083
+ },
4084
+ children: [
4085
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
4086
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, opacity: 0.7 }, children: [
4087
+ (m.roles || []).join(", ") || "\u2014",
4088
+ active ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { marginLeft: 8, color: accent, fontWeight: 600 }, children: "active" }) : null
4089
+ ] })
4090
+ ]
4091
+ }
4092
+ ) }, m.tenantId);
4093
+ }) }),
4094
+ showCreate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 12, paddingTop: 12, borderTop: "1px solid rgba(15,23,42,0.08)" }, children: showCreateForm ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
4095
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4096
+ CreateOrganization,
4097
+ {
4098
+ iqAuthBaseUrl,
4099
+ unstyled: true,
4100
+ appearance,
4101
+ onCreated: (t2) => {
4102
+ onSelect?.(t2.id);
4103
+ reloadList();
4104
+ },
4105
+ redirectUrl: createRedirectUrl
4106
+ }
4107
+ ),
4108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4109
+ "button",
4110
+ {
4111
+ type: "button",
4112
+ "data-testid": "button-org-list-create-cancel",
4113
+ onClick: () => setShowCreateForm(false),
4114
+ style: { marginTop: 8, background: "transparent", border: "none", color: branding?.accentColor || "#6366f1", cursor: "pointer", fontSize: 12, padding: 0 },
4115
+ children: "Cancel"
4116
+ }
4117
+ )
4118
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4119
+ "button",
4120
+ {
4121
+ type: "button",
4122
+ "data-testid": "button-org-list-create",
4123
+ onClick: () => setShowCreateForm(true),
4124
+ style: { background: "transparent", border: `1px dashed ${accent}`, color: branding?.primaryColor || "#0f172a", padding: "10px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13, width: "100%" },
4125
+ children: "+ Create new organization"
4126
+ }
4127
+ ) }) : null
4128
+ ] }) });
4129
+ }
4130
+ function Waitlist({ iqAuthBaseUrl, appKey, appId, title, subtitle, successMessage, appearance, className }) {
4131
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl, appId);
4132
+ const [email, setEmail] = (0, import_react.useState)("");
4133
+ const [name, setName] = (0, import_react.useState)("");
4134
+ const [organizationName, setOrganizationName] = (0, import_react.useState)("");
4135
+ const [submitting, setSubmitting] = (0, import_react.useState)(false);
4136
+ const [error, setError] = (0, import_react.useState)(null);
4137
+ const [submitted, setSubmitted] = (0, import_react.useState)(null);
4138
+ const submit = async (e) => {
4139
+ e.preventDefault();
4140
+ setSubmitting(true);
4141
+ setError(null);
4142
+ try {
4143
+ const res = await jsonFetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/waitlist`, {
4144
+ method: "POST",
4145
+ headers: { "Content-Type": "application/json" },
4146
+ body: JSON.stringify({
4147
+ email: email.trim().toLowerCase(),
4148
+ name: name.trim() || void 0,
4149
+ organizationName: organizationName.trim() || void 0,
4150
+ appKey: appKey || void 0,
4151
+ appId: appId || void 0
4152
+ })
4153
+ });
4154
+ const data = res?.data ?? res;
4155
+ setSubmitted({ duplicate: !!data?.duplicate });
4156
+ } catch (err) {
4157
+ setError(err instanceof Error ? err.message : "Failed to join the waitlist");
4158
+ } finally {
4159
+ setSubmitting(false);
4160
+ }
4161
+ };
4162
+ if (submitted) {
4163
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding, className, title: title || "You\u2019re on the list", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-waitlist-success", style: { fontSize: 14 }, children: successMessage || (submitted.duplicate ? "You were already on the waitlist \u2014 we\u2019ll be in touch." : "Thanks! We\u2019ll email you when access opens up.") }) });
4164
+ }
4165
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { appearance, branding, className, title: title || "Join the waitlist", subtitle: subtitle || "Enter your email and we\u2019ll be in touch when access opens up.", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: submit, style: { display: "flex", flexDirection: "column", gap: 12 }, "data-iqauth-sdk-waitlist": "", children: [
4166
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error }) : null,
4167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Work email", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-waitlist-email", type: "email", autoComplete: "email", required: true, style: inputStyle(), value: email, onChange: (e) => setEmail(e.target.value) }) }),
4168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Name (optional)", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-waitlist-name", autoComplete: "name", style: inputStyle(), value: name, onChange: (e) => setName(e.target.value) }) }),
4169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Organization (optional)", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "data-testid": "input-waitlist-org", autoComplete: "organization", style: inputStyle(), value: organizationName, onChange: (e) => setOrganizationName(e.target.value) }) }),
4170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { "data-testid": "button-waitlist-submit", type: "submit", disabled: submitting || !email, children: submitting ? "Joining\u2026" : "Join the waitlist" })
4171
+ ] }) });
4172
+ }
4173
+ function usePasswordlessOptions(override) {
4174
+ const ctx = (0, import_react.useContext)(IQAuthContext);
4175
+ const baseFromCtx = ctx?.manager?.issuerUrl;
4176
+ const iqAuthBaseUrl = override?.iqAuthBaseUrl || baseFromCtx || (typeof window !== "undefined" ? window.location.origin : "");
4177
+ return { iqAuthBaseUrl, cookieSession: override?.cookieSession ?? true };
4178
+ }
4179
+ function useMagicLink(override) {
4180
+ const opts = usePasswordlessOptions(override);
4181
+ const [sent, setSent] = (0, import_react.useState)(false);
4182
+ const [busy, setBusy] = (0, import_react.useState)(false);
4183
+ const [error, setError] = (0, import_react.useState)(null);
4184
+ const request = (0, import_react.useCallback)(async (input) => {
4185
+ setBusy(true);
4186
+ setError(null);
4187
+ setSent(false);
4188
+ try {
4189
+ await requestMagicLink(opts, input);
4190
+ setSent(true);
4191
+ } catch (e) {
4192
+ setError(e instanceof Error ? e.message : "Magic link request failed");
4193
+ } finally {
4194
+ setBusy(false);
4195
+ }
4196
+ }, [opts.iqAuthBaseUrl, opts.cookieSession]);
4197
+ return { request, sent, busy, error };
4198
+ }
4199
+ function usePasskey(override) {
4200
+ const opts = usePasswordlessOptions(override);
4201
+ const [busy, setBusy] = (0, import_react.useState)(false);
4202
+ const [error, setError] = (0, import_react.useState)(null);
4203
+ const signIn2 = (0, import_react.useCallback)(async (input = {}) => {
4204
+ setBusy(true);
4205
+ setError(null);
4206
+ try {
4207
+ return await signInWithPasskey(opts, input);
4208
+ } catch (e) {
4209
+ const msg = e instanceof Error ? e.message : "Passkey sign-in failed";
4210
+ setError(msg);
4211
+ throw e;
4212
+ } finally {
4213
+ setBusy(false);
4214
+ }
4215
+ }, [opts.iqAuthBaseUrl]);
4216
+ const enroll = (0, import_react.useCallback)(async (name) => {
4217
+ setBusy(true);
4218
+ setError(null);
4219
+ try {
4220
+ return await enrollPasskey(opts, name);
4221
+ } catch (e) {
4222
+ const msg = e instanceof Error ? e.message : "Passkey enrollment failed";
4223
+ setError(msg);
4224
+ throw e;
4225
+ } finally {
4226
+ setBusy(false);
4227
+ }
4228
+ }, [opts.iqAuthBaseUrl]);
4229
+ return { signIn: signIn2, enroll, busy, error };
4230
+ }
4231
+ function useLinkedIdentities(override) {
4232
+ const opts = usePasswordlessOptions(override);
4233
+ const [identities, setIdentities] = (0, import_react.useState)([]);
4234
+ const [loading, setLoading] = (0, import_react.useState)(true);
4235
+ const [error, setError] = (0, import_react.useState)(null);
4236
+ const refresh = (0, import_react.useCallback)(async () => {
4237
+ setLoading(true);
4238
+ setError(null);
4239
+ try {
4240
+ setIdentities(await listLinkedIdentities(opts));
4241
+ } catch (e) {
4242
+ setError(e instanceof Error ? e.message : "Failed to load identities");
4243
+ } finally {
4244
+ setLoading(false);
4245
+ }
4246
+ }, [opts.iqAuthBaseUrl]);
4247
+ const link = (0, import_react.useCallback)(async (input) => {
4248
+ await linkProvider(opts, input);
4249
+ await refresh();
4250
+ }, [opts.iqAuthBaseUrl, refresh]);
4251
+ const unlink = (0, import_react.useCallback)(async (provider, password) => {
4252
+ await unlinkProvider(opts, { provider, reauth: { password } });
4253
+ await refresh();
4254
+ }, [opts.iqAuthBaseUrl, refresh]);
4255
+ (0, import_react.useEffect)(() => {
4256
+ refresh();
4257
+ }, [refresh]);
4258
+ return { identities, loading, error, refresh, link, unlink };
4259
+ }
4260
+ function MagicLinkSignInForm(props) {
4261
+ const { request, sent, busy, error } = useMagicLink(props);
4262
+ const [email, setEmail] = (0, import_react.useState)("");
1866
4263
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1867
- "div",
4264
+ "form",
1868
4265
  {
1869
- className,
1870
- style: { position: "relative", display: "inline-block", ...brandStyle(branding) },
1871
- "data-iqauth-sdk-orgswitcher": "",
1872
- "data-branding-rev": branding?.brandingRev || "",
4266
+ "data-testid": "form-magic-link",
4267
+ className: props.className,
4268
+ onSubmit: (e) => {
4269
+ e.preventDefault();
4270
+ if (email) void request({ email, appId: props.appId, redirectUri: props.redirectUri });
4271
+ },
4272
+ style: { display: "flex", flexDirection: "column", gap: 8 },
1873
4273
  children: [
1874
4274
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1875
- "button",
4275
+ "input",
1876
4276
  {
1877
- type: "button",
1878
- "aria-haspopup": "menu",
1879
- "aria-expanded": open,
1880
- onClick: () => setOpen((o) => !o),
1881
- style: { background: "transparent", border: `1px solid ${accent}55`, color: branding?.primaryColor || "#0f172a", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
1882
- children: active?.tenantName || active?.tenantSlug || "Select organization"
4277
+ "data-testid": "input-magic-link-email",
4278
+ type: "email",
4279
+ required: true,
4280
+ value: email,
4281
+ placeholder: props.placeholder ?? "you@example.com",
4282
+ onChange: (e) => setEmail(e.target.value),
4283
+ style: { padding: 8, border: "1px solid rgba(15,23,42,0.15)", borderRadius: 6 }
1883
4284
  }
1884
4285
  ),
1885
- open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "menu", style: {
1886
- position: "absolute",
1887
- left: 0,
1888
- top: 36,
1889
- minWidth: 220,
1890
- background: "#fff",
1891
- border: "1px solid rgba(15,23,42,0.12)",
1892
- borderRadius: 8,
1893
- boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1894
- padding: 8,
1895
- zIndex: 100
1896
- }, children: memberships.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "No memberships" }) : memberships.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1897
- "button",
1898
- {
1899
- role: "menuitem",
1900
- type: "button",
1901
- onClick: () => switchTo(m.tenantId),
1902
- style: {
1903
- display: "block",
1904
- width: "100%",
1905
- textAlign: "left",
1906
- padding: "8px 10px",
1907
- background: m.tenantId === activeTenantId ? `${accent}14` : "transparent",
1908
- border: "none",
1909
- borderRadius: 4,
1910
- cursor: "pointer",
1911
- fontSize: 13,
1912
- color: "#0f172a"
1913
- },
1914
- children: [
1915
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
1916
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, opacity: 0.6 }, children: m.roles.join(", ") })
1917
- ]
1918
- },
1919
- m.tenantId
1920
- )) }) : null
4286
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { "data-testid": "button-magic-link-submit", type: "submit", disabled: busy || !email, children: busy ? "Sending\u2026" : props.buttonLabel ?? "Email me a sign-in link" }),
4287
+ sent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-magic-link-sent", style: { fontSize: 12 }, children: "If the email is on file, a link is on the way." }) : null,
4288
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-magic-link-error", style: { fontSize: 12, color: "#b91c1c" }, children: error }) : null
1921
4289
  ]
1922
4290
  }
1923
4291
  );
1924
4292
  }
4293
+ function PasskeySignInButton({ email, className, children, ...rest }) {
4294
+ const { signIn: signIn2, busy, error } = usePasskey(rest);
4295
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
4296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4297
+ "button",
4298
+ {
4299
+ "data-testid": "button-passkey-signin",
4300
+ disabled: busy,
4301
+ onClick: () => void signIn2({ email }).catch(() => {
4302
+ }),
4303
+ style: { padding: "8px 12px", borderRadius: 6, border: "1px solid rgba(15,23,42,0.15)", background: "transparent", cursor: "pointer" },
4304
+ children: busy ? "Verifying\u2026" : children ?? "Sign in with a passkey"
4305
+ }
4306
+ ),
4307
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-passkey-error", style: { fontSize: 12, color: "#b91c1c", marginTop: 4 }, children: error }) : null
4308
+ ] });
4309
+ }
4310
+ function LinkedAccounts({ className, onChange, ...rest }) {
4311
+ const { identities, loading, error, unlink } = useLinkedIdentities(rest);
4312
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "section-linked-accounts", className, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "#b91c1c" }, children: error }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: identities.map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { "data-testid": `row-identity-${i.provider}`, style: { display: "flex", justifyContent: "space-between", padding: "6px 0" }, children: [
4313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
4314
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { textTransform: "capitalize" }, children: i.provider }),
4315
+ " ",
4316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { opacity: 0.7 }, children: i.label || i.providerUserId || "" })
4317
+ ] }),
4318
+ i.canUnlink ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4319
+ "button",
4320
+ {
4321
+ "data-testid": `button-unlink-${i.provider}`,
4322
+ onClick: async () => {
4323
+ const pw = window.prompt("Confirm your password to unlink this identity") || void 0;
4324
+ try {
4325
+ await unlink(i.provider, pw);
4326
+ onChange?.();
4327
+ } catch {
4328
+ }
4329
+ },
4330
+ children: "Unlink"
4331
+ }
4332
+ ) : null
4333
+ ] }, i.id)) }) });
4334
+ }
1925
4335
  var __version__ = "phase-bc-1.0.0";
1926
4336
  // Annotate the CommonJS export names for ESM import in node:
1927
4337
  0 && (module.exports = {
1928
4338
  AuthCallback,
4339
+ CreateOrganization,
1929
4340
  IQAuthLoaded,
1930
4341
  IQAuthLoading,
1931
4342
  IQAuthProvider,
4343
+ IQAuthReturnToBouncer,
4344
+ ImpersonationBanner,
4345
+ LinkedAccounts,
4346
+ MagicLinkSignInForm,
4347
+ MultisessionAppSupport,
4348
+ OrganizationList,
4349
+ OrganizationProfile,
1932
4350
  OrganizationSwitcher,
4351
+ PasskeySignInButton,
4352
+ Protect,
1933
4353
  RedirectToSignIn,
4354
+ RedirectToSignedIn,
1934
4355
  SignIn,
1935
4356
  SignUp,
1936
4357
  SignedIn,
1937
4358
  SignedOut,
1938
4359
  UserButton,
1939
4360
  UserProfile,
4361
+ Waitlist,
1940
4362
  __version__,
4363
+ isReturnToAllowed,
1941
4364
  isSilentSsoEligible,
4365
+ preflightReturnTo,
4366
+ revokeSession,
1942
4367
  sanitizeBrandCss,
4368
+ sanitizeReturnTo,
4369
+ slugify,
4370
+ useAccountList,
4371
+ useAccountSwitcher,
1943
4372
  useAuth,
1944
4373
  useAuthFetch,
1945
4374
  useIQAuthSignInContext,
4375
+ useImpersonation,
4376
+ useLinkedIdentities,
4377
+ useLocale,
4378
+ useMagicLink,
1946
4379
  useOrganization,
4380
+ usePasskey,
1947
4381
  useResolvedSdkBranding,
4382
+ useReturnTo,
4383
+ useReverification,
1948
4384
  useSession,
4385
+ useSessionList,
4386
+ useT,
1949
4387
  useUser
1950
4388
  });