authscape 1.0.768 → 1.0.773
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/index.js +829 -1106
- package/package.json +4 -2
- package/src/components/AuthScapeApp.js +62 -8
- package/src/services/apiService.js +6 -6
- package/src/services/authService.js +15 -47
- package/src/services/signInValidator.js +0 -117
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "authscape",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.773",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -27,11 +27,13 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@babel/cli": "^7.27.0",
|
|
29
29
|
"@babel/core": "^7.26.10",
|
|
30
|
+
"@babel/plugin-transform-runtime": "^7.29.0",
|
|
30
31
|
"@babel/preset-env": "^7.26.9",
|
|
31
32
|
"@babel/preset-react": "^7.26.3",
|
|
32
33
|
"react-dom": "^18.3.1"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
36
|
+
"@babel/runtime": "^7.29.2",
|
|
35
37
|
"@lexical/code": "^0.21.0",
|
|
36
38
|
"@lexical/html": "^0.21.0",
|
|
37
39
|
"@lexical/link": "^0.21.0",
|
|
@@ -40,7 +42,6 @@
|
|
|
40
42
|
"@lexical/rich-text": "^0.21.0",
|
|
41
43
|
"@lexical/selection": "^0.21.0",
|
|
42
44
|
"@microsoft/signalr": "^8.0.0",
|
|
43
|
-
"lexical": "^0.21.0",
|
|
44
45
|
"audit": "^0.0.6",
|
|
45
46
|
"axios": "^1.6.1",
|
|
46
47
|
"eslint-config-next": "^16.0.3",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"js-cookie": "^3.0.5",
|
|
51
52
|
"js-file-download": "^0.4.12",
|
|
52
53
|
"jspdf": "^4.0.0",
|
|
54
|
+
"lexical": "^0.21.0",
|
|
53
55
|
"query-string": "^7.1.1",
|
|
54
56
|
"react": "^18.2.0",
|
|
55
57
|
"react-color": "^2.19.3",
|
|
@@ -4,7 +4,7 @@ import Head from "next/head";
|
|
|
4
4
|
|
|
5
5
|
// Re-export toast and transitions so pages can import from authscape
|
|
6
6
|
export { toast, Bounce, Slide, Zoom, Flip };
|
|
7
|
-
import {
|
|
7
|
+
import { useRouter } from "next/router";
|
|
8
8
|
import axios from "axios";
|
|
9
9
|
import querystring from "query-string";
|
|
10
10
|
import Router from "next/router";
|
|
@@ -501,9 +501,22 @@ export function AuthScapeApp({
|
|
|
501
501
|
const errorTrackingInitializedRef = useRef(false);
|
|
502
502
|
const loginRedirectPending = useRef(false);
|
|
503
503
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
504
|
+
// Use Pages Router's `useRouter` instead of `useSearchParams`/`usePathname`
|
|
505
|
+
// from `next/navigation`. The `next/navigation` hooks don't hydrate query
|
|
506
|
+
// params reliably on the 404 page in Webkit/Safari — when the IDP redirects
|
|
507
|
+
// back to /signin-oidc (a URL with no Next.js page in the consumer project),
|
|
508
|
+
// useSearchParams() returns null and the PKCE code is never picked up.
|
|
509
|
+
// The Pages Router router parses query from `asPath` and works on 404 too.
|
|
510
|
+
const router = useRouter();
|
|
511
|
+
const rawQueryCode = router.isReady ? router.query.code : null;
|
|
512
|
+
// router.query values can be string | string[] — coerce to a single string.
|
|
513
|
+
const queryCode = typeof rawQueryCode === "string"
|
|
514
|
+
? rawQueryCode
|
|
515
|
+
: (Array.isArray(rawQueryCode) ? rawQueryCode[0] : null);
|
|
516
|
+
// Pages Router's `router.pathname` returns the route file path (e.g. "/_error"
|
|
517
|
+
// for the 404 page). The auth-redirect guard below compares against
|
|
518
|
+
// "/signin-oidc", so we derive the actual URL path from `asPath` instead.
|
|
519
|
+
const pathname = (router.asPath || "").split("?")[0].split("#")[0];
|
|
507
520
|
|
|
508
521
|
const signInValidator = async (codeFromQuery) => {
|
|
509
522
|
if (queryCodeUsed.current === codeFromQuery) return;
|
|
@@ -547,19 +560,19 @@ export function AuthScapeApp({
|
|
|
547
560
|
expires: 365,
|
|
548
561
|
path: "/",
|
|
549
562
|
domain: domainHost,
|
|
550
|
-
secure:
|
|
563
|
+
secure: (typeof window !== "undefined" && window.location.protocol === "https:"),
|
|
551
564
|
});
|
|
552
565
|
Cookies.set("expires_in", String(response.data.expires_in), {
|
|
553
566
|
expires: 365,
|
|
554
567
|
path: "/",
|
|
555
568
|
domain: domainHost,
|
|
556
|
-
secure:
|
|
569
|
+
secure: (typeof window !== "undefined" && window.location.protocol === "https:"),
|
|
557
570
|
});
|
|
558
571
|
Cookies.set("refresh_token", response.data.refresh_token, {
|
|
559
572
|
expires: 365,
|
|
560
573
|
path: "/",
|
|
561
574
|
domain: domainHost,
|
|
562
|
-
secure:
|
|
575
|
+
secure: (typeof window !== "undefined" && window.location.protocol === "https:"),
|
|
563
576
|
});
|
|
564
577
|
|
|
565
578
|
resetRedirectCounter();
|
|
@@ -567,7 +580,43 @@ export function AuthScapeApp({
|
|
|
567
580
|
const redirectUri = window.localStorage.getItem("redirectUri") || "/";
|
|
568
581
|
window.localStorage.clear();
|
|
569
582
|
|
|
570
|
-
|
|
583
|
+
// Pre-load user while spinner is still showing — eliminates the
|
|
584
|
+
// second GetCurrentUser call (and resulting re-renders) on the destination page.
|
|
585
|
+
let usr = null;
|
|
586
|
+
try {
|
|
587
|
+
usr = await module.exports.apiService().GetCurrentUser();
|
|
588
|
+
} catch (fetchErr) {
|
|
589
|
+
console.warn("[AuthScape] GetCurrentUser failed after token exchange:", fetchErr);
|
|
590
|
+
}
|
|
591
|
+
const enrichedUser = ensureUserHelpers(usr);
|
|
592
|
+
|
|
593
|
+
signedInUser.current = enrichedUser;
|
|
594
|
+
setSignedInUserState(enrichedUser);
|
|
595
|
+
setFrontEndLoadedState(true);
|
|
596
|
+
|
|
597
|
+
// Prevent the useEffect from calling GetCurrentUser again when queryCode → null.
|
|
598
|
+
loadingAuth.current = true;
|
|
599
|
+
|
|
600
|
+
if (enableErrorTracking && enrichedUser && !errorTrackingInitializedRef.current) {
|
|
601
|
+
initializeErrorTracking(enrichedUser);
|
|
602
|
+
errorTrackingInitializedRef.current = true;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (onUserLoaded && enrichedUser) {
|
|
606
|
+
onUserLoaded(enrichedUser);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Dismiss spinner before navigating so destination renders logged-in UI on first paint.
|
|
610
|
+
setIsSigningIn(false);
|
|
611
|
+
|
|
612
|
+
// Client-side navigation preserves the React component tree and all ref values,
|
|
613
|
+
// eliminating the hard-reload → remount → re-render chain.
|
|
614
|
+
// Fall back to hard navigation for absolute external URLs.
|
|
615
|
+
if (redirectUri.startsWith("http://") || redirectUri.startsWith("https://")) {
|
|
616
|
+
window.location.href = redirectUri;
|
|
617
|
+
} else {
|
|
618
|
+
Router.push(redirectUri);
|
|
619
|
+
}
|
|
571
620
|
} catch (exp) {
|
|
572
621
|
console.error("PKCE sign-in failed", exp);
|
|
573
622
|
window.localStorage.clear();
|
|
@@ -805,3 +854,8 @@ export function AuthScapeApp({
|
|
|
805
854
|
</>
|
|
806
855
|
);
|
|
807
856
|
}
|
|
857
|
+
|
|
858
|
+
// AuthScapeProvider is the umbrella component that bundles the three always-on AuthScape
|
|
859
|
+
// features (auth, error tracking, analytics) plus optional in-app notifications. Wrap your
|
|
860
|
+
// NextJS _app.js return value with it. Existing call sites can continue using AuthScapeApp.
|
|
861
|
+
export const AuthScapeProvider = AuthScapeApp;
|
|
@@ -58,21 +58,21 @@ const RefreshToken = async (originalRequest, instance) => {
|
|
|
58
58
|
expires: 365,
|
|
59
59
|
path: '/',
|
|
60
60
|
domain: domainHost,
|
|
61
|
-
secure:
|
|
61
|
+
secure: (typeof window !== "undefined" && window.location.protocol === "https:")
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
Cookies.set('expires_in', String(response.data.expires_in), {
|
|
65
65
|
expires: 365,
|
|
66
66
|
path: '/',
|
|
67
67
|
domain: domainHost,
|
|
68
|
-
secure:
|
|
68
|
+
secure: (typeof window !== "undefined" && window.location.protocol === "https:")
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
Cookies.set('refresh_token', response.data.refresh_token, {
|
|
72
72
|
expires: 365,
|
|
73
73
|
path: '/',
|
|
74
74
|
domain: domainHost,
|
|
75
|
-
secure:
|
|
75
|
+
secure: (typeof window !== "undefined" && window.location.protocol === "https:")
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
return true;
|
|
@@ -116,9 +116,9 @@ export const apiService = (ctx = null) => {
|
|
|
116
116
|
if (error.response.status === 400) {
|
|
117
117
|
if (error.response.config.url.includes("/connect/token")) {
|
|
118
118
|
let domainHost = window.location.hostname.split('.').slice(-2).join('.');
|
|
119
|
-
Cookies.remove('access_token', { path: '/', domain: domainHost, secure:
|
|
120
|
-
Cookies.remove('refresh_token', { path: '/', domain: domainHost, secure:
|
|
121
|
-
Cookies.remove('expires_in', { path: '/', domain: domainHost, secure:
|
|
119
|
+
Cookies.remove('access_token', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
|
|
120
|
+
Cookies.remove('refresh_token', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
|
|
121
|
+
Cookies.remove('expires_in', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
|
|
122
122
|
}
|
|
123
123
|
return Promise.reject(error);
|
|
124
124
|
}
|
|
@@ -5,7 +5,7 @@ export const authService = () => {
|
|
|
5
5
|
return {
|
|
6
6
|
|
|
7
7
|
dec2hex: (dec) => {
|
|
8
|
-
return ('0' + dec.toString(16)).
|
|
8
|
+
return ('0' + dec.toString(16)).slice(-2)
|
|
9
9
|
},
|
|
10
10
|
generateRandomString: () => {
|
|
11
11
|
var array = new Uint32Array(56/2);
|
|
@@ -64,9 +64,10 @@ export const authService = () => {
|
|
|
64
64
|
|
|
65
65
|
return response;
|
|
66
66
|
},
|
|
67
|
-
login: async (redirectUserUri = null,
|
|
67
|
+
login: async (redirectUserUri = null, deviceId = null) => {
|
|
68
|
+
|
|
69
|
+
let state = authService().generateRandomString();
|
|
68
70
|
|
|
69
|
-
let state = "1234";
|
|
70
71
|
if (redirectUserUri != null)
|
|
71
72
|
{
|
|
72
73
|
localStorage.setItem("redirectUri", redirectUserUri);
|
|
@@ -75,18 +76,8 @@ export const authService = () => {
|
|
|
75
76
|
let verifier = authService().generateRandomString();
|
|
76
77
|
var challenge = await authService().challenge_from_verifier(verifier);
|
|
77
78
|
|
|
78
|
-
console.log('[PKCE] Login initiated');
|
|
79
|
-
console.log('[PKCE] Verifier generated | length:', verifier.length, '| preview:', verifier.substring(0, 10) + '...');
|
|
80
|
-
console.log('[PKCE] Challenge generated | value:', challenge);
|
|
81
|
-
console.log('[PKCE] authorityUri:', process.env.authorityUri);
|
|
82
|
-
console.log('[PKCE] client_id:', process.env.client_id);
|
|
83
|
-
|
|
84
79
|
window.localStorage.setItem("verifier", verifier);
|
|
85
80
|
|
|
86
|
-
const storedVerifier = window.localStorage.getItem("verifier");
|
|
87
|
-
console.log('[PKCE] Verifier stored successfully:', storedVerifier === verifier);
|
|
88
|
-
console.log('[PKCE] Stored verifier length:', storedVerifier?.length);
|
|
89
|
-
|
|
90
81
|
let redirectUri = window.location.origin + "/signin-oidc";
|
|
91
82
|
let loginUri = process.env.authorityUri + "/connect/authorize?response_type=code&state=" + state + "&client_id=" + process.env.client_id + "&scope=email%20openid%20offline_access%20profile%20api1&redirect_uri=" + redirectUri + "&code_challenge=" + challenge + "&code_challenge_method=S256";
|
|
92
83
|
|
|
@@ -95,7 +86,6 @@ export const authService = () => {
|
|
|
95
86
|
loginUri += "&deviceId=" + deviceId; // will be for chrome extention and mobile apps later
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
console.log('[PKCE] Redirecting to:', loginUri);
|
|
99
89
|
window.location.href = loginUri;
|
|
100
90
|
},
|
|
101
91
|
signUp: (redirectUrl = null) => {
|
|
@@ -126,40 +116,18 @@ export const authService = () => {
|
|
|
126
116
|
let domainHost = window.location.hostname.split('.').slice(-2).join('.');
|
|
127
117
|
let AuthUri = process.env.authorityUri;
|
|
128
118
|
|
|
119
|
+
Cookies.remove('access_token', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
|
|
120
|
+
Cookies.remove('refresh_token', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
|
|
121
|
+
Cookies.remove('expires_in', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
|
|
129
122
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// domain: domainHost
|
|
139
|
-
// });
|
|
140
|
-
|
|
141
|
-
// destroyCookie({}, "refresh_token", {
|
|
142
|
-
// maxAge: 2147483647,
|
|
143
|
-
// path: '/',
|
|
144
|
-
// domain: domainHost
|
|
145
|
-
// });
|
|
146
|
-
|
|
147
|
-
// destroyCookie({}, "expires_in", {
|
|
148
|
-
// maxAge: 2147483647,
|
|
149
|
-
// path: '/',
|
|
150
|
-
// domain: domainHost
|
|
151
|
-
// });
|
|
152
|
-
|
|
153
|
-
setTimeout(() => {
|
|
154
|
-
if (redirectUri == null)
|
|
155
|
-
{
|
|
156
|
-
window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;
|
|
157
|
-
}
|
|
158
|
-
else
|
|
159
|
-
{
|
|
160
|
-
window.location.href = AuthUri + "/connect/logout?redirect=" + redirectUri;
|
|
161
|
-
}
|
|
162
|
-
}, 500);
|
|
123
|
+
if (redirectUri == null)
|
|
124
|
+
{
|
|
125
|
+
window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
{
|
|
129
|
+
window.location.href = AuthUri + "/connect/logout?redirect=" + redirectUri;
|
|
130
|
+
}
|
|
163
131
|
|
|
164
132
|
},
|
|
165
133
|
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import querystring from "query-string";
|
|
4
|
-
// import Cookies from 'js-cookie';
|
|
5
|
-
|
|
6
|
-
export const signInValidator = async (queryCode) => {
|
|
7
|
-
|
|
8
|
-
let codeVerifier = window.localStorage.getItem("verifier");
|
|
9
|
-
|
|
10
|
-
console.log('[PKCE] signInValidator called');
|
|
11
|
-
console.log('[PKCE] queryCode present:', !!queryCode, '| value:', queryCode);
|
|
12
|
-
console.log('[PKCE] codeVerifier present:', !!codeVerifier, '| length:', codeVerifier?.length);
|
|
13
|
-
|
|
14
|
-
if (queryCode != null && codeVerifier != null)
|
|
15
|
-
{
|
|
16
|
-
const headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
|
17
|
-
|
|
18
|
-
const tokenParams = {
|
|
19
|
-
code: queryCode,
|
|
20
|
-
grant_type: "authorization_code",
|
|
21
|
-
redirect_uri: window.location.origin + "/signin-oidc",
|
|
22
|
-
client_id: process.env.client_id,
|
|
23
|
-
client_secret: process.env.client_secret,
|
|
24
|
-
code_verifier: codeVerifier
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
console.log('[PKCE] Token request params:', {
|
|
28
|
-
...tokenParams,
|
|
29
|
-
client_secret: tokenParams.client_secret ? '***' : '(missing!)',
|
|
30
|
-
code: tokenParams.code?.substring(0, 10) + '...',
|
|
31
|
-
code_verifier: tokenParams.code_verifier?.substring(0, 10) + '... (length: ' + tokenParams.code_verifier?.length + ')',
|
|
32
|
-
});
|
|
33
|
-
console.log('[PKCE] Token endpoint:', process.env.authorityUri + '/connect/token');
|
|
34
|
-
console.log('[PKCE] redirect_uri:', tokenParams.redirect_uri);
|
|
35
|
-
|
|
36
|
-
let queryString = querystring.stringify(tokenParams);
|
|
37
|
-
|
|
38
|
-
let response;
|
|
39
|
-
try {
|
|
40
|
-
response = await axios.post(process.env.authorityUri + '/connect/token', queryString, {
|
|
41
|
-
headers: headers
|
|
42
|
-
});
|
|
43
|
-
console.log('[PKCE] Token response status:', response.status);
|
|
44
|
-
console.log('[PKCE] Token response has access_token:', !!response.data?.access_token);
|
|
45
|
-
console.log('[PKCE] Token response has refresh_token:', !!response.data?.refresh_token);
|
|
46
|
-
} catch (err) {
|
|
47
|
-
console.error('[PKCE] Token request FAILED');
|
|
48
|
-
console.error('[PKCE] Status:', err.response?.status);
|
|
49
|
-
console.error('[PKCE] Error data:', JSON.stringify(err.response?.data));
|
|
50
|
-
console.error('[PKCE] Error message:', err.message);
|
|
51
|
-
throw err;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let domainHost = window.location.hostname.split('.').slice(-2).join('.');
|
|
55
|
-
window.localStorage.removeItem("verifier");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
await setCookie('access_token', response.data.access_token, {
|
|
60
|
-
maxAge: 60 * 60 * 24 * 365, // 1 year,
|
|
61
|
-
path: '/',
|
|
62
|
-
domain: domainHost,
|
|
63
|
-
secure: true
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
await setCookie('expires_in', response.data.expires_in, {
|
|
67
|
-
maxAge: 60 * 60 * 24 * 365, // 1 year,
|
|
68
|
-
path: '/',
|
|
69
|
-
domain: domainHost,
|
|
70
|
-
secure: true
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
await setCookie('refresh_token', response.data.refresh_token, {
|
|
74
|
-
maxAge: 60 * 60 * 24 * 365, // 1 year,
|
|
75
|
-
path: '/',
|
|
76
|
-
domain: domainHost,
|
|
77
|
-
secure: true
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// await setCookie(null, "access_token", response.data.access_token,
|
|
82
|
-
// {
|
|
83
|
-
// maxAge: 2147483647,
|
|
84
|
-
// path: '/',
|
|
85
|
-
// domain: domainHost,
|
|
86
|
-
// secure: true
|
|
87
|
-
// });
|
|
88
|
-
|
|
89
|
-
// await setCookie(null, "expires_in", response.data.expires_in,
|
|
90
|
-
// {
|
|
91
|
-
// maxAge: 2147483647,
|
|
92
|
-
// path: '/',
|
|
93
|
-
// domain: domainHost,
|
|
94
|
-
// secure: true
|
|
95
|
-
// });
|
|
96
|
-
|
|
97
|
-
// await setCookie(null, "refresh_token", response.data.refresh_token,
|
|
98
|
-
// {
|
|
99
|
-
// maxAge: 2147483647,
|
|
100
|
-
// path: '/',
|
|
101
|
-
// domain: domainHost,
|
|
102
|
-
// secure: true
|
|
103
|
-
// });
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let redirectUri = localStorage.getItem("redirectUri")
|
|
107
|
-
localStorage.clear();
|
|
108
|
-
if (redirectUri != null)
|
|
109
|
-
{
|
|
110
|
-
window.location.href = redirectUri;
|
|
111
|
-
}
|
|
112
|
-
else
|
|
113
|
-
{
|
|
114
|
-
window.location.href = "/";
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|