@axa-fr/react-oidc 5.14.0 → 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 (63) hide show
  1. package/dist/OidcProvider.d.ts +1 -0
  2. package/dist/OidcProvider.d.ts.map +1 -1
  3. package/dist/OidcProvider.js +13 -5
  4. package/dist/OidcProvider.js.map +1 -1
  5. package/dist/OidcServiceWorker.js +13 -0
  6. package/dist/ReactOidc.d.ts.map +1 -1
  7. package/dist/ReactOidc.js +29 -7
  8. package/dist/ReactOidc.js.map +1 -1
  9. package/dist/core/default-component/ServiceWorkerInstall.component.d.ts.map +1 -1
  10. package/dist/core/default-component/ServiceWorkerInstall.component.js +21 -9
  11. package/dist/core/default-component/ServiceWorkerInstall.component.js.map +1 -1
  12. package/dist/core/default-component/SilentCallback.component.d.ts.map +1 -1
  13. package/dist/core/default-component/SilentCallback.component.js +23 -15
  14. package/dist/core/default-component/SilentCallback.component.js.map +1 -1
  15. package/dist/core/default-component/SilentSignin.component.d.ts +4 -0
  16. package/dist/core/default-component/SilentSignin.component.d.ts.map +1 -0
  17. package/dist/core/default-component/SilentSignin.component.js +58 -0
  18. package/dist/core/default-component/SilentSignin.component.js.map +1 -0
  19. package/dist/core/routes/OidcRoutes.d.ts +1 -0
  20. package/dist/core/routes/OidcRoutes.d.ts.map +1 -1
  21. package/dist/core/routes/OidcRoutes.js +8 -2
  22. package/dist/core/routes/OidcRoutes.js.map +1 -1
  23. package/dist/vanilla/checkSessionIFrame.d.ts +17 -0
  24. package/dist/vanilla/checkSessionIFrame.d.ts.map +1 -0
  25. package/dist/vanilla/checkSessionIFrame.js +78 -0
  26. package/dist/vanilla/checkSessionIFrame.js.map +1 -0
  27. package/dist/vanilla/initSession.d.ts +3 -1
  28. package/dist/vanilla/initSession.d.ts.map +1 -1
  29. package/dist/vanilla/initSession.js +20 -11
  30. package/dist/vanilla/initSession.js.map +1 -1
  31. package/dist/vanilla/initWorker.d.ts +4 -0
  32. package/dist/vanilla/initWorker.d.ts.map +1 -1
  33. package/dist/vanilla/initWorker.js +31 -3
  34. package/dist/vanilla/initWorker.js.map +1 -1
  35. package/dist/vanilla/oidc.d.ts +24 -5
  36. package/dist/vanilla/oidc.d.ts.map +1 -1
  37. package/dist/vanilla/oidc.js +504 -224
  38. package/dist/vanilla/oidc.js.map +1 -1
  39. package/dist/vanilla/route-utils.d.ts +13 -0
  40. package/dist/vanilla/route-utils.d.ts.map +1 -0
  41. package/dist/vanilla/route-utils.js +65 -0
  42. package/dist/vanilla/route-utils.js.map +1 -0
  43. package/package.json +1 -1
  44. package/src/App.tsx +1 -1
  45. package/src/configurations.ts +8 -4
  46. package/src/oidc/OidcProvider.tsx +11 -0
  47. package/src/oidc/ReactOidc.tsx +32 -8
  48. package/src/oidc/core/default-component/ServiceWorkerInstall.component.tsx +15 -3
  49. package/src/oidc/core/default-component/SilentCallback.component.tsx +10 -15
  50. package/src/oidc/core/default-component/SilentSignin.component.tsx +35 -0
  51. package/src/oidc/core/routes/OidcRoutes.tsx +10 -1
  52. package/src/oidc/vanilla/OidcServiceWorker.js +13 -0
  53. package/src/oidc/vanilla/checkSessionIFrame.ts +82 -0
  54. package/src/oidc/vanilla/initSession.ts +23 -11
  55. package/src/oidc/vanilla/initWorker.ts +19 -2
  56. package/src/oidc/vanilla/oidc.ts +400 -137
  57. package/src/oidc/{core/routes → vanilla}/route-utils.spec.ts +0 -0
  58. package/src/oidc/vanilla/route-utils.ts +76 -0
  59. package/dist/core/routes/route-utils.d.ts +0 -2
  60. package/dist/core/routes/route-utils.d.ts.map +0 -1
  61. package/dist/core/routes/route-utils.js +0 -32
  62. package/dist/core/routes/route-utils.js.map +0 -1
  63. 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,15 +92,18 @@ 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
