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