@witsauth/react-client 1.3.6 → 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 +7 -2
- package/dist/core/pkce.d.ts +1 -0
- package/dist/core/storage.d.ts +1 -1
- package/dist/index.es.js +203 -117
- 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,15 +1,20 @@
|
|
|
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;
|
|
5
5
|
private storage;
|
|
6
|
+
private refreshPromise;
|
|
6
7
|
constructor(config: OAuth2Config, storage?: TokenStorage);
|
|
7
8
|
log(level: 'error' | 'warn' | 'info' | 'debug', ...args: any[]): void;
|
|
8
|
-
startAuthorization(): Promise<void>;
|
|
9
|
+
startAuthorization(options?: AuthorizeOptions): Promise<void>;
|
|
9
10
|
exchangeCodeForTokens(code: string, state: string): Promise<TokenInfo>;
|
|
10
11
|
refreshAccessToken(): Promise<TokenInfo>;
|
|
11
12
|
private saveTokenInfo;
|
|
12
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>;
|
|
13
18
|
getExpiresAt(): number | null;
|
|
14
19
|
isAuthenticated(): boolean;
|
|
15
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,17 +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;
|
|
103
|
+
refreshPromise = null;
|
|
77
104
|
constructor(e, t) {
|
|
78
|
-
this.config = e, this.storage = t || new
|
|
105
|
+
this.config = e, this.storage = t || new h(e.storage, e.customStorage);
|
|
79
106
|
}
|
|
80
107
|
log(e, ...t) {
|
|
81
108
|
let n = {
|
|
@@ -87,29 +114,30 @@ var f = class {
|
|
|
87
114
|
}, r = n[this.config.logLevel || "warn"] || 0, i = n[e] || 0;
|
|
88
115
|
i > 0 && r >= i && console[e](...t);
|
|
89
116
|
}
|
|
90
|
-
async startAuthorization() {
|
|
91
|
-
let
|
|
92
|
-
this.storage.setItem(
|
|
93
|
-
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({
|
|
94
121
|
client_id: this.config.clientId,
|
|
95
122
|
redirect_uri: this.config.redirectUri,
|
|
96
123
|
response_type: this.config.responseType || "code",
|
|
97
124
|
code_challenge_method: this.config.codeChallengeMethod || "S256",
|
|
98
|
-
code_challenge:
|
|
99
|
-
state:
|
|
100
|
-
nonce:
|
|
125
|
+
code_challenge: n,
|
|
126
|
+
state: r,
|
|
127
|
+
nonce: i
|
|
101
128
|
});
|
|
102
129
|
if (this.config.theme) {
|
|
103
130
|
let e = typeof this.config.theme == "function" ? this.config.theme() : this.config.theme;
|
|
104
|
-
e &&
|
|
131
|
+
e && a.append("theme", e);
|
|
105
132
|
}
|
|
106
|
-
|
|
107
|
-
|
|
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()}`;
|
|
108
136
|
if (typeof window > "u" || !window.location) throw Error("startAuthorization requires a browser environment to redirect the user.");
|
|
109
|
-
window.location.href =
|
|
137
|
+
window.location.href = c;
|
|
110
138
|
}
|
|
111
139
|
async exchangeCodeForTokens(e, t) {
|
|
112
|
-
let n = this.storage.getItem(
|
|
140
|
+
let n = this.storage.getItem(l.STATE), r = this.storage.getItem(l.CODE_VERIFIER);
|
|
113
141
|
if (t !== n) throw Error("Invalid state parameter. Possible CSRF attack.");
|
|
114
142
|
if (!r) throw Error("Code verifier missing from storage.");
|
|
115
143
|
let i = new URLSearchParams({
|
|
@@ -135,36 +163,76 @@ var f = class {
|
|
|
135
163
|
return this.saveTokenInfo(s), this.storage.clearTemporary(), s;
|
|
136
164
|
}
|
|
137
165
|
async refreshAccessToken() {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
166
|
+
return this.refreshPromise ||= (async () => {
|
|
167
|
+
let e = this.storage.getItem(l.REFRESH_TOKEN);
|
|
168
|
+
if (!e) throw Error("No refresh token available");
|
|
169
|
+
let t = new URLSearchParams({
|
|
170
|
+
grant_type: "refresh_token",
|
|
171
|
+
client_id: this.config.clientId,
|
|
172
|
+
refresh_token: e
|
|
173
|
+
}), n;
|
|
174
|
+
try {
|
|
175
|
+
n = await fetch(this.config.tokenEndpoint, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
178
|
+
body: t.toString()
|
|
179
|
+
});
|
|
180
|
+
} catch (e) {
|
|
181
|
+
throw this.log("error", "Token refresh network request failed:", e), e;
|
|
182
|
+
}
|
|
183
|
+
if (!n.ok) {
|
|
184
|
+
let e = {};
|
|
185
|
+
try {
|
|
186
|
+
e = await n.json();
|
|
187
|
+
} catch {
|
|
188
|
+
try {
|
|
189
|
+
e = { error: await n.text() };
|
|
190
|
+
} catch {
|
|
191
|
+
e = {};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
this.log("error", "Token refresh failed with status:", n.status, e), (n.status === 400 || n.status === 401) && this.storage.clearTokens();
|
|
195
|
+
let t = e.error_description || e.error || n.statusText || "Failed to refresh token";
|
|
196
|
+
throw Error(`Failed to refresh token (${n.status}): ${t}`);
|
|
197
|
+
}
|
|
198
|
+
let r = await n.json(), i = {
|
|
199
|
+
accessToken: r.access_token,
|
|
200
|
+
refreshToken: r.refresh_token || e,
|
|
201
|
+
tokenType: r.token_type || "Bearer",
|
|
202
|
+
expiresAt: Date.now() + r.expires_in * 1e3,
|
|
203
|
+
scope: r.scope,
|
|
204
|
+
idToken: r.id_token
|
|
205
|
+
};
|
|
206
|
+
return this.saveTokenInfo(i), i;
|
|
207
|
+
})().finally(() => {
|
|
208
|
+
this.refreshPromise = null;
|
|
209
|
+
}), this.refreshPromise;
|
|
159
210
|
}
|
|
160
211
|
saveTokenInfo(e) {
|
|
161
|
-
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);
|
|
162
213
|
}
|
|
163
214
|
getAccessToken() {
|
|
164
|
-
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
|
+
}
|
|
165
233
|
}
|
|
166
234
|
getExpiresAt() {
|
|
167
|
-
let e = this.storage.getItem(
|
|
235
|
+
let e = this.storage.getItem(l.TOKEN_EXPIRES_AT);
|
|
168
236
|
if (!e) return null;
|
|
169
237
|
let t = Number(e);
|
|
170
238
|
return Number.isFinite(t) ? t : null;
|
|
@@ -174,9 +242,9 @@ var f = class {
|
|
|
174
242
|
return e !== null && Date.now() < e - 3e4 && !!this.getAccessToken();
|
|
175
243
|
}
|
|
176
244
|
async navigateToAccountManagement() {
|
|
177
|
-
let e = this.storage.getItem(
|
|
245
|
+
let e = this.storage.getItem(l.REFRESH_TOKEN);
|
|
178
246
|
if (!e) throw Error("No refresh token available to access account management.");
|
|
179
|
-
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.");
|
|
180
248
|
let t = document.createElement("form");
|
|
181
249
|
t.method = "POST";
|
|
182
250
|
let n = this.config.authorizationEndpoint.replace(/\/authorize\/?$/, "");
|
|
@@ -194,96 +262,109 @@ var f = class {
|
|
|
194
262
|
r("refreshToken", e), r("clientId", this.config.clientId), r("redirectUri", this.config.redirectUri), document.body.appendChild(t), t.submit();
|
|
195
263
|
}
|
|
196
264
|
async logout() {
|
|
197
|
-
let e = this.getAccessToken();
|
|
198
|
-
if (this.storage.clearTokens(), this.storage.clearTemporary(), this.config.revokeEndpoint
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
}
|
|
206
287
|
}
|
|
207
288
|
}
|
|
208
|
-
},
|
|
209
|
-
let [
|
|
289
|
+
}, _ = e(null), v = ({ config: e, children: n }) => {
|
|
290
|
+
let [a, s] = o({
|
|
210
291
|
isAuthenticated: !1,
|
|
211
292
|
isLoading: !0
|
|
212
|
-
}),
|
|
213
|
-
|
|
293
|
+
}), l = i(() => new g(e), [e]);
|
|
294
|
+
r(() => {
|
|
214
295
|
(async () => {
|
|
215
296
|
try {
|
|
216
|
-
if (
|
|
297
|
+
if (l.isAuthenticated()) s({
|
|
217
298
|
isAuthenticated: !0,
|
|
218
299
|
isLoading: !1
|
|
219
300
|
});
|
|
220
301
|
else try {
|
|
221
|
-
await
|
|
302
|
+
await l.refreshAccessToken(), s({
|
|
222
303
|
isAuthenticated: !0,
|
|
223
304
|
isLoading: !1
|
|
224
305
|
});
|
|
225
306
|
} catch {
|
|
226
|
-
|
|
307
|
+
s({
|
|
227
308
|
isAuthenticated: !1,
|
|
228
309
|
isLoading: !1
|
|
229
310
|
});
|
|
230
311
|
}
|
|
231
312
|
} catch (e) {
|
|
232
|
-
|
|
313
|
+
s({
|
|
233
314
|
isAuthenticated: !1,
|
|
234
315
|
isLoading: !1,
|
|
235
316
|
error: e instanceof Error ? e.message : String(e)
|
|
236
317
|
});
|
|
237
318
|
}
|
|
238
319
|
})();
|
|
239
|
-
}, [
|
|
240
|
-
if (e.autoRefresh === !1 || !
|
|
320
|
+
}, [l]), r(() => {
|
|
321
|
+
if (e.autoRefresh === !1 || !a.isAuthenticated) return;
|
|
241
322
|
let t = !1, n = setInterval(async () => {
|
|
242
323
|
if (!t) try {
|
|
243
|
-
let n =
|
|
324
|
+
let n = l.getExpiresAt();
|
|
244
325
|
if (!n) return;
|
|
245
326
|
let r = (e.refreshThreshold ?? 60) * 1e3;
|
|
246
327
|
if (Date.now() >= n - r) {
|
|
247
328
|
t = !0;
|
|
248
329
|
try {
|
|
249
|
-
let e = await
|
|
250
|
-
|
|
330
|
+
let e = await l.refreshAccessToken();
|
|
331
|
+
s((t) => ({
|
|
251
332
|
...t,
|
|
252
333
|
tokenInfo: e
|
|
253
334
|
}));
|
|
254
335
|
} catch {
|
|
255
|
-
|
|
336
|
+
d();
|
|
256
337
|
} finally {
|
|
257
338
|
t = !1;
|
|
258
339
|
}
|
|
259
340
|
}
|
|
260
341
|
} catch (e) {
|
|
261
|
-
|
|
342
|
+
l.log("error", "Auto-refresh error:", e);
|
|
262
343
|
}
|
|
263
344
|
}, 1e4);
|
|
264
345
|
return () => clearInterval(n);
|
|
265
346
|
}, [
|
|
266
347
|
e.autoRefresh,
|
|
267
348
|
e.refreshThreshold,
|
|
268
|
-
|
|
269
|
-
|
|
349
|
+
a.isAuthenticated,
|
|
350
|
+
l
|
|
270
351
|
]);
|
|
271
|
-
let
|
|
272
|
-
|
|
352
|
+
let u = t(async (e) => {
|
|
353
|
+
s((e) => ({
|
|
273
354
|
...e,
|
|
274
355
|
isLoading: !0
|
|
275
|
-
})), await
|
|
276
|
-
},
|
|
277
|
-
|
|
356
|
+
})), await l.startAuthorization(e);
|
|
357
|
+
}, [l]), d = async () => {
|
|
358
|
+
s((e) => ({
|
|
278
359
|
...e,
|
|
279
360
|
isLoading: !0
|
|
280
361
|
}));
|
|
281
362
|
try {
|
|
282
|
-
await
|
|
363
|
+
await l.logout();
|
|
283
364
|
} catch (e) {
|
|
284
|
-
|
|
365
|
+
l.log("error", "Logout error:", e);
|
|
285
366
|
} finally {
|
|
286
|
-
if (
|
|
367
|
+
if (s({
|
|
287
368
|
isAuthenticated: !1,
|
|
288
369
|
isLoading: !1
|
|
289
370
|
}), e.logoutEndpoint && typeof window < "u") {
|
|
@@ -296,14 +377,14 @@ var f = class {
|
|
|
296
377
|
window.location.assign(t.toString());
|
|
297
378
|
}
|
|
298
379
|
}
|
|
299
|
-
},
|
|
300
|
-
|
|
380
|
+
}, f = async (t, n) => {
|
|
381
|
+
s((e) => ({
|
|
301
382
|
...e,
|
|
302
383
|
isLoading: !0
|
|
303
384
|
}));
|
|
304
385
|
try {
|
|
305
|
-
let r = await
|
|
306
|
-
return
|
|
386
|
+
let r = await l.exchangeCodeForTokens(t, n);
|
|
387
|
+
return s({
|
|
307
388
|
isAuthenticated: !0,
|
|
308
389
|
isLoading: !1,
|
|
309
390
|
tokenInfo: r
|
|
@@ -311,58 +392,63 @@ var f = class {
|
|
|
311
392
|
window.location.assign(e.redirectRoute);
|
|
312
393
|
}, 0), r;
|
|
313
394
|
} catch (e) {
|
|
314
|
-
throw
|
|
395
|
+
throw s({
|
|
315
396
|
isAuthenticated: !1,
|
|
316
397
|
isLoading: !1,
|
|
317
398
|
error: e instanceof Error ? e.message : String(e)
|
|
318
399
|
}), e;
|
|
319
400
|
}
|
|
320
|
-
},
|
|
321
|
-
...
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
327
412
|
};
|
|
328
|
-
return /* @__PURE__ */
|
|
329
|
-
value:
|
|
330
|
-
children:
|
|
413
|
+
return /* @__PURE__ */ c(_.Provider, {
|
|
414
|
+
value: y,
|
|
415
|
+
children: n
|
|
331
416
|
});
|
|
332
|
-
},
|
|
333
|
-
let e =
|
|
417
|
+
}, y = () => {
|
|
418
|
+
let e = n(_);
|
|
334
419
|
if (!e) throw Error("useAuth must be used within an AuthProvider");
|
|
335
420
|
return e;
|
|
336
|
-
},
|
|
337
|
-
let { handleCallback:
|
|
338
|
-
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(() => {
|
|
339
424
|
l.current || (l.current = !0, (async () => {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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}`);
|
|
343
429
|
t && t(e);
|
|
344
430
|
return;
|
|
345
431
|
}
|
|
346
|
-
if (
|
|
347
|
-
await a
|
|
432
|
+
if (a && s) try {
|
|
433
|
+
await i(a, s), e && e();
|
|
348
434
|
} catch (e) {
|
|
349
435
|
t && t(e instanceof Error ? e : Error(String(e)));
|
|
350
436
|
}
|
|
351
|
-
else if (!
|
|
352
|
-
let e =
|
|
437
|
+
else if (!a) {
|
|
438
|
+
let e = o.config;
|
|
353
439
|
window.location.assign(e.redirectRoute || "/");
|
|
354
440
|
}
|
|
355
441
|
})());
|
|
356
442
|
}, [
|
|
357
|
-
|
|
358
|
-
|
|
443
|
+
i,
|
|
444
|
+
o,
|
|
359
445
|
e,
|
|
360
446
|
t
|
|
361
|
-
]), /* @__PURE__ */ s
|
|
447
|
+
]), /* @__PURE__ */ c(s, { children: n });
|
|
362
448
|
};
|
|
363
449
|
//#endregion
|
|
364
450
|
//#region src/axios/interceptor.ts
|
|
365
|
-
function
|
|
451
|
+
function x(e, t) {
|
|
366
452
|
e.interceptors.request.use((e) => {
|
|
367
453
|
let n = t.getAccessToken();
|
|
368
454
|
return n && e.headers && (e.headers.Authorization = `Bearer ${n}`), e;
|
|
@@ -381,4 +467,4 @@ function v(e, t) {
|
|
|
381
467
|
});
|
|
382
468
|
}
|
|
383
469
|
//#endregion
|
|
384
|
-
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;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(){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=await fetch(this.config.tokenEndpoint,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:t.toString()});if(!n.ok)throw this.storage.clearTokens(),Error(`Failed to refresh token`);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}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
|
}
|