98
  export type OidcConfiguration = {
76
99
  client_id: string,
77
100
  redirect_uri: string,
78
101
  silent_redirect_uri?:string,
102
+ silent_signin_uri?:string,
79
103
  silent_signin_timeout?:number,
80
104
  scope: string,
81
105
  authority: string,
106
+ authority_time_cache_wellknowurl_in_second?: number,
82
107
  authority_configuration?: AuthorityConfiguration,
83
108
  refresh_time_before_tokens_expiration_in_second?: number,
84
109
  token_request_timeout?: number,
@@ -87,6 +112,7 @@ export interface AuthorityConfiguration {
87
112
  extras?:StringMap
88
113
  token_request_extras?:StringMap,
89
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,6 +309,7 @@ export class Oidc {
234
309
  private serviceWorker?: any;
235
310
  private configurationName: string;
236
311
  private session?: any;
312
+ private checkSessionIFrame: CheckSessionIFrame;
237
313
  constructor(configuration:OidcConfiguration, configurationName="default") {
238
314
  this.configuration = configuration
239
315
  this.configurationName= configurationName;
@@ -247,6 +323,7 @@ export class Oidc {
247
323
  this.loginCallbackWithAutoTokensRenewAsync.bind(this);
248
324
  this.initAsync.bind(this);
249
325
  this.loginCallbackAsync.bind(this);
326
+ this._loginCallbackAsync.bind(this);
250
327
  this.subscriveEvents.bind(this);
251
328
  this.removeEventSubscription.bind(this);
252
329
  this.publishEvent.bind(this);
@@ -284,11 +361,18 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
284
361
 
285
362
  silentSigninCallbackFromIFrame(){
286
363
  if (this.configuration.silent_redirect_uri) {
287
- 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);
288
372
  }
289
373
  }
290
- async silentSigninAsync() {
291
- 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) {
292
376
  return Promise.resolve(null);
293
377
  }
294
378
  while (document.hidden) {
@@ -299,10 +383,38 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
299
383
  try {
300
384
  this.publishEvent(eventNames.silentSigninAsync_begin, {});
301
385
  const configuration = this.configuration
302
- 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);
303
414
  const iframe = document.createElement('iframe');
304
415
  iframe.width = "0px";
305
416
  iframe.height = "0px";
417
+
306
418
  iframe.id = `${this.configurationName}_oidc_iframe`;
307
419
  iframe.setAttribute("src", link);
308
420
  document.body.appendChild(iframe);
@@ -311,18 +423,33 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
311
423
  try {
312
424
  let isResolved = false;
313
425
  window.onmessage = function (e) {
314
- const key = `${self.configurationName}_oidc_tokens:`;
315
- if (e.data && typeof (e.data) === "string" && e.data.startsWith(key)) {
316
- if (!isResolved) {
317
- const result = JSON.parse(e.data.replace(key, ''));
318
- self.publishEvent(eventNames.silentSigninAsync_end, result);
319
- iframe.remove();
320
- isResolved = true;
321
- 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
+ }
322
449
  }
323
450
  }
324
451
  };
325
- const silentSigninTimeout = configuration.silent_signin_timeout ? configuration.silent_signin_timeout : 12000
452
+ const silentSigninTimeout = configuration.silent_signin_timeout ?? 12000
326
453
  setTimeout(() => {
327
454
  if (!isResolved) {
328
455
  self.publishEvent(eventNames.silentSigninAsync_error, "timeout");
@@ -345,17 +472,20 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
345
472
  initAsyncPromise = null;
346
473
  async initAsync(authority:string, authorityConfiguration:AuthorityConfiguration) {
347
474
  if (authorityConfiguration != null) {
348
- return new AuthorizationServiceConfiguration( {
475
+ return new OidcAuthorizationServiceConfiguration( {
349
476
  authorization_endpoint: authorityConfiguration.authorization_endpoint,
350
477
  end_session_endpoint: authorityConfiguration.end_session_endpoint,
351
478
  revocation_endpoint: authorityConfiguration.revocation_endpoint,
352
479
  token_endpoint: authorityConfiguration.token_endpoint,
353
- userinfo_endpoint: authorityConfiguration.userinfo_endpoint});
480
+ userinfo_endpoint: authorityConfiguration.userinfo_endpoint,
481
+ check_session_iframe:authorityConfiguration.check_session_iframe,
482
+ });
354
483
  }
355
484
  if(this.initAsyncPromise){
356
485
  return this.initAsyncPromise;
357
486
  }
358
- 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);
359
489
  return this.initAsyncPromise;
360
490
  }
361
491
 
@@ -379,12 +509,21 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
379
509
  const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
380
510
  if (tokens) {
381
511
  serviceWorker.startKeepAliveServiceWorker();
382
- 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);
383
515
  // @ts-ignore
384
- 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);
385
524
  this.serviceWorker = serviceWorker;
386
525
  // @ts-ignore
387
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
526
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
388
527
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
389
528
  success: true,
390
529
  message: "tokens inside ServiceWorker are valid"
@@ -401,16 +540,20 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
401
540
  message: "service worker is not supported by this browser"
402
541
  });
403
542
  }
404
- const session = initSession(this.configurationName, configuration.storage ?? sessionStorage);
543
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
405
544
  const {tokens} = await session.initAsync();
545
+ console.log("const {tokens} = await session.initAsync();")
546
+ console.log(tokens)
406
547
  if (tokens) {
407
- 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);
408
551
  // @ts-ignore
409
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
410
- session.setTokens(this.tokens);
552
+ this.tokens = await setTokensAsync(serviceWorker, tokens);
553
+ //session.setTokens(this.tokens);
411
554
  this.session = session;
412
555
  // @ts-ignore
413
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
556
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
414
557
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
415
558
  success: true,
416
559
  message: `tokens inside storage are valid`
@@ -424,6 +567,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
424
567
  });
425
568
  return false;
426
569
  } catch (exception) {
570
+ console.error(exception);
427
571
  if (serviceWorker) {
428
572
  await serviceWorker.clearAsync();
429
573
  }
@@ -439,109 +583,168 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
439
583
  });
