@axa-fr/react-oidc 5.13.11 → 6.0.0-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 (64) hide show
  1. package/README.md +1 -0
  2. package/dist/OidcProvider.d.ts +1 -0
  3. package/dist/OidcProvider.d.ts.map +1 -1
  4. package/dist/OidcProvider.js +13 -5
  5. package/dist/OidcProvider.js.map +1 -1
  6. package/dist/OidcServiceWorker.js +13 -0
  7. package/dist/ReactOidc.d.ts.map +1 -1
  8. package/dist/ReactOidc.js +29 -7
  9. package/dist/ReactOidc.js.map +1 -1
  10. package/dist/core/default-component/ServiceWorkerInstall.component.d.ts.map +1 -1
  11. package/dist/core/default-component/ServiceWorkerInstall.component.js +21 -9
  12. package/dist/core/default-component/ServiceWorkerInstall.component.js.map +1 -1
  13. package/dist/core/default-component/SilentCallback.component.d.ts.map +1 -1
  14. package/dist/core/default-component/SilentCallback.component.js +23 -15
  15. package/dist/core/default-component/SilentCallback.component.js.map +1 -1
  16. package/dist/core/default-component/SilentSignin.component.d.ts +4 -0
  17. package/dist/core/default-component/SilentSignin.component.d.ts.map +1 -0
  18. package/dist/core/default-component/SilentSignin.component.js +58 -0
  19. package/dist/core/default-component/SilentSignin.component.js.map +1 -0
  20. package/dist/core/routes/OidcRoutes.d.ts +1 -0
  21. package/dist/core/routes/OidcRoutes.d.ts.map +1 -1
  22. package/dist/core/routes/OidcRoutes.js +8 -2
  23. package/dist/core/routes/OidcRoutes.js.map +1 -1
  24. package/dist/vanilla/checkSessionIFrame.d.ts +17 -0
  25. package/dist/vanilla/checkSessionIFrame.d.ts.map +1 -0
  26. package/dist/vanilla/checkSessionIFrame.js +78 -0
  27. package/dist/vanilla/checkSessionIFrame.js.map +1 -0
  28. package/dist/vanilla/initSession.d.ts +3 -1
  29. package/dist/vanilla/initSession.d.ts.map +1 -1
  30. package/dist/vanilla/initSession.js +21 -11
  31. package/dist/vanilla/initSession.js.map +1 -1
  32. package/dist/vanilla/initWorker.d.ts +4 -0
  33. package/dist/vanilla/initWorker.d.ts.map +1 -1
  34. package/dist/vanilla/initWorker.js +31 -3
  35. package/dist/vanilla/initWorker.js.map +1 -1
  36. package/dist/vanilla/oidc.d.ts +25 -6
  37. package/dist/vanilla/oidc.d.ts.map +1 -1
  38. package/dist/vanilla/oidc.js +526 -246
  39. package/dist/vanilla/oidc.js.map +1 -1
  40. package/dist/vanilla/route-utils.d.ts +13 -0
  41. package/dist/vanilla/route-utils.d.ts.map +1 -0
  42. package/dist/vanilla/route-utils.js +65 -0
  43. package/dist/vanilla/route-utils.js.map +1 -0
  44. package/package.json +1 -1
  45. package/src/App.tsx +1 -1
  46. package/src/configurations.ts +8 -3
  47. package/src/oidc/OidcProvider.tsx +11 -0
  48. package/src/oidc/ReactOidc.tsx +32 -8
  49. package/src/oidc/core/default-component/ServiceWorkerInstall.component.tsx +15 -3
  50. package/src/oidc/core/default-component/SilentCallback.component.tsx +10 -15
  51. package/src/oidc/core/default-component/SilentSignin.component.tsx +35 -0
  52. package/src/oidc/core/routes/OidcRoutes.tsx +10 -1
  53. package/src/oidc/vanilla/OidcServiceWorker.js +13 -0
  54. package/src/oidc/vanilla/checkSessionIFrame.ts +82 -0
  55. package/src/oidc/vanilla/initSession.ts +23 -11
  56. package/src/oidc/vanilla/initWorker.ts +19 -2
  57. package/src/oidc/vanilla/oidc.ts +411 -148
  58. package/src/oidc/{core/routes → vanilla}/route-utils.spec.ts +0 -0
  59. package/src/oidc/vanilla/route-utils.ts +76 -0
  60. package/dist/core/routes/route-utils.d.ts +0 -2
  61. package/dist/core/routes/route-utils.d.ts.map +0 -1
  62. package/dist/core/routes/route-utils.js +0 -32
  63. package/dist/core/routes/route-utils.js.map +0 -1
  64. package/src/oidc/core/routes/route-utils.ts +0 -34
