@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,1230 @@
1
+ import {
2
+ AuthorizationNotifier,
3
+ AuthorizationRequest,
4
+ AuthorizationServiceConfiguration,
5
+ BaseTokenRequestHandler,
6
+ DefaultCrypto,
7
+ FetchRequestor,
8
+ GRANT_TYPE_AUTHORIZATION_CODE,
9
+ GRANT_TYPE_REFRESH_TOKEN,
10
+ RedirectRequestHandler,
11
+ TokenRequest
12
+ } from '@openid/appauth';
13
+ import {HashQueryStringUtils, NoHashQueryStringUtils} from './noHashQueryStringUtils';
14
+ import {initWorkerAsync, sleepAsync} from './initWorker'
15
+ import {MemoryStorageBackend} from "./memoryStorageBackend";
16
+ import {initSession} from "./initSession";
17
+ import timer from './timer';
18
+
19
+ import {CheckSessionIFrame} from "./checkSessionIFrame"
20
+ import {getParseQueryStringFromLocation} from "./route-utils";
21
+ import {AuthorizationServiceConfigurationJson} from "@openid/appauth/src/authorization_service_configuration";
22
+ import {computeTimeLeft, isTokensOidcValid, isTokensValid, parseOriginalTokens, setTokens} from "./parseTokens";
23
+
24
+ const performTokenRequestAsync= async (url, details, extras, oldTokens) => {
25
+
26
+ for (let [key, value] of Object.entries(extras)) {
27
+ if (details[key] === undefined) {
28
+ details[key] = value;
29
+ }
30
+ }
31
+
32
+ let formBody = [];
33
+ for (const property in details) {
34
+ const encodedKey = encodeURIComponent(property);
35
+ const encodedValue = encodeURIComponent(details[property]);
36
+ formBody.push(`${encodedKey}=${encodedValue}`);
37
+ }
38
+ const formBodyString = formBody.join("&");
39
+
40
+ const response = await internalFetch(url, {
41
+ method: 'POST',
42
+ headers: {
43
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
44
+ },
45
+ body: formBodyString,
46
+ });
47
+ if(response.status !== 200){
48
+ return {success:false, status: response.status}
49
+ }
50
+ const tokens = await response.json();
51
+ return {
52
+ success : true,
53
+ data: parseOriginalTokens(tokens, oldTokens)
54
+ };
55
+ }
56
+
57
+ const internalFetch = async (url, headers, numberRetry=0) => {
58
+ let response;
59
+ try {
60
+ let controller = new AbortController();
61
+ setTimeout(() => controller.abort(), 10000);
62
+ response = await fetch(url, {...headers, signal: controller.signal});
63
+ } catch (e) {
64
+ if (e.message === 'AbortError'
65
+ || e.message === 'Network request failed') {
66
+ if(numberRetry <=1) {
67
+ return await internalFetch(url, headers, numberRetry + 1);
68
+ }
69
+ else {
70
+ throw e;
71
+ }
72
+ } else {
73
+ console.error(e.message);
74
+ throw e; // rethrow other unexpected errors
75
+ }
76
+ }
77
+ return response;
78
+ }
79
+
80
+ export interface OidcAuthorizationServiceConfigurationJson extends AuthorizationServiceConfigurationJson{
81
+ check_session_iframe?: string;
82
+ issuer:string;
83
+ }
84
+
85
+ export class OidcAuthorizationServiceConfiguration extends AuthorizationServiceConfiguration{
86
+ private check_session_iframe: string;
87
+ private issuer: string;
88
+
89
+ constructor(request: any) {
90
+ super(request);
91
+ this.authorizationEndpoint = request.authorization_endpoint;
92
+ this.tokenEndpoint = request.token_endpoint;
93
+ this.revocationEndpoint = request.revocation_endpoint;
94
+ this.userInfoEndpoint = request.userinfo_endpoint;
95
+ this.check_session_iframe = request.check_session_iframe;
96
+ this.issuer = request.issuer;
97
+ }
98
+
99
+ }
100
+
101
+
102
+ export interface StringMap {
103
+ [key: string]: string;
104
+ }
105
+
106
+ export interface loginCallbackResult {
107
+ state: string,
108
+ callbackPath: string,
109
+ }
110
+
111
+ export interface AuthorityConfiguration {
112
+ authorization_endpoint: string;
113
+ token_endpoint: string;
114
+ revocation_endpoint: string;
115
+ end_session_endpoint?: string;
116
+ userinfo_endpoint?: string;
117
+ check_session_iframe?:string;
118
+ issuer:string;
119
+ }
120
+
121
+ export type OidcConfiguration = {
122
+ client_id: string,
123
+ redirect_uri: string,
124
+ silent_redirect_uri?:string,
125
+ silent_login_uri?:string,
126
+ silent_login_timeout?:number,
127
+ scope: string,
128
+ authority: string,
129
+ authority_time_cache_wellknowurl_in_second?: number,
130
+ authority_configuration?: AuthorityConfiguration,
131
+ refresh_time_before_tokens_expiration_in_second?: number,
132
+ token_request_timeout?: number,
133
+ service_worker_relative_url?:string,
134
+ service_worker_only?:boolean,
135
+ extras?:StringMap
136
+ token_request_extras?:StringMap,
137
+ storage?: Storage
138
+ monitor_session?: boolean
139
+ };
140
+
141
+ const oidcDatabase = {};
142
+ const oidcFactory = (configuration: OidcConfiguration, name="default") => {
143
+ if(oidcDatabase[name]){
144
+ return oidcDatabase[name];
145
+ }
146
+ oidcDatabase[name] = new Oidc(configuration, name)
147
+ return oidcDatabase[name];
148
+ }
149
+
150
+ const loginCallbackWithAutoTokensRenewAsync = async (oidc) => {
151
+ const { parsedTokens, state, callbackPath } = await oidc.loginCallbackAsync();
152
+ oidc.timeoutId = autoRenewTokens(oidc, parsedTokens.refreshToken, parsedTokens.expiresAt)
153
+ return { state, callbackPath };
154
+ }
155
+
156
+ async function renewTokensAndStartTimerAsync(oidc, refreshToken, forceRefresh =false, extras:StringMap=null) {
157
+ const {tokens, status} = await oidc.synchroniseTokensAsync(refreshToken, 0, forceRefresh, extras);
158
+ oidc.tokens = tokens;
159
+ const serviceWorker = await initWorkerAsync(oidc.configuration.service_worker_relative_url, oidc.configurationName);
160
+ if (!serviceWorker) {
161
+ const session = initSession(oidc.configurationName, oidc.configuration.redirect_uri, oidc.configuration.storage);
162
+ await session.setTokens(oidc.tokens);
163
+ }
164
+
165
+ if (!oidc.tokens) {
166
+ await oidc.destroyAsync(status);
167
+ return;
168
+ }
169
+
170
+ if (oidc.timeoutId) {
171
+ oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt, extras);
172
+ }
173
+ }
174
+
175
+ const autoRenewTokens = (oidc, refreshToken, expiresAt, extras:StringMap=null) => {
176
+ const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second;
177
+ return timer.setTimeout(async () => {
178
+ const timeLeft = computeTimeLeft(refreshTimeBeforeTokensExpirationInSecond, expiresAt);
179
+ const timeInfo = { timeLeft };
180
+ oidc.publishEvent(Oidc.eventNames.token_timer, timeInfo);
181
+ await renewTokensAndStartTimerAsync(oidc, refreshToken, false, extras);
182
+ }, 1000);
183
+ }
184
+
185
+ const getLoginSessionKey = (configurationName:string, redirectUri:string) => {
186
+ return `oidc_login.${configurationName}:${redirectUri}`;
187
+ }
188
+
189
+ const setLoginParams = (configurationName:string, redirectUri:string, data) =>{
190
+ const sessionKey = getLoginSessionKey(configurationName, redirectUri);
191
+ getLoginParamsCache = data
192
+ sessionStorage[sessionKey] = JSON.stringify(data);
193
+ }
194
+
195
+ let getLoginParamsCache = null;
196
+ const getLoginParams = (configurationName, redirectUri) => {
197
+ const dataString = sessionStorage[getLoginSessionKey(configurationName, redirectUri)];
198
+ if(!getLoginParamsCache){
199
+ getLoginParamsCache = JSON.parse(dataString);
200
+ }
201
+ return getLoginParamsCache;
202
+ }
203
+
204
+ const userInfoAsync = async (oidc) => {
205
+ if(oidc.userInfo != null){
206
+ return oidc.userInfo;
207
+ }
208
+ if(!oidc.tokens){
209
+ return null;
210
+ }
211
+ const accessToken = oidc.tokens.accessToken;
212
+ if(!accessToken){
213
+ return null;
214
+ }
215
+ // We wait the synchronisation before making a request
216
+ while (oidc.tokens && !isTokensValid(oidc.tokens)){
217
+ await sleepAsync(200);
218
+ }
219
+
220
+ const oidcServerConfiguration = await oidc.initAsync(oidc.configuration.authority, oidc.configuration.authority_configuration);
221
+ const url = oidcServerConfiguration.userInfoEndpoint;
222
+ const fetchUserInfo = async (accessToken) => {
223
+ const res = await fetch(url, {
224
+ headers: {
225
+ authorization: `Bearer ${accessToken}`,
226
+ }
227
+ });
228
+
229
+ if(res.status != 200 ){
230
+ return null;
231
+ }
232
+
233
+ return res.json();
234
+ };
235
+ const userInfo = await fetchUserInfo(accessToken);
236
+ oidc.userInfo= userInfo;
237
+ return userInfo;
238
+ }
239
+
240
+ const eventNames = {
241
+ service_worker_not_supported_by_browser: "service_worker_not_supported_by_browser",
242
+ token_aquired: "token_aquired",
243
+ logout_from_another_tab: "logout_from_another_tab",
244
+ logout_from_same_tab: "logout_from_same_tab",
245
+ token_renewed: "token_renewed",
246
+ token_timer: "token_timer",
247
+ loginAsync_begin:"loginAsync_begin",
248
+ loginAsync_error:"loginAsync_error",
249
+ loginCallbackAsync_begin:"loginCallbackAsync_begin",
250
+ loginCallbackAsync_end:"loginCallbackAsync_end",
251
+ loginCallbackAsync_error:"loginCallbackAsync_error",
252
+ refreshTokensAsync_begin: "refreshTokensAsync_begin",
253
+ refreshTokensAsync: "refreshTokensAsync",
254
+ refreshTokensAsync_end: "refreshTokensAsync_end",
255
+ refreshTokensAsync_error: "refreshTokensAsync_error",
256
+ refreshTokensAsync_silent_error: "refreshTokensAsync_silent_error",
257
+ tryKeepExistingSessionAsync_begin: "tryKeepExistingSessionAsync_begin",
258
+ tryKeepExistingSessionAsync_end: "tryKeepExistingSessionAsync_end",
259
+ tryKeepExistingSessionAsync_error: "tryKeepExistingSessionAsync_error",
260
+ silentLoginAsync_begin: "silentLoginAsync_begin",
261
+ silentLoginAsync: "silentLoginAsync",
262
+ silentLoginAsync_end: "silentLoginAsync_end",
263
+ silentLoginAsync_error: "silentLoginAsync_error",
264
+ syncTokensAsync_begin: "syncTokensAsync_begin",
265
+ syncTokensAsync_end: "syncTokensAsync_end",
266
+ syncTokensAsync_error: "syncTokensAsync_error"
267
+ }
268
+
269
+ const getRandomInt = (max) => {
270
+ return Math.floor(Math.random() * max);
271
+ }
272
+
273
+ const oneHourSecond = 60 * 60;
274
+ let fetchFromIssuerCache = {};
275
+ const fetchFromIssuer = async (openIdIssuerUrl: string, timeCacheSecond = oneHourSecond, storage= window.sessionStorage):
276
+ Promise<OidcAuthorizationServiceConfiguration> => {
277
+ const fullUrl = `${openIdIssuerUrl}/.well-known/openid-configuration`;
278
+
279
+ const localStorageKey = `oidc.server:${openIdIssuerUrl}`;
280
+ if(!fetchFromIssuerCache[localStorageKey]) {
281
+ if(storage) {
282
+ const cacheJson = storage.getItem(localStorageKey);
283
+ if (cacheJson) {
284
+ fetchFromIssuerCache[localStorageKey] = JSON.parse(cacheJson);
285
+ }
286
+ }
287
+ }
288
+ const oneHourMinisecond = 1000 * timeCacheSecond;
289
+ // @ts-ignore
290
+ if(fetchFromIssuerCache[localStorageKey] && (fetchFromIssuerCache[localStorageKey].timestamp + oneHourMinisecond) > Date.now()){
291
+ return new OidcAuthorizationServiceConfiguration(fetchFromIssuerCache[localStorageKey].result);
292
+ }
293
+ const response = await fetch(fullUrl);
294
+
295
+ if (response.status != 200) {
296
+ return null;
297
+ }
298
+
299
+ const result = await response.json();
300
+
301
+ const timestamp = Date.now();
302
+ fetchFromIssuerCache[localStorageKey] = {result, timestamp};
303
+ if(storage) {
304
+ storage.setItem(localStorageKey, JSON.stringify({result, timestamp}));
305
+ }
306
+ return new OidcAuthorizationServiceConfiguration(result);
307
+ }
308
+
309
+ const buildQueries = (extras:StringMap) => {
310
+ let queries = '';
311
+ if(extras != null){
312
+ for (let [key, value] of Object.entries(extras)) {
313
+ if (queries === ""){
314
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
315
+ } else {
316
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
317
+ }
318
+ }
319
+ }
320
+ return queries;
321
+ }
322
+
323
+ export class Oidc {
324
+ public configuration: OidcConfiguration;
325
+ public userInfo: null;
326
+ public tokens: null;
327
+ public events: Array<any>;
328
+ private timeoutId: NodeJS.Timeout;
329
+ private configurationName: string;
330
+ private checkSessionIFrame: CheckSessionIFrame;
331
+ constructor(configuration:OidcConfiguration, configurationName="default") {
332
+ let silent_login_uri = configuration.silent_login_uri;
333
+ if(configuration.silent_redirect_uri && !configuration.silent_login_uri){
334
+ silent_login_uri = `${configuration.silent_redirect_uri.replace("-callback", "").replace("callback", "")}-login`;
335
+ }
336
+
337
+ this.configuration = {...configuration,
338
+ silent_login_uri,
339
+ monitor_session: configuration.monitor_session ?? false,
340
+ refresh_time_before_tokens_expiration_in_second : configuration.refresh_time_before_tokens_expiration_in_second ?? 60,
341
+ silent_login_timeout: configuration.silent_login_timeout ?? 12000,
342
+ };
343
+ this.configurationName= configurationName;
344
+ this.tokens = null
345
+ this.userInfo = null;
346
+ this.events = [];
347
+ this.timeoutId = null;
348
+ this.synchroniseTokensAsync.bind(this);
349
+ this.loginCallbackWithAutoTokensRenewAsync.bind(this);
350
+ this.initAsync.bind(this);
351
+ this.loginCallbackAsync.bind(this);
352
+ this._loginCallbackAsync.bind(this);
353
+ this.subscriveEvents.bind(this);
354
+ this.removeEventSubscription.bind(this);
355
+ this.publishEvent.bind(this);
356
+ this.destroyAsync.bind(this);
357
+ this.logoutAsync.bind(this);
358
+ this.renewTokensAsync.bind(this);
359
+
360
+ this.initAsync(this.configuration.authority, this.configuration.authority_configuration);
361
+ }
362
+
363
+ subscriveEvents(func){
364
+ const id = getRandomInt(9999999999999).toString();
365
+ this.events.push({id, func});
366
+ return id;
367
+ }
368
+
369
+ removeEventSubscription(id){
370
+ const newEvents = this.events.filter(e => e.id !== id);
371
+ this.events = newEvents;
372
+ }
373
+
374
+ publishEvent(eventName, data){
375
+ this.events.forEach(event => {
376
+ event.func(eventName, data)
377
+ });
378
+ }
379
+ static getOrCreate(configuration, name="default") {
380
+ return oidcFactory(configuration, name);
381
+ }
382
+ static get(name="default") {
383
+ const insideBrowser = (typeof process === 'undefined');
384
+ if(!oidcDatabase.hasOwnProperty(name) && insideBrowser){
385
+ throw Error(`Oidc library does seem initialized.
386
+ Please checkout that you are using OIDC hook inside a <OidcProvider configurationName="${name}"></OidcProvider> compoment.`)
387
+ }
388
+ return oidcDatabase[name];
389
+ }
390
+ static eventNames = eventNames;
391
+
392
+ _silentLoginCallbackFromIFrame(){
393
+ if (this.configuration.silent_redirect_uri && this.configuration.silent_login_uri) {
394
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
395
+ window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify({tokens:this.tokens, sessionState:queryParams.session_state})}`, window.location.origin);
396
+ }
397
+ }
398
+ _silentLoginErrorCallbackFromIFrame() {
399
+ if (this.configuration.silent_redirect_uri && this.configuration.silent_login_uri) {
400
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
401
+ window.top.postMessage(`${this.configurationName}_oidc_error:${JSON.stringify({error: queryParams.error})}`, window.location.origin);
402
+ }
403
+ }
404
+
405
+ async silentLoginCallBackAsync() {
406
+ try {
407
+ await this.loginCallbackAsync(true);
408
+ this._silentLoginCallbackFromIFrame();
409
+ } catch (error) {
410
+ console.error(error)
411
+ this._silentLoginErrorCallbackFromIFrame();
412
+ }
413
+ }
414
+
415
+ async silentLoginAsync(extras:StringMap=null, state:string=null, scope:string=null) {
416
+ if (!this.configuration.silent_redirect_uri || !this.configuration.silent_login_uri) {
417
+ return Promise.resolve(null);
418
+ }
419
+
420
+ try {
421
+ this.publishEvent(eventNames.silentLoginAsync_begin, {});
422
+ const configuration = this.configuration
423
+ let queries = "";
424
+
425
+ if(state){
426
+ if(extras == null){
427
+ extras = {};
428
+ }
429
+ extras.state = state;
430
+ }
431
+
432
+ if(scope){
433
+ if(extras == null){
434
+ extras = {};
435
+ }
436
+ extras.scope = scope;
437
+ }
438
+
439
+ if(extras != null){
440
+ for (let [key, value] of Object.entries(extras)) {
441
+ if (queries === ""){
442
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
443
+ } else {
444
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
445
+ }
446
+ }
447
+ }
448
+ const link = configuration.silent_login_uri + queries;
449
+ const idx = link.indexOf("/", link.indexOf("//") + 2);
450
+ const iFrameOrigin = link.substr(0, idx);
451
+ const iframe = document.createElement('iframe');
452
+ iframe.width = "0px";
453
+ iframe.height = "0px";
454
+
455
+ iframe.id = `${this.configurationName}_oidc_iframe`;
456
+ iframe.setAttribute("src", link);
457
+ document.body.appendChild(iframe);
458
+ const self = this;
459
+ return new Promise((resolve, reject) => {
460
+ try {
461
+ let isResolved = false;
462
+ window.onmessage = function (e) {
463
+ if (e.origin === iFrameOrigin &&
464
+ e.source === iframe.contentWindow
465
+ ) {
466
+ const key = `${self.configurationName}_oidc_tokens:`;
467
+ const key_error = `${self.configurationName}_oidc_error:`;
468
+ const data = e.data;
469
+ if (data && typeof (data) === "string") {
470
+ if (!isResolved) {
471
+ if(data.startsWith(key)) {
472
+ const result = JSON.parse(e.data.replace(key, ''));
473
+ self.publishEvent(eventNames.silentLoginAsync_end, {});
474
+ iframe.remove();
475
+ isResolved = true;
476
+ resolve(result);
477
+ }
478
+ else if(data.startsWith(key_error)) {
479
+ const result = JSON.parse(e.data.replace(key_error, ''));
480
+ self.publishEvent(eventNames.silentLoginAsync_error, result);
481
+ iframe.remove();
482
+ isResolved = true;
483
+ reject(new Error("oidc_"+result.error));
484
+ }
485
+ }
486
+ }
487
+ }
488
+ };
489
+ const silentSigninTimeout = configuration.silent_login_timeout;
490
+ setTimeout(() => {
491
+ if (!isResolved) {
492
+ self.publishEvent(eventNames.silentLoginAsync_error, {reason: "timeout"});
493
+ iframe.remove();
494
+ isResolved = true;
495
+ reject(new Error("timeout"));
496
+ }
497
+ }, silentSigninTimeout);
498
+ } catch (e) {
499
+ iframe.remove();
500
+ self.publishEvent(eventNames.silentLoginAsync_error, e);
501
+ reject(e);
502
+ }
503
+ });
504
+ } catch (e) {
505
+ this.publishEvent(eventNames.silentLoginAsync_error, e);
506
+ throw e;
507
+ }
508
+ }
509
+ initPromise = null;
510
+ async initAsync(authority:string, authorityConfiguration:AuthorityConfiguration) {
511
+ if(this.initPromise !== null){
512
+ return this.initPromise;
513
+ }
514
+ const localFuncAsync = async () => {
515
+ if (authorityConfiguration != null) {
516
+ return new OidcAuthorizationServiceConfiguration({
517
+ authorization_endpoint: authorityConfiguration.authorization_endpoint,
518
+ end_session_endpoint: authorityConfiguration.end_session_endpoint,
519
+ revocation_endpoint: authorityConfiguration.revocation_endpoint,
520
+ token_endpoint: authorityConfiguration.token_endpoint,
521
+ userinfo_endpoint: authorityConfiguration.userinfo_endpoint,
522
+ check_session_iframe: authorityConfiguration.check_session_iframe,
523
+ issuer: authorityConfiguration.issuer,
524
+ });
525
+ }
526
+
527
+ const serviceWorker = await initWorkerAsync(this.configuration.service_worker_relative_url, this.configurationName);
528
+ const storage = serviceWorker ? window.localStorage : null;
529
+ return await fetchFromIssuer(authority, this.configuration.authority_time_cache_wellknowurl_in_second ?? 60 * 60, storage);
530
+ }
531
+ this.initPromise = localFuncAsync();
532
+ return this.initPromise.then((result) =>{
533
+ this.initPromise = null;
534
+ return result;
535
+ })
536
+ }
537
+
538
+ tryKeepExistingSessionPromise = null;
539
+ async tryKeepExistingSessionAsync() {
540
+ if(this.tryKeepExistingSessionPromise !== null){
541
+ return this.tryKeepExistingSessionPromise;
542
+ }
543
+
544
+ const funcAsync =async () => {
545
+ let serviceWorker
546
+ if (this.tokens != null) {
547
+ return false;
548
+ }
549
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_begin, {});
550
+ try {
551
+ const configuration = this.configuration;
552
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
553
+ serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
554
+ if (serviceWorker) {
555
+ const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
556
+ if (tokens) {
557
+ serviceWorker.startKeepAliveServiceWorker();
558
+ // @ts-ignore
559
+ this.tokens = tokens;
560
+ // @ts-ignore
561
+ this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt);
562
+ const sessionState = await serviceWorker.getSessionStateAsync();
563
+ // @ts-ignore
564
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
565
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
566
+ success: true,
567
+ message: "tokens inside ServiceWorker are valid"
568
+ });
569
+ return true;
570
+ }
571
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
572
+ success: false,
573
+ message: "no exiting session found"
574
+ });
575
+ } else {
576
+ if (configuration.service_worker_relative_url) {
577
+ this.publishEvent(eventNames.service_worker_not_supported_by_browser, {
578
+ message: "service worker is not supported by this browser"
579
+ });
580
+ }
581
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
582
+ const {tokens} = await session.initAsync();
583
+ if (tokens) {
584
+ // @ts-ignore
585
+ this.tokens = setTokens(tokens);
586
+ // @ts-ignore
587
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
588
+ const sessionState = session.getSessionState();
589
+ // @ts-ignore
590
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
591
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
592
+ success: true,
593
+ message: `tokens inside storage are valid`
594
+ });
595
+ return true;
596
+ }
597
+ }
598
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
599
+ success: false,
600
+ message: serviceWorker ? "service worker sessions not retrieved" : "session storage sessions not retrieved"
601
+ });
602
+ return false;
603
+ } catch (exception) {
604
+ console.error(exception);
605
+ if (serviceWorker) {
606
+ await serviceWorker.clearAsync();
607
+ }
608
+ this.publishEvent(eventNames.tryKeepExistingSessionAsync_error, "tokens inside ServiceWorker are invalid");
609
+ return false;
610
+ }
611
+ }
612
+
613
+ this.tryKeepExistingSessionPromise = funcAsync();
614
+ return this.tryKeepExistingSessionPromise.then((result) => {
615
+ this.tryKeepExistingSessionPromise =null;
616
+ return result;
617
+ });
618
+ }
619
+
620
+ loginPromise: Promise<any>=null;
621
+ async loginAsync(callbackPath:string=undefined, extras:StringMap=null, isSilentSignin:boolean=false, scope:string=undefined, silentLoginOnly = false) {
622
+ if(this.loginPromise !== null){
623
+ return this.loginPromise;
624
+ }
625
+
626
+ const loginLocalAsync=async () => {
627
+
628
+ const location = window.location;
629
+ const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
630
+
631
+ const configuration = this.configuration;
632
+ let state = undefined;
633
+ if(extras && "state" in extras){
634
+ state = extras["state"];
635
+ delete extras["state"];
636
+ }
637
+
638
+
639
+ if(silentLoginOnly){
640
+ try {
641
+ const extraFinal = extras ?? configuration.extras ?? {};
642
+ const silentResult = await this.silentLoginAsync({
643
+ ...extraFinal,
644
+ prompt: "none"
645
+ }, state, scope);
646
+
647
+ if (silentResult) {
648
+ this.tokens = silentResult.tokens;
649
+ this.publishEvent(eventNames.token_aquired, {});
650
+ // @ts-ignore
651
+ this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt, extras);
652
+ return {};
653
+ }
654
+ }catch (e) {
655
+ return e;
656
+ }
657
+ }
658
+ this.publishEvent(eventNames.loginAsync_begin, {});
659
+ try {
660
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
661
+ if (!scope) {
662
+ scope = configuration.scope;
663
+ }
664
+
665
+ const randomString = function(length) {
666
+ let text = "";
667
+ const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
668
+ for(let i = 0; i < length; i++) {
669
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
670
+ }
671
+ return text;
672
+ }
673
+
674
+ setLoginParams(this.configurationName, redirectUri, {callbackPath: url, extras, state});
675
+ const extraFinal = extras ?? configuration.extras ?? {};
676
+ if(!extraFinal.nonce) {
677
+ extraFinal["nonce"] = randomString(12);
678
+ }
679
+ const nonce = {"nonce":extraFinal.nonce};
680
+ let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
681
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
682
+ let storage;
683
+ if (serviceWorker) {
684
+ serviceWorker.startKeepAliveServiceWorker();
685
+ await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
686
+ await serviceWorker.setNonceAsync(nonce);
687
+ storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
688
+ await storage.setItem("dummy", {});
689
+
690
+ } else {
691
+ const session = initSession(this.configurationName, redirectUri);
692
+ await session.setNonceAsync(nonce);
693
+ storage = new MemoryStorageBackend(session.saveItemsAsync, {});
694
+ }
695
+
696
+
697
+ // @ts-ignore
698
+ const queryStringUtil = redirectUri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
699
+ const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
700
+ const authRequest = new AuthorizationRequest({
701
+ client_id: configuration.client_id,
702
+ redirect_uri: redirectUri,
703
+ scope,
704
+ response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
705
+ state,
706
+ extras: extraFinal
707
+ });
708
+ authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
709
+ } catch (exception) {
710
+ this.publishEvent(eventNames.loginAsync_error, exception);
711
+ throw exception;
712
+ }
713
+ }
714
+ this.loginPromise = loginLocalAsync();
715
+ return this.loginPromise.then(result =>{
716
+ this.loginPromise = null;
717
+ return result;
718
+ });
719
+ }
720
+
721
+ async startCheckSessionAsync(checkSessionIFrameUri, clientId, sessionState, isSilentSignin=false){
722
+ return new Promise((resolve:Function, reject) => {
723
+ if (this.configuration.silent_login_uri && this.configuration.silent_redirect_uri && this.configuration.monitor_session && checkSessionIFrameUri && sessionState && !isSilentSignin) {
724
+ const checkSessionCallback = () => {
725
+ this.checkSessionIFrame.stop();
726
+
727
+ if(this.tokens === null){
728
+ return;
729
+ }
730
+ // @ts-ignore
731
+ const idToken = this.tokens.idToken;
732
+ // @ts-ignore
733
+ const idTokenPayload = this.tokens.idTokenPayload;
734
+ this.silentLoginAsync({
735
+ prompt: "none",
736
+ id_token_hint: idToken,
737
+ scope: "openid"
738
+ }).then((silentSigninResponse) => {
739
+ const iFrameIdTokenPayload = silentSigninResponse.tokens.idTokenPayload;
740
+ if (idTokenPayload.sub === iFrameIdTokenPayload.sub) {
741
+ const sessionState = silentSigninResponse.sessionState;
742
+ this.checkSessionIFrame.start(silentSigninResponse.sessionState);
743
+ if (idTokenPayload.sid === iFrameIdTokenPayload.sid) {
744
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:", sessionState);
745
+ } else {
746
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:", sessionState);
747
+ }
748
+ }
749
+ else {
750
+ console.debug("SessionMonitor._callback: Different subject signed into OP:", iFrameIdTokenPayload.sub);
751
+ }
752
+ }).catch(async (e) => {
753
+ for (const [key, oidc] of Object.entries(oidcDatabase)) {
754
+ //if(oidc !== this) {
755
+ // @ts-ignore
756
+ await oidc.logoutOtherTabAsync(this.configuration.client_id, idTokenPayload.sub);
757
+ //}
758
+ }
759
+ //await this.destroyAsync();
760
+ //this.publishEvent(eventNames.logout_from_another_tab, {message : "SessionMonitor"});
761
+
762
+ });
763
+ };
764
+
765
+ this.checkSessionIFrame = new CheckSessionIFrame(checkSessionCallback, clientId, checkSessionIFrameUri);
766
+ this.checkSessionIFrame.load().then(() => {
767
+ this.checkSessionIFrame.start(sessionState);
768
+ resolve();
769
+ }).catch((e) =>{
770
+ reject(e);
771
+ });
772
+ } else {
773
+ resolve();
774
+ }
775
+ });
776
+ }
777
+
778
+ loginCallbackPromise : Promise<any>=null
779
+ async loginCallbackAsync(isSilenSignin:boolean=false){
780
+ if(this.loginCallbackPromise !== null){
781
+ return this.loginCallbackPromise;
782
+ }
783
+
784
+ const loginCallbackLocalAsync= async( ) =>{
785
+ const response = await this._loginCallbackAsync(isSilenSignin);
786
+ // @ts-ignore
787
+ const tokens = response.tokens;
788
+ const parsedTokens = setTokens(tokens);
789
+ this.tokens = parsedTokens;
790
+ const oidc = this;
791
+ const serviceWorker = await initWorkerAsync(oidc.configuration.service_worker_relative_url, oidc.configurationName);
792
+ if (!serviceWorker) {
793
+ const session = initSession(this.configurationName, oidc.configuration.redirect_uri, oidc.configuration.storage);
794
+ await session.setTokens(parsedTokens);
795
+ }
796
+
797
+ this.publishEvent(Oidc.eventNames.token_aquired, parsedTokens);
798
+ // @ts-ignore
799
+ return { parsedTokens, state:response.state, callbackPath : response.callbackPath};
800
+ }
801
+
802
+ this.loginCallbackPromise = loginCallbackLocalAsync();
803
+ return this.loginCallbackPromise.then(result =>{
804
+ this.loginCallbackPromise = null;
805
+ return result;
806
+ })
807
+ }
808
+
809
+ async _loginCallbackAsync(isSilentSignin:boolean=false){
810
+ try {
811
+ this.publishEvent(eventNames.loginCallbackAsync_begin, {});
812
+ const configuration = this.configuration;
813
+ const clientId = configuration.client_id;
814
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
815
+ const authority = configuration.authority;
816
+ const tokenRequestTimeout = configuration.token_request_timeout;
817
+ const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
818
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
819
+ const sessionState = queryParams.session_state;
820
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
821
+ let storage = null;
822
+ let nonceData = null;
823
+ if(serviceWorker){
824
+ serviceWorker.startKeepAliveServiceWorker();
825
+ await serviceWorker.initAsync(oidcServerConfiguration, "loginCallbackAsync");
826
+ const items = await serviceWorker.loadItemsAsync();
827
+ storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, items);
828
+ const dummy =await storage.getItem("dummy");
829
+ if(!dummy){
830
+ throw new Error("Service Worker storage disapear");
831
+ }
832
+ await storage.removeItem("dummy");
833
+ await serviceWorker.setSessionStateAsync(sessionState);
834
+ nonceData = await serviceWorker.getNonceAsync();
835
+ }else{
836
+ const session = initSession(this.configurationName, redirectUri);
837
+ session.setSessionState(sessionState);
838
+ const items = await session.loadItemsAsync();
839
+ storage = new MemoryStorageBackend(session.saveItemsAsync, items);
840
+ nonceData = await session.getNonceAsync();
841
+ }
842
+
843
+ return new Promise((resolve, reject) => {
844
+ // @ts-ignore
845
+ let queryStringUtil = new NoHashQueryStringUtils();
846
+ if(redirectUri.includes("#")) {
847
+ const splithash = window.location.href.split("#");
848
+ if (splithash.length === 2 && splithash[1].includes("?")) {
849
+ queryStringUtil = new HashQueryStringUtils();
850
+ }
851
+ }
852
+ // @ts-ignore
853
+ const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location , new DefaultCrypto());
854
+ const notifier = new AuthorizationNotifier();
855
+ authorizationHandler.setAuthorizationNotifier(notifier);
856
+
857
+ notifier.setAuthorizationListener( (request, response, error) => {
858
+ if (error) {
859
+ reject(error);
860
+ return;
861
+ }
862
+ if (!response) {
863
+ reject("no response");
864
+ return;
865
+ }
866
+
867
+ let extras = null;
868
+ if (request && request.internal) {
869
+ extras = {};
870
+ extras.code_verifier = request.internal.code_verifier;
871
+ if (configuration.token_request_extras) {
872
+ for (let [key, value] of Object.entries(configuration.token_request_extras)) {
873
+ extras[key] = value;
874
+ }
875
+ }
876
+ }
877
+
878
+ const tokenRequest = new TokenRequest({
879
+ client_id: clientId,
880
+ redirect_uri: redirectUri,
881
+ grant_type: GRANT_TYPE_AUTHORIZATION_CODE,
882
+ code: response.code,
883
+ refresh_token: undefined,
884
+ extras,
885
+ });
886
+
887
+ let timeoutId = setTimeout(()=>{
888
+ reject("performTokenRequest timeout");
889
+ timeoutId=null;
890
+ }, tokenRequestTimeout ?? 12000);
891
+ try {
892
+ const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
893
+ tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest).then(async (tokenResponse) => {
894
+ if (timeoutId) {
895
+ clearTimeout(timeoutId);
896
+ this.timeoutId = null;
897
+ const loginParams = getLoginParams(this.configurationName, redirectUri);
898
+
899
+ if (serviceWorker) {
900
+ const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
901
+ tokenResponse = tokens;
902
+ }
903
+ if(!isTokensOidcValid(tokenResponse, nonceData.nonce, oidcServerConfiguration)){
904
+ const exception = new Error("Tokens are not OpenID valid");
905
+ if(timeoutId) {
906
+ clearTimeout(timeoutId);
907
+ this.timeoutId=null;
908
+ this.publishEvent(eventNames.loginCallbackAsync_error, exception);
909
+ console.error(exception);
910
+ reject(exception);
911
+ }
912
+ }
913
+
914
+ // @ts-ignore
915
+ this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, clientId, sessionState, isSilentSignin).then(() => {
916
+ this.publishEvent(eventNames.loginCallbackAsync_end, {});
917
+ resolve({
918
+ tokens: tokenResponse,
919
+ state: request.state,
920
+ callbackPath: loginParams.callbackPath,
921
+ });
922
+ });
923
+ }
924
+ });
925
+ } catch (exception) {
926
+ if(timeoutId) {
927
+ clearTimeout(timeoutId);
928
+ this.timeoutId=null;
929
+ this.publishEvent(eventNames.loginCallbackAsync_error, exception);
930
+ console.error(exception);
931
+ reject(exception);
932
+ }
933
+ }
934
+ });
935
+ authorizationHandler.completeAuthorizationRequestIfPossible();
936
+ });
937
+ } catch(exception) {
938
+ console.error(exception);
939
+ this.publishEvent(eventNames.loginCallbackAsync_error, exception);
940
+ throw exception;
941
+ }
942
+ }
943
+
944
+ async synchroniseTokensAsync(refreshToken, index=0, forceRefresh =false, extras:StringMap=null) {
945
+
946
+ if (document.hidden) {
947
+ await sleepAsync(1000);
948
+ this.publishEvent(eventNames.refreshTokensAsync, {message: "wait because document is hidden"});
949
+ return await this.synchroniseTokensAsync(refreshToken, index, forceRefresh);
950
+ }
951
+ let numberTryOnline = 6;
952
+ while (!navigator.onLine && numberTryOnline > 0) {
953
+ await sleepAsync(1000);
954
+ numberTryOnline--;
955
+ this.publishEvent(eventNames.refreshTokensAsync, {message: `wait because navigator is offline try ${numberTryOnline}` });
956
+ }
957
+ if(!extras){
958
+ extras = {}
959
+ }
960
+ const configuration = this.configuration;
961
+ const localsilentLoginAsync= async () => {
962
+ try {
963
+ const loginParams = getLoginParams(this.configurationName, configuration.redirect_uri);
964
+
965
+ const silent_token_response = await this.silentLoginAsync({
966
+ ...loginParams.extras,
967
+ ...extras,
968
+ prompt: "none"
969
+ }, loginParams.state);
970
+ if (silent_token_response) {
971
+ this.publishEvent(Oidc.eventNames.token_renewed, {});
972
+ return {tokens:silent_token_response.tokens, status:"LOGGED"};
973
+ }
974
+ } catch (exceptionSilent) {
975
+ console.error(exceptionSilent);
976
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, {message: "exceptionSilent" ,exception: exceptionSilent.message});
977
+ if(exceptionSilent && exceptionSilent.message && exceptionSilent.message.startsWith("oidc")){
978
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token silent` });
979
+ return {tokens:null, status:"SESSION_LOST"};
980
+ }
981
+ await sleepAsync(1000);
982
+ throw exceptionSilent;
983
+ }
984
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token silent return` });
985
+ return {tokens:null, status:"SESSION_LOST"};
986
+ }
987
+
988
+ if (index <=4) {
989
+ try {
990
+ const { status, tokens, nonce } = await this.syncTokensInfoAsync(configuration, this.configurationName, this.tokens, forceRefresh);
991
+ switch (status) {
992
+ case "SESSION_LOST":
993
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token session lost` });
994
+ return {tokens:null, status:"SESSION_LOST"};
995
+ case "NOT_CONNECTED":
996
+ return {tokens:null, status:null};
997
+ case "TOKENS_VALID":
998
+ return {tokens, status:"LOGGED_IN"};
999
+ case "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID":
1000
+ this.publishEvent(Oidc.eventNames.token_renewed, {});
1001
+ return {tokens, status:"LOGGED_IN"};
1002
+ case "LOGOUT_FROM_ANOTHER_TAB":
1003
+ this.publishEvent(eventNames.logout_from_another_tab, {"status": "session syncTokensAsync"});
1004
+ return {tokens:null, status:"LOGGED_OUT"};
1005
+ case "REQUIRE_SYNC_TOKENS":
1006
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, status, tryNumber: index});
1007
+ return await localsilentLoginAsync();
1008
+ default:
1009
+ if(!refreshToken)
1010
+ {
1011
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, tryNumber: index});
1012
+ return await localsilentLoginAsync();
1013
+ }
1014
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken, status, tryNumber: index});
1015
+ const clientId = configuration.client_id;
1016
+ const redirectUri = configuration.redirect_uri;
1017
+ const authority = configuration.authority;
1018
+ const tokenExtras = configuration.token_request_extras ? configuration.token_request_extras : {};
1019
+ let finalExtras = {...tokenExtras, ...extras};
1020
+
1021
+ const details = {
1022
+ client_id: clientId,
1023
+ redirect_uri: redirectUri,
1024
+ grant_type: GRANT_TYPE_REFRESH_TOKEN,
1025
+ refresh_token: tokens.refreshToken,
1026
+ };
1027
+ const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
1028
+ const tokenResponse = await performTokenRequestAsync(oidcServerConfiguration.tokenEndpoint, details, finalExtras, tokens);
1029
+ if (tokenResponse.success) {
1030
+ if(!isTokensOidcValid(tokenResponse.data, nonce.nonce, oidcServerConfiguration)){
1031
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token return not valid tokens` });
1032
+ return {tokens:null, status:"SESSION_LOST"};
1033
+ }
1034
+ this.publishEvent(eventNames.refreshTokensAsync_end, {success: tokenResponse.success});
1035
+ this.publishEvent(Oidc.eventNames.token_renewed, {});
1036
+ return {tokens: tokenResponse.data, status:"LOGGED_IN"};
1037
+ } else {
1038
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, {
1039
+ message: "bad request",
1040
+ tokenResponse: tokenResponse
1041
+ });
1042
+ return await this.synchroniseTokensAsync(null, index+1, forceRefresh);
1043
+ }
1044
+ }
1045
+ } catch (exception) {
1046
+ console.error(exception);
1047
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, {message: "exception" ,exception: exception.message});
1048
+ return this.synchroniseTokensAsync(refreshToken, index+1, forceRefresh);
1049
+ }
1050
+ }
1051
+
1052
+ this.publishEvent(eventNames.refreshTokensAsync_error, {message: `refresh token` });
1053
+ return {tokens:null, status:"SESSION_LOST"};
1054
+ }
1055
+
1056
+ async syncTokensInfoAsync(configuration, configurationName, currentTokens, forceRefresh =false) {
1057
+ // 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)
1058
+ //const configuration = this.configuration;
1059
+ const nullNonce = { nonce:null };
1060
+ if (!currentTokens) {
1061
+ return { tokens : null, status: "NOT_CONNECTED", nonce: nullNonce};
1062
+ }
1063
+ let nonce = nullNonce;
1064
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
1065
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, configurationName);
1066
+ if (serviceWorker) {
1067
+ const {status, tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
1068
+ if (status == "LOGGED_OUT") {
1069
+ return {tokens: null, status: "LOGOUT_FROM_ANOTHER_TAB", nonce: nullNonce};
1070
+ }else if (status == "SESSIONS_LOST") {
1071
+ return { tokens : null, status: "SESSIONS_LOST", nonce: nullNonce};
1072
+ } else if (!status || !tokens) {
1073
+ return { tokens : null, status: "REQUIRE_SYNC_TOKENS", nonce: nullNonce};
1074
+ } else if(tokens.issuedAt !== currentTokens.issuedAt) {
1075
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
1076
+ const status = (timeLeft > 0) ? "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID" : "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID";
1077
+ const nonce = await serviceWorker.getNonceAsync();
1078
+ return { tokens : tokens, status, nonce};
1079
+ }
1080
+ nonce = await serviceWorker.getNonceAsync();
1081
+ } else {
1082
+ const session = initSession(configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
1083
+ const { tokens, status } = await session.initAsync();
1084
+ if (!tokens) {
1085
+ return {tokens: null, status: "LOGOUT_FROM_ANOTHER_TAB", nonce: nullNonce};
1086
+ } else if (status == "SESSIONS_LOST") {
1087
+ return { tokens : null, status: "SESSIONS_LOST", nonce: nullNonce};
1088
+ }
1089
+ else if(tokens.issuedAt !== currentTokens.issuedAt){
1090
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, tokens.expiresAt);
1091
+ const status = (timeLeft > 0) ? "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_VALID" : "TOKEN_UPDATED_BY_ANOTHER_TAB_TOKENS_INVALID";
1092
+ const nonce = await session.getNonceAsync();
1093
+ return { tokens : tokens, status , nonce };
1094
+ }
1095
+ nonce = await session.getNonceAsync();
1096
+ }
1097
+
1098
+ const timeLeft = computeTimeLeft(configuration.refresh_time_before_tokens_expiration_in_second, currentTokens.expiresAt);
1099
+ const status = (timeLeft > 0) ? "TOKENS_VALID" : "TOKENS_INVALID";
1100
+ if(forceRefresh){
1101
+ return { tokens:currentTokens, status:"FORCE_REFRESH", nonce};
1102
+ }
1103
+ return { tokens:currentTokens, status, nonce};
1104
+ }
1105
+
1106
+ loginCallbackWithAutoTokensRenewPromise:Promise<loginCallbackResult> = null;
1107
+ loginCallbackWithAutoTokensRenewAsync():Promise<loginCallbackResult>{
1108
+ if(this.loginCallbackWithAutoTokensRenewPromise !== null){
1109
+ return this.loginCallbackWithAutoTokensRenewPromise;
1110
+ }
1111
+ this.loginCallbackWithAutoTokensRenewPromise = loginCallbackWithAutoTokensRenewAsync(this);
1112
+ return this.loginCallbackWithAutoTokensRenewPromise.then(result =>{
1113
+ this.loginCallbackWithAutoTokensRenewPromise = null;
1114
+ return result;
1115
+ })
1116
+ }
1117
+
1118
+ userInfoPromise:Promise<any> = null;
1119
+ userInfoAsync(){
1120
+ if(this.userInfoPromise !== null){
1121
+ return this.userInfoPromise;
1122
+ }
1123
+ this.userInfoPromise = userInfoAsync(this);
1124
+ return this.userInfoPromise.then(result =>{
1125
+ this.userInfoPromise = null;
1126
+ return result;
1127
+ })
1128
+ }
1129
+
1130
+ async renewTokensAsync (extras:StringMap=null){
1131
+ if(!this.timeoutId){
1132
+ return;
1133
+ }
1134
+ timer.clearTimeout(this.timeoutId);
1135
+ // @ts-ignore
1136
+ await renewTokensAndStartTimerAsync(this, this.tokens.refreshToken, true, extras);
1137
+ }
1138
+
1139
+ async destroyAsync(status) {
1140
+ timer.clearTimeout(this.timeoutId);
1141
+ this.timeoutId=null;
1142
+ if(this.checkSessionIFrame){
1143
+ this.checkSessionIFrame.stop();
1144
+ }
1145
+ const oidc = this;
1146
+ const serviceWorker = await initWorkerAsync(oidc.configuration.service_worker_relative_url, oidc.configurationName);
1147
+ if (!serviceWorker) {
1148
+ const session = initSession(this.configurationName, oidc.configuration.redirect_uri, oidc.configuration.storage);
1149
+ await session.clearAsync(status);
1150
+ } else{
1151
+ await serviceWorker.clearAsync(status);
1152
+ }
1153
+ this.tokens = null;
1154
+ this.userInfo = null;
1155
+ // this.events = [];
1156
+ }
1157
+
1158
+ async logoutSameTabAsync(clientId, sub){
1159
+ // @ts-ignore
1160
+ if(this.configuration.monitor_session&& this.configuration.client_id === clientId && sub && this.tokens && this.tokens.idTokenPayload && this.tokens.idTokenPayload.sub === sub) {
1161
+ this.publishEvent(eventNames.logout_from_same_tab, {"message": sub});
1162
+ await this.destroyAsync("LOGGED_OUT");
1163
+ }
1164
+ }
1165
+
1166
+ async logoutOtherTabAsync(clientId, sub){
1167
+ // @ts-ignore
1168
+ if(this.configuration.monitor_session && this.configuration.client_id === clientId && sub && this.tokens && this.tokens.idTokenPayload && this.tokens.idTokenPayload.sub === sub) {
1169
+ await this.destroyAsync("LOGGED_OUT");
1170
+ this.publishEvent(eventNames.logout_from_another_tab, {message : "SessionMonitor", "sub": sub});
1171
+ }
1172
+ }
1173
+
1174
+ async logoutAsync(callbackPathOrUrl: string | null | undefined = undefined, extras: StringMap = null) {
1175
+ const configuration = this.configuration;
1176
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
1177
+ if(callbackPathOrUrl && (typeof callbackPathOrUrl !== 'string'))
1178
+ {
1179
+ callbackPathOrUrl = undefined;
1180
+ console.warn('callbackPathOrUrl path is not a string');
1181
+ }
1182
+ const path = (callbackPathOrUrl === null || callbackPathOrUrl === undefined) ? location.pathname + (location.search || '') + (location.hash || '') : callbackPathOrUrl;
1183
+ let isUri = false;
1184
+ if(callbackPathOrUrl) {
1185
+ isUri = callbackPathOrUrl.includes("https://") || callbackPathOrUrl.includes("http://");
1186
+ }
1187
+ const url = isUri ? callbackPathOrUrl : window.location.origin + path;
1188
+ // @ts-ignore
1189
+ const idToken = this.tokens ? this.tokens.idToken : "";
1190
+ // @ts-ignore
1191
+ const sub = this.tokens && this.tokens.idTokenPayload ? this.tokens.idTokenPayload.sub : null;
1192
+ await this.destroyAsync("LOGGED_OUT");
1193
+ for (const [key, oidc] of Object.entries(oidcDatabase)) {
1194
+ if(oidc !== this) {
1195
+ // @ts-ignore
1196
+ await oidc.logoutSameTabAsync(this.configuration.client_id, sub);
1197
+ }
1198
+ }
1199
+
1200
+ if(oidcServerConfiguration.endSessionEndpoint) {
1201
+ if(!extras){
1202
+ extras= {
1203
+ id_token_hint: idToken
1204
+ };
1205
+ if(callbackPathOrUrl !== null){
1206
+ extras["post_logout_redirect_uri"] = url;
1207
+ }
1208
+ }
1209
+ let queryString = "";
1210
+ if(extras){
1211
+ for (let [key, value] of Object.entries(extras)) {
1212
+ if(queryString === "")
1213
+ {
1214
+ queryString += "?";
1215
+ } else{
1216
+ queryString += "&";
1217
+ }
1218
+ queryString +=`${key}=${encodeURIComponent(value)}`;
1219
+ }
1220
+ }
1221
+ window.location.href = `${oidcServerConfiguration.endSessionEndpoint}${queryString}`;
1222
+ }
1223
+ else{
1224
+ window.location.reload();
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+
1230
+ export default Oidc;