@witsauth/react-client 1.3.7 → 1.3.9
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/core/AuthClient.d.ts +6 -2
- package/dist/core/pkce.d.ts +1 -0
- package/dist/core/storage.d.ts +1 -1
- package/dist/index.es.js +159 -97
- package/dist/index.umd.js +1 -1
- package/dist/models/types.d.ts +20 -0
- package/dist/react/AuthCallback.d.ts +1 -1
- package/dist/react/AuthContext.d.ts +8 -5
- package/package.json +2 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OAuth2Config, TokenInfo } from '../models/types';
|
|
1
|
+
import { AuthorizeOptions, OAuth2Config, TokenInfo, UserInfo } from '../models/types';
|
|
2
2
|
import { TokenStorage } from './storage';
|
|
3
3
|
export declare class AuthClient {
|
|
4
4
|
private config;
|
|
@@ -6,11 +6,15 @@ export declare class AuthClient {
|
|
|
6
6
|
private refreshPromise;
|
|
7
7
|
constructor(config: OAuth2Config, storage?: TokenStorage);
|
|
8
8
|
log(level: 'error' | 'warn' | 'info' | 'debug', ...args: any[]): void;
|
|
9
|
-
startAuthorization(): Promise<void>;
|
|
9
|
+
startAuthorization(options?: AuthorizeOptions): Promise<void>;
|
|
10
10
|
exchangeCodeForTokens(code: string, state: string): Promise<TokenInfo>;
|
|
11
11
|
refreshAccessToken(): Promise<TokenInfo>;
|
|
12
12
|
private saveTokenInfo;
|
|
13
13
|
getAccessToken(): string | null;
|
|
14
|
+
getIdToken(): string | null;
|
|
15
|
+
getRefreshToken(): string | null;
|
|
16
|
+
getUserProfile<T extends UserInfo = UserInfo>(): T | null;
|
|
17
|
+
getToken(): Promise<string | null>;
|
|
14
18
|
getExpiresAt(): number | null;
|
|
15
19
|
isAuthenticated(): boolean;
|
|
16
20
|
navigateToAccountManagement(): Promise<void>;
|
package/dist/core/pkce.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export declare function generateState(): string;
|
|
|
11
11
|
* Creates a SHA-256 code challenge from the code verifier
|
|
12
12
|
*/
|
|
13
13
|
export declare function createCodeChallenge(verifier: string): Promise<string>;
|
|
14
|
+
export declare function decodeJwtPayload<T = Record<string, any>>(token: string | null | undefined): T | null;
|
package/dist/core/storage.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare class TokenStorage {
|
|
|
3
3
|
private storageType?;
|
|
4
4
|
private customStorage?;
|
|
5
5
|
private memoryStorage;
|
|
6
|
-
constructor(storageType?:
|
|
6
|
+
constructor(storageType?: 'localStorage' | 'sessionStorage' | 'custom' | undefined, customStorage?: OAuth2Storage | undefined);
|
|
7
7
|
private getStorage;
|
|
8
8
|
setItem(key: string, value: string): void;
|
|
9
9
|
getItem(key: string): string | null;
|
package/dist/index.es.js
CHANGED
|
@@ -1,33 +1,59 @@
|
|
|
1
|
-
import { createContext as e,
|
|
2
|
-
import { Fragment as
|
|
1
|
+
import { createContext as e, useCallback as t, useContext as n, useEffect as r, useMemo as i, useRef as a, useState as o } from "react";
|
|
2
|
+
import { Fragment as s, jsx as c } from "react/jsx-runtime";
|
|
3
3
|
//#region src/models/types.ts
|
|
4
|
-
var
|
|
4
|
+
var l = {
|
|
5
5
|
ACCESS_TOKEN: "witsauth_access_token",
|
|
6
6
|
REFRESH_TOKEN: "witsauth_refresh_token",
|
|
7
7
|
TOKEN_EXPIRES_AT: "witsauth_expires_at",
|
|
8
8
|
TOKEN_TYPE: "witsauth_token_type",
|
|
9
9
|
SCOPE: "witsauth_scope",
|
|
10
|
+
ID_TOKEN: "witsauth_id_token",
|
|
10
11
|
CODE_VERIFIER: "witsauth_code_verifier",
|
|
11
12
|
STATE: "witsauth_state",
|
|
12
13
|
NONCE: "witsauth_nonce"
|
|
13
14
|
};
|
|
14
15
|
//#endregion
|
|
15
16
|
//#region src/core/pkce.ts
|
|
16
|
-
function
|
|
17
|
+
function u() {
|
|
17
18
|
let e = /* @__PURE__ */ new Uint8Array(32);
|
|
18
19
|
return crypto.getRandomValues(e), btoa(String.fromCharCode(...e)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
19
20
|
}
|
|
20
|
-
function
|
|
21
|
+
function d() {
|
|
21
22
|
let e = /* @__PURE__ */ new Uint8Array(32);
|
|
22
23
|
return crypto.getRandomValues(e), btoa(String.fromCharCode(...e)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
23
24
|
}
|
|
24
|
-
async function
|
|
25
|
+
async function f(e) {
|
|
25
26
|
let t = new TextEncoder().encode(e), n = await crypto.subtle.digest("SHA-256", t), r = new Uint8Array(n);
|
|
26
27
|
return btoa(String.fromCharCode(...r)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
27
28
|
}
|
|
29
|
+
function p(e) {
|
|
30
|
+
let t = e.replace(/-/g, "+").replace(/_/g, "/"), n = t + "=".repeat((4 - t.length % 4) % 4);
|
|
31
|
+
if (typeof atob == "function") {
|
|
32
|
+
let e = atob(n), t = new Uint8Array(e.length);
|
|
33
|
+
for (let n = 0; n < e.length; n++) t[n] = e.charCodeAt(n);
|
|
34
|
+
if (typeof TextDecoder < "u") return new TextDecoder().decode(t);
|
|
35
|
+
let r = "";
|
|
36
|
+
for (let e = 0; e < t.length; e++) r += String.fromCharCode(t[e]);
|
|
37
|
+
return decodeURIComponent(escape(r));
|
|
38
|
+
}
|
|
39
|
+
let r = typeof globalThis < "u" ? globalThis : void 0;
|
|
40
|
+
if (r?.Buffer) return r.Buffer.from(n, "base64").toString("utf8");
|
|
41
|
+
throw Error("Unable to decode base64url string.");
|
|
42
|
+
}
|
|
43
|
+
function m(e) {
|
|
44
|
+
if (!e || typeof e != "string") return null;
|
|
45
|
+
let t = e.split(".");
|
|
46
|
+
if (t.length < 2) return null;
|
|
47
|
+
try {
|
|
48
|
+
let e = p(t[1]);
|
|
49
|
+
return JSON.parse(e);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
28
54
|
//#endregion
|
|
29
55
|
//#region src/core/storage.ts
|
|
30
|
-
var
|
|
56
|
+
var h = class {
|
|
31
57
|
storageType;
|
|
32
58
|
customStorage;
|
|
33
59
|
memoryStorage = /* @__PURE__ */ new Map();
|
|
@@ -36,10 +62,10 @@ var f = class {
|
|
|
36
62
|
}
|
|
37
63
|
getStorage() {
|
|
38
64
|
if (this.storageType === "custom" && this.customStorage) return this.customStorage;
|
|
39
|
-
if (typeof window < "u") {
|
|
65
|
+
if (typeof window < "u") try {
|
|
40
66
|
if (this.storageType === "sessionStorage" && window.sessionStorage) return window.sessionStorage;
|
|
41
67
|
if ((!this.storageType || this.storageType === "localStorage") && window.localStorage) return window.localStorage;
|
|
42
|
-
}
|
|
68
|
+
} catch {}
|
|
43
69
|
let e = this.memoryStorage;
|
|
44
70
|
return {
|
|
45
71
|
getItem: (t) => e.has(t) ? e.get(t) : null,
|
|
@@ -65,18 +91,18 @@ var f = class {
|
|
|
65
91
|
}
|
|
66
92
|
clearTokens() {
|
|
67
93
|
let e = this.getStorage();
|
|
68
|
-
e.removeItem(
|
|
94
|
+
e.removeItem(l.ACCESS_TOKEN), e.removeItem(l.REFRESH_TOKEN), e.removeItem(l.TOKEN_EXPIRES_AT), e.removeItem(l.TOKEN_TYPE), e.removeItem(l.SCOPE), e.removeItem(l.ID_TOKEN);
|
|
69
95
|
}
|
|
70
96
|
clearTemporary() {
|
|
71
97
|
let e = this.getStorage();
|
|
72
|
-
e.removeItem(
|
|
98
|
+
e.removeItem(l.CODE_VERIFIER), e.removeItem(l.STATE), e.removeItem(l.NONCE);
|
|
73
99
|
}
|
|
74
|
-
},
|
|
100
|
+
}, g = class {
|
|
75
101
|
config;
|
|
76
102
|
storage;
|
|
77
103
|
refreshPromise = null;
|
|
78
104
|
constructor(e, t) {
|
|
79
|
-
this.config = e, this.storage = t || new
|
|
105
|
+
this.config = e, this.storage = t || new h(e.storage, e.customStorage);
|
|
80
106
|
}
|
|
81
107
|
log(e, ...t) {
|
|
82
108
|
let n = {
|
|
@@ -88,29 +114,30 @@ var f = class {
|
|
|
88
114
|
}, r = n[this.config.logLevel || "warn"] || 0, i = n[e] || 0;
|
|
89
115
|
i > 0 && r >= i && console[e](...t);
|
|
90
116
|
}
|
|
91
|
-
async startAuthorization() {
|
|
92
|
-
let
|
|
93
|
-
this.storage.setItem(
|
|
94
|
-
let
|
|
117
|
+
async startAuthorization(e) {
|
|
118
|
+
let t = u(), n = await f(t), r = d(), i = this.config.nonce || d();
|
|
119
|
+
this.storage.setItem(l.CODE_VERIFIER, t), this.storage.setItem(l.STATE, r), this.storage.setItem(l.NONCE, i);
|
|
120
|
+
let a = new URLSearchParams({
|
|
95
121
|
client_id: this.config.clientId,
|
|
96
122
|
redirect_uri: this.config.redirectUri,
|
|
97
123
|
response_type: this.config.responseType || "code",
|
|
98
124
|
code_challenge_method: this.config.codeChallengeMethod || "S256",
|
|
99
|
-
code_challenge:
|
|
100
|
-
state:
|
|
101
|
-
nonce:
|
|
125
|
+
code_challenge: n,
|
|
126
|
+
state: r,
|
|
127
|
+
nonce: i
|
|
102
128
|
});
|
|
103
129
|
if (this.config.theme) {
|
|
104
130
|
let e = typeof this.config.theme == "function" ? this.config.theme() : this.config.theme;
|
|
105
|
-
e &&
|
|
131
|
+
e && a.append("theme", e);
|
|
106
132
|
}
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
let o = e?.scope ?? this.config.scope, s = e?.audience ?? this.config.audience;
|
|
134
|
+
o && a.append("scope", o), s && a.append("audience", s), e?.prompt && a.append("prompt", e.prompt);
|
|
135
|
+
let c = `${this.config.authorizationEndpoint}?${a.toString()}`;
|
|
109
136
|
if (typeof window > "u" || !window.location) throw Error("startAuthorization requires a browser environment to redirect the user.");
|
|
110
|
-
window.location.href =
|
|
137
|
+
window.location.href = c;
|
|
111
138
|
}
|
|
112
139
|
async exchangeCodeForTokens(e, t) {
|
|
113
|
-
let n = this.storage.getItem(
|
|
140
|
+
let n = this.storage.getItem(l.STATE), r = this.storage.getItem(l.CODE_VERIFIER);
|
|
114
141
|
if (t !== n) throw Error("Invalid state parameter. Possible CSRF attack.");
|
|
115
142
|
if (!r) throw Error("Code verifier missing from storage.");
|
|
116
143
|
let i = new URLSearchParams({
|
|
@@ -137,7 +164,7 @@ var f = class {
|
|
|
137
164
|
}
|
|
138
165
|
async refreshAccessToken() {
|
|
139
166
|
return this.refreshPromise ||= (async () => {
|
|
140
|
-
let e = this.storage.getItem(
|
|
167
|
+
let e = this.storage.getItem(l.REFRESH_TOKEN);
|
|
141
168
|
if (!e) throw Error("No refresh token available");
|
|
142
169
|
let t = new URLSearchParams({
|
|
143
170
|
grant_type: "refresh_token",
|
|
@@ -182,13 +209,30 @@ var f = class {
|
|
|
182
209
|
}), this.refreshPromise;
|
|
183
210
|
}
|
|
184
211
|
saveTokenInfo(e) {
|
|
185
|
-
this.storage.setItem(
|
|
212
|
+
this.storage.setItem(l.ACCESS_TOKEN, e.accessToken), this.storage.setItem(l.TOKEN_EXPIRES_AT, e.expiresAt.toString()), this.storage.setItem(l.TOKEN_TYPE, e.tokenType), e.refreshToken && this.storage.setItem(l.REFRESH_TOKEN, e.refreshToken), e.scope && this.storage.setItem(l.SCOPE, e.scope), e.idToken && this.storage.setItem(l.ID_TOKEN, e.idToken);
|
|
186
213
|
}
|
|
187
214
|
getAccessToken() {
|
|
188
|
-
return this.storage.getItem(
|
|
215
|
+
return this.storage.getItem(l.ACCESS_TOKEN);
|
|
216
|
+
}
|
|
217
|
+
getIdToken() {
|
|
218
|
+
return this.storage.getItem(l.ID_TOKEN);
|
|
219
|
+
}
|
|
220
|
+
getRefreshToken() {
|
|
221
|
+
return this.storage.getItem(l.REFRESH_TOKEN);
|
|
222
|
+
}
|
|
223
|
+
getUserProfile() {
|
|
224
|
+
return m(this.getIdToken() || this.getAccessToken());
|
|
225
|
+
}
|
|
226
|
+
async getToken() {
|
|
227
|
+
if (this.isAuthenticated()) return this.getAccessToken();
|
|
228
|
+
try {
|
|
229
|
+
return (await this.refreshAccessToken()).accessToken;
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
189
233
|
}
|
|
190
234
|
getExpiresAt() {
|
|
191
|
-
let e = this.storage.getItem(
|
|
235
|
+
let e = this.storage.getItem(l.TOKEN_EXPIRES_AT);
|
|
192
236
|
if (!e) return null;
|
|
193
237
|
let t = Number(e);
|
|
194
238
|
return Number.isFinite(t) ? t : null;
|
|
@@ -198,9 +242,9 @@ var f = class {
|
|
|
198
242
|
return e !== null && Date.now() < e - 3e4 && !!this.getAccessToken();
|
|
199
243
|
}
|
|
200
244
|
async navigateToAccountManagement() {
|
|
201
|
-
let e = this.storage.getItem(
|
|
245
|
+
let e = this.storage.getItem(l.REFRESH_TOKEN);
|
|
202
246
|
if (!e) throw Error("No refresh token available to access account management.");
|
|
203
|
-
if (typeof window > "u" || !document) throw Error("navigateToAccountManagement requires a browser environment.");
|
|
247
|
+
if (typeof window > "u" || typeof document > "u" || !document) throw Error("navigateToAccountManagement requires a browser environment.");
|
|
204
248
|
let t = document.createElement("form");
|
|
205
249
|
t.method = "POST";
|
|
206
250
|
let n = this.config.authorizationEndpoint.replace(/\/authorize\/?$/, "");
|
|
@@ -218,96 +262,109 @@ var f = class {
|
|
|
218
262
|
r("refreshToken", e), r("clientId", this.config.clientId), r("redirectUri", this.config.redirectUri), document.body.appendChild(t), t.submit();
|
|
219
263
|
}
|
|
220
264
|
async logout() {
|
|
221
|
-
let e = this.getAccessToken();
|
|
222
|
-
if (this.storage.clearTokens(), this.storage.clearTemporary(), this.config.revokeEndpoint
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
let e = this.getAccessToken(), t = this.storage.getItem(l.REFRESH_TOKEN);
|
|
266
|
+
if (this.storage.clearTokens(), this.storage.clearTemporary(), this.config.revokeEndpoint) {
|
|
267
|
+
let n = [...t ? [{
|
|
268
|
+
token: t,
|
|
269
|
+
token_type_hint: "refresh_token"
|
|
270
|
+
}] : [], ...e ? [{
|
|
271
|
+
token: e,
|
|
272
|
+
token_type_hint: "access_token"
|
|
273
|
+
}] : []];
|
|
274
|
+
for (let e of n) try {
|
|
275
|
+
await fetch(this.config.revokeEndpoint, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
278
|
+
body: new URLSearchParams({
|
|
279
|
+
token: e.token,
|
|
280
|
+
token_type_hint: e.token_type_hint,
|
|
281
|
+
client_id: this.config.clientId
|
|
282
|
+
}).toString()
|
|
283
|
+
});
|
|
284
|
+
} catch (e) {
|
|
285
|
+
this.log("error", "Failed to revoke token during logout", e);
|
|
286
|
+
}
|
|
230
287
|
}
|
|
231
288
|
}
|
|
232
|
-
},
|
|
233
|
-
let [
|
|
289
|
+
}, _ = e(null), v = ({ config: e, children: n }) => {
|
|
290
|
+
let [a, s] = o({
|
|
234
291
|
isAuthenticated: !1,
|
|
235
292
|
isLoading: !0
|
|
236
|
-
}),
|
|
237
|
-
|
|
293
|
+
}), l = i(() => new g(e), [e]);
|
|
294
|
+
r(() => {
|
|
238
295
|
(async () => {
|
|
239
296
|
try {
|
|
240
|
-
if (
|
|
297
|
+
if (l.isAuthenticated()) s({
|
|
241
298
|
isAuthenticated: !0,
|
|
242
299
|
isLoading: !1
|
|
243
300
|
});
|
|
244
301
|
else try {
|
|
245
|
-
await
|
|
302
|
+
await l.refreshAccessToken(), s({
|
|
246
303
|
isAuthenticated: !0,
|
|
247
304
|
isLoading: !1
|
|
248
305
|
});
|
|
249
306
|
} catch {
|
|
250
|
-
|
|
307
|
+
s({
|
|
251
308
|
isAuthenticated: !1,
|
|
252
309
|
isLoading: !1
|
|
253
310
|
});
|
|
254
311
|
}
|
|
255
312
|
} catch (e) {
|
|
256
|
-
|
|
313
|
+
s({
|
|
257
314
|
isAuthenticated: !1,
|
|
258
315
|
isLoading: !1,
|
|
259
316
|
error: e instanceof Error ? e.message : String(e)
|
|
260
317
|
});
|
|
261
318
|
}
|
|
262
319
|
})();
|
|
263
|
-
}, [
|
|
264
|
-
if (e.autoRefresh === !1 || !
|
|
320
|
+
}, [l]), r(() => {
|
|
321
|
+
if (e.autoRefresh === !1 || !a.isAuthenticated) return;
|
|
265
322
|
let t = !1, n = setInterval(async () => {
|
|
266
323
|
if (!t) try {
|
|
267
|
-
let n =
|
|
324
|
+
let n = l.getExpiresAt();
|
|
268
325
|
if (!n) return;
|
|
269
326
|
let r = (e.refreshThreshold ?? 60) * 1e3;
|
|
270
327
|
if (Date.now() >= n - r) {
|
|
271
328
|
t = !0;
|
|
272
329
|
try {
|
|
273
|
-
let e = await
|
|
274
|
-
|
|
330
|
+
let e = await l.refreshAccessToken();
|
|
331
|
+
s((t) => ({
|
|
275
332
|
...t,
|
|
276
333
|
tokenInfo: e
|
|
277
334
|
}));
|
|
278
335
|
} catch {
|
|
279
|
-
|
|
336
|
+
d();
|
|
280
337
|
} finally {
|
|
281
338
|
t = !1;
|
|
282
339
|
}
|
|
283
340
|
}
|
|
284
341
|
} catch (e) {
|
|
285
|
-
|
|
342
|
+
l.log("error", "Auto-refresh error:", e);
|
|
286
343
|
}
|
|
287
344
|
}, 1e4);
|
|
288
345
|
return () => clearInterval(n);
|
|
289
346
|
}, [
|
|
290
347
|
e.autoRefresh,
|
|
291
348
|
e.refreshThreshold,
|
|
292
|
-
|
|
293
|
-
|
|
349
|
+
a.isAuthenticated,
|
|
350
|
+
l
|
|
294
351
|
]);
|
|
295
|
-
let
|
|
296
|
-
|
|
352
|
+
let u = t(async (e) => {
|
|
353
|
+
s((e) => ({
|
|
297
354
|
...e,
|
|
298
355
|
isLoading: !0
|
|
299
|
-
})), await
|
|
300
|
-
},
|
|
301
|
-
|
|
356
|
+
})), await l.startAuthorization(e);
|
|
357
|
+
}, [l]), d = async () => {
|
|
358
|
+
s((e) => ({
|
|
302
359
|
...e,
|
|
303
360
|
isLoading: !0
|
|
304
361
|
}));
|
|
305
362
|
try {
|
|
306
|
-
await
|
|
363
|
+
await l.logout();
|
|
307
364
|
} catch (e) {
|
|
308
|
-
|
|
365
|
+
l.log("error", "Logout error:", e);
|
|
309
366
|
} finally {
|
|
310
|
-
if (
|
|
367
|
+
if (s({
|
|
311
368
|
isAuthenticated: !1,
|
|
312
369
|
isLoading: !1
|
|
313
370
|
}), e.logoutEndpoint && typeof window < "u") {
|
|
@@ -320,14 +377,14 @@ var f = class {
|
|
|
320
377
|
window.location.assign(t.toString());
|
|
321
378
|
}
|
|
322
379
|
}
|
|
323
|
-
},
|
|
324
|
-
|
|
380
|
+
}, f = async (t, n) => {
|
|
381
|
+
s((e) => ({
|
|
325
382
|
...e,
|
|
326
383
|
isLoading: !0
|
|
327
384
|
}));
|
|
328
385
|
try {
|
|
329
|
-
let r = await
|
|
330
|
-
return
|
|
386
|
+
let r = await l.exchangeCodeForTokens(t, n);
|
|
387
|
+
return s({
|
|
331
388
|
isAuthenticated: !0,
|
|
332
389
|
isLoading: !1,
|
|
333
390
|
tokenInfo: r
|
|
@@ -335,58 +392,63 @@ var f = class {
|
|
|
335
392
|
window.location.assign(e.redirectRoute);
|
|
336
393
|
}, 0), r;
|
|
337
394
|
} catch (e) {
|
|
338
|
-
throw
|
|
395
|
+
throw s({
|
|
339
396
|
isAuthenticated: !1,
|
|
340
397
|
isLoading: !1,
|
|
341
398
|
error: e instanceof Error ? e.message : String(e)
|
|
342
399
|
}), e;
|
|
343
400
|
}
|
|
344
|
-
},
|
|
345
|
-
...
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
401
|
+
}, p = t(async () => l.getToken(), [l]), m = a.isAuthenticated ? l.getAccessToken() : null, h = a.isAuthenticated ? l.getIdToken() : null, v = a.isAuthenticated ? l.getUserProfile() : null, y = {
|
|
402
|
+
...a,
|
|
403
|
+
accessToken: m,
|
|
404
|
+
idToken: h,
|
|
405
|
+
user: v,
|
|
406
|
+
login: u,
|
|
407
|
+
logout: d,
|
|
408
|
+
getToken: p,
|
|
409
|
+
navigateToAccountManagement: () => l.navigateToAccountManagement(),
|
|
410
|
+
handleCallback: f,
|
|
411
|
+
client: l
|
|
351
412
|
};
|
|
352
|
-
return /* @__PURE__ */
|
|
353
|
-
value:
|
|
354
|
-
children:
|
|
413
|
+
return /* @__PURE__ */ c(_.Provider, {
|
|
414
|
+
value: y,
|
|
415
|
+
children: n
|
|
355
416
|
});
|
|
356
|
-
},
|
|
357
|
-
let e =
|
|
417
|
+
}, y = () => {
|
|
418
|
+
let e = n(_);
|
|
358
419
|
if (!e) throw Error("useAuth must be used within an AuthProvider");
|
|
359
420
|
return e;
|
|
360
|
-
},
|
|
361
|
-
let { handleCallback:
|
|
362
|
-
return
|
|
421
|
+
}, b = ({ onSuccess: e, onError: t, loadingComponent: n = /* @__PURE__ */ c("div", { children: "Processing authentication..." }) }) => {
|
|
422
|
+
let { handleCallback: i, client: o } = y(), l = a(!1);
|
|
423
|
+
return r(() => {
|
|
363
424
|
l.current || (l.current = !0, (async () => {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
425
|
+
if (typeof window > "u" || !window.location) return;
|
|
426
|
+
let n = new URLSearchParams(window.location.search), r = new URLSearchParams(window.location.hash.substring(1)), a = n.get("code") || r.get("code"), s = n.get("state") || r.get("state"), c = n.get("error") || r.get("error");
|
|
427
|
+
if (c) {
|
|
428
|
+
let e = /* @__PURE__ */ Error(`OAuth Error: ${c}`);
|
|
367
429
|
t && t(e);
|
|
368
430
|
return;
|
|
369
431
|
}
|
|
370
|
-
if (
|
|
371
|
-
await a
|
|
432
|
+
if (a && s) try {
|
|
433
|
+
await i(a, s), e && e();
|
|
372
434
|
} catch (e) {
|
|
373
435
|
t && t(e instanceof Error ? e : Error(String(e)));
|
|
374
436
|
}
|
|
375
|
-
else if (!
|
|
376
|
-
let e =
|
|
437
|
+
else if (!a) {
|
|
438
|
+
let e = o.config;
|
|
377
439
|
window.location.assign(e.redirectRoute || "/");
|
|
378
440
|
}
|
|
379
441
|
})());
|
|
380
442
|
}, [
|
|
381
|
-
|
|
382
|
-
|
|
443
|
+
i,
|
|
444
|
+
o,
|
|
383
445
|
e,
|
|
384
446
|
t
|
|
385
|
-
]), /* @__PURE__ */ s
|
|
447
|
+
]), /* @__PURE__ */ c(s, { children: n });
|
|
386
448
|
};
|
|
387
449
|
//#endregion
|
|
388
450
|
//#region src/axios/interceptor.ts
|
|
389
|
-
function
|
|
451
|
+
function x(e, t) {
|
|
390
452
|
e.interceptors.request.use((e) => {
|
|
391
453
|
let n = t.getAccessToken();
|
|
392
454
|
return n && e.headers && (e.headers.Authorization = `Bearer ${n}`), e;
|
|
@@ -405,4 +467,4 @@ function v(e, t) {
|
|
|
405
467
|
});
|
|
406
468
|
}
|
|
407
469
|
//#endregion
|
|
408
|
-
export {
|
|
470
|
+
export { b as AuthCallback, g as AuthClient, v as AuthProvider, l as STORAGE_KEYS, h as TokenStorage, f as createCodeChallenge, m as decodeJwtPayload, u as generateCodeVerifier, d as generateState, x as setupAxiosInterceptors, y as useAuth };
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.WitsAuthReactClient={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={ACCESS_TOKEN:`witsauth_access_token`,REFRESH_TOKEN:`witsauth_refresh_token`,TOKEN_EXPIRES_AT:`witsauth_expires_at`,TOKEN_TYPE:`witsauth_token_type`,SCOPE:`witsauth_scope`,CODE_VERIFIER:`witsauth_code_verifier`,STATE:`witsauth_state`,NONCE:`witsauth_nonce`};function d(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}function f(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest(`SHA-256`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}var m=class{storageType;customStorage;memoryStorage=new Map;constructor(e,t){this.storageType=e,this.customStorage=t}getStorage(){if(this.storageType===`custom`&&this.customStorage)return this.customStorage;if(typeof window<`u`){if(this.storageType===`sessionStorage`&&window.sessionStorage)return window.sessionStorage;if((!this.storageType||this.storageType===`localStorage`)&&window.localStorage)return window.localStorage}let e=this.memoryStorage;return{getItem:t=>e.has(t)?e.get(t):null,setItem:(t,n)=>{e.set(t,n)},removeItem:t=>{e.delete(t)},clear:()=>{e.clear()}}}setItem(e,t){this.getStorage().setItem(e,t)}getItem(e){return this.getStorage().getItem(e)}removeItem(e){this.getStorage().removeItem(e)}clearTokens(){let e=this.getStorage();e.removeItem(u.ACCESS_TOKEN),e.removeItem(u.REFRESH_TOKEN),e.removeItem(u.TOKEN_EXPIRES_AT),e.removeItem(u.TOKEN_TYPE),e.removeItem(u.SCOPE)}clearTemporary(){let e=this.getStorage();e.removeItem(u.CODE_VERIFIER),e.removeItem(u.STATE),e.removeItem(u.NONCE)}},h=class{config;storage;refreshPromise=null;constructor(e,t){this.config=e,this.storage=t||new m(e.storage,e.customStorage)}log(e,...t){let n={none:0,error:1,warn:2,info:3,debug:4},r=n[this.config.logLevel||`warn`]||0,i=n[e]||0;i>0&&r>=i&&console[e](...t)}async startAuthorization(){let e=d(),t=await p(e),n=f(),r=this.config.nonce||f();this.storage.setItem(u.CODE_VERIFIER,e),this.storage.setItem(u.STATE,n),this.storage.setItem(u.NONCE,r);let i=new URLSearchParams({client_id:this.config.clientId,redirect_uri:this.config.redirectUri,response_type:this.config.responseType||`code`,code_challenge_method:this.config.codeChallengeMethod||`S256`,code_challenge:t,state:n,nonce:r});if(this.config.theme){let e=typeof this.config.theme==`function`?this.config.theme():this.config.theme;e&&i.append(`theme`,e)}this.config.scope&&i.append(`scope`,this.config.scope),this.config.audience&&i.append(`audience`,this.config.audience);let a=`${this.config.authorizationEndpoint}?${i.toString()}`;if(typeof window>`u`||!window.location)throw Error(`startAuthorization requires a browser environment to redirect the user.`);window.location.href=a}async exchangeCodeForTokens(e,t){let n=this.storage.getItem(u.STATE),r=this.storage.getItem(u.CODE_VERIFIER);if(t!==n)throw Error(`Invalid state parameter. Possible CSRF attack.`);if(!r)throw Error(`Code verifier missing from storage.`);let i=new URLSearchParams({grant_type:`authorization_code`,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,code:e,code_verifier:r}),a=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i.toString()});if(!a.ok)throw Error(`Failed to exchange code for tokens`);let o=await a.json(),s={accessToken:o.access_token,refreshToken:o.refresh_token,tokenType:o.token_type||`Bearer`,expiresAt:Date.now()+o.expires_in*1e3,scope:o.scope,idToken:o.id_token};return this.saveTokenInfo(s),this.storage.clearTemporary(),s}async refreshAccessToken(){return this.refreshPromise||=(async()=>{let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available`);let t=new URLSearchParams({grant_type:`refresh_token`,client_id:this.config.clientId,refresh_token:e}),n;try{n=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:t.toString()})}catch(e){throw this.log(`error`,`Token refresh network request failed:`,e),e}if(!n.ok){let e={};try{e=await n.json()}catch{try{e={error:await n.text()}}catch{e={}}}this.log(`error`,`Token refresh failed with status:`,n.status,e),(n.status===400||n.status===401)&&this.storage.clearTokens();let t=e.error_description||e.error||n.statusText||`Failed to refresh token`;throw Error(`Failed to refresh token (${n.status}): ${t}`)}let r=await n.json(),i={accessToken:r.access_token,refreshToken:r.refresh_token||e,tokenType:r.token_type||`Bearer`,expiresAt:Date.now()+r.expires_in*1e3,scope:r.scope,idToken:r.id_token};return this.saveTokenInfo(i),i})().finally(()=>{this.refreshPromise=null}),this.refreshPromise}saveTokenInfo(e){this.storage.setItem(u.ACCESS_TOKEN,e.accessToken),this.storage.setItem(u.TOKEN_EXPIRES_AT,e.expiresAt.toString()),this.storage.setItem(u.TOKEN_TYPE,e.tokenType),e.refreshToken&&this.storage.setItem(u.REFRESH_TOKEN,e.refreshToken),e.scope&&this.storage.setItem(u.SCOPE,e.scope)}getAccessToken(){return this.storage.getItem(u.ACCESS_TOKEN)}getExpiresAt(){let e=this.storage.getItem(u.TOKEN_EXPIRES_AT);if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}isAuthenticated(){let e=this.getExpiresAt();return e!==null&&Date.now()<e-3e4&&!!this.getAccessToken()}async navigateToAccountManagement(){let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available to access account management.`);if(typeof window>`u`||!document)throw Error(`navigateToAccountManagement requires a browser environment.`);let t=document.createElement(`form`);t.method=`POST`;let n=this.config.authorizationEndpoint.replace(/\/authorize\/?$/,``);t.action=`${n}/account/session`;let r=(e,n)=>{let r=document.createElement(`input`);r.type=`hidden`,r.name=e,r.value=n,t.appendChild(r)};try{let e=await(await fetch(`${n}/csrf-token`,{credentials:`include`})).json();e.csrfToken&&r(`_csrf`,e.csrfToken)}catch(e){this.log(`warn`,`Failed to fetch CSRF token for Account Management navigation`,e)}r(`refreshToken`,e),r(`clientId`,this.config.clientId),r(`redirectUri`,this.config.redirectUri),document.body.appendChild(t),t.submit()}async logout(){let e=this.getAccessToken();if(this.storage.clearTokens(),this.storage.clearTemporary(),this.config.revokeEndpoint&&e)try{await fetch(this.config.revokeEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({token:e}).toString()})}catch(e){this.log(`error`,`Failed to revoke token during logout`,e)}}},g=(0,t.createContext)(null),_=({config:e,children:r})=>{let[i,a]=(0,t.useState)({isAuthenticated:!1,isLoading:!0}),o=(0,t.useMemo)(()=>new h(e),[e]);(0,t.useEffect)(()=>{(async()=>{try{if(o.isAuthenticated())a({isAuthenticated:!0,isLoading:!1});else try{await o.refreshAccessToken(),a({isAuthenticated:!0,isLoading:!1})}catch{a({isAuthenticated:!1,isLoading:!1})}}catch(e){a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)})}})()},[o]),(0,t.useEffect)(()=>{if(e.autoRefresh===!1||!i.isAuthenticated)return;let t=!1,n=setInterval(async()=>{if(!t)try{let n=o.getExpiresAt();if(!n)return;let r=(e.refreshThreshold??60)*1e3;if(Date.now()>=n-r){t=!0;try{let e=await o.refreshAccessToken();a(t=>({...t,tokenInfo:e}))}catch{c()}finally{t=!1}}}catch(e){o.log(`error`,`Auto-refresh error:`,e)}},1e4);return()=>clearInterval(n)},[e.autoRefresh,e.refreshThreshold,i.isAuthenticated,o]);let s=async()=>{a(e=>({...e,isLoading:!0})),await o.startAuthorization()},c=async()=>{a(e=>({...e,isLoading:!0}));try{await o.logout()}catch(e){o.log(`error`,`Logout error:`,e)}finally{if(a({isAuthenticated:!1,isLoading:!1}),e.logoutEndpoint&&typeof window<`u`){let t=new URL(e.logoutEndpoint);t.searchParams.set(`client_id`,e.clientId);let n=e.redirectUri;e.logoutRedirectRoute&&(n=new URL(e.logoutRedirectRoute,window.location.origin).toString()),t.searchParams.set(`redirect_uri`,n),window.location.assign(t.toString())}else if(e.logoutRedirectRoute&&typeof window<`u`){let t=new URL(e.logoutRedirectRoute,window.location.origin);window.location.assign(t.toString())}}},l=async(t,n)=>{a(e=>({...e,isLoading:!0}));try{let r=await o.exchangeCodeForTokens(t,n);return a({isAuthenticated:!0,isLoading:!1,tokenInfo:r}),e.redirectRoute&&typeof window<`u`&&setTimeout(()=>{window.location.assign(e.redirectRoute)},0),r}catch(e){throw a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)}),e}},u={...i,login:s,logout:c,navigateToAccountManagement:()=>o.navigateToAccountManagement(),handleCallback:l,client:o};return(0,n.jsx)(g.Provider,{value:u,children:r})},v=()=>{let e=(0,t.useContext)(g);if(!e)throw Error(`useAuth must be used within an AuthProvider`);return e},y=({onSuccess:e,onError:r,loadingComponent:i=(0,n.jsx)(`div`,{children:`Processing authentication...`})})=>{let{handleCallback:a,client:o}=v(),s=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{s.current||(s.current=!0,(async()=>{let t=new URLSearchParams(window.location.search),n=new URLSearchParams(window.location.hash.substring(1)),i=t.get(`code`)||n.get(`code`),s=t.get(`state`)||n.get(`state`),c=t.get(`error`)||n.get(`error`);if(c){let e=Error(`OAuth Error: ${c}`);r&&r(e);return}if(i&&s)try{await a(i,s),e&&e()}catch(e){r&&r(e instanceof Error?e:Error(String(e)))}else if(!i){let e=o.config;window.location.assign(e.redirectRoute||`/`)}})())},[a,o,e,r]),(0,n.jsx)(n.Fragment,{children:i})};function b(e,t){e.interceptors.request.use(e=>{let n=t.getAccessToken();return n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),e},e=>Promise.reject(e)),e.interceptors.response.use(e=>e,async n=>{let r=n.config;if(n.response?.status===401&&r&&!r._retry){r._retry=!0;try{let n=await t.refreshAccessToken();return r.headers&&(r.headers.Authorization=`Bearer ${n.accessToken}`),e(r)}catch(e){return t.logout(),Promise.reject(e)}}return Promise.reject(n)})}e.AuthCallback=y,e.AuthClient=h,e.AuthProvider=_,e.STORAGE_KEYS=u,e.TokenStorage=m,e.createCodeChallenge=p,e.generateCodeVerifier=d,e.generateState=f,e.setupAxiosInterceptors=b,e.useAuth=v});
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react"),require("react/jsx-runtime")):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.WitsAuthReactClient={},e.React,e.jsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);var u={ACCESS_TOKEN:`witsauth_access_token`,REFRESH_TOKEN:`witsauth_refresh_token`,TOKEN_EXPIRES_AT:`witsauth_expires_at`,TOKEN_TYPE:`witsauth_token_type`,SCOPE:`witsauth_scope`,ID_TOKEN:`witsauth_id_token`,CODE_VERIFIER:`witsauth_code_verifier`,STATE:`witsauth_state`,NONCE:`witsauth_nonce`};function d(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}function f(){let e=new Uint8Array(32);return crypto.getRandomValues(e),btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest(`SHA-256`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=/g,``)}function m(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=t+`=`.repeat((4-t.length%4)%4);if(typeof atob==`function`){let e=atob(n),t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);if(typeof TextDecoder<`u`)return new TextDecoder().decode(t);let r=``;for(let e=0;e<t.length;e++)r+=String.fromCharCode(t[e]);return decodeURIComponent(escape(r))}let r=typeof globalThis<`u`?globalThis:void 0;if(r?.Buffer)return r.Buffer.from(n,`base64`).toString(`utf8`);throw Error(`Unable to decode base64url string.`)}function h(e){if(!e||typeof e!=`string`)return null;let t=e.split(`.`);if(t.length<2)return null;try{let e=m(t[1]);return JSON.parse(e)}catch{return null}}var g=class{storageType;customStorage;memoryStorage=new Map;constructor(e,t){this.storageType=e,this.customStorage=t}getStorage(){if(this.storageType===`custom`&&this.customStorage)return this.customStorage;if(typeof window<`u`)try{if(this.storageType===`sessionStorage`&&window.sessionStorage)return window.sessionStorage;if((!this.storageType||this.storageType===`localStorage`)&&window.localStorage)return window.localStorage}catch{}let e=this.memoryStorage;return{getItem:t=>e.has(t)?e.get(t):null,setItem:(t,n)=>{e.set(t,n)},removeItem:t=>{e.delete(t)},clear:()=>{e.clear()}}}setItem(e,t){this.getStorage().setItem(e,t)}getItem(e){return this.getStorage().getItem(e)}removeItem(e){this.getStorage().removeItem(e)}clearTokens(){let e=this.getStorage();e.removeItem(u.ACCESS_TOKEN),e.removeItem(u.REFRESH_TOKEN),e.removeItem(u.TOKEN_EXPIRES_AT),e.removeItem(u.TOKEN_TYPE),e.removeItem(u.SCOPE),e.removeItem(u.ID_TOKEN)}clearTemporary(){let e=this.getStorage();e.removeItem(u.CODE_VERIFIER),e.removeItem(u.STATE),e.removeItem(u.NONCE)}},_=class{config;storage;refreshPromise=null;constructor(e,t){this.config=e,this.storage=t||new g(e.storage,e.customStorage)}log(e,...t){let n={none:0,error:1,warn:2,info:3,debug:4},r=n[this.config.logLevel||`warn`]||0,i=n[e]||0;i>0&&r>=i&&console[e](...t)}async startAuthorization(e){let t=d(),n=await p(t),r=f(),i=this.config.nonce||f();this.storage.setItem(u.CODE_VERIFIER,t),this.storage.setItem(u.STATE,r),this.storage.setItem(u.NONCE,i);let a=new URLSearchParams({client_id:this.config.clientId,redirect_uri:this.config.redirectUri,response_type:this.config.responseType||`code`,code_challenge_method:this.config.codeChallengeMethod||`S256`,code_challenge:n,state:r,nonce:i});if(this.config.theme){let e=typeof this.config.theme==`function`?this.config.theme():this.config.theme;e&&a.append(`theme`,e)}let o=e?.scope??this.config.scope,s=e?.audience??this.config.audience;o&&a.append(`scope`,o),s&&a.append(`audience`,s),e?.prompt&&a.append(`prompt`,e.prompt);let c=`${this.config.authorizationEndpoint}?${a.toString()}`;if(typeof window>`u`||!window.location)throw Error(`startAuthorization requires a browser environment to redirect the user.`);window.location.href=c}async exchangeCodeForTokens(e,t){let n=this.storage.getItem(u.STATE),r=this.storage.getItem(u.CODE_VERIFIER);if(t!==n)throw Error(`Invalid state parameter. Possible CSRF attack.`);if(!r)throw Error(`Code verifier missing from storage.`);let i=new URLSearchParams({grant_type:`authorization_code`,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,code:e,code_verifier:r}),a=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i.toString()});if(!a.ok)throw Error(`Failed to exchange code for tokens`);let o=await a.json(),s={accessToken:o.access_token,refreshToken:o.refresh_token,tokenType:o.token_type||`Bearer`,expiresAt:Date.now()+o.expires_in*1e3,scope:o.scope,idToken:o.id_token};return this.saveTokenInfo(s),this.storage.clearTemporary(),s}async refreshAccessToken(){return this.refreshPromise||=(async()=>{let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available`);let t=new URLSearchParams({grant_type:`refresh_token`,client_id:this.config.clientId,refresh_token:e}),n;try{n=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:t.toString()})}catch(e){throw this.log(`error`,`Token refresh network request failed:`,e),e}if(!n.ok){let e={};try{e=await n.json()}catch{try{e={error:await n.text()}}catch{e={}}}this.log(`error`,`Token refresh failed with status:`,n.status,e),(n.status===400||n.status===401)&&this.storage.clearTokens();let t=e.error_description||e.error||n.statusText||`Failed to refresh token`;throw Error(`Failed to refresh token (${n.status}): ${t}`)}let r=await n.json(),i={accessToken:r.access_token,refreshToken:r.refresh_token||e,tokenType:r.token_type||`Bearer`,expiresAt:Date.now()+r.expires_in*1e3,scope:r.scope,idToken:r.id_token};return this.saveTokenInfo(i),i})().finally(()=>{this.refreshPromise=null}),this.refreshPromise}saveTokenInfo(e){this.storage.setItem(u.ACCESS_TOKEN,e.accessToken),this.storage.setItem(u.TOKEN_EXPIRES_AT,e.expiresAt.toString()),this.storage.setItem(u.TOKEN_TYPE,e.tokenType),e.refreshToken&&this.storage.setItem(u.REFRESH_TOKEN,e.refreshToken),e.scope&&this.storage.setItem(u.SCOPE,e.scope),e.idToken&&this.storage.setItem(u.ID_TOKEN,e.idToken)}getAccessToken(){return this.storage.getItem(u.ACCESS_TOKEN)}getIdToken(){return this.storage.getItem(u.ID_TOKEN)}getRefreshToken(){return this.storage.getItem(u.REFRESH_TOKEN)}getUserProfile(){return h(this.getIdToken()||this.getAccessToken())}async getToken(){if(this.isAuthenticated())return this.getAccessToken();try{return(await this.refreshAccessToken()).accessToken}catch{return null}}getExpiresAt(){let e=this.storage.getItem(u.TOKEN_EXPIRES_AT);if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}isAuthenticated(){let e=this.getExpiresAt();return e!==null&&Date.now()<e-3e4&&!!this.getAccessToken()}async navigateToAccountManagement(){let e=this.storage.getItem(u.REFRESH_TOKEN);if(!e)throw Error(`No refresh token available to access account management.`);if(typeof window>`u`||typeof document>`u`||!document)throw Error(`navigateToAccountManagement requires a browser environment.`);let t=document.createElement(`form`);t.method=`POST`;let n=this.config.authorizationEndpoint.replace(/\/authorize\/?$/,``);t.action=`${n}/account/session`;let r=(e,n)=>{let r=document.createElement(`input`);r.type=`hidden`,r.name=e,r.value=n,t.appendChild(r)};try{let e=await(await fetch(`${n}/csrf-token`,{credentials:`include`})).json();e.csrfToken&&r(`_csrf`,e.csrfToken)}catch(e){this.log(`warn`,`Failed to fetch CSRF token for Account Management navigation`,e)}r(`refreshToken`,e),r(`clientId`,this.config.clientId),r(`redirectUri`,this.config.redirectUri),document.body.appendChild(t),t.submit()}async logout(){let e=this.getAccessToken(),t=this.storage.getItem(u.REFRESH_TOKEN);if(this.storage.clearTokens(),this.storage.clearTemporary(),this.config.revokeEndpoint){let n=[...t?[{token:t,token_type_hint:`refresh_token`}]:[],...e?[{token:e,token_type_hint:`access_token`}]:[]];for(let e of n)try{await fetch(this.config.revokeEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({token:e.token,token_type_hint:e.token_type_hint,client_id:this.config.clientId}).toString()})}catch(e){this.log(`error`,`Failed to revoke token during logout`,e)}}}},v=(0,t.createContext)(null),y=({config:e,children:r})=>{let[i,a]=(0,t.useState)({isAuthenticated:!1,isLoading:!0}),o=(0,t.useMemo)(()=>new _(e),[e]);(0,t.useEffect)(()=>{(async()=>{try{if(o.isAuthenticated())a({isAuthenticated:!0,isLoading:!1});else try{await o.refreshAccessToken(),a({isAuthenticated:!0,isLoading:!1})}catch{a({isAuthenticated:!1,isLoading:!1})}}catch(e){a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)})}})()},[o]),(0,t.useEffect)(()=>{if(e.autoRefresh===!1||!i.isAuthenticated)return;let t=!1,n=setInterval(async()=>{if(!t)try{let n=o.getExpiresAt();if(!n)return;let r=(e.refreshThreshold??60)*1e3;if(Date.now()>=n-r){t=!0;try{let e=await o.refreshAccessToken();a(t=>({...t,tokenInfo:e}))}catch{c()}finally{t=!1}}}catch(e){o.log(`error`,`Auto-refresh error:`,e)}},1e4);return()=>clearInterval(n)},[e.autoRefresh,e.refreshThreshold,i.isAuthenticated,o]);let s=(0,t.useCallback)(async e=>{a(e=>({...e,isLoading:!0})),await o.startAuthorization(e)},[o]),c=async()=>{a(e=>({...e,isLoading:!0}));try{await o.logout()}catch(e){o.log(`error`,`Logout error:`,e)}finally{if(a({isAuthenticated:!1,isLoading:!1}),e.logoutEndpoint&&typeof window<`u`){let t=new URL(e.logoutEndpoint);t.searchParams.set(`client_id`,e.clientId);let n=e.redirectUri;e.logoutRedirectRoute&&(n=new URL(e.logoutRedirectRoute,window.location.origin).toString()),t.searchParams.set(`redirect_uri`,n),window.location.assign(t.toString())}else if(e.logoutRedirectRoute&&typeof window<`u`){let t=new URL(e.logoutRedirectRoute,window.location.origin);window.location.assign(t.toString())}}},l=async(t,n)=>{a(e=>({...e,isLoading:!0}));try{let r=await o.exchangeCodeForTokens(t,n);return a({isAuthenticated:!0,isLoading:!1,tokenInfo:r}),e.redirectRoute&&typeof window<`u`&&setTimeout(()=>{window.location.assign(e.redirectRoute)},0),r}catch(e){throw a({isAuthenticated:!1,isLoading:!1,error:e instanceof Error?e.message:String(e)}),e}},u=(0,t.useCallback)(async()=>o.getToken(),[o]),d=i.isAuthenticated?o.getAccessToken():null,f=i.isAuthenticated?o.getIdToken():null,p=i.isAuthenticated?o.getUserProfile():null,m={...i,accessToken:d,idToken:f,user:p,login:s,logout:c,getToken:u,navigateToAccountManagement:()=>o.navigateToAccountManagement(),handleCallback:l,client:o};return(0,n.jsx)(v.Provider,{value:m,children:r})},b=()=>{let e=(0,t.useContext)(v);if(!e)throw Error(`useAuth must be used within an AuthProvider`);return e},x=({onSuccess:e,onError:r,loadingComponent:i=(0,n.jsx)(`div`,{children:`Processing authentication...`})})=>{let{handleCallback:a,client:o}=b(),s=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{s.current||(s.current=!0,(async()=>{if(typeof window>`u`||!window.location)return;let t=new URLSearchParams(window.location.search),n=new URLSearchParams(window.location.hash.substring(1)),i=t.get(`code`)||n.get(`code`),s=t.get(`state`)||n.get(`state`),c=t.get(`error`)||n.get(`error`);if(c){let e=Error(`OAuth Error: ${c}`);r&&r(e);return}if(i&&s)try{await a(i,s),e&&e()}catch(e){r&&r(e instanceof Error?e:Error(String(e)))}else if(!i){let e=o.config;window.location.assign(e.redirectRoute||`/`)}})())},[a,o,e,r]),(0,n.jsx)(n.Fragment,{children:i})};function S(e,t){e.interceptors.request.use(e=>{let n=t.getAccessToken();return n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),e},e=>Promise.reject(e)),e.interceptors.response.use(e=>e,async n=>{let r=n.config;if(n.response?.status===401&&r&&!r._retry){r._retry=!0;try{let n=await t.refreshAccessToken();return r.headers&&(r.headers.Authorization=`Bearer ${n.accessToken}`),e(r)}catch(e){return t.logout(),Promise.reject(e)}}return Promise.reject(n)})}e.AuthCallback=x,e.AuthClient=_,e.AuthProvider=y,e.STORAGE_KEYS=u,e.TokenStorage=g,e.createCodeChallenge=p,e.decodeJwtPayload=h,e.generateCodeVerifier=d,e.generateState=f,e.setupAxiosInterceptors=S,e.useAuth=b});
|
package/dist/models/types.d.ts
CHANGED
|
@@ -44,15 +44,35 @@ export interface AuthState {
|
|
|
44
44
|
export interface UserInfo {
|
|
45
45
|
sub?: string;
|
|
46
46
|
name?: string;
|
|
47
|
+
given_name?: string;
|
|
48
|
+
family_name?: string;
|
|
47
49
|
email?: string;
|
|
50
|
+
email_verified?: boolean;
|
|
51
|
+
phone_number?: string;
|
|
52
|
+
phone_number_verified?: boolean;
|
|
53
|
+
roles?: string[];
|
|
54
|
+
permissions?: string[];
|
|
55
|
+
amr?: string[];
|
|
56
|
+
act?: {
|
|
57
|
+
sub: string;
|
|
58
|
+
ticket_ref?: string;
|
|
59
|
+
[key: string]: any;
|
|
60
|
+
};
|
|
48
61
|
[key: string]: any;
|
|
49
62
|
}
|
|
63
|
+
export type UserProfile = UserInfo;
|
|
64
|
+
export interface AuthorizeOptions {
|
|
65
|
+
prompt?: 'login' | 'consent' | 'select_account';
|
|
66
|
+
scope?: string;
|
|
67
|
+
audience?: string;
|
|
68
|
+
}
|
|
50
69
|
export declare const STORAGE_KEYS: {
|
|
51
70
|
ACCESS_TOKEN: string;
|
|
52
71
|
REFRESH_TOKEN: string;
|
|
53
72
|
TOKEN_EXPIRES_AT: string;
|
|
54
73
|
TOKEN_TYPE: string;
|
|
55
74
|
SCOPE: string;
|
|
75
|
+
ID_TOKEN: string;
|
|
56
76
|
CODE_VERIFIER: string;
|
|
57
77
|
STATE: string;
|
|
58
78
|
NONCE: string;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { OAuth2Config, AuthState, TokenInfo } from '../models/types';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AuthorizeOptions, OAuth2Config, AuthState, TokenInfo, UserInfo } from '../models/types';
|
|
3
3
|
import { AuthClient } from '../core/AuthClient';
|
|
4
|
-
interface AuthContextValue extends AuthState {
|
|
5
|
-
|
|
4
|
+
export interface AuthContextValue extends AuthState {
|
|
5
|
+
accessToken: string | null;
|
|
6
|
+
idToken: string | null;
|
|
7
|
+
user: UserInfo | null;
|
|
8
|
+
login: (options?: AuthorizeOptions) => Promise<void>;
|
|
6
9
|
logout: () => Promise<void>;
|
|
10
|
+
getToken: () => Promise<string | null>;
|
|
7
11
|
navigateToAccountManagement: () => Promise<void>;
|
|
8
12
|
client: AuthClient;
|
|
9
13
|
handleCallback: (code: string, state: string) => Promise<TokenInfo>;
|
|
@@ -13,4 +17,3 @@ export declare const AuthProvider: React.FC<{
|
|
|
13
17
|
children: React.ReactNode;
|
|
14
18
|
}>;
|
|
15
19
|
export declare const useAuth: () => AuthContextValue;
|
|
16
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witsauth/react-client",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "Official React client library for integrating Witsauth Single Sign-On (SSO) authentication or similar services into your React applications.",
|
|
5
5
|
"main": "./dist/index.umd.js",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
@@ -42,16 +42,14 @@
|
|
|
42
42
|
"@types/node": "^26.1.1",
|
|
43
43
|
"@types/react": "^19.2.17",
|
|
44
44
|
"@types/react-dom": "^19.2.3",
|
|
45
|
-
"@typescript/typescript6": "^6.0.2",
|
|
46
45
|
"jsdom": "^29.1.1",
|
|
47
46
|
"typescript": "^7.0.2",
|
|
48
47
|
"vite": "^8.1.5",
|
|
49
|
-
"vite-plugin-dts": "^5.0.3",
|
|
50
48
|
"vitest": "^4.1.10"
|
|
51
49
|
},
|
|
52
50
|
"scripts": {
|
|
53
51
|
"dev": "vite",
|
|
54
|
-
"build": "tsc && vite build",
|
|
52
|
+
"build": "tsc && vite build && tsc -p tsconfig.build.json",
|
|
55
53
|
"test": "vitest run"
|
|
56
54
|
}
|
|
57
55
|
}
|