440
584
  }
441
585
 
442
- async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined) {
443
- try {
444
- const location = window.location;
445
- const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
446
- this.publishEvent(eventNames.loginAsync_begin, {});
447
- const configuration = this.configuration
448
- // Security we cannot loggin from Iframe
449
- if (!configuration.silent_redirect_uri && isInIframe()) {
450
- throw new Error("Login from iframe is forbidden");
451
- }
452
- sessionStorage[`oidc_login.${this.configurationName}`] = JSON.stringify({callbackPath:url,extras,state});
453
-
454
- let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
455
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
456
- if(serviceWorker && installServiceWorker) {
457
- const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
458
- if(!isServiceWorkerProxyActive) {
459
- window.location.href = `${configuration.redirect_uri}/service-worker-install`;
460
- 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, {});
461
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;
462
649
  }
463
- let storage;
464
- if(serviceWorker) {
465
- serviceWorker.startKeepAliveServiceWorker();
466
- await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
467
- storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
468
- 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
+ });
469
704
  } else {
470
- const session = initSession(this.configurationName);
471
- storage = new MemoryStorageBackend(session.saveItemsAsync, {});
705
+ resolve();
472
706
  }
473
-
474
- // @ts-ignore
475
- const queryStringUtil = configuration.redirect_uri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
476
- const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
477
- const authRequest = new AuthorizationRequest({
478
- client_id: configuration.client_id,
479
- redirect_uri: configuration.redirect_uri,
480
- scope: configuration.scope,
481
- response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
482
- state,
483
- extras: extras ?? configuration.extras
484
- });
485
- authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
486
- } catch(exception) {
487
- this.publishEvent(eventNames.loginAsync_error, exception);
488
- throw exception;
489
- }
707
+ });
490
708
  }
