tokenidp-react 0.3.5

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/dist/index.mjs ADDED
@@ -0,0 +1,691 @@
1
+ // src/AuthProvider.jsx
2
+ import React, {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useReducer,
8
+ useRef
9
+ } from "react";
10
+
11
+ // src/config.js
12
+ var defaultAuthConfig = {
13
+ authority: "",
14
+ // e.g. https://idp.tokentresor.com
15
+ clientId: "",
16
+ tenantKey: "",
17
+ tenantPropagationMode: "all",
18
+ // all | api | none
19
+ tenantQueryParameter: "tenant",
20
+ tenantHeaderName: "X-Tenant-Key",
21
+ tenantKeyStorageKey: "idp_tenant_key",
22
+ redirectUri: "",
23
+ // e.g. https://app.com/auth/callback
24
+ postLoginRedirectUri: "/",
25
+ // where to go after success
26
+ postLogoutRedirectUri: "/login",
27
+ // where to go after logout
28
+ scope: "openid profile offline_access",
29
+ audience: "",
30
+ // optional
31
+ // endpoints (default paths)
32
+ authorizePath: "/authorize",
33
+ tokenPath: "/token",
34
+ revokePath: "/revoke",
35
+ logoutPath: "/logout",
36
+ // storage: "memory" | "sessionStorage" | "localStorage"
37
+ storage: "sessionStorage",
38
+ // keys
39
+ storageKey: "idp_user",
40
+ pkceVerifierKey: "idp_pkce_verifier",
41
+ oauthStateKey: "idp_oauth_state",
42
+ // refresh behavior
43
+ autoRefresh: true,
44
+ // refresh skew in seconds (refresh token slightly before expiry)
45
+ refreshSkewSeconds: 180
46
+ };
47
+
48
+ // src/storage.js
49
+ function createStorage(mode) {
50
+ if (mode === "localStorage") return window.localStorage;
51
+ if (mode === "sessionStorage") return window.sessionStorage;
52
+ let mem = {};
53
+ return {
54
+ getItem: (k) => k in mem ? mem[k] : null,
55
+ setItem: (k, v) => {
56
+ mem[k] = String(v);
57
+ },
58
+ removeItem: (k) => {
59
+ delete mem[k];
60
+ },
61
+ clear: () => {
62
+ mem = {};
63
+ }
64
+ };
65
+ }
66
+
67
+ // src/pkce.js
68
+ function base64UrlEncode(arrayBuffer) {
69
+ const bytes = new Uint8Array(arrayBuffer);
70
+ let str = "";
71
+ for (let i = 0; i < bytes.byteLength; i++)
72
+ str += String.fromCharCode(bytes[i]);
73
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
74
+ }
75
+ function generateCodeVerifier(length = 64) {
76
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
77
+ const randomValues = new Uint8Array(length);
78
+ crypto.getRandomValues(randomValues);
79
+ let verifier = "";
80
+ for (let i = 0; i < randomValues.length; i++) {
81
+ verifier += charset[randomValues[i] % charset.length];
82
+ }
83
+ return verifier;
84
+ }
85
+ async function generateCodeChallenge(verifier) {
86
+ const enc = new TextEncoder();
87
+ const data = enc.encode(verifier);
88
+ const digest = await crypto.subtle.digest("SHA-256", data);
89
+ return base64UrlEncode(digest);
90
+ }
91
+
92
+ // src/tenant.js
93
+ function normalizeTenantPropagationMode(value) {
94
+ const normalized = String(value || "all").trim().toLowerCase();
95
+ if (normalized === "none" || normalized === "api") {
96
+ return normalized;
97
+ }
98
+ return "all";
99
+ }
100
+ function getAuthTenantKey(config) {
101
+ return normalizeTenantPropagationMode(config == null ? void 0 : config.tenantPropagationMode) === "all" ? String((config == null ? void 0 : config.tenantKey) || "").trim() : "";
102
+ }
103
+ function getApiTenantKey(config) {
104
+ const mode = normalizeTenantPropagationMode(config == null ? void 0 : config.tenantPropagationMode);
105
+ return mode === "all" || mode === "api" ? String((config == null ? void 0 : config.tenantKey) || "").trim() : "";
106
+ }
107
+ function resolveRawTenantKey(config, overrides = {}) {
108
+ const explicitTenantKey = String(
109
+ (overrides == null ? void 0 : overrides.tenantKey) || (config == null ? void 0 : config.tenantKey) || ""
110
+ ).trim();
111
+ if (explicitTenantKey) {
112
+ return explicitTenantKey;
113
+ }
114
+ if (typeof window !== "undefined") {
115
+ const tenantFromQuery = new URLSearchParams(window.location.search).get(
116
+ (config == null ? void 0 : config.tenantQueryParameter) || "tenant"
117
+ );
118
+ if (tenantFromQuery) {
119
+ return tenantFromQuery.trim();
120
+ }
121
+ }
122
+ if (typeof sessionStorage !== "undefined") {
123
+ const tenantFromStorage = sessionStorage.getItem(
124
+ (config == null ? void 0 : config.tenantKeyStorageKey) || "idp_tenant_key"
125
+ );
126
+ if (tenantFromStorage) {
127
+ return tenantFromStorage.trim();
128
+ }
129
+ }
130
+ return "";
131
+ }
132
+ function resolveAuthTenantKey(config, overrides = {}) {
133
+ return normalizeTenantPropagationMode(config == null ? void 0 : config.tenantPropagationMode) === "all" ? resolveRawTenantKey(config, overrides) : "";
134
+ }
135
+ function resolveApiTenantKey(config, overrides = {}) {
136
+ const mode = normalizeTenantPropagationMode(config == null ? void 0 : config.tenantPropagationMode);
137
+ return mode === "all" || mode === "api" ? resolveRawTenantKey(config, overrides) : "";
138
+ }
139
+
140
+ // src/oauth.js
141
+ function randomState(length = 32) {
142
+ const bytes = new Uint8Array(length);
143
+ crypto.getRandomValues(bytes);
144
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
145
+ }
146
+ function buildAuthorizeUrl(config, params) {
147
+ const url = new URL(config.authority + config.authorizePath);
148
+ const tenantKey = getAuthTenantKey({
149
+ ...config,
150
+ tenantPropagationMode: normalizeTenantPropagationMode(
151
+ config == null ? void 0 : config.tenantPropagationMode
152
+ ),
153
+ tenantKey: params.tenantKey || config.tenantKey
154
+ });
155
+ url.searchParams.set("response_type", "code");
156
+ url.searchParams.set("client_id", config.clientId);
157
+ url.searchParams.set("redirect_uri", config.redirectUri);
158
+ url.searchParams.set("scope", config.scope);
159
+ url.searchParams.set("code_challenge", params.codeChallenge);
160
+ url.searchParams.set("code_challenge_method", "S256");
161
+ if (params.state) url.searchParams.set("state", params.state);
162
+ if (params.audience || config.audience)
163
+ url.searchParams.set("audience", params.audience || config.audience);
164
+ if (tenantKey) {
165
+ url.searchParams.set(
166
+ config.tenantQueryParameter || "tenant",
167
+ tenantKey
168
+ );
169
+ }
170
+ if (params.prompt) url.searchParams.set("prompt", params.prompt);
171
+ if (params.loginHint) url.searchParams.set("login_hint", params.loginHint);
172
+ return url.toString();
173
+ }
174
+
175
+ // src/authApi.js
176
+ async function httpPostJson(url, body, extraHeaders = {}) {
177
+ const res = await fetch(url, {
178
+ method: "POST",
179
+ headers: { "Content-Type": "application/json", ...extraHeaders },
180
+ body: JSON.stringify(body)
181
+ });
182
+ const text = await res.text();
183
+ let data = null;
184
+ try {
185
+ data = text ? JSON.parse(text) : null;
186
+ } catch {
187
+ data = text;
188
+ }
189
+ if (!res.ok) {
190
+ const msg = getErrorMessage(data, res.status);
191
+ const err = new Error(msg);
192
+ err.status = res.status;
193
+ err.data = data;
194
+ throw err;
195
+ }
196
+ return data;
197
+ }
198
+ function getErrorMessage(data, status) {
199
+ var _a;
200
+ if (!data) return `HTTP ${status}`;
201
+ const wrappedError = data.error && typeof data.error === "object" ? data.error : ((_a = data.value) == null ? void 0 : _a.error) && typeof data.value.error === "object" ? data.value.error : null;
202
+ return data.error_description || (wrappedError == null ? void 0 : wrappedError.error) || (wrappedError == null ? void 0 : wrappedError.Error) || (wrappedError == null ? void 0 : wrappedError.message) || (wrappedError == null ? void 0 : wrappedError.Message) || (typeof data.error === "string" ? data.error : "") || data.message || `HTTP ${status}`;
203
+ }
204
+ function withTenant(url, config, target = "api") {
205
+ const tenantKey = target === "auth" ? getAuthTenantKey(config) : getApiTenantKey(config);
206
+ if (!tenantKey) {
207
+ return url;
208
+ }
209
+ const tenantUrl = new URL(url, typeof window !== "undefined" ? window.location.origin : void 0);
210
+ tenantUrl.searchParams.set(config.tenantQueryParameter || "tenant", tenantKey);
211
+ return tenantUrl.toString();
212
+ }
213
+ function extractToken(tokenPayload) {
214
+ if (!tokenPayload)
215
+ return { accessToken: "", refreshToken: "", expiresIn: 0, idToken: "" };
216
+ const accessToken = tokenPayload.value.accessToken || tokenPayload.value.access_token || "";
217
+ const refreshToken = tokenPayload.value.refreshToken || tokenPayload.value.refresh_token || "";
218
+ const expiresIn = Number(
219
+ tokenPayload.value.expiresIn || tokenPayload.value.expires_in || 0
220
+ ) || 0;
221
+ const idToken = tokenPayload.idToken || tokenPayload.id_token || "";
222
+ return { accessToken, refreshToken, expiresIn, idToken };
223
+ }
224
+ async function exchangeAuthorizationCode(config, payload) {
225
+ const url = withTenant(config.authority + config.tokenPath, config, "auth");
226
+ return await httpPostJson(url, payload);
227
+ }
228
+ async function refreshWithToken(config, payload) {
229
+ const url = withTenant(config.authority + config.tokenPath, config, "auth");
230
+ return await httpPostJson(url, payload);
231
+ }
232
+ async function revokeToken(config, { accessToken, token, reasonRevoked }) {
233
+ if (!(config == null ? void 0 : config.authority) || !accessToken || !token) {
234
+ return null;
235
+ }
236
+ const url = withTenant(
237
+ new URL(config.revokePath || "/revoke", config.authority).toString(),
238
+ config,
239
+ "auth"
240
+ );
241
+ const res = await fetch(url, {
242
+ method: "DELETE",
243
+ headers: {
244
+ Authorization: `Bearer ${accessToken}`,
245
+ "Content-Type": "application/json"
246
+ },
247
+ body: JSON.stringify({
248
+ token,
249
+ reasonRevoked: reasonRevoked || "logout"
250
+ })
251
+ });
252
+ const text = await res.text();
253
+ let data = null;
254
+ try {
255
+ data = text ? JSON.parse(text) : null;
256
+ } catch {
257
+ data = text;
258
+ }
259
+ if (!res.ok) {
260
+ const msg = getErrorMessage(data, res.status);
261
+ const err = new Error(msg);
262
+ err.status = res.status;
263
+ err.data = data;
264
+ throw err;
265
+ }
266
+ return data;
267
+ }
268
+ function buildLogoutUrl(config) {
269
+ if (!(config == null ? void 0 : config.authority)) return "";
270
+ const url = new URL(
271
+ withTenant(
272
+ new URL(config.logoutPath || "/logout", config.authority).toString(),
273
+ config,
274
+ "auth"
275
+ )
276
+ );
277
+ if (config.clientId) {
278
+ url.searchParams.set("client_id", config.clientId);
279
+ }
280
+ const postLogoutRedirectUri = resolvePostLogoutRedirectUri(config);
281
+ if (postLogoutRedirectUri) {
282
+ url.searchParams.set("post_logout_redirect_uri", postLogoutRedirectUri);
283
+ }
284
+ return url.toString();
285
+ }
286
+ function resolvePostLogoutRedirectUri(config) {
287
+ var _a;
288
+ const candidate = config == null ? void 0 : config.postLogoutRedirectUri;
289
+ if (!candidate) {
290
+ return "";
291
+ }
292
+ if (typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.origin)) {
293
+ return new URL(candidate, window.location.origin).toString();
294
+ }
295
+ if (config == null ? void 0 : config.redirectUri) {
296
+ return new URL(candidate, config.redirectUri).toString();
297
+ }
298
+ return String(candidate);
299
+ }
300
+
301
+ // src/AuthProvider.jsx
302
+ var AuthContext = createContext(null);
303
+ var initialState = {
304
+ isAuthenticated: false,
305
+ tenantKey: "",
306
+ landingPage: "",
307
+ accessToken: "",
308
+ refreshToken: "",
309
+ idToken: "",
310
+ expiresAt: 0,
311
+ error: ""
312
+ };
313
+ function reducer(state, action) {
314
+ switch (action.type) {
315
+ case "LOGIN_SUCCESS":
316
+ return {
317
+ ...state,
318
+ ...action.payload,
319
+ isAuthenticated: true,
320
+ error: ""
321
+ };
322
+ case "SET_ERROR":
323
+ return { ...state, error: action.payload || "Unknown error" };
324
+ case "LOGOUT":
325
+ return { ...initialState };
326
+ case "TOKENS_UPDATED":
327
+ return { ...state, ...action.payload };
328
+ default:
329
+ return state;
330
+ }
331
+ }
332
+ function IdpAuthProvider({ children, config }) {
333
+ const baseConfig = useMemo(
334
+ () => ({ ...defaultAuthConfig, ...config || {} }),
335
+ [config]
336
+ );
337
+ const storage = useMemo(
338
+ () => createStorage(baseConfig.storage),
339
+ [baseConfig.storage]
340
+ );
341
+ const persistedRaw = storage.getItem(baseConfig.storageKey);
342
+ const persisted = persistedRaw ? safeJsonParse(persistedRaw) : null;
343
+ const mergedConfig = useMemo(() => {
344
+ const normalizedConfig = {
345
+ ...baseConfig,
346
+ tenantPropagationMode: normalizeTenantPropagationMode(
347
+ baseConfig == null ? void 0 : baseConfig.tenantPropagationMode
348
+ )
349
+ };
350
+ const resolvedTenantKey = resolveApiTenantKey(normalizedConfig) || getApiTenantKey({
351
+ ...normalizedConfig,
352
+ tenantKey: persisted == null ? void 0 : persisted.tenantKey
353
+ });
354
+ return {
355
+ ...normalizedConfig,
356
+ tenantKey: resolvedTenantKey
357
+ };
358
+ }, [baseConfig, persisted == null ? void 0 : persisted.tenantKey]);
359
+ const [state, dispatch] = useReducer(
360
+ reducer,
361
+ buildInitialState(persisted, mergedConfig)
362
+ );
363
+ useEffect(() => {
364
+ storage.setItem(mergedConfig.storageKey, JSON.stringify(state));
365
+ }, [state, storage, mergedConfig.storageKey]);
366
+ const refreshTimerRef = useRef(null);
367
+ const refreshInFlightRef = useRef(false);
368
+ function clearRefreshTimer() {
369
+ if (refreshTimerRef.current) {
370
+ clearTimeout(refreshTimerRef.current);
371
+ refreshTimerRef.current = null;
372
+ }
373
+ }
374
+ function clearLocalSession() {
375
+ storage.removeItem(mergedConfig.storageKey);
376
+ sessionStorage.removeItem(mergedConfig.pkceVerifierKey);
377
+ sessionStorage.removeItem(mergedConfig.oauthStateKey);
378
+ sessionStorage.removeItem(mergedConfig.tenantKeyStorageKey);
379
+ dispatch({ type: "LOGOUT" });
380
+ }
381
+ async function tryRefreshWithRetry(retries, retryDelayMs) {
382
+ try {
383
+ await api.refresh();
384
+ return true;
385
+ } catch (err) {
386
+ if (retries > 0) {
387
+ await new Promise((res) => setTimeout(res, retryDelayMs));
388
+ return tryRefreshWithRetry(retries - 1, retryDelayMs);
389
+ }
390
+ return false;
391
+ }
392
+ }
393
+ function scheduleAutoRefresh(nextExpiresAtMs) {
394
+ clearRefreshTimer();
395
+ if (!mergedConfig.autoRefresh || !nextExpiresAtMs) return;
396
+ const skewMs = (mergedConfig.refreshSkewSeconds || 60) * 1e3;
397
+ const delay = Math.max(0, nextExpiresAtMs - Date.now() - skewMs);
398
+ refreshTimerRef.current = setTimeout(async () => {
399
+ if (refreshInFlightRef.current) return;
400
+ refreshInFlightRef.current = true;
401
+ const ok = await tryRefreshWithRetry(1, 5e3);
402
+ refreshInFlightRef.current = false;
403
+ if (!ok) {
404
+ api.logout();
405
+ }
406
+ }, delay);
407
+ }
408
+ useEffect(() => {
409
+ if (!state.isAuthenticated || !state.expiresAt) return;
410
+ scheduleAutoRefresh(state.expiresAt);
411
+ return () => {
412
+ clearRefreshTimer();
413
+ };
414
+ }, [state.isAuthenticated, state.expiresAt]);
415
+ const api = useMemo(() => {
416
+ return {
417
+ ...state,
418
+ login: async (options = {}) => {
419
+ if (!mergedConfig.authority || !mergedConfig.clientId || !mergedConfig.redirectUri) {
420
+ throw new Error(
421
+ "Missing authority/clientId/redirectUri in IdpAuthProvider config."
422
+ );
423
+ }
424
+ const verifier = generateCodeVerifier();
425
+ const challenge = await generateCodeChallenge(verifier);
426
+ const stateVal = randomState();
427
+ const authorizeTenantKey = resolveAuthTenantKey(mergedConfig, options);
428
+ const apiTenantKey = resolveApiTenantKey(mergedConfig, options);
429
+ sessionStorage.setItem(mergedConfig.pkceVerifierKey, verifier);
430
+ sessionStorage.setItem(mergedConfig.oauthStateKey, stateVal);
431
+ if (apiTenantKey) {
432
+ sessionStorage.setItem(mergedConfig.tenantKeyStorageKey, apiTenantKey);
433
+ } else {
434
+ sessionStorage.removeItem(mergedConfig.tenantKeyStorageKey);
435
+ }
436
+ const authorizeUrl = buildAuthorizeUrl(mergedConfig, {
437
+ codeChallenge: challenge,
438
+ state: stateVal,
439
+ prompt: options.prompt,
440
+ loginHint: options.loginHint,
441
+ audience: options.audience,
442
+ tenantKey: authorizeTenantKey
443
+ });
444
+ window.location.assign(authorizeUrl);
445
+ },
446
+ logout: async () => {
447
+ const logoutUrl = buildLogoutUrl(mergedConfig);
448
+ clearRefreshTimer();
449
+ try {
450
+ await revokeToken(mergedConfig, {
451
+ accessToken: state.accessToken,
452
+ token: state.refreshToken,
453
+ reasonRevoked: "logout"
454
+ });
455
+ } catch (error) {
456
+ console.warn("Token revocation during logout failed.", error);
457
+ }
458
+ if (typeof window !== "undefined" && logoutUrl) {
459
+ window.addEventListener("pagehide", clearLocalSession, { once: true });
460
+ window.location.assign(logoutUrl);
461
+ return;
462
+ }
463
+ clearLocalSession();
464
+ },
465
+ // exchanges code->tokens and stores the OAuth session
466
+ handleCallback: async ({ code, state: returnedState }) => {
467
+ const verifier = sessionStorage.getItem(mergedConfig.pkceVerifierKey);
468
+ if (!verifier) throw new Error("Missing code verifier (PKCE).");
469
+ const tenantKey = resolveApiTenantKey(mergedConfig);
470
+ const expectedState = sessionStorage.getItem(
471
+ mergedConfig.oauthStateKey
472
+ );
473
+ if (expectedState && returnedState && expectedState !== returnedState) {
474
+ throw new Error("Invalid OAuth state. Possible CSRF.");
475
+ }
476
+ var tokenPayload = {};
477
+ try {
478
+ tokenPayload = await exchangeAuthorizationCode(mergedConfig, {
479
+ grantType: "authorization_code",
480
+ clientId: mergedConfig.clientId,
481
+ redirectUri: mergedConfig.redirectUri,
482
+ code,
483
+ codeVerifier: verifier,
484
+ scope: mergedConfig.scope
485
+ });
486
+ } catch (e) {
487
+ console.error("exchangeAuthorizationCode failed:", e);
488
+ console.error("Status:", e == null ? void 0 : e.status);
489
+ console.error("Data:", e == null ? void 0 : e.data);
490
+ throw e;
491
+ }
492
+ const { accessToken, refreshToken, expiresIn, idToken } = extractToken(tokenPayload);
493
+ if (!accessToken)
494
+ throw new Error("Token response did not include an access token.");
495
+ const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : 0;
496
+ dispatch({
497
+ type: "LOGIN_SUCCESS",
498
+ payload: {
499
+ tenantKey,
500
+ accessToken,
501
+ refreshToken: refreshToken || "",
502
+ idToken: idToken || "",
503
+ expiresAt,
504
+ landingPage: mergedConfig.postLoginRedirectUri || "/"
505
+ }
506
+ });
507
+ sessionStorage.removeItem(mergedConfig.pkceVerifierKey);
508
+ sessionStorage.removeItem(mergedConfig.oauthStateKey);
509
+ return {
510
+ tenantKey,
511
+ accessToken,
512
+ refreshToken: refreshToken || "",
513
+ idToken: idToken || "",
514
+ expiresAt
515
+ };
516
+ },
517
+ refresh: async () => {
518
+ if (!state.refreshToken) throw new Error("No refresh token available.");
519
+ const tokenPayload = await refreshWithToken(mergedConfig, {
520
+ grantType: "refresh_token",
521
+ clientId: mergedConfig.clientId,
522
+ refreshToken: state.refreshToken,
523
+ scope: mergedConfig.scope
524
+ });
525
+ const { accessToken, refreshToken, expiresIn, idToken } = extractToken(tokenPayload);
526
+ if (!accessToken)
527
+ throw new Error("Refresh response did not include an access token.");
528
+ const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : 0;
529
+ dispatch({
530
+ type: "TOKENS_UPDATED",
531
+ payload: {
532
+ accessToken,
533
+ // if rotation: use new refresh token if provided
534
+ refreshToken: refreshToken || state.refreshToken,
535
+ idToken: idToken || state.idToken,
536
+ expiresAt
537
+ }
538
+ });
539
+ return {
540
+ accessToken,
541
+ refreshToken: refreshToken || state.refreshToken,
542
+ idToken,
543
+ expiresAt
544
+ };
545
+ },
546
+ setError: (message) => dispatch({ type: "SET_ERROR", payload: message })
547
+ };
548
+ }, [state, mergedConfig, storage]);
549
+ return /* @__PURE__ */ React.createElement(AuthContext.Provider, { value: api }, children);
550
+ }
551
+ function useAuth() {
552
+ const ctx = useContext(AuthContext);
553
+ if (!ctx) throw new Error("useAuth must be used inside IdpAuthProvider");
554
+ return ctx;
555
+ }
556
+ function safeJsonParse(raw) {
557
+ try {
558
+ return JSON.parse(raw);
559
+ } catch {
560
+ return null;
561
+ }
562
+ }
563
+ function buildInitialState(persistedState, config) {
564
+ if (!persistedState) {
565
+ return {
566
+ ...initialState,
567
+ tenantKey: config.tenantKey
568
+ };
569
+ }
570
+ return {
571
+ ...initialState,
572
+ isAuthenticated: !!persistedState.isAuthenticated,
573
+ landingPage: persistedState.landingPage || "",
574
+ accessToken: persistedState.accessToken || "",
575
+ refreshToken: persistedState.refreshToken || "",
576
+ idToken: persistedState.idToken || "",
577
+ expiresAt: persistedState.expiresAt || 0,
578
+ error: persistedState.error || "",
579
+ tenantKey: config.tenantKey
580
+ };
581
+ }
582
+
583
+ // src/AuthCallback.jsx
584
+ import React2, { useEffect as useEffect2, useRef as useRef2, useState } from "react";
585
+ import { useNavigate } from "react-router-dom";
586
+ function renderLogoContent(logo, logoAlt, fallback) {
587
+ if (!logo) {
588
+ return /* @__PURE__ */ React2.createElement("div", { className: "logo mb-3" }, fallback);
589
+ }
590
+ if (typeof logo === "string") {
591
+ return /* @__PURE__ */ React2.createElement("div", { className: "mb-3" }, /* @__PURE__ */ React2.createElement("img", { src: logo, alt: logoAlt, width: "250" }));
592
+ }
593
+ return /* @__PURE__ */ React2.createElement("div", { className: "mb-3" }, logo);
594
+ }
595
+ function AuthCallback({
596
+ redirectTo,
597
+ logo = null,
598
+ logoAlt = "Application logo",
599
+ fallbackBadge = "ID"
600
+ }) {
601
+ const navigate = useNavigate();
602
+ const auth = useAuth();
603
+ const [error, setError] = useState(null);
604
+ const ranRef = useRef2(false);
605
+ useEffect2(() => {
606
+ const run = async () => {
607
+ if (ranRef.current) return;
608
+ ranRef.current = true;
609
+ const qs = new URLSearchParams(window.location.search);
610
+ const code = qs.get("code");
611
+ const state = qs.get("state");
612
+ const err = qs.get("error");
613
+ const errDesc = qs.get("error_description");
614
+ if (err) {
615
+ const msg = errDesc || err || "Authentication error.";
616
+ setError(msg);
617
+ auth.setError(msg);
618
+ return;
619
+ }
620
+ if (!code || !state) {
621
+ const msg = "Missing authorization code or state.";
622
+ setError(msg);
623
+ auth.setError(msg);
624
+ return;
625
+ }
626
+ try {
627
+ await auth.handleCallback({ code, state });
628
+ const target = redirectTo || auth.landingPage || "/";
629
+ navigate(target, { replace: true });
630
+ } catch (e) {
631
+ const msg = (e == null ? void 0 : e.message) || "Login callback failed.";
632
+ setError(msg);
633
+ auth.setError(msg);
634
+ }
635
+ };
636
+ run();
637
+ }, [auth, navigate, redirectTo]);
638
+ return /* @__PURE__ */ React2.createElement("div", { className: "login-page" }, /* @__PURE__ */ React2.createElement("section", { className: "login-section-container" }, /* @__PURE__ */ React2.createElement("div", { className: "login-section redirect-card" }, /* @__PURE__ */ React2.createElement("div", { className: "col-12 p-4 text-center" }, renderLogoContent(logo, logoAlt, fallbackBadge), /* @__PURE__ */ React2.createElement("h1", null, "Completing sign-in"), /* @__PURE__ */ React2.createElement("p", { className: "text-muted" }, error ? error : "Please wait while we finish authentication.")))));
639
+ }
640
+
641
+ // src/LoginPage.jsx
642
+ import React3, { useEffect as useEffect3 } from "react";
643
+ function renderLogoContent2(logo, logoAlt, fallback) {
644
+ if (!logo) {
645
+ return /* @__PURE__ */ React3.createElement("div", { className: "logo mb-3" }, fallback);
646
+ }
647
+ if (typeof logo === "string") {
648
+ return /* @__PURE__ */ React3.createElement("div", { className: "mb-3" }, /* @__PURE__ */ React3.createElement("img", { src: logo, alt: logoAlt, width: "250" }));
649
+ }
650
+ return /* @__PURE__ */ React3.createElement("div", { className: "mb-3" }, logo);
651
+ }
652
+ function LoginPage({
653
+ logo = null,
654
+ logoAlt = "Application logo",
655
+ title = "Redirecting to sign-in...",
656
+ subtitle = "Please wait while we securely connect to Identity.",
657
+ signedOutBadge = "Signed out",
658
+ signedOutTitle = "You have been signed out",
659
+ signedOutSubtitle = "Start a new session when you are ready.",
660
+ signInAgainLabel = "Sign in again",
661
+ fallbackBadge = "ID",
662
+ loginOptions
663
+ }) {
664
+ const auth = useAuth();
665
+ const isLoggedOut = new URLSearchParams(window.location.search).get("logged_out") === "1";
666
+ useEffect3(() => {
667
+ if (isLoggedOut) {
668
+ return;
669
+ }
670
+ auth.login(loginOptions);
671
+ }, [auth, isLoggedOut, loginOptions]);
672
+ if (isLoggedOut) {
673
+ return /* @__PURE__ */ React3.createElement("div", { className: "login-page" }, /* @__PURE__ */ React3.createElement("section", { className: "login-section-container" }, /* @__PURE__ */ React3.createElement("div", { className: "login-section redirect-card" }, /* @__PURE__ */ React3.createElement("div", { className: "p-4 text-center" }, renderLogoContent2(logo, logoAlt, fallbackBadge), /* @__PURE__ */ React3.createElement("h1", null, signedOutTitle), /* @__PURE__ */ React3.createElement("p", { className: "text-muted" }, signedOutSubtitle), /* @__PURE__ */ React3.createElement(
674
+ "button",
675
+ {
676
+ className: "btn btn-primary mt-3",
677
+ onClick: () => auth.login(loginOptions)
678
+ },
679
+ signInAgainLabel
680
+ )))));
681
+ }
682
+ return /* @__PURE__ */ React3.createElement("div", { className: "login-page" }, /* @__PURE__ */ React3.createElement("section", { className: "login-section-container" }, /* @__PURE__ */ React3.createElement("div", { className: "login-section redirect-card" }, /* @__PURE__ */ React3.createElement("div", { className: "p-4 text-center" }, renderLogoContent2(logo, logoAlt, fallbackBadge), /* @__PURE__ */ React3.createElement("h1", null, title), /* @__PURE__ */ React3.createElement("p", { className: "text-muted" }, subtitle), /* @__PURE__ */ React3.createElement("div", { className: "spinner mt-3" })))));
683
+ }
684
+ export {
685
+ AuthCallback,
686
+ IdpAuthProvider,
687
+ LoginPage,
688
+ defaultAuthConfig,
689
+ useAuth
690
+ };
691
+ //# sourceMappingURL=index.mjs.map