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