491
709
 
492
- syncTokensAsyncPromise=null;
493
- async syncTokensAsync() {
494
- // 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)
495
- const configuration = this.configuration;
496
- if(!this.tokens){
497
- return;
710
+ loginCallbackPromise : Promise<any>=null
711
+ async loginCallbackAsync(isSilenSignin:boolean=false){
712
+ if(this.loginCallbackPromise !== null){
713
+ return this.loginCallbackPromise;
498
714
  }
499
-
500
- const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
501
- const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
502
- if (serviceWorker) {
503
- const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
504
- if(!tokens){
505
- try {
506
- this.publishEvent(eventNames.syncTokensAsync_begin, {});
507
- this.syncTokensAsyncPromise = this.silentSigninAsync();
508
- const silent_token_response = await this.syncTokensAsyncPromise;
509
- console.log("silent_token_response")
510
- console.log(silent_token_response)
511
- if (silent_token_response) {
512
- this.tokens = await setTokensAsync(serviceWorker, silent_token_response);
513
- } else{
514
- this.publishEvent(eventNames.syncTokensAsync_error, null);
515
- if(this.timeoutId){
516
- timer.clearTimeout(this.timeoutId);
517
- this.timeoutId=null;
518
- }
519
- return;
520
- }
521
- } catch (exceptionSilent) {
522
- console.error(exceptionSilent);
523
- this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
524
- if(this.timeoutId){
525
- timer.clearTimeout(this.timeoutId);
526
- this.timeoutId=null;
527
- }
528
- return;
529
- }
530
- this.syncTokensAsyncPromise = null;
531
- 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);
532
724
  }
725
+ this.publishEvent(Oidc.eventNames.token_aquired, parsedTokens);
726
+ // @ts-ignore
727
+ return { parsedTokens, state:response.state, callbackPath : response.callbackPath};
533
728
  }
729
+
730
+ this.loginCallbackPromise = loginCallbackLocalAsync();
731
+ return this.loginCallbackPromise.then(result =>{
732
+ this.loginCallbackPromise = null;
733
+ return result;
734
+ })
534
735
  }
535
-
536
- async loginCallbackAsync(){
736
+
737
+ async _loginCallbackAsync(isSilentSignin:boolean=false){
537
738
  try {
538
739
  this.publishEvent(eventNames.loginCallbackAsync_begin, {});
539
740
  const configuration = this.configuration;
540
741
  const clientId = configuration.client_id;
541
- const redirectURL = configuration.redirect_uri;
742
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
542
743
  const authority = configuration.authority;
543
744
  const tokenRequestTimeout = configuration.token_request_timeout;
544
745
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
746
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
747
+ const sessionState = queryParams.session_state;
545
748
  const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
546
749
  let storage = null;
547
750
  if(serviceWorker){
@@ -555,17 +758,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
555
758
  throw new Error("Service Worker storage disapear");
556
759
  }
557
760
  await storage.removeItem("dummy");
761
+ await serviceWorker.setSessionStateAsync(sessionState);
558
762
  }else{
559
763
 
560
- this.session = initSession(this.configurationName, configuration.storage ?? sessionStorage);
561
- const session = initSession(this.configurationName);
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
  }