@axa-fr/react-oidc 5.14.0 → 6.0.0-alpha2

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 (66) 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 +29 -1
  6. package/dist/OidcTrustedDomains.js +7 -4
  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 +20 -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 +29 -3
  35. package/dist/vanilla/initWorker.js.map +1 -1
  36. package/dist/vanilla/oidc.d.ts +24 -7
  37. package/dist/vanilla/oidc.d.ts.map +1 -1
  38. package/dist/vanilla/oidc.js +526 -241
  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/MultiAuth.tsx +2 -2
  47. package/src/configurations.ts +6 -2
  48. package/src/oidc/OidcProvider.tsx +11 -0
  49. package/src/oidc/ReactOidc.tsx +32 -8
  50. package/src/oidc/core/default-component/ServiceWorkerInstall.component.tsx +15 -3
  51. package/src/oidc/core/default-component/SilentCallback.component.tsx +10 -15
  52. package/src/oidc/core/default-component/SilentSignin.component.tsx +35 -0
  53. package/src/oidc/core/routes/OidcRoutes.tsx +10 -1
  54. package/src/oidc/vanilla/OidcServiceWorker.js +29 -1
  55. package/src/oidc/vanilla/OidcTrustedDomains.js +7 -4
  56. package/src/oidc/vanilla/checkSessionIFrame.ts +82 -0
  57. package/src/oidc/vanilla/initSession.ts +23 -11
  58. package/src/oidc/vanilla/initWorker.ts +19 -2
  59. package/src/oidc/vanilla/oidc.ts +410 -152
  60. package/src/oidc/{core/routes → vanilla}/route-utils.spec.ts +0 -0
  61. package/src/oidc/vanilla/route-utils.ts +76 -0
  62. package/dist/core/routes/route-utils.d.ts +0 -2
  63. package/dist/core/routes/route-utils.d.ts.map +0 -1
  64. package/dist/core/routes/route-utils.js +0 -32
  65. package/dist/core/routes/route-utils.js.map +0 -1
  66. package/src/oidc/core/routes/route-utils.ts +0 -34
@@ -10,18 +10,32 @@ 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
- const isInIframe = () => {
20
- try {
21
- return window.self !== window.top;
22
- } catch (e) {
23
- return true;
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;
24
37
  }
38
+
25
39
  }
26
40
 
27
41
  const idTokenPayload = (token) => {
@@ -70,15 +84,18 @@ export interface AuthorityConfiguration {
70
84
  revocation_endpoint: string;
71
85
  end_session_endpoint?: string;
72
86
  userinfo_endpoint?: string;
87
+ check_session_iframe?:string;
73
88
  }
74
89
 
75
90
  export type OidcConfiguration = {
76
91
  client_id: string,
77
92
  redirect_uri: string,
78
93
  silent_redirect_uri?:string,
94
+ silent_signin_uri?:string,
79
95
  silent_signin_timeout?:number,
80
96
  scope: string,
81
97
  authority: string,
98
+ authority_time_cache_wellknowurl_in_second?: number,
82
99
  authority_configuration?: AuthorityConfiguration,
83
100
  refresh_time_before_tokens_expiration_in_second?: number,
84
101
  token_request_timeout?: number,
@@ -87,6 +104,7 @@ export interface AuthorityConfiguration {
87
104
  extras?:StringMap
88
105
  token_request_extras?:StringMap,
89
106
  storage?: Storage
107
+ monitor_session?: boolean
90
108
  };
91
109
 
92
110
  const oidcDatabase = {};
@@ -99,22 +117,16 @@ const oidcFactory = (configuration: OidcConfiguration, name="default") => {
99
117
  }
100
118
 
101
119
  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 };
120
+ const { parsedTokens, state, callbackPath } = await oidc.loginCallbackAsync();
121
+ oidc.timeoutId = autoRenewTokens(oidc, parsedTokens.refreshToken, parsedTokens.expiresAt)
122
+ return { state, callbackPath };
111
123
  }
112
124
 
113
125
  const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
114
126
  const refreshTimeBeforeTokensExpirationInSecond = oidc.configuration.refresh_time_before_tokens_expiration_in_second ?? 60;
115
127
  return timer.setTimeout(async () => {
116
128
  const currentTimeUnixSecond = new Date().getTime() /1000;
117
- const timeInfo = { timeLeft: Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond)- currentTimeUnixSecond))};
129
+ const timeInfo = { timeLeft: Math.round(((expiresAt - refreshTimeBeforeTokensExpirationInSecond) - currentTimeUnixSecond))};
118
130
  oidc.publishEvent(Oidc.eventNames.token_timer, timeInfo);