@@ -10,12 +10,34 @@ import {
10
10
  RedirectRequestHandler,
11
11
  TokenRequest
12
12
  } from '@openid/appauth';
13
- import {NoHashQueryStringUtils, HashQueryStringUtils} from './noHashQueryStringUtils';
13
+ import {HashQueryStringUtils, NoHashQueryStringUtils} from './noHashQueryStringUtils';
14
14
  import {initWorkerAsync, sleepAsync} from './initWorker'
15
15
  import {MemoryStorageBackend} from "./memoryStorageBackend";
16
16
  import {initSession} from "./initSession";
17
17
  import timer from './timer';
18
18
 
19
+ import {CheckSessionIFrame} from "./checkSessionIFrame"
20
+ import {getParseQueryStringFromLocation} from "./route-utils";
21
+ import {AuthorizationServiceConfigurationJson} from "@openid/appauth/src/authorization_service_configuration";
22
+
23
+ export interface OidcAuthorizationServiceConfigurationJson extends AuthorizationServiceConfigurationJson{
24
+ check_session_iframe?: string;
25
+ }
26
+
27
+ export class OidcAuthorizationServiceConfiguration extends AuthorizationServiceConfiguration{
28
+ private check_session_iframe: string;
29
+
30
+ constructor(request: any) {
31
+ super(request);
32
+ this.authorizationEndpoint = request.authorization_endpoint;
33
+ this.tokenEndpoint = request.token_endpoint;
34
+ this.revocationEndpoint = request.revocation_endpoint;
35
+ this.userInfoEndpoint = request.userinfo_endpoint;
36
+ this.check_session_iframe = request.check_session_iframe;
37
+ }
38
+
39
+ }
40
+
19
41
  const isInIframe = () => {
20
42
  try {
21
43
  return window.self !== window.top;
@@ -70,23 +92,27 @@ export interface AuthorityConfiguration {
70
92
  revocation_endpoint: string;
71
93
  end_session_endpoint?: string;
72
94
  userinfo_endpoint?: string;
95
+ check_session_iframe?:string;
73
96
  }
74
97
 
75
- const refresh_token_scope = "offline_access";
76
98
  export type OidcConfiguration = {
77
- client_id: string,
78
- redirect_uri: string,
99
+ client_id: string,
100
+ redirect_uri: string,
79
101
  silent_redirect_uri?:string,
102
+ silent_signin_uri?:string,
80
103
  silent_signin_timeout?:number,
81
- scope: string,
82
- authority: string,
104
+ scope: string,
105
+ authority: string,
106
+ authority_time_cache_wellknowurl_in_second?: number,
83
107
  authority_configuration?: AuthorityConfiguration,
84
- refresh_time_before_tokens_expiration_in_second?: number,
85
- token_request_timeout?: number,
86
- service_worker_relative_url?:string,
108
+ refresh_time_before_tokens_expiration_in_second?: number,
109
+ token_request_timeout?: number,
110
+ service_worker_relative_url?:string,
87
111
  service_worker_only?:boolean,
88
112
  extras?:StringMap
89
- token_request_extras?:StringMap,
113
+ token_request_extras?:StringMap,
114
+ storage?: Storage
115
+ monitor_session?: boolean
90
116
  };
91
117
 
92
118
  const oidcDatabase = {};
@@ -99,15 +125,9 @@ const oidcFactory = (configuration: OidcConfiguration, name="default") => {
99
125
  }
100
126
 
101
127
  const loginCallbackWithAutoTokensRenewAsync = async (oidc) => {
102
- const response = await oidc.loginCallbackAsync();
103
- const tokens = response.tokens
104
- oidc.tokens = await setTokensAsync(oidc.serviceWorker, tokens);
105
- if(!oidc.serviceWorker){
106
- await oidc.session.setTokens(oidc.tokens);
107
- }
108
- oidc.publishEvent(Oidc.eventNames.token_aquired, oidc.tokens);
109
- oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt)
110
- return { state:response.state, callbackPath : response.callbackPath };
128
+ const { parsedTokens, state, callbackPath } = await oidc.loginCallbackAsync();
129
+ oidc.timeoutId = autoRenewTokens(oidc, parsedTokens.refreshToken, parsedTokens.expiresAt)
130
+ return { state, callbackPath };
111
131
  }
112
132
 
113
133
  const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
@@ -123,6 +143,10 @@ const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
123
143
  await oidc.session.setTokens(oidc.tokens);
124
144
  }
125
145
  if(!oidc.tokens){
146
+ if(oidc.checkSessionIFrame){
147
+ oidc.checkSessionIFrame.stop();
148
+ oidc.checkSessionIFrame = null;
149
+ }
126
150
  return;
127
151
  }
128
152
  oidc.publishEvent(Oidc.eventNames.token_renewed, oidc.tokens);
@@ -138,10 +162,14 @@ const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
138
162
  }, 1000);
