@axa-fr/react-oidc 6.6.1 → 6.6.2-alpha0

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.
Files changed (54) hide show
  1. package/package.json +2 -1
  2. package/src/App.css +38 -0
  3. package/src/App.specold.tsx +46 -0
  4. package/src/App.tsx +103 -0
  5. package/src/FetchUser.tsx +53 -0
  6. package/src/Home.tsx +24 -0
  7. package/src/MultiAuth.tsx +129 -0
  8. package/src/Profile.tsx +77 -0
  9. package/src/configurations.ts +70 -0
  10. package/src/index.css +13 -0
  11. package/src/index.tsx +9 -0
  12. package/src/logo.svg +7 -0
  13. package/src/oidc/FetchToken.tsx +61 -0
  14. package/src/oidc/OidcProvider.tsx +206 -0
  15. package/src/oidc/OidcSecure.tsx +37 -0
  16. package/src/oidc/ReactOidc.tsx +139 -0
  17. package/src/oidc/User.ts +38 -0
  18. package/src/oidc/core/default-component/AuthenticateError.component.tsx +13 -0
  19. package/src/oidc/core/default-component/Authenticating.component.tsx +13 -0
  20. package/src/oidc/core/default-component/Callback.component.tsx +46 -0
  21. package/src/oidc/core/default-component/Loading.component.tsx +10 -0
  22. package/src/oidc/core/default-component/ServiceWorkerNotSupported.component.tsx +13 -0
  23. package/src/oidc/core/default-component/SessionLost.component.tsx +14 -0
  24. package/src/oidc/core/default-component/SilentCallback.component.tsx +22 -0
  25. package/src/oidc/core/default-component/SilentLogin.component.tsx +35 -0
  26. package/src/oidc/core/default-component/index.ts +6 -0
  27. package/src/oidc/core/routes/OidcRoutes.spec.tsx +15 -0
  28. package/src/oidc/core/routes/OidcRoutes.tsx +69 -0
  29. package/src/oidc/core/routes/__snapshots__/OidcRoutes.spec.tsx.snap +7 -0
  30. package/src/oidc/core/routes/index.ts +2 -0
  31. package/src/oidc/core/routes/withRouter.spec.tsx +48 -0
  32. package/src/oidc/core/routes/withRouter.tsx +64 -0
  33. package/src/oidc/index.ts +5 -0
  34. package/src/oidc/vanilla/OidcServiceWorker.js +442 -0
  35. package/src/oidc/vanilla/OidcTrustedDomains.js +16 -0
  36. package/src/oidc/vanilla/checkSessionIFrame.ts +82 -0
  37. package/src/oidc/vanilla/index.ts +1 -0
  38. package/src/oidc/vanilla/initSession.ts +67 -0
  39. package/src/oidc/vanilla/initWorker.ts +165 -0
  40. package/src/oidc/vanilla/memoryStorageBackend.ts +33 -0
  41. package/src/oidc/vanilla/noHashQueryStringUtils.ts +33 -0
  42. package/src/oidc/vanilla/oidc.ts +1230 -0
  43. package/src/oidc/vanilla/parseTokens.ts +142 -0
  44. package/src/oidc/vanilla/route-utils.spec.ts +15 -0
  45. package/src/oidc/vanilla/route-utils.ts +76 -0
  46. package/src/oidc/vanilla/timer.ts +165 -0
  47. package/src/override/AuthenticateError.component.tsx +14 -0
  48. package/src/override/Authenticating.component.tsx +14 -0
  49. package/src/override/Callback.component.tsx +13 -0
  50. package/src/override/Loading.component.tsx +13 -0
  51. package/src/override/ServiceWorkerNotSupported.component.tsx +15 -0
  52. package/src/override/SessionLost.component.tsx +21 -0
  53. package/src/override/style.ts +10 -0
  54. package/src/setupTests.js +5 -0