119
131
  if(currentTimeUnixSecond > (expiresAt - refreshTimeBeforeTokensExpirationInSecond)) {
120
132
  const tokens = await oidc.refreshTokensAsync(refreshToken);
@@ -123,9 +135,13 @@ const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
123
135
  await oidc.session.setTokens(oidc.tokens);
124
136
  }
125
137
  if(!oidc.tokens){
138
+ if(oidc.checkSessionIFrame){
139
+ oidc.checkSessionIFrame.stop();
140
+ oidc.checkSessionIFrame = null;
141
+ }
126
142
  return;
127
143
  }
128
- oidc.publishEvent(Oidc.eventNames.token_renewed, oidc.tokens);
144
+ oidc.publishEvent(Oidc.eventNames.token_renewed, {});
129
145
  if(oidc.timeoutId) {
130
146
  oidc.timeoutId = autoRenewTokens(oidc, tokens.refreshToken, oidc.tokens.expiresAt);
131
147
  }
@@ -138,10 +154,14 @@ const autoRenewTokens = (oidc, refreshToken, expiresAt) => {
138
154
  }, 1000);
139
155
  }
140
156
 
141
- export const getLoginParams = (configurationName) => {
142
- return JSON.parse(sessionStorage[`oidc_login.${configurationName}`]);
157
+ const getLoginSessionKey = (configurationName:string, redirectUri:string) => {
158
+ return `oidc_login.${configurationName}:${redirectUri}`;
159
+ }
160
+ const getLoginParams = (configurationName, redirectUri) => {
161
+ return JSON.parse(sessionStorage[getLoginSessionKey(configurationName, redirectUri)]);
143
162
  }
144
163
 
164
+
145
165
  const userInfoAsync = async (oidc) => {
146
166
  if(oidc.userInfo != null){
147
167
  return oidc.userInfo;
@@ -188,13 +208,15 @@ const setTokensAsync = async (serviceWorker, tokens) =>{
188
208
  else {
189
209
  accessTokenPayload = extractAccessTokenPayload(tokens);
190
210
  }
191
- const expiresAt = tokens.issuedAt + tokens.expiresIn;
192
- return {...tokens, idTokenPayload: idTokenPayload(tokens.idToken), accessTokenPayload, expiresAt};
211
+ const _idTokenPayload = idTokenPayload(tokens.idToken);
212
+ const expiresAt = (_idTokenPayload && _idTokenPayload.exp) ? _idTokenPayload.exp : tokens.issuedAt + tokens.expiresIn;
213
+ return {...tokens, idTokenPayload: _idTokenPayload, accessTokenPayload, expiresAt};
193
214
  }
194
215
 
195
216
  const eventNames = {
196
217
  service_worker_not_supported_by_browser: "service_worker_not_supported_by_browser",
197
218
  token_aquired: "token_aquired",
219
+ logout_from_another_tab: "logout_from_another_tab",
198
220
  token_renewed: "token_renewed",
199
221
  token_timer: "token_timer",
200
222
  loginAsync_begin:"loginAsync_begin",
@@ -205,8 +227,6 @@ const eventNames = {
205
227
  refreshTokensAsync_begin: "refreshTokensAsync_begin",
206
228
  refreshTokensAsync_end: "refreshTokensAsync_end",
207
229
  refreshTokensAsync_error: "refreshTokensAsync_error",
208
- refreshTokensAsync_silent_begin: "refreshTokensAsync_silent_begin",
209
- refreshTokensAsync_silent_end: "refreshTokensAsync_silent_end",
210
230
  refreshTokensAsync_silent_error: "refreshTokensAsync_silent_error",
211
231
  tryKeepExistingSessionAsync_begin: "tryKeepExistingSessionAsync_begin",
212
232
  tryKeepExistingSessionAsync_end: "tryKeepExistingSessionAsync_end",
@@ -225,6 +245,51 @@ const getRandomInt = (max) => {
225
245
  return Math.floor(Math.random() * max);
226
246
  }
227
247
 
248
+ const WELL_KNOWN_PATH = '.well-known';
249
+ const OPENID_CONFIGURATION = 'openid-configuration';
250
+
251
+
252
+ const oneHourSecond = 60 * 60;
253
+ const fetchFromIssuer = async (openIdIssuerUrl: string, timeCacheSecond = oneHourSecond):
254
+ Promise<OidcAuthorizationServiceConfiguration> => {
255
+ const fullUrl = `${openIdIssuerUrl}/${WELL_KNOWN_PATH}/${OPENID_CONFIGURATION}`;
256
+
257
+ const localStorageKey = `oidc.server:${openIdIssuerUrl}`;
258
+ const cacheJson = window.localStorage.getItem(localStorageKey);
259
+
260
+ const oneHourMinisecond = 1000 * timeCacheSecond;
261
+ // @ts-ignore
262
+ if(cacheJson && (cacheJson.timestamp + oneHourMinisecond) > Date.now()){
263
+ return new OidcAuthorizationServiceConfiguration(JSON.parse(cacheJson));
264
+ }
265
+
266
+ const res = await fetch(fullUrl);
267
+
268
+ if (res.status != 200) {
269
+ return null;
270
+ }
271
+
272
+
273
+ const result = await res.json();
274
+ window.localStorage.setItem(localStorageKey, JSON.stringify({result, timestamp:Date.now()}));
275
+
276
+ return new OidcAuthorizationServiceConfiguration(result);
277
+ }
278
+
279
+ const buildQueries = (extras:StringMap) => {
280
+ let queries = '';
281
+ if(extras != null){
282
+ for (let [key, value] of Object.entries(extras)) {
283
+ if (queries === ""){
284
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
285
+ } else {
286
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
287
+ }
288
+ }
289
+ }
290
+ return queries;
291
+ }
292
+
228
293
  export class Oidc {
229
294
  public configuration: OidcConfiguration;
230
295
  public userInfo: null;
@@ -234,6 +299,7 @@ export class Oidc {
234
299
  private serviceWorker?: any;
235
300
  private configurationName: string;
236
301
  private session?: any;
302
+ private checkSessionIFrame: CheckSessionIFrame;
237
303
  constructor(configuration:OidcConfiguration, configurationName="default") {
238
304
  this.configuration = configuration
239
305
  this.configurationName= configurationName;
@@ -247,6 +313,7 @@ export class Oidc {
247
313
  this.loginCallbackWithAutoTokensRenewAsync.bind(this);
248
314
  this.initAsync.bind(this);
249
315
  this.loginCallbackAsync.bind(this);
316
+ this._loginCallbackAsync.bind(this);
250
317
  this.subscriveEvents.bind(this);
251
318
  this.removeEventSubscription.bind(this);
252
319
  this.publishEvent.bind(this);
@@ -284,11 +351,18 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
284
351
 
285
352
  silentSigninCallbackFromIFrame(){
286
353
  if (this.configuration.silent_redirect_uri) {
287
- window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify(this.tokens)}`, window.location.origin);
354
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
355
+ window.top.postMessage(`${this.configurationName}_oidc_tokens:${JSON.stringify({tokens:this.tokens, sessionState:queryParams.session_state})}`, window.location.origin);
288
356
  }
289
357
  }
290
- async silentSigninAsync() {
291
- if (!this.configuration.silent_redirect_uri) {
358
+ silentSigninErrorCallbackFromIFrame(){
359
+ if (this.configuration.silent_redirect_uri) {
360
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
361
+ window.top.postMessage(`${this.configurationName}_oidc_error:${JSON.stringify({error:queryParams.error})}`, window.location.origin);
362
+ }
363
+ }
364
+ async silentSigninAsync(extras:StringMap=null, state:string=null, scope:string=null) {
365
+ if (!this.configuration.silent_redirect_uri || !this.configuration.silent_signin_uri) {
292
366
  return Promise.resolve(null);
293
367
  }
294
368
  while (document.hidden) {
@@ -299,10 +373,38 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
299
373
  try {
300
374
  this.publishEvent(eventNames.silentSigninAsync_begin, {});
301
375
  const configuration = this.configuration
302
- const link = configuration.silent_redirect_uri;
376
+ let queries = "";
377
+
378
+ if(state){
379
+ if(extras == null){
380
+ extras = {};
381
+ }
382
+ extras.state = state;
383
+ }
384
+
385
+ if(scope){
386
+ if(extras == null){
387
+ extras = {};
388
+ }
389
+ extras.scope = scope;
390
+ }
391
+
392
+ if(extras != null){
393
+ for (let [key, value] of Object.entries(extras)) {
394
+ if (queries === ""){
395
+ queries = `?${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
396
+ } else {
397
+ queries+= `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
398
+ }
399
+ }
400
+ }
401
+ const link = configuration.silent_signin_uri + queries;
402
+ const idx = link.indexOf("/", link.indexOf("//") + 2);
403
+ const iFrameOrigin = link.substr(0, idx);
303
404
  const iframe = document.createElement('iframe');
304
405
  iframe.width = "0px";
305
406
  iframe.height = "0px";
407
+
306
408
  iframe.id = `${this.configurationName}_oidc_iframe`;
307
409
  iframe.setAttribute("src", link);
308
410
  document.body.appendChild(iframe);
@@ -311,18 +413,33 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
311
413
  try {
312
414
  let isResolved = false;
313
415
  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);
416
+ if (e.origin === iFrameOrigin &&
417
+ e.source === iframe.contentWindow
418
+ ) {
419
+ const key = `${self.configurationName}_oidc_tokens:`;
420
+ const key_error = `${self.configurationName}_oidc_error:`;
421
+ const data = e.data;
422
+ if (data && typeof (data) === "string") {
423
+ if (!isResolved) {
424
+ if(data.startsWith(key)) {
425
+ const result = JSON.parse(e.data.replace(key, ''));
426
+ self.publishEvent(eventNames.silentSigninAsync_end, {});
427
+ iframe.remove();
428
+ isResolved = true;
429
+ resolve(result);
430
+ }
431
+ else if(data.startsWith(key_error)) {
432
+ const result = JSON.parse(e.data.replace(key_error, ''));
433
+ self.publishEvent(eventNames.silentSigninAsync_error, result);
434
+ iframe.remove();
435
+ isResolved = true;
436
+ reject(result);
437
+ }
438
+ }
322
439
  }
323
440
  }
324
441
  };
325
- const silentSigninTimeout = configuration.silent_signin_timeout ? configuration.silent_signin_timeout : 12000
442
+ const silentSigninTimeout = configuration.silent_signin_timeout ?? 12000
326
443
  setTimeout(() => {
327
444
  if (!isResolved) {
328
445
  self.publishEvent(eventNames.silentSigninAsync_error, "timeout");
@@ -345,17 +462,20 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
345
462
  initAsyncPromise = null;
346
463
  async initAsync(authority:string, authorityConfiguration:AuthorityConfiguration) {
347
464
  if (authorityConfiguration != null) {
348
- return new AuthorizationServiceConfiguration( {
465
+ return new OidcAuthorizationServiceConfiguration( {
349
466
  authorization_endpoint: authorityConfiguration.authorization_endpoint,
350
467
  end_session_endpoint: authorityConfiguration.end_session_endpoint,
351
468
  revocation_endpoint: authorityConfiguration.revocation_endpoint,
352
469
  token_endpoint: authorityConfiguration.token_endpoint,
353
- userinfo_endpoint: authorityConfiguration.userinfo_endpoint});
470
+ userinfo_endpoint: authorityConfiguration.userinfo_endpoint,
471
+ check_session_iframe:authorityConfiguration.check_session_iframe,
472
+ });
354
473
  }
355
474
  if(this.initAsyncPromise){
356
475
  return this.initAsyncPromise;
357
476
  }
358
- this.initAsyncPromise = await AuthorizationServiceConfiguration.fetchFromIssuer(authority, new FetchRequestor());
477
+
478
+ this.initAsyncPromise = await fetchFromIssuer(authority, this.configuration.authority_time_cache_wellknowurl_in_second ?? 60 * 60);
359
479
  return this.initAsyncPromise;
360
480
  }
361
481
 
@@ -379,12 +499,21 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
379
499
  const {tokens} = await serviceWorker.initAsync(oidcServerConfiguration, "tryKeepExistingSessionAsync");
380
500
  if (tokens) {
381
501
  serviceWorker.startKeepAliveServiceWorker();
382
- const updatedTokens = await this.refreshTokensAsync(tokens.refresh_token, true);
383
502
  // @ts-ignore
384
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
503
+ const reformattedToken = {
504
+ accessToken : tokens.access_token,
505
+ refreshToken : tokens.refresh_token,
506
+ expiresIn: tokens.expires_in,
507
+ idToken: tokens.id_token,
508
+ scope: tokens.scope,
509
+ tokenType: tokens.token_type
510
+ }
511
+ this.tokens = await setTokensAsync(serviceWorker, reformattedToken);
385
512
  this.serviceWorker = serviceWorker;
386
513
  // @ts-ignore
387
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
514
+ this.timeoutId = autoRenewTokens(this, this.tokens.refreshToken, this.tokens.expiresAt);
515
+ const sessionState = await serviceWorker.getSessionStateAsync();
516
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
388
517
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
389
518
  success: true,
390
519
  message: "tokens inside ServiceWorker are valid"
@@ -401,16 +530,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
401
530
  message: "service worker is not supported by this browser"
402
531
  });
403
532
  }
404
- const session = initSession(this.configurationName, configuration.storage ?? sessionStorage);
533
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
405
534
  const {tokens} = await session.initAsync();
535
+ console.log("const {tokens} = await session.initAsync();")
536
+ console.log(tokens)
406
537
  if (tokens) {
407
- const updatedTokens = await this.refreshTokensAsync(tokens.refreshToken, true);
408
538
  // @ts-ignore
409
- this.tokens = await setTokensAsync(serviceWorker, updatedTokens);
410
- session.setTokens(this.tokens);
539
+ this.tokens = await setTokensAsync(serviceWorker, tokens);
540
+ //session.setTokens(this.tokens);
411
541
  this.session = session;
412
542
  // @ts-ignore
413
- this.timeoutId = autoRenewTokens(this, updatedTokens.refreshToken, this.tokens.expiresAt);
543
+ this.timeoutId = autoRenewTokens(this, tokens.refreshToken, this.tokens.expiresAt);
544
+ const sessionState = session.getSessionState();
545
+ await this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, configuration.client_id, sessionState);
414
546
  this.publishEvent(eventNames.tryKeepExistingSessionAsync_end, {
415
547
  success: true,
416
548
  message: `tokens inside storage are valid`
@@ -424,6 +556,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
424
556
  });
425
557
  return false;
426
558
  } catch (exception) {
559
+ console.error(exception);
427
560
  if (serviceWorker) {
428
561
  await serviceWorker.clearAsync();
429
562
  }
@@ -439,109 +572,172 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
439
572
  });
440
573
  }
441
574
 
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;
575
+ loginPromise: Promise<any>=null;
576
+ async loginAsync(callbackPath:string=undefined, extras:StringMap=null, installServiceWorker=true, state:string=undefined, isSilentSignin:boolean=false, scope:string=undefined) {
577
+ if(this.loginPromise !== null){
578
+ return this.loginPromise;
579
+ }
580
+
581
+ const loginLocalAsync=async () => {
582
+ try {
583
+ const location = window.location;
584
+ const url = callbackPath || location.pathname + (location.search || '') + (location.hash || '');
585
+ this.publishEvent(eventNames.loginAsync_begin, {});
586
+ const configuration = this.configuration;
587
+
588
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
589
+ if (!scope) {
590
+ scope = configuration.scope;
591
+ }
592
+
593
+ const sessionKey = getLoginSessionKey(this.configurationName, redirectUri);
594
+ sessionStorage[sessionKey] = JSON.stringify({callbackPath: url, extras, state});
595
+
596
+ let serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
597
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
598
+ if (serviceWorker && installServiceWorker) {
599
+ const isServiceWorkerProxyActive = await serviceWorker.isServiceWorkerProxyActiveAsync();
600
+ if (!isServiceWorkerProxyActive) {
601
+ const isUnregistered = await serviceWorker.unregisterAsync();
602
+ console.log("isUnregistered")
603
+ console.log(isUnregistered)
604
+ if(isUnregistered){
605
+ serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
606
+ }
607
+ /*const extrasQueries = extras != null ? {...extras}: {};
608
+ extrasQueries.callbackPath = url;
609
+ extrasQueries.state = state;
610
+ const queryString = buildQueries(extrasQueries);
611
+ window.location.href = `${redirectUri}/service-worker-install${queryString}`;*/
612
+ //return;
613
+ }
614
+ }
615
+ let storage;
616
+ if (serviceWorker) {
617
+ serviceWorker.startKeepAliveServiceWorker();
618
+ await serviceWorker.initAsync(oidcServerConfiguration, "loginAsync");
619
+ storage = new MemoryStorageBackend(serviceWorker.saveItemsAsync, {});
620
+ await storage.setItem("dummy", {});
621
+ } else {
622
+ const session = initSession(this.configurationName, redirectUri);
623
+ storage = new MemoryStorageBackend(session.saveItemsAsync, {});
461
624
  }
625
+
626
+ const extraFinal = extras ?? configuration.extras ?? {};
627
+
628
+ // @ts-ignore
629
+ const queryStringUtil = redirectUri.includes("#") ? new HashQueryStringUtils() : new NoHashQueryStringUtils();
630
+ const authorizationHandler = new RedirectRequestHandler(storage, queryStringUtil, window.location, new DefaultCrypto());
631
+ const authRequest = new AuthorizationRequest({
632
+ client_id: configuration.client_id,
633
+ redirect_uri: redirectUri,
634
+ scope,
635
+ response_type: AuthorizationRequest.RESPONSE_TYPE_CODE,
636
+ state,
637
+ extras: extraFinal
638
+ });
639
+ authorizationHandler.performAuthorizationRequest(oidcServerConfiguration, authRequest);
640
+ } catch (exception) {
641
+ this.publishEvent(eventNames.loginAsync_error, exception);
642
+ throw exception;
462
643
  }
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",{});
644
+ }
645
+ this.loginPromise = loginLocalAsync();
646
+ return this.loginPromise.then(result =>{
647
+ this.loginPromise = null;
648
+ return result;
649
+ });
650
+
651
+ }
652
+
653
+ async startCheckSessionAsync(checkSessionIFrameUri, clientId, sessionState, isSilentSignin=false){
654
+ return new Promise((resolve:Function, reject) => {
655
+ if (this.configuration.silent_signin_uri && this.configuration.silent_redirect_uri && this.configuration.monitor_session && checkSessionIFrameUri && sessionState && !isSilentSignin) {
656
+ const checkSessionCallback = () => {
657
+ this.checkSessionIFrame.stop();
658
+
659
+ if(this.tokens === null){
660
+ return;
661
+ }
662
+ // @ts-ignore
663
+ const idToken = this.tokens.idToken;
664
+ // @ts-ignore
665
+ const idTokenPayload = this.tokens.idTokenPayload;
666
+ this.silentSigninAsync({
667
+ prompt: "none",
668
+ id_token_hint: idToken,
669
+ scope: "openid"
670
+ }).then((silentSigninResponse) => {
671
+ const iFrameIdTokenPayload = silentSigninResponse.tokens.idTokenPayload;
672
+ if (idTokenPayload.sub === iFrameIdTokenPayload.sub) {
673
+ const sessionState = silentSigninResponse.sessionState;
674
+ this.checkSessionIFrame.start(silentSigninResponse.sessionState);
675
+ if (idTokenPayload.sid === iFrameIdTokenPayload.sid) {
676
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:", sessionState);
677
+ } else {
678
+ console.debug("SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:", sessionState);
679
+ }
680
+ }
681
+ else {
682
+ console.debug("SessionMonitor._callback: Different subject signed into OP:", iFrameIdTokenPayload.sub);
683
+ }
684
+ }).catch((e) => {
685
+ this.publishEvent(eventNames.logout_from_another_tab, {});
686
+ this.destroyAsync();
687
+ });
688
+ };
689
+
690
+ this.checkSessionIFrame = new CheckSessionIFrame(checkSessionCallback, clientId, checkSessionIFrameUri);
691
+ this.checkSessionIFrame.load().then(() => {
692
+ this.checkSessionIFrame.start(sessionState);
693
+ resolve();
694
+ }).catch((e) =>{
695
+ reject(e);
696
+ });
469
697
  } else {
470
- const session = initSession(this.configurationName);
471
- storage = new MemoryStorageBackend(session.saveItemsAsync, {});
698
+ resolve();
472
699
  }
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
- }
700
+ });
490
701
  }
491
702
 
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;
703
+ loginCallbackPromise : Promise<any>=null
704
+ async loginCallbackAsync(isSilenSignin:boolean=false){
705
+ if(this.loginCallbackPromise !== null){
706
+ return this.loginCallbackPromise;
498
707
  }
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, {});
708
+
709
+ const loginCallbackLocalAsync= async( ) =>{
710
+ const response = await this._loginCallbackAsync(isSilenSignin);
711
+ // @ts-ignore
712
+ const tokens = response.tokens;
713
+ const parsedTokens = await setTokensAsync(this.serviceWorker, tokens);
714
+ this.tokens = parsedTokens;
715
+ if(!this.serviceWorker){
716
+ await this.session.setTokens(parsedTokens);
532
717
  }
718
+ this.publishEvent(Oidc.eventNames.token_aquired, parsedTokens);
719
+ // @ts-ignore
720
+ return { parsedTokens, state:response.state, callbackPath : response.callbackPath};
533
721
  }
722
+
723
+ this.loginCallbackPromise = loginCallbackLocalAsync();
724
+ return this.loginCallbackPromise.then(result =>{
725
+ this.loginCallbackPromise = null;
726
+ return result;
727
+ })
534
728
  }
535
-
536
- async loginCallbackAsync(){
729
+
730
+ async _loginCallbackAsync(isSilentSignin:boolean=false){
537
731
  try {
538
732
  this.publishEvent(eventNames.loginCallbackAsync_begin, {});
539
733
  const configuration = this.configuration;
540
734
  const clientId = configuration.client_id;
541
- const redirectURL = configuration.redirect_uri;
735
+ const redirectUri = isSilentSignin ? configuration.silent_redirect_uri : configuration.redirect_uri;
542
736
  const authority = configuration.authority;
543
737
  const tokenRequestTimeout = configuration.token_request_timeout;
544
738
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
739
+ const queryParams = getParseQueryStringFromLocation(window.location.href);
740
+ const sessionState = queryParams.session_state;
545
741
  const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
546
742
  let storage = null;
547
743
  if(serviceWorker){
@@ -555,17 +751,19 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
555
751
  throw new Error("Service Worker storage disapear");
556
752
  }
557
753
  await storage.removeItem("dummy");
754
+ await serviceWorker.setSessionStateAsync(sessionState);
558
755
  }else{
559
756
 
560
- this.session = initSession(this.configurationName, configuration.storage ?? sessionStorage);
561
- const session = initSession(this.configurationName);
757
+ this.session = initSession(this.configurationName, redirectUri, configuration.storage ?? sessionStorage);
758
+ const session = initSession(this.configurationName, redirectUri);
759
+ session.setSessionState(sessionState);
562
760
  const items = await session.loadItemsAsync();
563
761
  storage = new MemoryStorageBackend(session.saveItemsAsync, items);
564
762
  }
565
763
  return new Promise((resolve, reject) => {
566
764
  // @ts-ignore
567
765
  let queryStringUtil = new NoHashQueryStringUtils();
568
- if(configuration.redirect_uri.includes("#")) {
766
+ if(redirectUri.includes("#")) {
569
767
  const splithash = window.location.href.split("#");
570
768
  if (splithash.length === 2 && splithash[1].includes("?")) {
571
769
  queryStringUtil = new HashQueryStringUtils();
@@ -599,14 +797,14 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
599
797
 
600
798
  const tokenRequest = new TokenRequest({
601
799
  client_id: clientId,
602
- redirect_uri: redirectURL,
800
+ redirect_uri: redirectUri,
603
801
  grant_type: GRANT_TYPE_AUTHORIZATION_CODE,
604
802
  code: response.code,
605
803
  refresh_token: undefined,
606
804
  extras,
607
805
  });
608
806
 
609
- let timeoutId = setTimeout(function(){
807
+ let timeoutId = setTimeout(()=>{
610
808
  reject("performTokenRequest timeout");
611
809
  timeoutId=null;
612
810
  }, tokenRequestTimeout ?? 12000);
@@ -614,14 +812,16 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
614
812
  const tokenHandler = new BaseTokenRequestHandler(new FetchRequestor());
615
813
  tokenHandler.performTokenRequest(oidcServerConfiguration, tokenRequest).then((tokenResponse)=>{
616
814
  if(timeoutId) {
617
- const loginParams = getLoginParams(this.configurationName);
618
815
  clearTimeout(timeoutId);
619
816
  this.timeoutId=null;
620
- this.publishEvent(eventNames.loginCallbackAsync_end, {});
621
- resolve({
622
- tokens: tokenResponse,
623
- state: request.state,
624
- callbackPath: loginParams.callbackPath,
817
+ const loginParams = getLoginParams(this.configurationName, redirectUri);
818
+ this.startCheckSessionAsync(oidcServerConfiguration.check_session_iframe, clientId, sessionState, isSilentSignin).then(() =>{
819
+ this.publishEvent(eventNames.loginCallbackAsync_end, {});
820
+ resolve({
821
+ tokens: tokenResponse,
822
+ state: request.state,
823
+ callbackPath: loginParams.callbackPath,
824
+ });
625
825
  });
626
826
  }
627
827
  });
@@ -642,15 +842,14 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
642
842
  this.publishEvent(eventNames.loginCallbackAsync_error, exception);
643
843
  throw exception;
644
844
  }
645
-
646
845
  }
647
846
 
648
- async refreshTokensAsync(refreshToken, silentEvent = false) {
847
+ async refreshTokensAsync(refreshToken) {
649
848
  const localSilentSigninAsync= async (exception=null) => {
650
849
  try {
651
850
  const silent_token_response = await this.silentSigninAsync();
652
851
  if (silent_token_response) {
653
- return silent_token_response;
852
+ return silent_token_response.tokens;
654
853
  }
655
854
  } catch (exceptionSilent) {
656
855
  console.error(exceptionSilent);
@@ -659,12 +858,12 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
659
858
  timer.clearTimeout(this.timeoutId);
660
859
  this.timeoutId=null;
661
860
  }
662
- this.publishEvent(silentEvent ? eventNames.refreshTokensAsync_silent_error : eventNames.refreshTokensAsync_error, exception);
861
+ this.publishEvent(eventNames.refreshTokensAsync_error, exception);
663
862
  return null;
664
863
  }
665
864
 
666
865
  try{
667
- this.publishEvent(silentEvent ? eventNames.refreshTokensAsync_silent_begin : eventNames.refreshTokensAsync_begin, {})
866
+ this.publishEvent(eventNames.refreshTokensAsync_begin, {refreshToken:refreshToken})
668
867
  const configuration = this.configuration;
669
868
  const clientId = configuration.client_id;
670
869
  const redirectUri = configuration.redirect_uri;
@@ -696,14 +895,69 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
696
895
 
697
896
  const oidcServerConfiguration = await this.initAsync(authority, configuration.authority_configuration);
698
897
  const token_response = await tokenHandler.performTokenRequest(oidcServerConfiguration, request);
699
- this.publishEvent(silentEvent ? eventNames.refreshTokensAsync_silent_end :eventNames.refreshTokensAsync_end, {message:"success"});
898
+ this.publishEvent(eventNames.refreshTokensAsync_end, {message:"success"});
700
899
  return token_response;
701
900
  } catch(exception) {
702
901
  console.error(exception);
902
+ this.publishEvent(eventNames.refreshTokensAsync_silent_error, exception);
703
903
  return await localSilentSigninAsync(exception);
704
904
  }
705
905
  }
706
906
 
907
+ syncTokensAsyncPromise=null;
908
+ async syncTokensAsync() {
909
+ // 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)
910
+ const configuration = this.configuration;
911
+ if(!this.tokens){
912
+ return;
913
+ }
914
+
915
+ const oidcServerConfiguration = await this.initAsync(configuration.authority, configuration.authority_configuration);
916
+ const serviceWorker = await initWorkerAsync(configuration.service_worker_relative_url, this.configurationName);
917
+ if (serviceWorker) {
918
+ const { isLogin } = await serviceWorker.initAsync(oidcServerConfiguration, "syncTokensAsync");
919
+ if(isLogin == false){
920
+ this.publishEvent(eventNames.logout_from_another_tab, {});
921
+ await this.destroyAsync();
922
+ }
923
+ else if (isLogin == null){
924
+ try {
925
+ this.publishEvent(eventNames.syncTokensAsync_begin, {});
926
+ this.syncTokensAsyncPromise = this.silentSigninAsync({prompt:"none"});
927
+ const silent_token_response = await this.syncTokensAsyncPromise;
928
+ if (silent_token_response && silent_token_response.tokens) {
929
+ this.tokens = await setTokensAsync(serviceWorker, silent_token_response.tokens);
930
+ } else{
931
+ this.publishEvent(eventNames.syncTokensAsync_error, null);
932
+ if(this.timeoutId){
933
+ timer.clearTimeout(this.timeoutId);
934
+ this.timeoutId=null;
935
+ }
936
+ return;
937
+ }
938
+ } catch (exceptionSilent) {
939
+ console.error(exceptionSilent);
940
+ this.publishEvent(eventNames.syncTokensAsync_error, exceptionSilent);
941
+ if(this.timeoutId){
942
+ timer.clearTimeout(this.timeoutId);
943
+ this.timeoutId=null;
944
+ }
945
+ return;
946
+ }
947
+ this.syncTokensAsyncPromise = null;
948
+ this.publishEvent(eventNames.syncTokensAsync_end, {});
949
+ }
950
+ } else {
951
+ const session = initSession(this.configurationName, configuration.redirect_uri, configuration.storage ?? sessionStorage);
952
+ const {tokens} = await session.initAsync();
953
+ if(!tokens){
954
+ this.publishEvent(eventNames.logout_from_another_tab, {});
955
+ await this.destroyAsync();
956
+ }
957
+ }
958
+ }
959
+
960
+
707
961
  loginCallbackWithAutoTokensRenewPromise:Promise<loginCallbackResult> = null;
708
962
  loginCallbackWithAutoTokensRenewAsync():Promise<loginCallbackResult>{
709
963
  if(this.loginCallbackWithAutoTokensRenewPromise !== null){
@@ -721,6 +975,11 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
721
975
  }
722
976
 
723
977
  async destroyAsync() {
978
+ timer.clearTimeout(this.timeoutId);
979
+ this.timeoutId=null;
980
+ if(this.checkSessionIFrame){
981
+ this.checkSessionIFrame.stop();
982
+ }
724
983
  if(this.serviceWorker){
725
984
  await this.serviceWorker.clearAsync();
726
985
  }
@@ -730,8 +989,7 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
730
989
  this.tokens = null;
731
990
  this.userInfo = null;
732
991
  this.events = [];
733
- timer.clearTimeout(this.timeoutId);
734
- this.timeoutId=null;
992
+
735
993
  }
736
994
 
737
995
  async logoutAsync(callbackPathOrUrl: string | undefined = undefined, extras: StringMap = null) {