139
163
  }
140
164
 
141
- export const getLoginParams = (configurationName) => {
142
- return JSON.parse(sessionStorage[`oidc_login.${configurationName}`]);
165
+ const getLoginSessionKey = (configurationName:string, redirectUri:string) => {
166
+ return `oidc_login.${configurationName}:${redirectUri}`;
167
+ }
168
+ const getLoginParams = (configurationName, redirectUri) => {
169
+ return JSON.parse(sessionStorage[getLoginSessionKey(configurationName, redirectUri)]);
143
170
  }
144
171
 
172
+
145
173
  const userInfoAsync = async (oidc) => {
146
174
  if(oidc.userInfo != null){
147
175
  return oidc.userInfo;
@@ -188,13 +216,15 @@ const setTokensAsync = async (serviceWorker, tokens) =>{
188
216
  else {
189
217
  accessTokenPayload = extractAccessTokenPayload(tokens);
190
218
  }
191
- const expiresAt = tokens.issuedAt + tokens.expiresIn;
192
- return {...tokens, idTokenPayload: idTokenPayload(tokens.idToken), accessTokenPayload, expiresAt};
219
+ const _idTokenPayload = idTokenPayload(tokens.idToken);
220
+ const expiresAt = (_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp : tokens.issuedAt + tokens.expiresIn;
221
+ return {...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt};
193
222
  }
194
223
 
195
224
  const eventNames = {
196
225
  service_worker_not_supported_by_browser: "service_worker_not_supported_by_browser",
197
226
  token_aquired: "token_aquired",
227
+ logout_from_another_tab: "logout_from_another_tab",
198
228
  token_renewed: "token_renewed",
199
229
  token_timer: "token_timer",
200
230
  loginAsync_begin:"loginAsync_begin",
@@ -225,6 +255,51 @@ const getRandomInt = (max) => {
225
255
  return Math.floor(Math.random() * max);
226
256
  }
227
257
 
258
+ const WELL_KNOWN_PATH = '.well-known';
259
+ const OPENID_CONFIGURATION = 'openid-configuration';
260
+
261
+
262
+ const oneHourSecond = 60 * 60;
263
+ const fetchFromIssuer = async (openIdIssuerUrl: string, timeCacheSecond = oneHourSecond):
264
+ Promise<OidcAuthorizationServiceConfiguration> => {
265
+ const fullUrl = `${openIdIssuerUrl}/${WELL_KNOWN_PATH}/${OPENID_CONFIGURATION}`;
266
+
267
+ const localStorageKey = `oidc.server:${openIdIssuerUrl}`;
268
+ const cacheJson = window.localStorage.getItem(localStorageKey);
269
+
270
+ const oneHourMinisecond = 1000 * timeCacheSecond;
271
+ // @ts-ignore
272
+ if(cacheJson && (cacheJson.timestamp + oneHourMinisecond) > Date.now()){
273
+ return new OidcAuthorizationServiceConfiguration(JSON.parse(cacheJson));
274
+ }
275
+
276
+ const res = await fetch(fullUrl);
277
+
278
+ if (res.status != 200) {
279
+ return null;
280
+ }
281
+
282
+
283
+ const result = await res.json();
284
+ window.localStorage.setItem(localStorageKey, JSON.stringify({result, timestamp:Date.now()}));
285
+
286
+ return new OidcAuthorizationServiceConfiguration(result);
287
+ }
288
+
289
+ const buildQueries = (extras:StringMap) => {
290
+ let queries = '';
291
+ if(extras != null){
292
+ for (let [key, value] of Object.entries(extras)) {
293
+ if (queries === ""){
294
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
295
+ } else {
296
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
297
+ }
298
+ }
299
+ }
300
+ return queries;
301
+ }
302
+
228
303
  export class Oidc {
229
304
  public configuration: OidcConfiguration;
230
305
  public userInfo: null;
@@ -234,7 +309,7 @@ export class Oidc {
234
309
  private serviceWorker?: any;
235
310
  private configurationName: string;
236
311
  private session?: any;
237
- private iFrameSession= {}
312
+ private checkSessionIFrame: CheckSessionIFrame;
238
313
  constructor(configuration:OidcConfiguration, configurationName="default") {
239
314
  this.configuration = configuration
240
315
  this.configurationName= configurationName;
@@ -248,6 +323,7 @@ export class Oidc {
248
323
  this.loginCallbackWithAutoTokensRenewAsync.bind(this);
249
324
  this.initAsync.bind(this);
250
325
  this.loginCallbackAsync.bind(this);
326
+ this._loginCallbackAsync.bind(this);
251
327
  this.subscriveEvents.bind(this);
252
328
  this.removeEventSubscription.bind(this);
253
329
  this.publishEvent.bind(this);
@@ -285,11 +361,18 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
285
361
 
286
362
  silentSigninCallbackFromIFrame(){
287
363
  if (this.configuration.silent_redirect_uri) {
288
- window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify(this.tokens)}`, window.location.origin);
364
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
365
+ window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify({tokens:this.tokens, sessionState:queryParams.session_state})}`, window.location.origin);
366
+ }
367
+ }
368
+ silentSigninErrorCallbackFromIFrame(){
369
+ if (this.configuration.silent_redirect_uri) {
370
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
371
+ window.top.postMessage(`${this.configurationName}_oidc_error:${JSON.stringify({error:queryParams.error})}`, window.location.origin);
289
372
  }
290
373
  }
291
- async silentSigninAsync() {
292
- if (!this.configuration.silent_redirect_uri) {
374
+ async silentSigninAsync(extras:StringMap=null, state:string=null, scope:string=null) {
375
+ if (!this.configuration.silent_redirect_uri || !this.configuration.silent_signin_uri) {
293
376
  return Promise.resolve(null);
294
377
  }
295
378
  while (document.hidden) {
@@ -300,10 +383,38 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
300
383
  try {
301
384
  this.publishEvent(eventNames.silentSigninAsync_begin, {});
302
385
  const configuration = this.configuration
303
- const link = configuration.silent_redirect_uri;
386
+ let queries = "";
387
+
388
+ if(state){
389
+ if(extras == null){
390
+ extras = {};
391
+ }
392
+ extras.state = state;
393
+ }
394
+
395
+ if(scope){
396
+ if(extras == null){
397
+ extras = {};
398
+ }
399
+ extras.scope = scope;
400
+ }
401
+
402
+ if(extras != null){
403
+ for (let [key, value] of Object.entries(extras)) {
404
+ if (queries === ""){
405
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
406
+ } else {
407
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
408
+ }
409
+ }
410
+ }
411
+ const link = configuration.silent_signin_uri + queries;
412
+ const idx = link.indexOf("/", link.indexOf("//") + 2);
413
+ const iFrameOrigin = link.substr(0, idx);
304
414
  const iframe = document.createElement('iframe');
305
415
  iframe.width = "0px";
306
416
  iframe.height = "0px";
417
+
307
418
  iframe.id = `${this.configurationName}_oidc_iframe`;
308
419
  iframe.setAttribute("src", link);
309
420
  document.body.appendChild(iframe);
@@ -312,18 +423,33 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
312
423
  try {
313
424
  let isResolved = false;
314
425
  window.onmessage = function (e) {
315
- const key = `${self.configurationName}_oidc_tokens:`;
316
- if (e.data && typeof (e.data) === "string" && e.data.startsWith(key)) {
317
- if (!isResolved) {
318
- const result = JSON.parse(e.data.replace(key, ''));
319
- self.publishEvent(eventNames.silentSigninAsync_end, result);
320
- iframe.remove();
321
- isResolved = true;
322
- resolve(result);
426
+ if (e.origin === iFrameOrigin &&
427
+ e.source === iframe.contentWindow
428
+ ) {
429
+ const key = `${self.configurationName}_oidc_tokens:`;
430
+ const key_error = `${self.configurationName}_oidc_error:`;
431
+ const data = e.data;
432
+ if (data && typeof (data) === "string") {
433
+ if (!isResolved) {
434
+ if(data.startsWith(key)) {
435
+ const result = JSON.parse(e.data.replace(key, ''));
436
+ self.publishEvent(eventNames.silentSigninAsync_end, result);
437
+ iframe.remove();
438
+ isResolved = true;
439
+ resolve(result);
440
+ }
441
+ else if(data.startsWith(key_error)) {
442
+ const result = JSON.parse(e.data.replace(key_error, ''));
443
+ self.publishEvent(eventNames.silentSigninAsync_error, result);
444
+ iframe.remove();
445
+ isResolved = true;
446
+ reject(result);
447
+ }
448
+ }
323
449
  }
324
450
  }
325
451
  };
326
- const silentSigninTimeout = configuration.silent_signin_timeout ? configuration.silent_signin_timeout : 12000
452
+ const silentSigninTimeout = configuration.silent_signin_timeout ?? 12000
327
453
  setTimeout(() => {
328
454
  if (!isResolved) {
329
455
  self.publishEvent(eventNames.silentSigninAsync_error, "timeout");
@@ -346,17 +472,20 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
346
472
  initAsyncPromise = null;
347
473
  async initAsync(authority:string, authorityConfiguration:AuthorityConfiguration) {
348
474
  if (authorityConfiguration != null) {
349
- return new AuthorizationServiceConfiguration( {
475
+ return new OidcAuthorizationServiceConfiguration( {
350
476
  authorization_endpoint: authorityConfiguration.authorization_endpoint,
351
477
  end_session_endpoint: authorityConfiguration.end_session_endpoint,
352
478
  revocation_endpoint: authorityConfiguration.revocation_endpoint,
353
479
  token_endpoint: authorityConfiguration.token_endpoint,
354
- userinfo_endpoint: authorityConfiguration.userinfo_endpoint});
480
+ userinfo_endpoint: authorityConfiguration.userinfo_endpoint,
481
+ check_session_iframe:authorityConfiguration.check_session_iframe,
482
+ });
355
483
  }
356
484
  if(this.initAsyncPromise){
357
485
  return this.initAsyncPromise;
358
486
  }
359
- this.initAsyncPromise = await AuthorizationServiceConfiguration.fetchFromIssuer(authority, new FetchRequestor());
487
+
488
+ this.initAsyncPromise = await fetchFromIssuer(authority, this.configuration.authority_time_cache_wellknowurl_in_second ?? 60 * 60);
360
489
  return this.initAsyncPromise;
361
490
  }
362
491
 
@@ -380,12 +509,21 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
380
509
  const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
381
510
  if (tokens) {
382
511
  serviceWorker.startKeepAliveServiceWorker();
383
- const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true);
512
+ const sessionState = await serviceWorker.getSessionStateAsync();
513
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
514
+ //const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true);
384
515
  // @ts-ignore
385
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
516
+ const reformattedToken = {
517
+ accessToken : tokens.access_token,
518
+ expiresIn: tokens.expires_in,
519
+ idToken: tokens.id_token,
520
+ scope: tokens.scope,
521
+ tokenType: tokens.token_type
522
+ }
523
+ this.tokens = await setTokensAsync(serviceWorker, reformattedToken);
386
524
  this.serviceWorker = serviceWorker;
387
525
  // @ts-ignore
388
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
526
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
389
527
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
390
528
  success: true,
391
529
  message: "tokens inside ServiceWorker are valid"
@@ -402,19 +540,23 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
402
540
  message: "service worker is not supported by this browser"
403
541
  });
404
542
  }
405
- const session = initSession(this.configurationName);
543
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
406
544
  const {tokens} = await session.initAsync();
545
+ console.log("const {tokens} = await session.initAsync();")
546
+ console.log(tokens)
407
547
  if (tokens) {
408
- const updatedTokens = await this.refreshTokensAsync(tokens.refreshToken, true);
548
+ const sessionState = session.getSessionState();
549
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
550
+ //const updatedTokens = await this.refreshTokensAsync(tokens.refreshToken, true);
409
551
  // @ts-ignore
410
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
411
- session.setTokens(this.tokens);
552
+ this.tokens = await setTokensAsync(serviceWorker, tokens);
553
+ //session.setTokens(this.tokens);
412
554
  this.session = session;
413
555
  // @ts-ignore
414
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
556
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
415
557
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
416
558
  success: true,
417
- message: "tokens inside ServiceWorker are valid"
559
+ message: `tokens inside storage are valid`
418
560
  });
419
561
  return true;
420
562
  }
@@ -425,6 +567,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
425
567
  });
426
568
  return false;
427
569
  } catch (exception) {
570
+ console.error(exception);
428
571
  if (serviceWorker) {
429
572
  await serviceWorker.clearAsync();
430
573
  }
@@ -440,109 +583,168 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
440
583
  });
441
584
  }
442
585
 
443
- async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined) {
444
- try {
445
- const location = window.location;
446
- const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
447
- this.publishEvent(eventNames.loginAsync_begin, {});
448
- const configuration = this.configuration
449
- // Security we cannot loggin from Iframe
450
- if (!configuration.silent_redirect_uri && isInIframe()) {
451
- throw new Error("Login from iframe is forbidden");
452
- }
453
- sessionStorage[`oidc_login.${this.configurationName}`] = JSON.stringify({callbackPath:url,extras,state});
454
-
455
- let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
456
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
457
- if(serviceWorker && installServiceWorker) {
458
- const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
459
- if(!isServiceWorkerProxyActive) {
460
- window.location.href = `${configuration.redirect_uri}/service-worker-install`;
461
- return;
586
+ loginPromise: Promise<any>=null;
587
+ async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined, isSilentSignin:boolean=false, scope:string=undefined) {
588
+ if(this.loginPromise !== null){
589
+ return this.loginPromise;
590
+ }
591
+
592
+ const loginLocalAsync=async () => {
593
+ try {
594
+ const location = window.location;
595
+ const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
596
+ this.publishEvent(eventNames.loginAsync_begin, {});
597
+ const configuration = this.configuration;
598
+
599
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
600
+ if (!scope) {
601
+ scope = configuration.scope;
602
+ }
603
+
604
+ const sessionKey = getLoginSessionKey(this.configurationName, redirectUri);
605
+ sessionStorage[sessionKey] = JSON.stringify({callbackPath: url, extras, state});
606
+
607
+ let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
608
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
609
+ /*if (serviceWorker && installServiceWorker) {
610
+ const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
611
+ if (!isServiceWorkerProxyActive) {
612
+ await serviceWorker.unregisterAsync();
613
+ const extrasQueries = extras != null ? {...extras}: {};
614
+ extrasQueries.callbackPath = url;
615
+ extrasQueries.state = state;
616
+ const queryString = buildQueries(extrasQueries);
617
+ window.location.href = `${redirectUri}/service-worker-install${queryString}`;
618
+ return;
619
+ }
620
+ }*/
621
+ let storage;
622
+ if (serviceWorker) {
623
+ serviceWorker.startKeepAliveServiceWorker();
624
+ await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
625
+ storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
626
+ await storage.setItem("dummy", {});
627
+ } else {
628
+ const session = initSession(this.configurationName, redirectUri);
629
+ storage = new MemoryStorageBackend(session.saveItemsAsync, {});
462
630
  }
631
+
632
+ const extraFinal = extras ?? configuration.extras ?? {};
633
+
634
+ // @ts-ignore
635
+ const queryStringUtil = redirectUri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
636
+ const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
637
+ const authRequest = new AuthorizationRequest({
638
+ client_id: configuration.client_id,
639
+ redirect_uri: redirectUri,
640
+ scope,
641
+ response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
642
+ state,
643
+ extras: extraFinal
644
+ });
645
+ authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
646
+ } catch (exception) {
647
+ this.publishEvent(eventNames.loginAsync_error, exception);
648
+ throw exception;
463
649
  }
464
- let storage;
465
- if(serviceWorker) {
466
- serviceWorker.startKeepAliveServiceWorker();
467
- await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
468
- storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
469
- await storage.setItem("dummy",{});
650
+ }
651
+ this.loginPromise = loginLocalAsync();
652
+ return this.loginPromise.then(result =>{
653
+ this.loginPromise = null;
654
+ return result;
655
+ });
656
+
657
+ }
658
+
659
+ async startCheckSessionAsync(checkSessionIFrameUri, clientId, sessionState, isSilentSignin=false){
660
+ return new Promise((resolve:Function, reject) => {
661
+ if (this.configuration.silent_signin_uri && this.configuration.silent_redirect_uri && this.configuration.monitor_session && checkSessionIFrameUri && sessionState && !isSilentSignin) {
662
+ const checkSessionCallback = () => {
663
+ this.checkSessionIFrame.stop();
664
+
665
+ if(this.tokens === null){
666
+ return;
667
+ }
668
+ // @ts-ignore
669
+ const idToken = this.tokens.idToken;
670
+ // @ts-ignore
671
+ const idTokenPayload = this.tokens.idTokenPayload;
672
+ this.silentSigninAsync({
673
+ prompt: "none",
674
+ id_token_hint: idToken,
675
+ scope: "openid"
676
+ }).then((silentSigninResponse) => {
677
+ const iFrameIdTokenPayload = silentSigninResponse.tokens.idTokenPayload;
678
+ if (idTokenPayload.sub === iFrameIdTokenPayload.sub) {
679
+ const sessionState = silentSigninResponse.sessionState;
680
+ this.checkSessionIFrame.start(silentSigninResponse.sessionState);
681
+ if (idTokenPayload.sid === iFrameIdTokenPayload.sid) {
682
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:", sessionState);
683
+ } else {
684
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:", sessionState);
685
+ }
686
+ }
687
+ else {
688
+ console.debug("SessionMonitor._callback: Different subject signed into OP:", iFrameIdTokenPayload.sub);
689
+ }
690
+ }).catch((e) => {
691
+ this.publishEvent(eventNames.logout_from_another_tab, {});
692
+ this.destroyAsync();
693
+ });
694
+
695
+ };
696
+
697
+ this.checkSessionIFrame = new CheckSessionIFrame(checkSessionCallback, clientId, checkSessionIFrameUri);
698
+ this.checkSessionIFrame.load().then(() => {
699
+ this.checkSessionIFrame.start(sessionState);
700
+ resolve();
701
+ }).catch((e) =>{
702
+ reject(e);
703
+ });
470
704
  } else {
471
- const session = initSession(this.configurationName);
472
- storage = new MemoryStorageBackend(session.saveItemsAsync, {});
705
+ resolve();
473
706
  }
474
-
475
- // @ts-ignore
476
- const queryStringUtil = configuration.redirect_uri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
477
- const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
478
- const authRequest = new AuthorizationRequest({
479
- client_id: configuration.client_id,
480
- redirect_uri: configuration.redirect_uri,
481
- scope: configuration.scope,
482
- response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
483
- state,
484
- extras: extras ?? configuration.extras
485
- });
486
- authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
487
- } catch(exception) {
488
- this.publishEvent(eventNames.loginAsync_error, exception);
489
- throw exception;
490
- }
707
+ });
491
708
  }
492
709
 
493
- syncTokensAsyncPromise=null;
494
- async syncTokensAsync() {
495
- // 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)
496
- const configuration = this.configuration;
497
- if(!this.tokens){
498
- return;
710
+ loginCallbackPromise : Promise<any>=null
711
+ async loginCallbackAsync(isSilenSignin:boolean=false){
712
+ if(this.loginCallbackPromise !== null){
713
+ return this.loginCallbackPromise;
499
714
  }
500
-
501
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
502
- const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
503
- if (serviceWorker) {
504
- const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
505
- if(!tokens){
506
- try {
507
- this.publishEvent(eventNames.syncTokensAsync_begin, {});
508
- this.syncTokensAsyncPromise = this.silentSigninAsync();
509
- const silent_token_response = await this.syncTokensAsyncPromise;
510
- console.log("silent_token_response")
511
- console.log(silent_token_response)
512
- if (silent_token_response) {
513
- this.tokens = await setTokensAsync(serviceWorker, silent_token_response);
514
- } else{
515
- this.publishEvent(eventNames.syncTokensAsync_error, null);
516
- if(this.timeoutId){
517
- timer.clearTimeout(this.timeoutId);
518
- this.timeoutId=null;
519
- }
520
- return;
521
- }
522
- } catch (exceptionSilent) {
523
- console.error(exceptionSilent);
524
- this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
525
- if(this.timeoutId){
526
- timer.clearTimeout(this.timeoutId);
527
- this.timeoutId=null;
528
- }
529
- return;
530
- }
531
- this.syncTokensAsyncPromise = null;
532
- this.publishEvent(eventNames.syncTokensAsync_end, {});
715
+
716
+ const loginCallbackLocalAsync= async( ) =>{
717
+ const response = await this._loginCallbackAsync(isSilenSignin);
718
+ // @ts-ignore
719
+ const tokens = response.tokens;
720
+ const parsedTokens = await setTokensAsync(this.serviceWorker, tokens);
721
+ this.tokens = parsedTokens;
722
+ if(!this.serviceWorker){
723
+ await this.session.setTokens(parsedTokens);
533
724
  }
725
+ this.publishEvent(Oidc.eventNames.token_aquired, parsedTokens);
726
+ // @ts-ignore
727
+ return { parsedTokens, state:response.state, callbackPath : response.callbackPath};
534
728
  }
729
+
730
+ this.loginCallbackPromise = loginCallbackLocalAsync();
731
+ return this.loginCallbackPromise.then(result =>{
732
+ this.loginCallbackPromise = null;
733
+ return result;
734
+ })
535
735
  }
536
-
537
- async loginCallbackAsync(){
736
+
737
+ async _loginCallbackAsync(isSilentSignin:boolean=false){
538
738
  try {
539
739
  this.publishEvent(eventNames.loginCallbackAsync_begin, {});
540
740
  const configuration = this.configuration;
541
741
  const clientId = configuration.client_id;
542
- const redirectURL = configuration.redirect_uri;
742
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
543
743
  const authority = configuration.authority;
544
744
  const tokenRequestTimeout = configuration.token_request_timeout;
545
745
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
746
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
747
+ const sessionState = queryParams.session_state;
546
748
  const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
547
749
  let storage = null;
548
750
  if(serviceWorker){
@@ -556,16 +758,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
556
758
  throw new Error("Service Worker storage disapear");
557
759
  }
558
760
  await storage.removeItem("dummy");
761
+ await serviceWorker.setSessionStateAsync(sessionState);
559
762
  }else{
560
- const session = initSession(this.configurationName);
561
- this.session = session;
763
+
764
+ this.session = initSession(this.configurationName, redirectUri, configuration.storage ?? sessionStorage);
765
+ const session = initSession(this.configurationName, redirectUri);
766
+ session.setSessionState(sessionState);
562
767
  const items = await session.loadItemsAsync();
563
768
  storage = new MemoryStorageBackend(session.saveItemsAsync, items);
564
769
  }
565
770
  return new Promise((resolve, reject) => {
566
771
  // @ts-ignore
567
772
  let queryStringUtil = new NoHashQueryStringUtils();
568
- if(configuration.redirect_uri.includes("#")) {
773
+ if(redirectUri.includes("#")) {
569
774
  const splithash = window.location.href.split("#");
570
775
  if (splithash.length === 2 && splithash[1].includes("?")) {
571
776
  queryStringUtil = new HashQueryStringUtils();
@@ -599,14 +804,14 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
599
804
 
600
805
  const tokenRequest = new TokenRequest({
601
806
  client_id: clientId,
602
- redirect_uri: redirectURL,
807
+ redirect_uri: redirectUri,
603
808
  grant_type: GRANT_TYPE_AUTHORIZATION_CODE,
604
809
  code: response.code,
605
810
  refresh_token: undefined,
606
811
  extras,
607
812
  });
608
813
 
609
- let timeoutId = setTimeout(function(){
814
+ let timeoutId = setTimeout(()=>{
610
815
  reject("performTokenRequest timeout");
611
816
  timeoutId=null;
612
817
  }, tokenRequestTimeout ?? 12000);
@@ -614,14 +819,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
614
819
  const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
615
820
  tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest).then((tokenResponse)=>{
616
821
  if(timeoutId) {
617
- const loginParams = getLoginParams(this.configurationName);
618
822
  clearTimeout(timeoutId);
619
823
  this.timeoutId=null;
620
- this.publishEvent(eventNames.loginCallbackAsync_end, {});
621
- resolve({
622
- tokens: tokenResponse,
623
- state: request.state,
624
- callbackPath: loginParams.callbackPath,
824
+ const loginParams = getLoginParams(this.configurationName, redirectUri);
825
+ this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, clientId, sessionState, isSilentSignin).then(() =>{
826
+ this.publishEvent(eventNames.loginCallbackAsync_end, {});
827
+ resolve({
828
+ tokens: tokenResponse,
829
+ state: request.state,
830
+ callbackPath: loginParams.callbackPath,
831
+ });
625
832
  });
626
833
  }
627
834
  });
@@ -642,7 +849,6 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
642
849
  this.publishEvent(eventNames.loginCallbackAsync_error, exception);
643
850
  throw exception;
644
851
  }
645
-
646
852
  }
647
853
 
648
854
  async refreshTokensAsync(refreshToken, silentEvent = false) {
@@ -650,7 +856,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
650
856
  try {
651
857
  const silent_token_response = await this.silentSigninAsync();
652
858
  if (silent_token_response) {
653
- return silent_token_response;
859
+ return silent_token_response.tokens;
654
860
  }
655
861
  } catch (exceptionSilent) {
656
862
  console.error(exceptionSilent);
@@ -704,6 +910,60 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
704
910
  }
705
911
  }
706
912
 
913
+ syncTokensAsyncPromise=null;
914
+ async syncTokensAsync() {
915
+ // 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)
916
+ const configuration = this.configuration;
917
+ if(!this.tokens){
918
+ return;
919
+ }
920
+
921
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
922
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
923
+ if (serviceWorker) {
924
+ const { isLogin } = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
925
+ if(isLogin == false){
926
+ this.publishEvent(eventNames.logout_from_another_tab, {});
927
+ await this.destroyAsync();
928
+ }
929
+ else if (isLogin == null){
930
+ try {
931
+ this.publishEvent(eventNames.syncTokensAsync_begin, {});
932
+ this.syncTokensAsyncPromise = this.silentSigninAsync({prompt:"none"});
933
+ const silent_token_response = await this.syncTokensAsyncPromise;
934
+ if (silent_token_response && silent_token_response.tokens) {
935
+ this.tokens = await setTokensAsync(serviceWorker, silent_token_response.tokens);
936
+ } else{
937
+ this.publishEvent(eventNames.syncTokensAsync_error, null);
938
+ if(this.timeoutId){
939
+ timer.clearTimeout(this.timeoutId);
940
+ this.timeoutId=null;
941
+ }
942
+ return;
943
+ }
944
+ } catch (exceptionSilent) {
945
+ console.error(exceptionSilent);
946
+ this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
947
+ if(this.timeoutId){
948
+ timer.clearTimeout(this.timeoutId);
949
+ this.timeoutId=null;
950
+ }
951
+ return;
952
+ }
953
+ this.syncTokensAsyncPromise = null;
954
+ this.publishEvent(eventNames.syncTokensAsync_end, {});
955
+ }
956
+ } else {
957
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
958
+ const {tokens} = await session.initAsync();
959
+ if(!tokens){
960
+ this.publishEvent(eventNames.logout_from_another_tab, {});
961
+ await this.destroyAsync();
962
+ }
963
+ }
964
+ }
965
+
966
+
707
967
  loginCallbackWithAutoTokensRenewPromise:Promise<loginCallbackResult> = null;
708
968
  loginCallbackWithAutoTokensRenewAsync():Promise<loginCallbackResult>{
709
969
  if(this.loginCallbackWithAutoTokensRenewPromise !== null){
@@ -721,6 +981,9 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
721
981
  }
722
982
 
723
983
  async destroyAsync() {
984
+ if(this.checkSessionIFrame){
985
+ this.checkSessionIFrame.stop();
986
+ }
724
987
  if(this.serviceWorker){
725
988
  await this.serviceWorker.clearAsync();
726
989
  }