@axa-fr/oidc-client 7.13.16 → 7.14.1
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/README.md +31 -0
- package/dist/checkSession.d.ts +2 -1
- package/dist/checkSession.d.ts.map +1 -1
- package/dist/index.js +760 -747
- package/dist/index.umd.cjs +2 -2
- package/dist/initSession.d.ts +1 -1
- package/dist/initSession.d.ts.map +1 -1
- package/dist/initWorker.d.ts +1 -1
- package/dist/initWorker.d.ts.map +1 -1
- package/dist/jwt.d.ts +10 -6
- package/dist/jwt.d.ts.map +1 -1
- package/dist/keepSession.d.ts +3 -0
- package/dist/keepSession.d.ts.map +1 -0
- package/dist/login.d.ts +2 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/logout.d.ts.map +1 -1
- package/dist/oidc.d.ts +5 -10
- package/dist/oidc.d.ts.map +1 -1
- package/dist/renewTokens.d.ts +18 -2
- package/dist/renewTokens.d.ts.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/user.d.ts +2 -1
- package/dist/user.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/checkSession.ts +2 -1
- package/src/initSession.ts +1 -1
- package/src/initWorker.ts +1 -1
- package/src/jwt.ts +111 -93
- package/src/keepSession.ts +78 -0
- package/src/login.ts +4 -3
- package/src/logout.ts +1 -0
- package/src/oidc.ts +30 -136
- package/src/parseTokens.ts +1 -1
- package/src/renewTokens.ts +61 -3
- package/src/requests.ts +1 -1
- package/src/types.ts +9 -0
- package/src/user.ts +2 -1
- package/src/version.ts +1 -1
package/src/jwt.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// because... JavaScript, Strings, and Buffers
|
|
6
6
|
// @ts-ignore
|
|
7
|
+
import {DemonstratingProofOfPossessionConfiguration} from "./types";
|
|
8
|
+
|
|
7
9
|
function strToUint8(str) {
|
|
8
10
|
return new TextEncoder().encode(str);
|
|
9
11
|
}
|
|
@@ -56,19 +58,42 @@ function strToUrlBase64(str) {
|
|
|
56
58
|
return binToUrlBase64(utf8ToBinaryString(str));
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
export
|
|
61
|
+
export const defaultDemonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration ={
|
|
62
|
+
importKeyAlgorithm: {
|
|
63
|
+
name: 'ECDSA',
|
|
64
|
+
namedCurve: 'P-256',
|
|
65
|
+
hash: {name: 'ES256'}
|
|
66
|
+
},
|
|
67
|
+
signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
|
|
68
|
+
generateKeyAlgorithm: {
|
|
69
|
+
name: 'ECDSA',
|
|
70
|
+
namedCurve: 'P-256'
|
|
71
|
+
},
|
|
72
|
+
digestAlgorithm: { name: 'SHA-256' },
|
|
73
|
+
jwtHeaderAlgorithm : 'ES256'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
60
77
|
// @ts-ignore
|
|
61
|
-
|
|
78
|
+
const sign = async (jwk, headers, claims, demonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration, jwtHeaderType= 'dpop+jwt') => {
|
|
62
79
|
// Make a shallow copy of the key
|
|
63
80
|
// (to set ext if it wasn't already set)
|
|
64
81
|
jwk = Object.assign({}, jwk);
|
|
65
82
|
|
|
66
83
|
// The headers should probably be empty
|
|
67
84
|
headers.typ = jwtHeaderType;
|
|
68
|
-
headers.alg =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
headers.alg = demonstratingProofOfPossessionConfiguration.jwtHeaderAlgorithm;
|
|
86
|
+
switch (headers.alg) {
|
|
87
|
+
case 'ES256': //if (!headers.kid) {
|
|
88
|
+
// alternate: see thumbprint function below
|
|
89
|
+
headers.jwk = {kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y};
|
|
90
|
+
//}
|
|
91
|
+
break;
|
|
92
|
+
case 'RS256':
|
|
93
|
+
headers.jwk = {kty: jwk.kty, n: jwk.n, e: jwk.e, kid: headers.kid};
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
throw new Error('Unknown or not implemented JWS algorithm');
|
|
72
97
|
}
|
|
73
98
|
|
|
74
99
|
const jws = {
|
|
@@ -81,11 +106,7 @@ JWT.sign = (jwk, headers, claims, jwtHeaderType= 'dpop+jwt') => {
|
|
|
81
106
|
};
|
|
82
107
|
|
|
83
108
|
// To import as EC (ECDSA, P-256, SHA-256, ES256)
|
|
84
|
-
const keyType =
|
|
85
|
-
name: 'ECDSA',
|
|
86
|
-
namedCurve: 'P-256',
|
|
87
|
-
hash: {name: 'ES256'}
|
|
88
|
-
};
|
|
109
|
+
const keyType = demonstratingProofOfPossessionConfiguration.importKeyAlgorithm;
|
|
89
110
|
|
|
90
111
|
// To make re-exportable as JSON (or DER/PEM)
|
|
91
112
|
const exportable = true;
|
|
@@ -95,80 +116,110 @@ JWT.sign = (jwk, headers, claims, jwtHeaderType= 'dpop+jwt') => {
|
|
|
95
116
|
|
|
96
117
|
// Actually do the import, which comes out as an abstract key type
|
|
97
118
|
// @ts-ignore
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// JWT is just a "compressed", "protected" JWS
|
|
117
|
-
// @ts-ignore
|
|
118
|
-
return jws.protected + '.' + jws.payload + '.' + jws.signature;
|
|
119
|
-
});
|
|
120
|
-
});
|
|
119
|
+
const privateKey = await window.crypto.subtle.importKey('jwk', jwk, keyType, exportable, privileges);
|
|
120
|
+
// Convert UTF-8 to Uint8Array ArrayBuffer
|
|
121
|
+
// @ts-ignore
|
|
122
|
+
const data = strToUint8(`${jws.protected}.${jws.payload}`);
|
|
123
|
+
|
|
124
|
+
// The signature and hash should match the bit-entropy of the key
|
|
125
|
+
// https://tools.ietf.org/html/rfc7518#section-3
|
|
126
|
+
const signatureType = demonstratingProofOfPossessionConfiguration.signAlgorithm;
|
|
127
|
+
|
|
128
|
+
const signature = await window.crypto.subtle.sign(signatureType, privateKey, data);
|
|
129
|
+
// returns an ArrayBuffer containing a JOSE (not X509) signature,
|
|
130
|
+
// which must be converted to Uint8 to be useful
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
jws.signature = uint8ToUrlBase64(new Uint8Array(signature));
|
|
133
|
+
// JWT is just a "compressed", "protected" JWS
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
return `${jws.protected}.${jws.payload}.${jws.signature}`;
|
|
121
136
|
};
|
|
122
137
|
|
|
138
|
+
export var JWT = {sign};
|
|
139
|
+
|
|
123
140
|
|
|
124
|
-
const EC = {};
|
|
125
141
|
// @ts-ignore
|
|
126
|
-
|
|
127
|
-
const keyType =
|
|
128
|
-
name: 'ECDSA',
|
|
129
|
-
namedCurve: 'P-256'
|
|
130
|
-
};
|
|
142
|
+
const generate = async (generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams) => {
|
|
143
|
+
const keyType = generateKeyAlgorithm;
|
|
131
144
|
const exportable = true;
|
|
132
145
|
const privileges = ['sign', 'verify'];
|
|
133
146
|
// @ts-ignore
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
147
|
+
const key = await window.crypto.subtle.generateKey(keyType, exportable, privileges);
|
|
148
|
+
// returns an abstract and opaque WebCrypto object,
|
|
149
|
+
// which in most cases you'll want to export as JSON to be able to save
|
|
150
|
+
return await window.crypto.subtle.exportKey('jwk', key.privateKey);
|
|
139
151
|
};
|
|
140
152
|
|
|
141
153
|
// Create a Public Key from a Private Key
|
|
142
154
|
//
|
|
143
155
|
// chops off the private parts
|
|
144
156
|
// @ts-ignore
|
|
145
|
-
|
|
157
|
+
const neuter = jwk => {
|
|
146
158
|
const copy = Object.assign({}, jwk);
|
|
147
159
|
delete copy.d;
|
|
148
160
|
copy.key_ops = ['verify'];
|
|
149
161
|
return copy;
|
|
150
162
|
};
|
|
151
163
|
|
|
152
|
-
|
|
164
|
+
const EC = {
|
|
165
|
+
generate,
|
|
166
|
+
neuter
|
|
167
|
+
};
|
|
153
168
|
// @ts-ignore
|
|
154
|
-
|
|
169
|
+
const thumbprint = async (jwk, digestAlgorithm: AlgorithmIdentifier) => {
|
|
170
|
+
let sortedPub;
|
|
155
171
|
// lexigraphically sorted, no spaces
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
switch (jwk.kty) {
|
|
173
|
+
case 'EC':
|
|
174
|
+
sortedPub = '{"crv":"CRV","kty":"EC","x":"X","y":"Y"}'
|
|
175
|
+
.replace('CRV', jwk.crv)
|
|
176
|
+
.replace('X', jwk.x)
|
|
177
|
+
.replace('Y', jwk.y);
|
|
178
|
+
break;
|
|
179
|
+
case 'RSA':
|
|
180
|
+
sortedPub = '{"e":"E","kty":"RSA","n":"N"}'
|
|
181
|
+
.replace('E', jwk.e)
|
|
182
|
+
.replace('N', jwk.n);
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
throw new Error('Unknown or not implemented JWK type');
|
|
186
|
+
}
|
|
161
187
|
// The hash should match the size of the key,
|
|
162
188
|
// but we're only dealing with P-256
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
189
|
+
const hash = await window.crypto.subtle.digest(digestAlgorithm, strToUint8(sortedPub));
|
|
190
|
+
return uint8ToUrlBase64(new Uint8Array(hash));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export var JWK = {thumbprint};
|
|
194
|
+
|
|
195
|
+
export const generateJwkAsync = async (generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams) => {
|
|
196
|
+
// @ts-ignore
|
|
197
|
+
const jwk = await EC.generate(generateKeyAlgorithm);
|
|
198
|
+
// console.info('Private Key:', JSON.stringify(jwk));
|
|
199
|
+
// @ts-ignore
|
|
200
|
+
// console.info('Public Key:', JSON.stringify(EC.neuter(jwk)));
|
|
201
|
+
return jwk;
|
|
202
|
+
}
|
|
169
203
|
|
|
204
|
+
export const generateJwtDemonstratingProofOfPossessionAsync = (demonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration) => async (jwk, method = 'POST', url: string, extrasClaims={}) => {
|
|
170
205
|
|
|
171
|
-
const
|
|
206
|
+
const claims = {
|
|
207
|
+
// https://www.rfc-editor.org/rfc/rfc9449.html#name-concept
|
|
208
|
+
jti: btoa(guid()),
|
|
209
|
+
htm: method,
|
|
210
|
+
htu: url,
|
|
211
|
+
iat: Math.round(Date.now() / 1000),
|
|
212
|
+
...extrasClaims,
|
|
213
|
+
};
|
|
214
|
+
// @ts-ignore
|
|
215
|
+
const kid = await JWK.thumbprint(jwk, demonstratingProofOfPossessionConfiguration.digestAlgorithm);
|
|
216
|
+
// @ts-ignore
|
|
217
|
+
const jwt = await JWT.sign(jwk, { kid: kid }, claims, demonstratingProofOfPossessionConfiguration)
|
|
218
|
+
// console.info('JWT:', jwt);
|
|
219
|
+
return jwt;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const guid = () => {
|
|
172
223
|
// RFC4122: The version 4 UUID is meant for generating UUIDs from truly-random or
|
|
173
224
|
// pseudo-random numbers.
|
|
174
225
|
// The algorithm is as follows:
|
|
@@ -213,36 +264,3 @@ const guid = function () {
|
|
|
213
264
|
|
|
214
265
|
return guidResponse;
|
|
215
266
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
export const generateJwkAsync = () => {
|
|
219
|
-
// @ts-ignore
|
|
220
|
-
return EC.generate().then(function(jwk) {
|
|
221
|
-
// console.info('Private Key:', JSON.stringify(jwk));
|
|
222
|
-
// @ts-ignore
|
|
223
|
-
// console.info('Public Key:', JSON.stringify(EC.neuter(jwk)));
|
|
224
|
-
return jwk;
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export const generateJwtDemonstratingProofOfPossessionAsync = (jwk, method = 'POST', url: string, extrasClaims={}) => {
|
|
229
|
-
|
|
230
|
-
const claims = {
|
|
231
|
-
// https://www.rfc-editor.org/rfc/rfc9449.html#name-concept
|
|
232
|
-
jti: btoa(guid()),
|
|
233
|
-
htm: method,
|
|
234
|
-
htu: url,
|
|
235
|
-
iat: Math.round(Date.now() / 1000),
|
|
236
|
-
...extrasClaims,
|
|
237
|
-
};
|
|
238
|
-
// @ts-ignore
|
|
239
|
-
return JWK.thumbprint(jwk).then(function(kid) {
|
|
240
|
-
// @ts-ignore
|
|
241
|
-
return JWT.sign(jwk, { /*kid: kid*/ }, claims).then(function(jwt) {
|
|
242
|
-
// console.info('JWT:', jwt);
|
|
243
|
-
return jwt;
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export default EC;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {initWorkerAsync} from "./initWorker";
|
|
2
|
+
import {autoRenewTokens} from "./renewTokens";
|
|
3
|
+
import {initSession} from "./initSession";
|
|
4
|
+
import {setTokens} from "./parseTokens";
|
|
5
|
+
import {eventNames} from "./events";
|
|
6
|
+
import Oidc from "./oidc";
|
|
7
|
+
|
|
8
|
+
export const tryKeepSessionAsync = async (oidc: Oidc) =>{
|
|
9
|
+
|
|
10
|
+
let serviceWorker;
|
|
11
|
+
if (oidc.tokens != null) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
|
|
15
|
+
try {
|
|
16
|
+
const configuration = oidc.configuration;
|
|
17
|
+
const oidcServerConfiguration = await oidc.initAsync(configuration.authority, configuration.authority_configuration);
|
|
18
|
+
serviceWorker = await initWorkerAsync(configuration, oidc.configurationName);
|
|
19
|
+
if (serviceWorker) {
|
|
20
|
+
const { tokens } = await serviceWorker.initAsync(oidcServerConfiguration, 'tryKeepExistingSessionAsync', configuration);
|
|
21
|
+
if (tokens) {
|
|
22
|
+
serviceWorker.startKeepAliveServiceWorker();
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
oidc.tokens = tokens;
|
|
25
|
+
const getLoginParams = serviceWorker.getLoginParams(oidc.configurationName);
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
oidc.timeoutId = autoRenewTokens(oidc, oidc.tokens.refreshToken, oidc.tokens.expiresAt, getLoginParams.extras);
|
|
28
|
+
const sessionState = await serviceWorker.getSessionStateAsync();
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
await oidc.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
|
|
31
|
+
oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
32
|
+
success: true,
|
|
33
|
+
message: 'tokens inside ServiceWorker are valid',
|
|
34
|
+
});
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
38
|
+
success: false,
|
|
39
|
+
message: 'no exiting session found',
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
if (configuration.service_worker_relative_url) {
|
|
43
|
+
oidc.publishEvent(eventNames.service_worker_not_supported_by_browser, {
|
|
44
|
+
message: 'service worker is not supported by this browser',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const session = initSession(oidc.configurationName, configuration.storage ?? sessionStorage);
|
|
48
|
+
const { tokens } = await session.initAsync();
|
|
49
|
+
if (tokens) {
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
oidc.tokens = setTokens(tokens, null, configuration.token_renew_mode);
|
|
52
|
+
const getLoginParams = session.getLoginParams();
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt, getLoginParams.extras);
|
|
55
|
+
const sessionState = await session.getSessionStateAsync();
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
await oidc.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
|
|
58
|
+
oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
59
|
+
success: true,
|
|
60
|
+
message: 'tokens inside storage are valid',
|
|
61
|
+
});
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
66
|
+
success: false,
|
|
67
|
+
message: serviceWorker ? 'service worker sessions not retrieved' : 'session storage sessions not retrieved',
|
|
68
|
+
});
|
|
69
|
+
return false;
|
|
70
|
+
} catch (exception) {
|
|
71
|
+
console.error(exception);
|
|
72
|
+
if (serviceWorker) {
|
|
73
|
+
await serviceWorker.clearAsync();
|
|
74
|
+
}
|
|
75
|
+
oidc.publishEvent(eventNames.tryKeepExistingSessionAsync_error, 'tokens inside ServiceWorker are invalid');
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/login.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {getParseQueryStringFromLocation} from './route-utils.js';
|
|
|
11
11
|
import {OidcConfiguration, StringMap} from './types.js';
|
|
12
12
|
import {generateJwkAsync, generateJwtDemonstratingProofOfPossessionAsync} from "./jwt";
|
|
13
13
|
import {ILOidcLocation} from "./location";
|
|
14
|
+
import Oidc from "./oidc";
|
|
14
15
|
|
|
15
16
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
16
17
|
export const defaultLoginAsync = (configurationName:string, configuration:OidcConfiguration, publishEvent :(string, any)=>void, initAsync:Function, oidcLocation: ILOidcLocation) => (callbackPath:string = undefined, extras:StringMap = null, isSilentSignin = false, scope:string = undefined) => {
|
|
@@ -75,7 +76,7 @@ export const defaultLoginAsync = (configurationName:string, configuration:OidcCo
|
|
|
75
76
|
return loginLocalAsync();
|
|
76
77
|
};
|
|
77
78
|
|
|
78
|
-
export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
|
|
79
|
+
export const loginCallbackAsync = (oidc:Oidc) => async (isSilentSignin = false) => {
|
|
79
80
|
try {
|
|
80
81
|
oidc.publishEvent(eventNames.loginCallbackAsync_begin, {});
|
|
81
82
|
const configuration = oidc.configuration;
|
|
@@ -148,14 +149,14 @@ export const loginCallbackAsync = (oidc) => async (isSilentSignin = false) => {
|
|
|
148
149
|
const url = oidcServerConfiguration.tokenEndpoint;
|
|
149
150
|
const headersExtras = {};
|
|
150
151
|
if(configuration.demonstrating_proof_of_possession) {
|
|
151
|
-
const jwk = await generateJwkAsync();
|
|
152
|
+
const jwk = await generateJwkAsync(configuration.demonstrating_proof_of_possession_configuration.generateKeyAlgorithm);
|
|
152
153
|
if (serviceWorker) {
|
|
153
154
|
await serviceWorker.setDemonstratingProofOfPossessionJwkAsync(jwk);
|
|
154
155
|
} else {
|
|
155
156
|
const session = initSession(oidc.configurationName, configuration.storage);
|
|
156
157
|
await session.setDemonstratingProofOfPossessionJwkAsync(jwk);
|
|
157
158
|
}
|
|
158
|
-
headersExtras['DPoP'] = await generateJwtDemonstratingProofOfPossessionAsync(jwk, 'POST', url);
|
|
159
|
+
headersExtras['DPoP'] = await generateJwtDemonstratingProofOfPossessionAsync(configuration.demonstrating_proof_of_possession_configuration)(jwk, 'POST', url);
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
const tokenResponse = await performFirstTokenRequestAsync(storage)(url,
|
package/src/logout.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { performRevocationRequestAsync, TOKEN_TYPE } from './requests.js';
|
|
|
4
4
|
import timer from './timer.js';
|
|
5
5
|
import { StringMap } from './types.js';
|
|
6
6
|
import {ILOidcLocation} from "./location";
|
|
7
|
+
import Oidc from "./oidc";
|
|
7
8
|
|
|
8
9
|
export const oidcLogoutTokens = {
|
|
9
10
|
access_token: 'access_token',
|
package/src/oidc.ts
CHANGED
|
@@ -5,8 +5,13 @@ import {initSession} from './initSession.js';
|
|
|
5
5
|
import {defaultServiceWorkerUpdateRequireCallback, initWorkerAsync, sleepAsync} from './initWorker.js';
|
|
6
6
|
import {defaultLoginAsync, loginCallbackAsync} from './login.js';
|
|
7
7
|
import {destroyAsync, logoutAsync} from './logout.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import {isTokensOidcValid, TokenRenewMode, Tokens,} from './parseTokens.js';
|
|
9
|
+
import {
|
|
10
|
+
autoRenewTokens,
|
|
11
|
+
renewTokensAndStartTimerAsync,
|
|
12
|
+
synchroniseTokensStatus,
|
|
13
|
+
syncTokensInfoAsync
|
|
14
|
+
} from './renewTokens.js';
|
|
10
15
|
import {fetchFromIssuer, performTokenRequestAsync} from './requests.js';
|
|
11
16
|
import {getParseQueryStringFromLocation} from './route-utils.js';
|
|
12
17
|
import defaultSilentLoginAsync, {_silentLoginAsync} from './silentLogin.js';
|
|
@@ -14,9 +19,13 @@ import timer from './timer.js';
|
|
|
14
19
|
import {AuthorityConfiguration, Fetch, OidcConfiguration, StringMap} from './types.js';
|
|
15
20
|
import {userInfoAsync} from './user.js';
|
|
16
21
|
import {base64urlOfHashOfASCIIEncodingAsync} from "./crypto";
|
|
17
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
defaultDemonstratingProofOfPossessionConfiguration,
|
|
24
|
+
generateJwtDemonstratingProofOfPossessionAsync
|
|
25
|
+
} from "./jwt";
|
|
18
26
|
import {ILOidcLocation, OidcLocation} from "./location";
|
|
19
27
|
import {activateServiceWorker} from "./initWorkerOption";
|
|
28
|
+
import {tryKeepSessionAsync} from "./keepSession";
|
|
20
29
|
|
|
21
30
|
|
|
22
31
|
|
|
@@ -83,11 +92,11 @@ export class Oidc {
|
|
|
83
92
|
public userInfo: null;
|
|
84
93
|
public tokens?: Tokens;
|
|
85
94
|
public events: Array<any>;
|
|
86
|
-
|
|
95
|
+
public timeoutId: NodeJS.Timeout | number;
|
|
87
96
|
public configurationName: string;
|
|
88
|
-
|
|
97
|
+
public checkSessionIFrame: CheckSessionIFrame;
|
|
89
98
|
private getFetch: () => Fetch;
|
|
90
|
-
|
|
99
|
+
public location: ILOidcLocation;
|
|
91
100
|
constructor(configuration:OidcConfiguration, configurationName = 'default', getFetch : () => Fetch, location: ILOidcLocation = new OidcLocation()) {
|
|
92
101
|
let silent_login_uri = configuration.silent_login_uri;
|
|
93
102
|
if (configuration.silent_redirect_uri && !configuration.silent_login_uri) {
|
|
@@ -112,6 +121,7 @@ export class Oidc {
|
|
|
112
121
|
logout_tokens_to_invalidate: configuration.logout_tokens_to_invalidate ?? ['access_token', 'refresh_token'],
|
|
113
122
|
service_worker_update_require_callback,
|
|
114
123
|
service_worker_activate: configuration.service_worker_activate ?? activateServiceWorker,
|
|
124
|
+
demonstrating_proof_of_possession_configuration: configuration.demonstrating_proof_of_possession_configuration ?? defaultDemonstratingProofOfPossessionConfiguration,
|
|
115
125
|
};
|
|
116
126
|
|
|
117
127
|
this.getFetch = getFetch ?? getFetchDefault;
|
|
@@ -225,78 +235,8 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
|
|
|
225
235
|
if (this.tryKeepExistingSessionPromise !== null) {
|
|
226
236
|
return this.tryKeepExistingSessionPromise;
|
|
227
237
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (this.tokens != null) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
this.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
|
|
234
|
-
try {
|
|
235
|
-
const configuration = this.configuration;
|
|
236
|
-
const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
|
|
237
|
-
serviceWorker = await initWorkerAsync(configuration, this.configurationName);
|
|
238
|
-
if (serviceWorker) {
|
|
239
|
-
const { tokens } = await serviceWorker.initAsync(oidcServerConfiguration, 'tryKeepExistingSessionAsync', configuration);
|
|
240
|
-
if (tokens) {
|
|
241
|
-
serviceWorker.startKeepAliveServiceWorker();
|
|
242
|
-
// @ts-ignore
|
|
243
|
-
this.tokens = tokens;
|
|
244
|
-
const getLoginParams = serviceWorker.getLoginParams(this.configurationName);
|
|
245
|
-
// @ts-ignore
|
|
246
|
-
this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt, getLoginParams.extras);
|
|
247
|
-
const sessionState = await serviceWorker.getSessionStateAsync();
|
|
248
|
-
// @ts-ignore
|
|
249
|
-
await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
|
|
250
|
-
this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
251
|
-
success: true,
|
|
252
|
-
message: 'tokens inside ServiceWorker are valid',
|
|
253
|
-
});
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
257
|
-
success: false,
|
|
258
|
-
message: 'no exiting session found',
|
|
259
|
-
});
|
|
260
|
-
} else {
|
|
261
|
-
if (configuration.service_worker_relative_url) {
|
|
262
|
-
this.publishEvent(eventNames.service_worker_not_supported_by_browser, {
|
|
263
|
-
message: 'service worker is not supported by this browser',
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
const session = initSession(this.configurationName, configuration.storage ?? sessionStorage);
|
|
267
|
-
const { tokens } = await session.initAsync();
|
|
268
|
-
if (tokens) {
|
|
269
|
-
// @ts-ignore
|
|
270
|
-
this.tokens = setTokens(tokens, null, configuration.token_renew_mode);
|
|
271
|
-
const getLoginParams = session.getLoginParams();
|
|
272
|
-
// @ts-ignore
|
|
273
|
-
this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt, getLoginParams.extras);
|
|
274
|
-
const sessionState = await session.getSessionStateAsync();
|
|
275
|
-
// @ts-ignore
|
|
276
|
-
await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
|
|
277
|
-
this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
278
|
-
success: true,
|
|
279
|
-
message: 'tokens inside storage are valid',
|
|
280
|
-
});
|
|
281
|
-
return true;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
|
|
285
|
-
success: false,
|
|
286
|
-
message: serviceWorker ? 'service worker sessions not retrieved' : 'session storage sessions not retrieved',
|
|
287
|
-
});
|
|
288
|
-
return false;
|
|
289
|
-
} catch (exception) {
|
|
290
|
-
console.error(exception);
|
|
291
|
-
if (serviceWorker) {
|
|
292
|
-
await serviceWorker.clearAsync();
|
|
293
|
-
}
|
|
294
|
-
this.publishEvent(eventNames.tryKeepExistingSessionAsync_error, 'tokens inside ServiceWorker are invalid');
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
this.tryKeepExistingSessionPromise = funcAsync();
|
|
238
|
+
|
|
239
|
+
this.tryKeepExistingSessionPromise = tryKeepSessionAsync(this);
|
|
300
240
|
return this.tryKeepExistingSessionPromise.then((result) => {
|
|
301
241
|
this.tryKeepExistingSessionPromise = null;
|
|
302
242
|
return result;
|
|
@@ -349,8 +289,11 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
|
|
|
349
289
|
return result;
|
|
350
290
|
});
|
|
351
291
|
}
|
|
292
|
+
|
|
293
|
+
|
|
352
294
|
|
|
353
295
|
async synchroniseTokensAsync(refreshToken, index = 0, forceRefresh = false, extras:StringMap = null, updateTokens) {
|
|
296
|
+
|
|
354
297
|
while (!navigator.onLine && document.hidden) {
|
|
355
298
|
await sleepAsync({milliseconds: 1000});
|
|
356
299
|
this.publishEvent(eventNames.refreshTokensAsync, { message: 'wait because navigator is offline and hidden' });
|
|
@@ -410,27 +353,27 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
|
|
|
410
353
|
return { tokens: null, status: 'SESSION_LOST' };
|
|
411
354
|
}
|
|
412
355
|
try {
|
|
413
|
-
const { status, tokens, nonce } = await
|
|
356
|
+
const { status, tokens, nonce } = await syncTokensInfoAsync(this)(configuration, this.configurationName, this.tokens, forceRefresh);
|
|
414
357
|
switch (status) {
|
|
415
|
-
case
|
|
358
|
+
case synchroniseTokensStatus.SESSION_LOST:
|
|
416
359
|
updateTokens(null);
|
|
417
360
|
this.publishEvent(eventNames.refreshTokensAsync_error, { message: 'refresh token session lost' });
|
|
418
361
|
return { tokens: null, status: 'SESSION_LOST' };
|
|
419
|
-
case
|
|
362
|
+
case synchroniseTokensStatus.NOT_CONNECTED:
|
|
420
363
|
updateTokens(null);
|
|
421
364
|
return { tokens: null, status: null };
|
|
422
|
-
case
|
|
365
|
+
case synchroniseTokensStatus.TOKENS_VALID:
|
|
423
366
|
updateTokens(tokens);
|
|
424
367
|
return { tokens, status: 'LOGGED_IN' };
|
|
425
|
-
case
|
|
368
|
+
case synchroniseTokensStatus.TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID:
|
|
426
369
|
updateTokens(tokens);
|
|
427
370
|
this.publishEvent(Oidc.eventNames.token_renewed, { reason: 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' });
|
|
428
371
|
return { tokens, status: 'LOGGED_IN' };
|
|
429
|
-
case
|
|
372
|
+
case synchroniseTokensStatus.LOGOUT_FROM_ANOTHER_TAB:
|
|
430
373
|
updateTokens(null);
|
|
431
374
|
this.publishEvent(eventNames.logout_from_another_tab, { status: 'session syncTokensAsync' });
|
|
432
375
|
return { tokens: null, status: 'LOGGED_OUT' };
|
|
433
|
-
case
|
|
376
|
+
case synchroniseTokensStatus.REQUIRE_SYNC_TOKENS:
|
|
434
377
|
this.publishEvent(eventNames.refreshTokensAsync_begin, { refreshToken, status, tryNumber: index });
|
|
435
378
|
return await localsilentLoginAsync();
|
|
436
379
|
default: {
|
|
@@ -516,7 +459,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
|
|
|
516
459
|
const claimsExtras = {ath: await base64urlOfHashOfASCIIEncodingAsync(accessToken),};
|
|
517
460
|
|
|
518
461
|
const serviceWorker = await initWorkerAsync(configuration, this.configurationName);
|
|
519
|
-
let demonstratingProofOfPossessionNonce:string
|
|
462
|
+
let demonstratingProofOfPossessionNonce:string;
|
|
520
463
|
let jwk;
|
|
521
464
|
if (serviceWorker) {
|
|
522
465
|
demonstratingProofOfPossessionNonce = await serviceWorker.getDemonstratingProofOfPossessionNonce();
|
|
@@ -531,56 +474,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
|
|
|
531
474
|
claimsExtras['nonce'] = demonstratingProofOfPossessionNonce;
|
|
532
475
|
}
|
|
533
476
|
|
|
534
|
-
return await generateJwtDemonstratingProofOfPossessionAsync(jwk, method, url, claimsExtras);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
async syncTokensInfoAsync(configuration, configurationName, currentTokens, forceRefresh = false) {
|
|
538
|
-
// Service Worker can be killed by the browser (when it wants,for example after 10 seconds of inactivity, so we retreieve the session if it happen)
|
|
539
|
-
// const configuration = this.configuration;
|
|
540
|
-
const nullNonce = { nonce: null };
|
|
541
|
-
if (!currentTokens) {
|
|
542
|
-
return { tokens: null, status: 'NOT_CONNECTED', nonce: nullNonce };
|
|
543
|
-
}
|
|
544
|
-
let nonce = nullNonce;
|
|
545
|
-
const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
|
|
546
|
-
const serviceWorker = await initWorkerAsync(configuration, configurationName);
|
|
547
|
-
if (serviceWorker) {
|
|
548
|
-
const { status, tokens } = await serviceWorker.initAsync(oidcServerConfiguration, 'syncTokensAsync', configuration);
|
|
549
|
-
if (status === 'LOGGED_OUT') {
|
|
550
|
-
return { tokens: null, status: 'LOGOUT_FROM_ANOTHER_TAB', nonce: nullNonce };
|
|
551
|
-
} else if (status === 'SESSIONS_LOST') {
|
|
552
|
-
return { tokens: null, status: 'SESSIONS_LOST', nonce: nullNonce };
|
|
553
|
-
} else if (!status || !tokens) {
|
|
554
|
-
return { tokens: null, status: 'REQUIRE_SYNC_TOKENS', nonce: nullNonce };
|
|
555
|
-
} else if (tokens.issuedAt !== currentTokens.issuedAt) {
|
|
556
|
-
const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
|
|
557
|
-
const status = (timeLeft > 0) ? 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' : 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID';
|
|
558
|
-
const nonce = await serviceWorker.getNonceAsync();
|
|
559
|
-
return { tokens, status, nonce };
|
|
560
|
-
}
|
|
561
|
-
nonce = await serviceWorker.getNonceAsync();
|
|
562
|
-
} else {
|
|
563
|
-
const session = initSession(configurationName, configuration.storage ?? sessionStorage);
|
|
564
|
-
const { tokens, status } = await session.initAsync();
|
|
565
|
-
if (!tokens) {
|
|
566
|
-
return { tokens: null, status: 'LOGOUT_FROM_ANOTHER_TAB', nonce: nullNonce };
|
|
567
|
-
} else if (status === 'SESSIONS_LOST') {
|
|
568
|
-
return { tokens: null, status: 'SESSIONS_LOST', nonce: nullNonce };
|
|
569
|
-
} else if (tokens.issuedAt !== currentTokens.issuedAt) {
|
|
570
|
-
const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
|
|
571
|
-
const status = (timeLeft > 0) ? 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID' : 'TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID';
|
|
572
|
-
const nonce = await session.getNonceAsync();
|
|
573
|
-
return { tokens, status, nonce };
|
|
574
|
-
}
|
|
575
|
-
nonce = await session.getNonceAsync();
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, currentTokens.expiresAt);
|
|
579
|
-
const status = (timeLeft > 0) ? 'TOKENS_VALID' : 'TOKENS_INVALID';
|
|
580
|
-
if (forceRefresh) {
|
|
581
|
-
return { tokens: currentTokens, status: 'FORCE_REFRESH', nonce };
|
|
582
|
-
}
|
|
583
|
-
return { tokens: currentTokens, status, nonce };
|
|
477
|
+
return await generateJwtDemonstratingProofOfPossessionAsync(configuration.demonstrating_proof_of_possession_configuration)(jwk, method, url, claimsExtras);
|
|
584
478
|
}
|
|
585
479
|
|
|
586
480
|
loginCallbackWithAutoTokensRenewPromise:Promise<LoginCallback> = null;
|