@iqauth/sdk 2.6.3 → 2.7.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 +173 -1
- package/dist/browser-session.d.mts +4 -4
- package/dist/browser-session.d.ts +4 -4
- package/dist/browser-session.js +181 -41
- package/dist/browser-session.mjs +3 -3
- package/dist/browser.d.mts +5 -5
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +271 -32
- package/dist/browser.mjs +10 -8
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/chunk-C2ZTBOAC.mjs +36 -0
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-TKZTCPEK.mjs → chunk-GN37E64I.mjs} +32 -40
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/chunk-PMAFENVI.mjs +229 -0
- package/dist/chunk-RR2MGPTK.mjs +2724 -0
- package/dist/{chunk-76W5TLQQ.mjs → chunk-RTJAIBXY.mjs} +220 -20
- package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
- package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
- package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
- package/dist/errors-Jl1Jtm-6.d.mts +107 -0
- package/dist/errors-Jl1Jtm-6.d.ts +107 -0
- package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
- package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +349 -52
- package/dist/express.mjs +39 -12
- package/dist/fastify.d.mts +2 -0
- package/dist/fastify.d.ts +2 -0
- package/dist/fastify.js +332 -52
- package/dist/fastify.mjs +23 -8
- package/dist/hono.d.mts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +329 -52
- package/dist/hono.mjs +20 -8
- package/dist/index-5KSZEnDe.d.ts +1626 -0
- package/dist/index-CKoZHAoc.d.mts +1626 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +565 -69
- package/dist/index.mjs +29 -9
- package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
- package/dist/locales.d.mts +1 -1
- package/dist/locales.d.ts +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +276 -41
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +391 -201
- package/dist/next.mjs +22 -7
- package/dist/pkce-7WKV4OIN.mjs +11 -0
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
- package/dist/react-permissions.d.mts +52 -0
- package/dist/react-permissions.d.ts +52 -0
- package/dist/react-permissions.js +239 -0
- package/dist/react-permissions.mjs +97 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +343 -36
- package/dist/react.mjs +59 -2611
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +148 -3
- package/dist/server/handlers.d.ts +148 -3
- package/dist/server/handlers.js +410 -11
- package/dist/server/handlers.mjs +12 -3
- package/dist/server.d.mts +151 -8
- package/dist/server.d.ts +151 -8
- package/dist/server.js +406 -50
- package/dist/server.mjs +93 -11
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +181 -41
- package/dist/service.mjs +3 -3
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-BLFnz8SV.d.ts} +78 -3
- package/dist/{signIn-CCY4JE5G.mjs → signIn-SHBW6Z4T.mjs} +2 -1
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-T-CZ6t6r.d.mts} +78 -3
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
- package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
- package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
- package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
- package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
- package/dist/webhooks.d.mts +100 -17
- package/dist/webhooks.d.ts +100 -17
- package/dist/webhooks.js +164 -15
- package/dist/webhooks.mjs +7 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/dist/ws.js +80 -30
- package/dist/ws.mjs +4 -4
- package/docs/error-handling.md +101 -0
- package/docs/guides/effective-permissions.md +171 -0
- package/package.json +13 -3
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- package/dist/errors-CDdl24MP.d.ts +0 -52
package/dist/react.js
CHANGED
|
@@ -25,13 +25,30 @@ var IQAuthError;
|
|
|
25
25
|
var init_errors = __esm({
|
|
26
26
|
"src/errors.ts"() {
|
|
27
27
|
"use strict";
|
|
28
|
-
IQAuthError = class extends Error {
|
|
29
|
-
constructor(code, message, status,
|
|
28
|
+
IQAuthError = class _IQAuthError extends Error {
|
|
29
|
+
constructor(code, message, status, cause) {
|
|
30
30
|
super(message);
|
|
31
31
|
this.name = "IQAuthError";
|
|
32
32
|
this.code = code;
|
|
33
33
|
this.status = status;
|
|
34
|
-
this.
|
|
34
|
+
this.cause = cause;
|
|
35
|
+
this.raw = cause;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Type guard: true when `value` is an `IQAuthError`. Useful for adapters
|
|
39
|
+
* that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
|
|
40
|
+
*/
|
|
41
|
+
static isIQAuthError(value) {
|
|
42
|
+
return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Type-narrowed code check. Lets callers write
|
|
46
|
+
* `if (err.is("token_expired")) …` with full IntelliSense for the typed
|
|
47
|
+
* taxonomy without losing the ability to handle server codes via
|
|
48
|
+
* `err.code === "TOKEN_REVOKED"`.
|
|
49
|
+
*/
|
|
50
|
+
is(code) {
|
|
51
|
+
return this.code === code;
|
|
35
52
|
}
|
|
36
53
|
};
|
|
37
54
|
}
|
|
@@ -104,6 +121,12 @@ var init_storage = __esm({
|
|
|
104
121
|
});
|
|
105
122
|
|
|
106
123
|
// src/browser/pkce.ts
|
|
124
|
+
var pkce_exports = {};
|
|
125
|
+
__export(pkce_exports, {
|
|
126
|
+
createPkcePair: () => createPkcePair,
|
|
127
|
+
randomUrlSafe: () => randomUrlSafe,
|
|
128
|
+
s256Challenge: () => s256Challenge
|
|
129
|
+
});
|
|
107
130
|
function getCrypto() {
|
|
108
131
|
if (typeof globalThis !== "undefined" && globalThis.crypto) {
|
|
109
132
|
return globalThis.crypto;
|
|
@@ -171,7 +194,7 @@ async function buildSignInUrl(manager, opts = {}) {
|
|
|
171
194
|
returnTo,
|
|
172
195
|
createdAt: Date.now()
|
|
173
196
|
});
|
|
174
|
-
const url2 = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.
|
|
197
|
+
const url2 = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.hostedIssuerUrl);
|
|
175
198
|
url2.searchParams.set("response_type", "code");
|
|
176
199
|
url2.searchParams.set("app", manager.appKey);
|
|
177
200
|
url2.searchParams.set("publishable_key", manager.publishableKey.raw);
|
|
@@ -186,33 +209,50 @@ async function buildSignInUrl(manager, opts = {}) {
|
|
|
186
209
|
return url2.toString();
|
|
187
210
|
}
|
|
188
211
|
async function redirectToSignIn(manager, opts = {}) {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
212
|
+
const t0 = Date.now();
|
|
213
|
+
let ok = false;
|
|
214
|
+
let code;
|
|
215
|
+
try {
|
|
216
|
+
const url2 = await buildSignInUrl(manager, opts);
|
|
217
|
+
if (typeof window === "undefined") {
|
|
218
|
+
code = "NO_WINDOW";
|
|
219
|
+
throw new Error("redirectToSignIn requires a browser environment");
|
|
220
|
+
}
|
|
221
|
+
ok = true;
|
|
222
|
+
manager.recordTiming("signIn", Date.now() - t0, true);
|
|
223
|
+
window.location.assign(url2);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (!ok) manager.recordTiming("signIn", Date.now() - t0, false, code ?? (err instanceof Error ? err.message : "ERROR"));
|
|
226
|
+
throw err;
|
|
192
227
|
}
|
|
193
|
-
window.location.assign(url2);
|
|
194
228
|
}
|
|
195
229
|
async function signIn(manager, opts = {}) {
|
|
196
230
|
return redirectToSignIn(manager, opts);
|
|
197
231
|
}
|
|
198
232
|
async function handleAuthCallback(manager, options = {}) {
|
|
233
|
+
const t0 = Date.now();
|
|
234
|
+
const emit = (ok, code2) => manager.recordTiming("signIn", Date.now() - t0, ok, code2);
|
|
199
235
|
const url2 = new URL(options.url ?? (typeof window !== "undefined" ? window.location.href : ""));
|
|
200
236
|
const code = url2.searchParams.get("code");
|
|
201
237
|
const state = url2.searchParams.get("state");
|
|
202
238
|
const errorParam = url2.searchParams.get("error");
|
|
203
239
|
if (errorParam) {
|
|
240
|
+
emit(false, errorParam);
|
|
204
241
|
return { ok: false, returnTo: "/", error: errorParam };
|
|
205
242
|
}
|
|
206
243
|
if (!code || !state) {
|
|
244
|
+
emit(false, "missing_code_or_state");
|
|
207
245
|
return { ok: false, returnTo: "/", error: "missing_code_or_state" };
|
|
208
246
|
}
|
|
209
247
|
const record = loadPkce(state);
|
|
210
248
|
if (!record) {
|
|
249
|
+
emit(false, "unknown_state");
|
|
211
250
|
return { ok: false, returnTo: "/", error: "unknown_state" };
|
|
212
251
|
}
|
|
213
252
|
clearPkce(state);
|
|
214
253
|
const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
|
|
215
254
|
if (!fetchImpl) {
|
|
255
|
+
emit(false, "no_fetch");
|
|
216
256
|
return { ok: false, returnTo: record.returnTo, error: "no_fetch" };
|
|
217
257
|
}
|
|
218
258
|
const tokenUrl = `${manager.issuerUrl}${options.tokenPath ?? DEFAULT_TOKEN_PATH}`;
|
|
@@ -231,10 +271,12 @@ async function handleAuthCallback(manager, options = {}) {
|
|
|
231
271
|
const body = await res.json().catch(() => ({}));
|
|
232
272
|
if (!res.ok) {
|
|
233
273
|
const desc = body.error_description ?? body.error ?? "token_exchange_failed";
|
|
274
|
+
emit(false, desc);
|
|
234
275
|
return { ok: false, returnTo: record.returnTo, error: desc };
|
|
235
276
|
}
|
|
236
277
|
const tokens = body;
|
|
237
278
|
if (!tokens.access_token) {
|
|
279
|
+
emit(false, "missing_access_token");
|
|
238
280
|
return { ok: false, returnTo: record.returnTo, error: "missing_access_token" };
|
|
239
281
|
}
|
|
240
282
|
if (tokens.refresh_token) {
|
|
@@ -242,21 +284,24 @@ async function handleAuthCallback(manager, options = {}) {
|
|
|
242
284
|
setCookie(cookieName, tokens.refresh_token, { maxAgeSeconds: 60 * 60 * 24 * 30 });
|
|
243
285
|
}
|
|
244
286
|
manager.applyAccessToken(tokens.access_token, tokens.refresh_token);
|
|
287
|
+
emit(true);
|
|
245
288
|
return { ok: true, returnTo: record.returnTo };
|
|
246
289
|
}
|
|
247
290
|
async function signOut(manager, opts = {}) {
|
|
248
291
|
if (!opts.localOnly) {
|
|
249
292
|
const issuer = manager.issuerUrl.replace(/\/$/, "");
|
|
293
|
+
const idempotency = manager.getIdempotencyToken();
|
|
250
294
|
try {
|
|
251
295
|
const url2 = `${issuer}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
|
|
252
|
-
await manager.fetch(url2, { method: "POST" }).catch(() => void 0);
|
|
296
|
+
await manager.fetch(url2, { method: "POST", headers: { "X-IQAuth-Idempotency": idempotency } }).catch(() => void 0);
|
|
253
297
|
} catch {
|
|
254
298
|
}
|
|
255
299
|
if (opts.endSsoSession !== false) {
|
|
256
300
|
try {
|
|
257
301
|
await fetch(`${issuer}${DEFAULT_SSO_LOGOUT_PATH}`, {
|
|
258
302
|
method: "POST",
|
|
259
|
-
credentials: "include"
|
|
303
|
+
credentials: "include",
|
|
304
|
+
headers: { "X-IQAuth-Idempotency": idempotency }
|
|
260
305
|
}).catch(() => void 0);
|
|
261
306
|
} catch {
|
|
262
307
|
}
|
|
@@ -783,6 +828,7 @@ __export(react_exports, {
|
|
|
783
828
|
UserButton: () => UserButton,
|
|
784
829
|
UserProfile: () => UserProfile,
|
|
785
830
|
Waitlist: () => Waitlist,
|
|
831
|
+
__useIQAuthInternal: () => __useIQAuthInternal,
|
|
786
832
|
__version__: () => __version__,
|
|
787
833
|
isReturnToAllowed: () => isReturnToAllowed,
|
|
788
834
|
isSilentSsoEligible: () => isSilentSsoEligible,
|
|
@@ -848,14 +894,14 @@ function assertPublishableKey(raw, opts) {
|
|
|
848
894
|
const ctx = opts?.context ? `${opts.context}: ` : "";
|
|
849
895
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
850
896
|
throw new IQAuthError(
|
|
851
|
-
"
|
|
897
|
+
"config_invalid",
|
|
852
898
|
`${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
899
|
);
|
|
854
900
|
}
|
|
855
901
|
const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
856
902
|
if (!shapeMatch) {
|
|
857
903
|
throw new IQAuthError(
|
|
858
|
-
"
|
|
904
|
+
"config_invalid",
|
|
859
905
|
`${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
906
|
);
|
|
861
907
|
}
|
|
@@ -864,19 +910,19 @@ function assertPublishableKey(raw, opts) {
|
|
|
864
910
|
decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
|
|
865
911
|
} catch {
|
|
866
912
|
throw new IQAuthError(
|
|
867
|
-
"
|
|
913
|
+
"config_invalid",
|
|
868
914
|
`${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
|
|
869
915
|
);
|
|
870
916
|
}
|
|
871
917
|
if (!isPublishableKeyPayload(decoded)) {
|
|
872
918
|
throw new IQAuthError(
|
|
873
|
-
"
|
|
919
|
+
"config_invalid",
|
|
874
920
|
`${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
|
|
875
921
|
);
|
|
876
922
|
}
|
|
877
923
|
if (!isValidIssuerUrl(decoded.iss)) {
|
|
878
924
|
throw new IQAuthError(
|
|
879
|
-
"
|
|
925
|
+
"config_invalid",
|
|
880
926
|
`${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
927
|
);
|
|
882
928
|
}
|
|
@@ -890,6 +936,7 @@ function isPublishableKeyPayload(value) {
|
|
|
890
936
|
|
|
891
937
|
// src/browser/sessionManager.ts
|
|
892
938
|
init_storage();
|
|
939
|
+
var PROBE_WAIT_MS = 80;
|
|
893
940
|
var DEFAULT_REFRESH_PATH = "/api/v1/auth/refresh";
|
|
894
941
|
var DEFAULT_USERINFO_PATH = "/api/v1/auth/me";
|
|
895
942
|
async function readAuthErrorCode(res) {
|
|
@@ -927,7 +974,13 @@ function claimsToSessionUser(claims) {
|
|
|
927
974
|
tenantId: claims.tenantId,
|
|
928
975
|
vendorId: claims.vendorId,
|
|
929
976
|
roles: claims.roles ?? [],
|
|
930
|
-
entitlements: claims.entitlements ?? []
|
|
977
|
+
entitlements: claims.entitlements ?? [],
|
|
978
|
+
// SDK 2.7.0 (Task #124) — pass through identity claims when issued.
|
|
979
|
+
...claims.picture !== void 0 ? { picture: claims.picture } : {},
|
|
980
|
+
...claims.email_verified !== void 0 ? { emailVerified: claims.email_verified } : {},
|
|
981
|
+
...claims.given_name !== void 0 ? { givenName: claims.given_name } : {},
|
|
982
|
+
...claims.family_name !== void 0 ? { familyName: claims.family_name } : {},
|
|
983
|
+
...claims.locale !== void 0 ? { locale: claims.locale } : {}
|
|
931
984
|
};
|
|
932
985
|
}
|
|
933
986
|
var EMPTY = {
|
|
@@ -951,11 +1004,50 @@ var NO_OP_STORE = {
|
|
|
951
1004
|
write: () => void 0,
|
|
952
1005
|
clear: () => void 0
|
|
953
1006
|
};
|
|
1007
|
+
var IDEMPOTENCY_HEADER = "X-IQAuth-Idempotency";
|
|
1008
|
+
function randomIdempotencyToken() {
|
|
1009
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1010
|
+
const bytes = new Uint8Array(16);
|
|
1011
|
+
crypto.getRandomValues(bytes);
|
|
1012
|
+
let out = "";
|
|
1013
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1014
|
+
return out;
|
|
1015
|
+
}
|
|
1016
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
1017
|
+
}
|
|
954
1018
|
var SessionManager = class {
|
|
955
1019
|
constructor(options) {
|
|
956
1020
|
this.snapshot = { ...EMPTY };
|
|
957
1021
|
this.listeners = /* @__PURE__ */ new Set();
|
|
958
1022
|
this.refreshPromise = null;
|
|
1023
|
+
/**
|
|
1024
|
+
* Cancellation handle for the in-flight refresh, if any. `signOut()` (or a
|
|
1025
|
+
* `session:signout` broadcast from another tab) calls `abort()` so the
|
|
1026
|
+
* refresh response is dropped before it can write a fresh access cookie
|
|
1027
|
+
* on top of the just-cleared session — the second root cause of "ghost
|
|
1028
|
+
* signed-in" sessions after Sign Out.
|
|
1029
|
+
*/
|
|
1030
|
+
this.refreshAbort = null;
|
|
1031
|
+
/**
|
|
1032
|
+
* Set to `true` by `signOut()` / `signOutLocal()` for the lifetime of the
|
|
1033
|
+
* call. Used as a safety belt: even if a refresh response arrives while
|
|
1034
|
+
* `refreshAbort` was unable to interrupt the network call (e.g. the body
|
|
1035
|
+
* was already streaming back), `runRefresh` checks this flag before
|
|
1036
|
+
* mutating session state and bails out.
|
|
1037
|
+
*/
|
|
1038
|
+
this.signoutInProgress = false;
|
|
1039
|
+
/**
|
|
1040
|
+
* Per-session opaque idempotency token. Sent as `X-IQAuth-Idempotency` on
|
|
1041
|
+
* every /refresh and /signout request the SDK makes through a framework
|
|
1042
|
+
* adapter (Express/Fastify/Hono/Next), so the adapter's `SignoutRegistry`
|
|
1043
|
+
* can collapse a refresh that lands moments after a signout — even when
|
|
1044
|
+
* the two requests are routed to different server instances (multi-replica
|
|
1045
|
+
* deployments).
|
|
1046
|
+
*
|
|
1047
|
+
* Generated lazily on first use, rotated on signout so the next session
|
|
1048
|
+
* starts with a fresh token. Opaque random — never the raw refresh token.
|
|
1049
|
+
*/
|
|
1050
|
+
this.idempotencyToken = null;
|
|
959
1051
|
this.channel = null;
|
|
960
1052
|
this.proactiveTimer = null;
|
|
961
1053
|
this.bootstrapped = false;
|
|
@@ -963,6 +1055,8 @@ var SessionManager = class {
|
|
|
963
1055
|
this.remoteRefreshWaiters = [];
|
|
964
1056
|
/** Active claims by other tabs (keyed by source tabId). */
|
|
965
1057
|
this.foreignClaim = null;
|
|
1058
|
+
/** Resolver for an in-flight cross-tab `session:probe`, set during bootstrap. */
|
|
1059
|
+
this.probeResolver = null;
|
|
966
1060
|
const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
|
|
967
1061
|
this.key = parsed;
|
|
968
1062
|
const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
|
|
@@ -975,6 +1069,8 @@ var SessionManager = class {
|
|
|
975
1069
|
this.refreshCookieName = options.cookieNames?.refresh ?? REFRESH_COOKIE;
|
|
976
1070
|
this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore(this.refreshCookieName) : NO_OP_STORE);
|
|
977
1071
|
this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
|
|
1072
|
+
this.debug = options.debug ?? false;
|
|
1073
|
+
this.onTimingEvent = options.onTimingEvent ?? null;
|
|
978
1074
|
this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
|
|
979
1075
|
throw new Error("global fetch is not available; pass fetchImpl");
|
|
980
1076
|
}));
|
|
@@ -1001,10 +1097,35 @@ var SessionManager = class {
|
|
|
1001
1097
|
get issuerUrl() {
|
|
1002
1098
|
return this.issuer;
|
|
1003
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* SDK 2.7.0 (Task #124) — The hosted IQAuth host derived from the
|
|
1102
|
+
* publishable key's `iss` claim, normalized to URL form. This is what
|
|
1103
|
+
* `<SignIn/>` and `buildSignInUrl` use to talk to the hosted UI; it
|
|
1104
|
+
* deliberately ignores the `issuer` constructor override so a misrouted
|
|
1105
|
+
* `issuer` (e.g. pointed at the consumer app's own domain) cannot break
|
|
1106
|
+
* the hosted flow. Use {@link issuerUrl} for token / discovery endpoints.
|
|
1107
|
+
*/
|
|
1108
|
+
get hostedIssuerUrl() {
|
|
1109
|
+
const iss = this.key.iss;
|
|
1110
|
+
return (iss.startsWith("http") ? iss : `https://${iss}`).replace(/\/+$/, "");
|
|
1111
|
+
}
|
|
1004
1112
|
/** Cookie name the SDK uses for the refresh token (overridable via `cookieNames.refresh`). */
|
|
1005
1113
|
get refreshCookie() {
|
|
1006
1114
|
return this.refreshCookieName;
|
|
1007
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Returns the current per-session idempotency token, generating one
|
|
1118
|
+
* lazily on first use. Sent as the `X-IQAuth-Idempotency` header on
|
|
1119
|
+
* /refresh and /signout requests so the framework adapter's
|
|
1120
|
+
* `SignoutRegistry` can collapse a refresh-vs-signout race even across
|
|
1121
|
+
* server instances.
|
|
1122
|
+
*/
|
|
1123
|
+
getIdempotencyToken() {
|
|
1124
|
+
if (!this.idempotencyToken) {
|
|
1125
|
+
this.idempotencyToken = randomIdempotencyToken();
|
|
1126
|
+
}
|
|
1127
|
+
return this.idempotencyToken;
|
|
1128
|
+
}
|
|
1008
1129
|
getSnapshot() {
|
|
1009
1130
|
return this.snapshot;
|
|
1010
1131
|
}
|
|
@@ -1018,9 +1139,44 @@ var SessionManager = class {
|
|
|
1018
1139
|
* One-time bootstrap: warm the session from the refresh cookie if present.
|
|
1019
1140
|
* Safe to call multiple times.
|
|
1020
1141
|
*/
|
|
1142
|
+
/**
|
|
1143
|
+
* Task #126: Public timing-event emitter. Used by the browser sign-in
|
|
1144
|
+
* helpers (redirectToSignIn / handleAuthCallback) to surface signIn-phase
|
|
1145
|
+
* timings through the same `debug` + `onTimingEvent` channel as
|
|
1146
|
+
* bootstrap/refresh. Safe to call from anywhere — internal callers
|
|
1147
|
+
* pre-compute durationMs.
|
|
1148
|
+
*/
|
|
1149
|
+
recordTiming(phase, durationMs, ok, code) {
|
|
1150
|
+
this.emitTiming(phase, durationMs, ok, code);
|
|
1151
|
+
}
|
|
1152
|
+
/** Task #126: emit a session timing event to debug log + onTimingEvent hook. */
|
|
1153
|
+
emitTiming(phase, durationMs, ok, code) {
|
|
1154
|
+
if (this.debug) {
|
|
1155
|
+
try {
|
|
1156
|
+
console.debug("[iqauth_session]", { phase, durationMs, ok, code });
|
|
1157
|
+
} catch {
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (this.onTimingEvent) {
|
|
1161
|
+
try {
|
|
1162
|
+
this.onTimingEvent({ phase, durationMs, ok, code });
|
|
1163
|
+
} catch {
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1021
1167
|
async bootstrap() {
|
|
1022
1168
|
if (this.bootstrapped) return;
|
|
1023
1169
|
this.bootstrapped = true;
|
|
1170
|
+
const t0 = Date.now();
|
|
1171
|
+
try {
|
|
1172
|
+
await this.bootstrapInner();
|
|
1173
|
+
this.emitTiming("bootstrap", Date.now() - t0, this.snapshot.status === "authenticated");
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
this.emitTiming("bootstrap", Date.now() - t0, false, err instanceof Error ? err.message : "ERROR");
|
|
1176
|
+
throw err;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
async bootstrapInner() {
|
|
1024
1180
|
if (this.serverManagedSession) {
|
|
1025
1181
|
try {
|
|
1026
1182
|
const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
|
|
@@ -1055,6 +1211,15 @@ var SessionManager = class {
|
|
|
1055
1211
|
return;
|
|
1056
1212
|
}
|
|
1057
1213
|
}
|
|
1214
|
+
const peerSnapshot = await this.probePeers();
|
|
1215
|
+
if (peerSnapshot && peerSnapshot.status === "authenticated") {
|
|
1216
|
+
this.update({
|
|
1217
|
+
...peerSnapshot,
|
|
1218
|
+
version: Math.max(this.snapshot.version, peerSnapshot.version) + 1
|
|
1219
|
+
});
|
|
1220
|
+
this.scheduleProactiveRefresh();
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1058
1223
|
const stored = await Promise.resolve(this.tokenStore.read());
|
|
1059
1224
|
if (!stored) {
|
|
1060
1225
|
this.setStatus("unauthenticated");
|
|
@@ -1063,6 +1228,22 @@ var SessionManager = class {
|
|
|
1063
1228
|
const ok = await this.refresh();
|
|
1064
1229
|
if (!ok) this.setStatus("unauthenticated");
|
|
1065
1230
|
}
|
|
1231
|
+
probePeers() {
|
|
1232
|
+
if (!this.channel) return Promise.resolve(null);
|
|
1233
|
+
return new Promise((resolve) => {
|
|
1234
|
+
let settled = false;
|
|
1235
|
+
const finish = (snap) => {
|
|
1236
|
+
if (settled) return;
|
|
1237
|
+
settled = true;
|
|
1238
|
+
this.probeResolver = null;
|
|
1239
|
+
clearTimeout(timer);
|
|
1240
|
+
resolve(snap);
|
|
1241
|
+
};
|
|
1242
|
+
this.probeResolver = (snap) => finish(snap);
|
|
1243
|
+
const timer = setTimeout(() => finish(null), PROBE_WAIT_MS);
|
|
1244
|
+
this.broadcastEnvelope({ type: "session:probe", source: this.tabId, ts: Date.now() });
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1066
1247
|
/**
|
|
1067
1248
|
* Single-flight token refresh, coordinated across tabs via BroadcastChannel.
|
|
1068
1249
|
*
|
|
@@ -1076,30 +1257,48 @@ var SessionManager = class {
|
|
|
1076
1257
|
*/
|
|
1077
1258
|
refresh() {
|
|
1078
1259
|
if (this.refreshPromise) return this.refreshPromise;
|
|
1079
|
-
|
|
1260
|
+
const t0 = Date.now();
|
|
1261
|
+
this.refreshPromise = this.runRefresh().then((ok) => {
|
|
1262
|
+
this.emitTiming("refresh", Date.now() - t0, ok, ok ? void 0 : this.snapshot.error?.code ?? "REFRESH_FAILED");
|
|
1263
|
+
return ok;
|
|
1264
|
+
}).finally(() => {
|
|
1080
1265
|
this.refreshPromise = null;
|
|
1081
1266
|
});
|
|
1082
1267
|
return this.refreshPromise;
|
|
1083
1268
|
}
|
|
1084
1269
|
async runRefresh() {
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
this.broadcastEnvelope({ type: "refresh:claim", source: myClaim.source, ts: myClaim.ts });
|
|
1088
|
-
await new Promise((r) => setTimeout(r, 25));
|
|
1089
|
-
const foreign = this.foreignClaim;
|
|
1090
|
-
if (foreign && this.claimWins(foreign, myClaim)) {
|
|
1091
|
-
return this.waitForForeignRefresh();
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1270
|
+
const abort = new AbortController();
|
|
1271
|
+
this.refreshAbort = abort;
|
|
1094
1272
|
try {
|
|
1273
|
+
const myClaim = { source: this.tabId, ts: Date.now() };
|
|
1274
|
+
if (this.channel) {
|
|
1275
|
+
this.broadcastEnvelope({ type: "refresh:claim", source: myClaim.source, ts: myClaim.ts });
|
|
1276
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
1277
|
+
if (abort.signal.aborted || this.signoutInProgress) {
|
|
1278
|
+
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: false });
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
const foreign = this.foreignClaim;
|
|
1282
|
+
if (foreign && this.claimWins(foreign, myClaim)) {
|
|
1283
|
+
return this.waitForForeignRefresh();
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1095
1286
|
const refreshToken = await Promise.resolve(this.tokenStore.read());
|
|
1096
1287
|
const res = await this.fetchImpl(`${this.issuer}${this.refreshPath}`, {
|
|
1097
1288
|
method: "POST",
|
|
1098
1289
|
credentials: "include",
|
|
1099
|
-
headers: {
|
|
1100
|
-
|
|
1290
|
+
headers: {
|
|
1291
|
+
"Content-Type": "application/json",
|
|
1292
|
+
[IDEMPOTENCY_HEADER]: this.getIdempotencyToken()
|
|
1293
|
+
},
|
|
1294
|
+
body: JSON.stringify(refreshToken ? { refreshToken } : {}),
|
|
1295
|
+
signal: abort.signal
|
|
1101
1296
|
});
|
|
1102
1297
|
const body = await res.json().catch(() => ({}));
|
|
1298
|
+
if (this.signoutInProgress || abort.signal.aborted) {
|
|
1299
|
+
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: false });
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1103
1302
|
const data = body.data;
|
|
1104
1303
|
if (!res.ok || !body.success || !data?.accessToken) {
|
|
1105
1304
|
const err = body.error;
|
|
@@ -1118,14 +1317,18 @@ var SessionManager = class {
|
|
|
1118
1317
|
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: true });
|
|
1119
1318
|
return true;
|
|
1120
1319
|
} catch (err) {
|
|
1121
|
-
this.
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1320
|
+
const aborted = err?.name === "AbortError" || abort.signal.aborted || this.signoutInProgress;
|
|
1321
|
+
if (!aborted) {
|
|
1322
|
+
this.setError({
|
|
1323
|
+
code: "NETWORK_ERROR",
|
|
1324
|
+
message: err instanceof Error ? err.message : "Refresh request failed"
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1125
1327
|
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: false });
|
|
1126
1328
|
return false;
|
|
1127
1329
|
} finally {
|
|
1128
1330
|
this.foreignClaim = null;
|
|
1331
|
+
if (this.refreshAbort === abort) this.refreshAbort = null;
|
|
1129
1332
|
}
|
|
1130
1333
|
}
|
|
1131
1334
|
claimWins(foreign, mine) {
|
|
@@ -1233,6 +1436,14 @@ var SessionManager = class {
|
|
|
1233
1436
|
* the server-side logout request.
|
|
1234
1437
|
*/
|
|
1235
1438
|
signOutLocal(status = "unauthenticated") {
|
|
1439
|
+
this.signoutInProgress = true;
|
|
1440
|
+
if (this.refreshAbort) {
|
|
1441
|
+
try {
|
|
1442
|
+
this.refreshAbort.abort();
|
|
1443
|
+
} catch {
|
|
1444
|
+
}
|
|
1445
|
+
this.refreshAbort = null;
|
|
1446
|
+
}
|
|
1236
1447
|
void Promise.resolve(this.tokenStore.clear());
|
|
1237
1448
|
if (this.proactiveTimer) {
|
|
1238
1449
|
clearTimeout(this.proactiveTimer);
|
|
@@ -1247,7 +1458,12 @@ var SessionManager = class {
|
|
|
1247
1458
|
error: null,
|
|
1248
1459
|
version: this.snapshot.version + 1
|
|
1249
1460
|
});
|
|
1461
|
+
this.broadcastEnvelope({ type: "refresh:abort", source: this.tabId, ts: Date.now() });
|
|
1250
1462
|
this.broadcast("session:signout");
|
|
1463
|
+
this.idempotencyToken = null;
|
|
1464
|
+
setTimeout(() => {
|
|
1465
|
+
this.signoutInProgress = false;
|
|
1466
|
+
}, 0);
|
|
1251
1467
|
}
|
|
1252
1468
|
/**
|
|
1253
1469
|
* Replace the refresh-token store at runtime. Used by the F22
|
|
@@ -1311,6 +1527,12 @@ var SessionManager = class {
|
|
|
1311
1527
|
}
|
|
1312
1528
|
onBroadcast(env) {
|
|
1313
1529
|
if (!env || env.source === this.tabId) return;
|
|
1530
|
+
if (env.type === "session:probe") {
|
|
1531
|
+
if (this.snapshot.status === "authenticated") {
|
|
1532
|
+
this.broadcast("session:update");
|
|
1533
|
+
}
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1314
1536
|
if (env.type === "refresh:claim") {
|
|
1315
1537
|
this.foreignClaim = { source: env.source, ts: env.ts };
|
|
1316
1538
|
return;
|
|
@@ -1323,6 +1545,24 @@ var SessionManager = class {
|
|
|
1323
1545
|
this.foreignClaim = null;
|
|
1324
1546
|
return;
|
|
1325
1547
|
}
|
|
1548
|
+
if (env.type === "refresh:abort") {
|
|
1549
|
+
this.signoutInProgress = true;
|
|
1550
|
+
if (this.refreshAbort) {
|
|
1551
|
+
try {
|
|
1552
|
+
this.refreshAbort.abort();
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1555
|
+
this.refreshAbort = null;
|
|
1556
|
+
}
|
|
1557
|
+
const waiters = this.remoteRefreshWaiters;
|
|
1558
|
+
this.remoteRefreshWaiters = [];
|
|
1559
|
+
for (const w of waiters) w(false);
|
|
1560
|
+
this.foreignClaim = null;
|
|
1561
|
+
setTimeout(() => {
|
|
1562
|
+
this.signoutInProgress = false;
|
|
1563
|
+
}, 0);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1326
1566
|
if (env.type === "session:signout") {
|
|
1327
1567
|
this.update({
|
|
1328
1568
|
status: "unauthenticated",
|
|
@@ -1336,6 +1576,12 @@ var SessionManager = class {
|
|
|
1336
1576
|
return;
|
|
1337
1577
|
}
|
|
1338
1578
|
if ((env.type === "session:update" || env.type === "session:refresh") && env.payload) {
|
|
1579
|
+
if (this.probeResolver && env.payload.status === "authenticated") {
|
|
1580
|
+
const r = this.probeResolver;
|
|
1581
|
+
this.probeResolver = null;
|
|
1582
|
+
r(env.payload);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1339
1585
|
this.update({
|
|
1340
1586
|
...env.payload,
|
|
1341
1587
|
version: Math.max(this.snapshot.version, env.payload.version) + 1
|
|
@@ -1349,6 +1595,32 @@ var SessionManager = class {
|
|
|
1349
1595
|
}
|
|
1350
1596
|
};
|
|
1351
1597
|
|
|
1598
|
+
// src/browser/hostedIssuerGuard.ts
|
|
1599
|
+
var HOSTED_ISSUER_MISMATCH_CODE = "IQAUTH_HOSTED_ISSUER_MISMATCH";
|
|
1600
|
+
var HOSTED_ISSUER_MISMATCH_DOCS_URL = "https://docs.dispositioniq.com/iqauth/errors#hosted-issuer-mismatch";
|
|
1601
|
+
function computeHostedIssuerMismatch(input) {
|
|
1602
|
+
const {
|
|
1603
|
+
nodeEnv,
|
|
1604
|
+
fetchError,
|
|
1605
|
+
explicitOverride,
|
|
1606
|
+
resolvedBaseUrl,
|
|
1607
|
+
managerIssuerUrl,
|
|
1608
|
+
hostedIssuerUrl,
|
|
1609
|
+
appKey
|
|
1610
|
+
} = input;
|
|
1611
|
+
if (nodeEnv === "production") return null;
|
|
1612
|
+
if (!fetchError) return null;
|
|
1613
|
+
if (explicitOverride) return null;
|
|
1614
|
+
if (!managerIssuerUrl || !hostedIssuerUrl) return null;
|
|
1615
|
+
if (resolvedBaseUrl !== managerIssuerUrl) return null;
|
|
1616
|
+
if (resolvedBaseUrl === hostedIssuerUrl) return null;
|
|
1617
|
+
const e = new Error(
|
|
1618
|
+
`[IQAuth] ${HOSTED_ISSUER_MISMATCH_CODE}: <SignIn /> targeted "${resolvedBaseUrl}" (inherited from <IQAuthProvider issuer="\u2026"/>), but that host does not serve /api/public/apps/${appKey}/sign-in-context. The hosted UI lives at the publishable key's issuer: "${hostedIssuerUrl}". Fix: drop the <IQAuthProvider issuer="\u2026"/> override so the SDK uses the publishable key's iss, OR pass <SignIn iqAuthBaseUrl="${hostedIssuerUrl}"/> explicitly. Docs: ${HOSTED_ISSUER_MISMATCH_DOCS_URL}. Underlying fetch error: ${fetchError}`
|
|
1619
|
+
);
|
|
1620
|
+
e.code = HOSTED_ISSUER_MISMATCH_CODE;
|
|
1621
|
+
return e;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1352
1624
|
// src/react/index.tsx
|
|
1353
1625
|
init_signIn();
|
|
1354
1626
|
|
|
@@ -1903,6 +2175,9 @@ function IQAuthProvider({
|
|
|
1903
2175
|
);
|
|
1904
2176
|
return (0, import_react.createElement)(IQAuthContext.Provider, { value }, children);
|
|
1905
2177
|
}
|
|
2178
|
+
function __useIQAuthInternal() {
|
|
2179
|
+
return useCtx();
|
|
2180
|
+
}
|
|
1906
2181
|
function useCtx() {
|
|
1907
2182
|
const ctx = (0, import_react.useContext)(IQAuthContext);
|
|
1908
2183
|
if (!ctx) throw new Error("IQAuth hooks must be used inside <IQAuthProvider>");
|
|
@@ -2811,7 +3086,7 @@ function isSilentSsoEligible(ctx, effectivePrompt) {
|
|
|
2811
3086
|
}
|
|
2812
3087
|
function SignIn(props) {
|
|
2813
3088
|
const providerCtx = (0, import_react.useContext)(IQAuthContext);
|
|
2814
|
-
const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.
|
|
3089
|
+
const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.hostedIssuerUrl ?? "";
|
|
2815
3090
|
const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
|
|
2816
3091
|
const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
|
|
2817
3092
|
const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
|
|
@@ -2825,6 +3100,16 @@ function SignIn(props) {
|
|
|
2825
3100
|
const t2 = useT();
|
|
2826
3101
|
const localeBundle = useLocale();
|
|
2827
3102
|
const { ctx, loading, error } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo);
|
|
3103
|
+
const guardError = computeHostedIssuerMismatch({
|
|
3104
|
+
nodeEnv: typeof process !== "undefined" ? process.env?.NODE_ENV : void 0,
|
|
3105
|
+
fetchError: error,
|
|
3106
|
+
explicitOverride: !!props.iqAuthBaseUrl,
|
|
3107
|
+
resolvedBaseUrl: iqAuthBaseUrl,
|
|
3108
|
+
managerIssuerUrl: providerCtx?.manager.issuerUrl,
|
|
3109
|
+
hostedIssuerUrl: providerCtx?.manager.hostedIssuerUrl,
|
|
3110
|
+
appKey
|
|
3111
|
+
});
|
|
3112
|
+
if (guardError) throw guardError;
|
|
2828
3113
|
const preflightLoggedRef = (0, import_react.useRef)(false);
|
|
2829
3114
|
(0, import_react.useEffect)(() => {
|
|
2830
3115
|
if (!ctx || preflightLoggedRef.current) return;
|
|
@@ -2934,13 +3219,34 @@ function SignIn(props) {
|
|
|
2934
3219
|
if (!handlePayload(payload)) setFormError(localizeError(localeBundle, { code: payload.error, message: payload.error_description }));
|
|
2935
3220
|
setSubmitting(false);
|
|
2936
3221
|
};
|
|
2937
|
-
const startGoogleLogin = () => {
|
|
3222
|
+
const startGoogleLogin = async () => {
|
|
2938
3223
|
if (!ctx?.app.defaultClientId) {
|
|
2939
3224
|
setFormError("Application is not configured for hosted sign-in.");
|
|
2940
3225
|
return;
|
|
2941
3226
|
}
|
|
2942
|
-
|
|
2943
|
-
|
|
3227
|
+
let pkce;
|
|
3228
|
+
try {
|
|
3229
|
+
const mod = await Promise.resolve().then(() => (init_pkce(), pkce_exports));
|
|
3230
|
+
pkce = await mod.createPkcePair();
|
|
3231
|
+
} catch (err) {
|
|
3232
|
+
setFormError(err.message || "Unable to initialize Google sign-in");
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
if (typeof document !== "undefined") {
|
|
3236
|
+
const cookieAttrs = "; path=/; SameSite=Lax" + (window.location.protocol === "https:" ? "; Secure" : "");
|
|
3237
|
+
document.cookie = `iqauth_pkce=${pkce.codeVerifier}${cookieAttrs}`;
|
|
3238
|
+
document.cookie = `iqauth_state=${pkce.state}${cookieAttrs}`;
|
|
3239
|
+
}
|
|
3240
|
+
const params = new URLSearchParams({
|
|
3241
|
+
redirect_uri: returnTo,
|
|
3242
|
+
client_id: ctx.app.defaultClientId,
|
|
3243
|
+
state: pkce.state,
|
|
3244
|
+
nonce: pkce.nonce,
|
|
3245
|
+
code_challenge: pkce.codeChallenge,
|
|
3246
|
+
code_challenge_method: "S256",
|
|
3247
|
+
scope: "openid profile email"
|
|
3248
|
+
});
|
|
3249
|
+
const url2 = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/google?${params.toString()}`;
|
|
2944
3250
|
window.location.href = url2;
|
|
2945
3251
|
};
|
|
2946
3252
|
(0, import_react.useEffect)(() => {
|
|
@@ -4382,6 +4688,7 @@ var __version__ = "phase-bc-1.0.0";
|
|
|
4382
4688
|
UserButton,
|
|
4383
4689
|
UserProfile,
|
|
4384
4690
|
Waitlist,
|
|
4691
|
+
__useIQAuthInternal,
|
|
4385
4692
|
__version__,
|
|
4386
4693
|
isReturnToAllowed,
|
|
4387
4694
|
isSilentSsoEligible,
|