@@ -0,0 +1,142 @@
1
+ 
2
+
3
+ const b64DecodeUnicode = (str) =>
4
+ decodeURIComponent(Array.prototype.map.call(atob(str), (c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
5
+ const parseJwt = (token) => JSON.parse(b64DecodeUnicode(token.split('.')[1].replace('-', '+').replace('_', '/')));
6
+
7
+ const extractTokenPayload = (token) => {
8
+ try{
9
+ if (!token) {
10
+ return null;
11
+ }
12
+ if(countLetter(token,'.') === 2) {
13
+ return parseJwt(token);
14
+ } else {
15
+ return null;
16
+ }
17
+ } catch (e) {
18
+ console.warn(e);
19
+ }
20
+ return null;
21
+ }
22
+
23
+ const countLetter = (str, find)=> {
24
+ return (str.split(find)).length - 1;
25
+ }
26
+
27
+
28
+ export const setTokens = (tokens, oldTokens=null) =>{
29
+
30
+ if(!tokens){
31
+ return null;
32
+ }
33
+ let accessTokenPayload;
34
+
35
+ if(!tokens.issuedAt) {
36
+ const currentTimeUnixSecond = new Date().getTime() /1000;
37
+ tokens.issuedAt = currentTimeUnixSecond;
38
+ }
39
+
40
+ if(tokens.accessTokenPayload !== undefined) {
41
+ accessTokenPayload = tokens.accessTokenPayload;
42
+ }
43
+ else {
44
+ accessTokenPayload = extractTokenPayload(tokens);
45
+ }
46
+ const _idTokenPayload = tokens.idTokenPayload ? tokens.idTokenPayload : extractTokenPayload(tokens.idToken);
47
+
48
+ const idTokenExipreAt =(_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp: Number.MAX_VALUE;
49
+ const accessTokenExpiresAt = (accessTokenPayload && accessTokenPayload.exp)? accessTokenPayload.exp : tokens.issuedAt + tokens.expiresIn;
50
+ const expiresAt = idTokenExipreAt < accessTokenExpiresAt ? idTokenExipreAt : accessTokenExpiresAt;
51
+
52
+ const newTokens = {...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt};
53
+ // When refresh_token is not rotated we reuse ald refresh_token
54
+ if(oldTokens != null && "refreshToken" in oldTokens && !("refreshToken" in tokens)){
55
+ const refreshToken = oldTokens.refreshToken
56
+ return {...newTokens, refreshToken};
57
+ }
58
+
59
+ return newTokens;
60
+ }
61
+
62
+
63
+
64
+ export const parseOriginalTokens= (tokens, oldTokens) =>{
65
+ if(!tokens){
66
+ return null;
67
+ }
68
+ if(!tokens.issued_at) {
69
+ const currentTimeUnixSecond = new Date().getTime() /1000;
70
+ tokens.issued_at = currentTimeUnixSecond;
71
+ }
72
+
73
+ const data = {
74
+ accessToken: tokens.access_token,
75
+ expiresIn: tokens.expires_in,
76
+ idToken: tokens.id_token,
77
+ scope: tokens.scope,
78
+ tokenType: tokens.token_type,
79
+ issuedAt: tokens.issued_at
80
+ };
81
+
82
+ if("refresh_token" in tokens) {
83
+ // @ts-ignore
84
+ data.refreshToken= tokens.refresh_token;
85
+ }
86
+
87
+
88
+ if(tokens.accessTokenPayload !== undefined){
89
+ // @ts-ignore
90
+ data.accessTokenPayload = tokens.accessTokenPayload;
91
+ }
92
+
93
+ if(tokens.idTokenPayload !== undefined){
94
+ // @ts-ignore
95
+ data.idTokenPayload = tokens.idTokenPayload;
96
+ }
97
+
98
+ return setTokens(data, oldTokens);
99
+ }
100
+
101
+ export const computeTimeLeft = (refreshTimeBeforeTokensExpirationInSecond, expiresAt)=>{
102
+ const currentTimeUnixSecond = new Date().getTime() /1000;
103
+ return Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond) - currentTimeUnixSecond));
104
+ }
105
+
106
+ export const isTokensValid= (tokens) =>{
107
+ if(!tokens){
108
+ return false;
109
+ }
110
+ return computeTimeLeft(0, tokens.expiresAt) > 0;
111
+ }
112
+
113
+ // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation (excluding rules #1, #4, #5, #7, #8, #12, and #13 which did not apply).
114
+ // https://github.com/openid/AppAuth-JS/issues/65
115
+ export const isTokensOidcValid =(tokens, nonce, oidcServerConfiguration) =>{
116
+ if(tokens.idTokenPayload) {
117
+ const idTokenPayload = tokens.idTokenPayload;
118
+ // 2: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery) MUST exactly match the value of the iss (issuer) Claim.
119
+ if(oidcServerConfiguration.issuer !== idTokenPayload.iss){
120
+ return false;
121
+ }
122
+ // 3: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience. The aud (audience) Claim MAY contain an array with more than one element. The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.
123
+
124
+ // 6: If the ID Token is received via direct communication between the Client and the Token Endpoint (which it is in this flow), the TLS server validation MAY be used to validate the issuer in place of checking the token signature. The Client MUST validate the signature of all other ID Tokens according to JWS [JWS] using the algorithm specified in the JWT alg Header Parameter. The Client MUST use the keys provided by the Issuer.
125
+
126
+ // 9: The current time MUST be before the time represented by the exp Claim.
127
+ const currentTimeUnixSecond = new Date().getTime() /1000;
128
+ if(idTokenPayload.exp && idTokenPayload.exp < currentTimeUnixSecond) {
129
+ return false;
130
+ }
131
+ // 10: The iat Claim can be used to reject tokens that were issued too far away from the current time, limiting the amount of time that nonces need to be stored to prevent attacks. The acceptable range is Client specific.
132
+ const timeInSevenDays = 60 * 60 * 24 * 7;
133
+ if(idTokenPayload.iat && (idTokenPayload.iat + timeInSevenDays) < currentTimeUnixSecond) {
134
+ return false;
135
+ }
136
+ // 11: If a nonce value was sent in the Authentication Request, a nonce Claim MUST be present and its value checked to verify that it is the same value as the one that was sent in the Authentication Request. The Client SHOULD check the nonce value for replay attacks. The precise method for detecting replay attacks is Client specific.
137
+ if (idTokenPayload.nonce && idTokenPayload.nonce !== nonce) {
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
@@ -0,0 +1,15 @@
1
+ import { getPath } from './route-utils';
2
+
3
+
4
+ test.each([['http://example.com/pathname', '/pathname'],
5
+ ['http://example.com:3000/pathname/?search=test#hash', '/pathname#hash'],
6
+ ['http://example.com:3000/pathname/#hash?search=test', '/pathname#hash'],
7
+ ['http://example.com:3000/pathname#hash?search=test', '/pathname#hash'],
8
+ ['http://example.com:3000/', ''],])(
9
+ 'getPath should return the full path of an url',
10
+ (uri, expected) => {
11
+
12
+ const path = getPath(uri);
13
+ expect(path).toBe(expected);
14
+ },
15
+ );
@@ -0,0 +1,76 @@
1
+ export const getLocation = (href: string) => {
2
+ const match = href.match(
3
+ // eslint-disable-next-line no-useless-escape
4
+ /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/
5
+ );
6
+
7
+ let search = match[6];
8
+ let hash = match[7];
9
+
10
+ if (hash) {
11
+ const splits = hash.split("?");
12
+ if(splits.length ==2){
13
+ hash = splits[0];
14
+ search = splits[1];
15
+ }
16
+ }
17
+
18
+ if(search){
19
+ search = search.slice(1);
20
+ }
21
+
22
+ return (
23
+ match && {
24
+ href,
25
+ protocol: match[1],
26
+ host: match[2],
27
+ hostname: match[3],
28
+ port: match[4],
29
+ path: match[5],
30
+ search,
31
+ hash,
32
+ }
33
+ );
34
+ };
35
+
36
+ export const getPath = (href: string) => {
37
+ const location = getLocation(href);
38
+ let { path } = location;
39
+
40
+ if(path.endsWith('/')){
41
+ path = path.slice(0, -1);
42
+ }
43
+ let { hash } = location;
44
+
45
+ if(hash === "#_=_"){
46
+ hash = "";
47
+ }
48
+
49
+ if (hash) {
50
+ path += hash;
51
+ }
52
+
53
+ return path;
54
+ };
55
+
56
+ export const getParseQueryStringFromLocation=(href: string) => {
57
+ const location = getLocation(href);
58
+ let { search } = location;
59
+
60
+ return parseQueryString(search);
61
+ }
62
+
63
+ const parseQueryString = (queryString:string) => {
64
+ let params:any = {}, queries, temp, i, l;
65
+
66
+ // Split into key/value pairs
67
+ queries = queryString.split("&");
68
+
69
+ // Convert the array of strings into an object
70
+ for (i = 0, l = queries.length; i < l; i++) {
71
+ temp = queries[i].split('=');
72
+ params[temp[0]] = temp[1];
73
+ }
74
+
75
+ return params;
76
+ };
@@ -0,0 +1,165 @@
1
+ const timer = (function () {
2
+ const workerPort = (function () {
3
+ let worker;
4
+ let blobURL;
5
+
6
+ const workerCode = function () {
7
+ const innerIdsByOuterIds = {};
8
+
9
+ const methods = {
10
+ setTimeout: function (port, id, timeout) {
11
+ innerIdsByOuterIds[id] = setTimeout(function () {
12
+ port.postMessage(id);
13
+ innerIdsByOuterIds[id] = null;
14
+ }, timeout);
15
+ },
16
+
17
+ setInterval: function (port, id, timeout) {
18
+ innerIdsByOuterIds[id] = setInterval(function () {
19
+ port.postMessage(id);
20
+ }, timeout);
21
+ },
22
+
23
+ clearTimeout: function (port, id) {
24
+ clearTimeout(innerIdsByOuterIds[id]);
25
+ innerIdsByOuterIds[id] = null;
26
+ },
27
+
28
+ clearInterval: function (port, id) {
29
+ clearInterval(innerIdsByOuterIds[id]);
30
+ innerIdsByOuterIds[id] = null;
31
+ }
32
+ };
33
+
34
+ function onMessage(port, event) {
35
+ var method = event.data[0];
36
+ var id = event.data[1];
37
+ var option = event.data[2];
38
+
39
+ if (methods[method]) {
40
+ methods[method](port, id, option);
41
+ }
42
+ }
43
+
44
+ // For Dedicated Worker
45
+ this.onmessage = function (event) {
46
+ onMessage(self, event);
47
+ };
48
+
49
+ // For Shared Worker
50
+ this.onconnect = function (event) {
51
+ const port = event.ports[0];
52
+
53
+ port.onmessage = function (event) {
54
+ onMessage(port, event);
55
+ };
56
+ };
57
+ }.toString();
58
+
59
+ try {
60
+ const blob = new Blob(['(', workerCode, ')()'], {type: 'application/javascript'});
61
+ blobURL = URL.createObjectURL(blob);
62
+ } catch (error) {
63
+ return null;
64
+ }
65
+ const insideBrowser = (typeof process === 'undefined');
66
+ try {
67
+ if (SharedWorker) {
68
+ worker = new SharedWorker(blobURL);
69
+ return worker.port;
70
+ }
71
+ } catch (error)
72
+ {
73
+ if(insideBrowser) {
74
+ console.warn("SharedWorker not available");
75
+ }
76
+ }
77
+ try {
78
+ if (Worker) {
79
+ worker = new Worker(blobURL);
80
+ return worker;
81
+ }
82
+ } catch (error)
83
+ {
84
+ if(insideBrowser) {
85
+ console.warn("Worker not available");
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }());
91
+
92
+ if (!workerPort) {
93
+ // In NextJS with SSR (Server Side Rendering) during rending in Node JS, the window object is undefined,
94
+ // the global object is used instead as it is the closest approximation of a browsers window object.
95
+ const bindContext = (typeof window === 'undefined')? global: window;
96
+
97
+ return {
98
+ setTimeout: setTimeout.bind(bindContext),
99
+ clearTimeout: clearTimeout.bind(bindContext),
100
+ setInterval: setInterval.bind(bindContext),
101
+ clearInterval: clearInterval.bind(bindContext)
102
+ };
103
+ }
104
+
105
+ const getId = (function () {
106
+ let currentId = 0;
107
+
108
+ return function () {
109
+ currentId++;
110
+ return currentId;
111
+ };
112
+ }());
113
+
114
+ const timeoutCallbacksById = {};
115
+ const intervalCallbacksById = {};
116
+
117
+ workerPort.onmessage = function (event) {
118
+ const id = event.data;
119
+
120
+ const timeoutCallback = timeoutCallbacksById[id];
121
+ if (timeoutCallback) {
122
+ timeoutCallback();
123
+ timeoutCallbacksById[id] = null;
124
+ return;
125
+ }
126
+
127
+ const intervalCallback = intervalCallbacksById[id];
128
+ if (intervalCallback) {
129
+ intervalCallback();
130
+ }
131
+ };
132
+
133
+ function setTimeoutWorker(callback, timeout) {
134
+ const id = getId();
135
+ workerPort.postMessage(['setTimeout', id, timeout]);
136
+ timeoutCallbacksById[id] = callback;
137
+ return id;
138
+ }
139
+
140
+ function clearTimeoutWorker(id) {
141
+ workerPort.postMessage(['clearTimeout', id]);
142
+ timeoutCallbacksById[id] = null;
143
+ }
144
+
145
+ function setIntervalWorker(callback, timeout) {
146
+ const id = getId();
147
+ workerPort.postMessage(['setInterval', id, timeout]);
148
+ intervalCallbacksById[id] = callback;
149
+ return id;
150
+ }
151
+
152
+ function clearIntervalWorker(id) {
153
+ workerPort.postMessage(['clearInterval', id]);
154
+ intervalCallbacksById[id] = null;
155
+ }
156
+
157
+ return {
158
+ setTimeout: setTimeoutWorker,
159
+ clearTimeout: clearTimeoutWorker,
160
+ setInterval: setIntervalWorker,
161
+ clearInterval: clearIntervalWorker
162
+ };
163
+ }());
164
+
165
+ export default timer;
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import {ComponentType} from "react";
3
+ import {style} from "./style";
4
+
5
+ const AuthenticatingError: ComponentType<any> = ({configurationName}) => (
6
+ <div className="oidc-authenticating" style={style}>
7
+ <div className="oidc-authenticating__container">
8
+ <h1 className="oidc-authenticating__title">Error authentication for {configurationName}</h1>
9
+ <p className="oidc-authenticating__content">An error occurred during authentication.</p>
10
+ </div>
11
+ </div>
12
+ );
13
+
14
+ export default AuthenticatingError;
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import {PropsWithChildren} from "react";
3
+ import {style} from "./style";
4
+
5
+ const Authenticating : PropsWithChildren<any> = ({configurationName}) => (
6
+ <div className="oidc-authenticating" style={style}>
7
+ <div className="oidc-authenticating__container">
8
+ <h1 className="oidc-authenticating__title">Authentication in progress for {configurationName}</h1>
9
+ <p className="oidc-authenticating__content">You will be redirected to the login page.</p>
10
+ </div>
11
+ </div>
12
+ );
13
+
14
+ export default Authenticating;
@@ -0,0 +1,13 @@
1
+ import React, {ComponentType} from 'react';
2
+ import {style} from "./style";
3
+
4
+ export const CallBackSuccess: ComponentType<any> = ({configurationName}) => (<><div className="oidc-callback" style={style}>
5
+ <div className="oidc-callback__container">
6
+ <h1 className="oidc-callback__title">Authentication complete for {configurationName}</h1>
7
+ <p className="oidc-callback__content">You will be redirected to your application.</p>
8
+ </div>
9
+ </div>
10
+ <div>
11
+ </div>
12
+ </>
13
+ );
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import {ComponentType } from "react";
3
+ import {style} from "./style";
4
+
5
+ const Loading : ComponentType<any> = ({children, configurationName}) => (
6
+ <>
7
+ <span className="oidc-loading" style={style}>
8
+ Loading for {configurationName}
9
+ </span>
10
+ </>
11
+ );
12
+
13
+ export default Loading;
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import {ComponentType} from "react";
3
+ import {style} from "./style";
4
+
5
+ const ServiceWorkerNotSupported : ComponentType<any> = ({configurationName}) => (
6
+ <><div className="oidc-serviceworker" style={style}>
7
+ <div className="oidc-serviceworker__container">
8
+ <h1 className="oidc-serviceworker__title">Unable to authenticate on this browser for {configurationName}</h1>
9
+ <p className="oidc-serviceworker__content">Your browser is not secure enough to make authentication work. Try updating your browser or use a newer browser.</p>
10
+ </div>
11
+ </div>
12
+ </>
13
+ );
14
+
15
+ export default ServiceWorkerNotSupported;
@@ -0,0 +1,21 @@
1
+ import React, {ComponentType} from 'react';
2
+ import {style} from "./style"
3
+ import {useOidc} from "../oidc";
4
+
5
+ export const SessionLost: ComponentType<any> = ({configurationName}) => {
6
+ const { login} = useOidc(configurationName);
7
+
8
+ return (<>
9
+ <div className="oidc-session-lost" style={style}>
10
+ <div className="oidc-session-lost__container">
11
+ <h1 className="oidc-session-lost__title">Session timed out for {configurationName}</h1>
12
+ <p className="oidc-session-lost__content">
13
+ Your session has expired. Please re-authenticate.
14
+ </p>
15
+ <button type="button" className="btn btn-primary" onClick={() => login(null)}>Login</button>
16
+ </div>
17
+ </div>
18
+ </>)
19
+ };
20
+
21
+ export default SessionLost;
@@ -0,0 +1,10 @@
1
+ export const style = {
2
+ "color": "rgb(53,110,255)",
3
+ "backgroundColor": "rgb(255 255 255 / 88%)",
4
+ "position": "absolute",
5
+ "zIndex": 1000,
6
+ "top": "0px",
7
+ "bottom": "0px",
8
+ "right": "0px",
9
+ "left": "0px",
10
+ };
@@ -0,0 +1,5 @@
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom/extend-expect';