oidc-spa 6.4.0 → 6.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/oidc/createOidc.d.ts +0 -2
- package/oidc/createOidc.js +307 -355
- package/oidc/createOidc.js.map +1 -1
- package/oidc/{createIsUserActive.js → isUserActive.js} +1 -1
- package/oidc/isUserActive.js.map +1 -0
- package/oidc/loginOrGoToAuthServer.d.ts +41 -0
- package/oidc/loginOrGoToAuthServer.js +296 -0
- package/oidc/loginOrGoToAuthServer.js.map +1 -0
- package/oidc/loginSilent.d.ts +2 -2
- package/oidc/loginSilent.js +2 -2
- package/oidc/loginSilent.js.map +1 -1
- package/oidc/oidcClientTsUserToTokens.d.ts +1 -0
- package/oidc/oidcClientTsUserToTokens.js +16 -0
- package/oidc/oidcClientTsUserToTokens.js.map +1 -1
- package/oidc/persistedAuthState.d.ts +9 -0
- package/oidc/persistedAuthState.js +28 -0
- package/oidc/persistedAuthState.js.map +1 -0
- package/package.json +26 -11
- package/src/oidc/createOidc.ts +291 -353
- package/src/oidc/loginOrGoToAuthServer.ts +267 -0
- package/src/oidc/loginSilent.ts +4 -4
- package/src/oidc/oidcClientTsUserToTokens.ts +24 -0
- package/src/oidc/persistedAuthState.ts +36 -0
- package/src/tools/ephemeralSessionStorage.ts +191 -0
- package/src/tools/haveSharedParentDomain.ts +13 -0
- package/src/tools/parseKeycloakIssuerUri.ts +9 -2
- package/tools/ephemeralSessionStorage.d.ts +3 -0
- package/tools/ephemeralSessionStorage.js +133 -0
- package/tools/ephemeralSessionStorage.js.map +1 -0
- package/tools/haveSharedParentDomain.d.ts +4 -0
- package/tools/haveSharedParentDomain.js +14 -0
- package/tools/haveSharedParentDomain.js.map +1 -0
- package/tools/parseKeycloakIssuerUri.d.ts +1 -0
- package/tools/parseKeycloakIssuerUri.js +4 -1
- package/tools/parseKeycloakIssuerUri.js.map +1 -1
- package/vendor/frontend/oidc-client-ts-and-jwt-decode.js +1 -1
- package/oidc/createIsUserActive.js.map +0 -1
- package/oidc/persistedLogoutState.d.ts +0 -9
- package/oidc/persistedLogoutState.js +0 -25
- package/oidc/persistedLogoutState.js.map +0 -1
- package/src/oidc/persistedLogoutState.ts +0 -29
- /package/oidc/{createIsUserActive.d.ts → isUserActive.d.ts} +0 -0
- /package/src/oidc/{createIsUserActive.ts → isUserActive.ts} +0 -0
package/src/oidc/createOidc.ts
CHANGED
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
type User as OidcClientTsUser,
|
|
5
5
|
InMemoryWebStorage
|
|
6
6
|
} from "../vendor/frontend/oidc-client-ts-and-jwt-decode";
|
|
7
|
-
import { id,
|
|
7
|
+
import { id, assert, is, type Equals, typeGuard } from "../vendor/frontend/tsafe";
|
|
8
8
|
import { setTimeout, clearTimeout } from "../tools/workerTimers";
|
|
9
9
|
import { Deferred } from "../tools/Deferred";
|
|
10
10
|
import { decodeJwt } from "../tools/decodeJwt";
|
|
11
|
-
import { create$isUserActive } from "./
|
|
11
|
+
import { create$isUserActive } from "./isUserActive";
|
|
12
12
|
import { createStartCountdown } from "../tools/startCountdown";
|
|
13
13
|
import type { StatefulObservable } from "../tools/StatefulObservable";
|
|
14
14
|
import { toHumanReadableDuration } from "../tools/toHumanReadableDuration";
|
|
@@ -27,16 +27,15 @@ import {
|
|
|
27
27
|
} from "./StateData";
|
|
28
28
|
import { notifyOtherTabsOfLogout, getPrOtherTabLogout } from "./logoutPropagationToOtherTabs";
|
|
29
29
|
import { getConfigId } from "./configId";
|
|
30
|
-
import { oidcClientTsUserToTokens } from "./oidcClientTsUserToTokens";
|
|
30
|
+
import { oidcClientTsUserToTokens, getMsBeforeExpiration } from "./oidcClientTsUserToTokens";
|
|
31
31
|
import { loginSilent, authResponseToUrl } from "./loginSilent";
|
|
32
32
|
import { handleOidcCallback, AUTH_RESPONSE_KEY } from "./handleOidcCallback";
|
|
33
|
-
import {
|
|
34
|
-
clearPersistedLogoutState,
|
|
35
|
-
getIsPersistedLogoutState,
|
|
36
|
-
persistLogoutState
|
|
37
|
-
} from "./persistedLogoutState";
|
|
33
|
+
import { getPersistedAuthState, persistAuthState } from "./persistedAuthState";
|
|
38
34
|
import type { Oidc } from "./Oidc";
|
|
39
35
|
import { type AwaitableEventEmitter, createAwaitableEventEmitter } from "../tools/AwaitableEventEmitter";
|
|
36
|
+
import { getHaveSharedParentDomain } from "../tools/haveSharedParentDomain";
|
|
37
|
+
import { createLoginOrGoToAuthServer } from "./loginOrGoToAuthServer";
|
|
38
|
+
import { createEphemeralSessionStorage } from "../tools/ephemeralSessionStorage";
|
|
40
39
|
|
|
41
40
|
// NOTE: Replaced at build time
|
|
42
41
|
const VERSION = "{{OIDC_SPA_VERSION}}";
|
|
@@ -130,9 +129,7 @@ declare global {
|
|
|
130
129
|
[GLOBAL_CONTEXT_KEY]: {
|
|
131
130
|
prOidcByConfigId: Map<string, Promise<Oidc<any>>>;
|
|
132
131
|
evtAuthResponseHandled: AwaitableEventEmitter<void>;
|
|
133
|
-
URL_real: typeof URL;
|
|
134
132
|
$isUserActive: StatefulObservable<boolean> | undefined;
|
|
135
|
-
hasLoginBeenCalled: boolean;
|
|
136
133
|
hasLogoutBeenCalled: boolean;
|
|
137
134
|
};
|
|
138
135
|
}
|
|
@@ -141,14 +138,14 @@ declare global {
|
|
|
141
138
|
window[GLOBAL_CONTEXT_KEY] ??= {
|
|
142
139
|
prOidcByConfigId: new Map(),
|
|
143
140
|
evtAuthResponseHandled: createAwaitableEventEmitter<void>(),
|
|
144
|
-
URL_real: window.URL,
|
|
145
141
|
$isUserActive: undefined,
|
|
146
|
-
hasLoginBeenCalled: false,
|
|
147
142
|
hasLogoutBeenCalled: false
|
|
148
143
|
};
|
|
149
144
|
|
|
150
145
|
const globalContext = window[GLOBAL_CONTEXT_KEY];
|
|
151
146
|
|
|
147
|
+
const MIN_RENEW_BEFORE_EXPIRE_MS = 2_000;
|
|
148
|
+
|
|
152
149
|
/** @see: https://docs.oidc-spa.dev/v/v6/usage */
|
|
153
150
|
export async function createOidc<
|
|
154
151
|
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
|
|
@@ -256,7 +253,7 @@ export async function createOidc_nonMemoized<
|
|
|
256
253
|
__unsafe_ssoSessionIdleSeconds,
|
|
257
254
|
autoLogoutParams = { redirectTo: "current page" },
|
|
258
255
|
autoLogin = false,
|
|
259
|
-
postLoginRedirectUrl,
|
|
256
|
+
postLoginRedirectUrl: postLoginRedirectUrl_default,
|
|
260
257
|
__unsafe_clientSecret,
|
|
261
258
|
__unsafe_useIdTokenAsAccessToken = false
|
|
262
259
|
} = params;
|
|
@@ -299,11 +296,32 @@ export async function createOidc_nonMemoized<
|
|
|
299
296
|
}
|
|
300
297
|
}
|
|
301
298
|
|
|
302
|
-
const
|
|
299
|
+
const stateQueryParamValue_instance = generateStateQueryParamValue();
|
|
303
300
|
|
|
304
|
-
|
|
301
|
+
let areThirdPartyCookiesAllowed: boolean;
|
|
302
|
+
{
|
|
303
|
+
const url1 = window.location.origin;
|
|
304
|
+
const url2 = issuerUri;
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
areThirdPartyCookiesAllowed = getHaveSharedParentDomain({
|
|
307
|
+
url1,
|
|
308
|
+
url2
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (areThirdPartyCookiesAllowed) {
|
|
312
|
+
log?.(`${url1} and ${url2} have shared parent domain, third party cookies are allowed`);
|
|
313
|
+
} else {
|
|
314
|
+
log?.(
|
|
315
|
+
[
|
|
316
|
+
`${url1} and ${url2} don't have shared parent domain, setting third party cookies`,
|
|
317
|
+
`on the auth server domain might not work. Making sure that everything works smoothly regardless`,
|
|
318
|
+
`by allowing oidc-spa to store the auth state in the session storage for a limited period of time.`
|
|
319
|
+
].join(" ")
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const isUserStorePersistent = !areThirdPartyCookiesAllowed;
|
|
307
325
|
|
|
308
326
|
const oidcClientTsUserManager = new OidcClientTsUserManager({
|
|
309
327
|
stateQueryParamValue: stateQueryParamValue_instance,
|
|
@@ -315,214 +333,29 @@ export async function createOidc_nonMemoized<
|
|
|
315
333
|
response_type: "code",
|
|
316
334
|
scope: Array.from(new Set(["openid", ...scopes])).join(" "),
|
|
317
335
|
automaticSilentRenew: false,
|
|
318
|
-
userStore: new WebStorageStateStore({
|
|
336
|
+
userStore: new WebStorageStateStore({
|
|
337
|
+
store: areThirdPartyCookiesAllowed
|
|
338
|
+
? new InMemoryWebStorage()
|
|
339
|
+
: createEphemeralSessionStorage({
|
|
340
|
+
sessionStorageTtlMs: 3 * 60_1000
|
|
341
|
+
})
|
|
342
|
+
}),
|
|
319
343
|
stateStore: new WebStorageStateStore({ store: localStorage, prefix: STATE_STORE_KEY_PREFIX }),
|
|
320
344
|
client_secret: __unsafe_clientSecret
|
|
321
345
|
});
|
|
322
346
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
Param0<Oidc.NotLoggedIn["login"]>,
|
|
336
|
-
"doesCurrentHrefRequiresAuth"
|
|
337
|
-
> &
|
|
338
|
-
({ action: "login"; doesCurrentHrefRequiresAuth: boolean } | { action: "go to auth server" });
|
|
339
|
-
|
|
340
|
-
const loginOrGoToAuthServer = async (params: ParamsOfLoginOrGoToAuthServer): Promise<never> => {
|
|
341
|
-
const {
|
|
342
|
-
extraQueryParams: extraQueryParams_fromLoginFn,
|
|
343
|
-
redirectUrl: redirectUrl_params,
|
|
344
|
-
transformUrlBeforeRedirect: transformUrlBeforeRedirect_fromLoginFn,
|
|
345
|
-
...rest
|
|
346
|
-
} = params;
|
|
347
|
-
|
|
348
|
-
log?.("Calling loginOrGoToAuthServer", { params });
|
|
349
|
-
|
|
350
|
-
// NOTE: This is for handling cases when user press the back button on the login pages.
|
|
351
|
-
// When the app is hosted on https (so not in dev mode) the browser will restore the state of the app
|
|
352
|
-
// instead of reloading the page.
|
|
353
|
-
if (rest.action === "login") {
|
|
354
|
-
if (globalContext.hasLoginBeenCalled) {
|
|
355
|
-
log?.("login() has already been called, ignoring the call");
|
|
356
|
-
return new Promise<never>(() => {});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
globalContext.hasLoginBeenCalled = true;
|
|
360
|
-
|
|
361
|
-
const callback = () => {
|
|
362
|
-
if (document.visibilityState === "visible") {
|
|
363
|
-
document.removeEventListener("visibilitychange", callback);
|
|
364
|
-
|
|
365
|
-
log?.(
|
|
366
|
-
"We came back from the login pages and the state of the app has been restored"
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
if (rest.doesCurrentHrefRequiresAuth) {
|
|
370
|
-
if (lastPublicUrl !== undefined) {
|
|
371
|
-
log?.(`Loading last public route: ${lastPublicUrl}`);
|
|
372
|
-
window.location.href = lastPublicUrl;
|
|
373
|
-
} else {
|
|
374
|
-
log?.("We don't know the last public route, navigating back in history");
|
|
375
|
-
window.history.back();
|
|
376
|
-
}
|
|
377
|
-
} else {
|
|
378
|
-
log?.("The current page doesn't require auth...");
|
|
379
|
-
|
|
380
|
-
if (localStorage.getItem(USER_LOGGED_IN_KEY)) {
|
|
381
|
-
log?.("but the user is now authenticated, reloading the page");
|
|
382
|
-
location.reload();
|
|
383
|
-
} else {
|
|
384
|
-
log?.("and the user doesn't seem to be authenticated, avoiding a reload");
|
|
385
|
-
globalContext.hasLoginBeenCalled = false;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
log?.("Start listening to visibility change event");
|
|
392
|
-
|
|
393
|
-
document.addEventListener("visibilitychange", callback);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const redirectUrl =
|
|
397
|
-
redirectUrl_params === undefined
|
|
398
|
-
? window.location.href
|
|
399
|
-
: toFullyQualifiedUrl({
|
|
400
|
-
urlish: redirectUrl_params,
|
|
401
|
-
doAssertNoQueryParams: false
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
log?.(`redirectUrl: ${redirectUrl}`);
|
|
405
|
-
|
|
406
|
-
//NOTE: We know there is a extraQueryParameter option but it doesn't allow
|
|
407
|
-
// to control the encoding so we have to highjack global URL Class that is
|
|
408
|
-
// used internally by oidc-client-ts. It's save to do so since this is the
|
|
409
|
-
// last thing that will be done before the redirect.
|
|
410
|
-
{
|
|
411
|
-
const { URL_real } = globalContext;
|
|
412
|
-
|
|
413
|
-
const URL = (...args: ConstructorParameters<typeof URL_real>) => {
|
|
414
|
-
const urlInstance = new URL_real(...args);
|
|
415
|
-
|
|
416
|
-
return new Proxy(urlInstance, {
|
|
417
|
-
get: (target, prop) => {
|
|
418
|
-
if (prop === "href") {
|
|
419
|
-
Object.defineProperty(window, "URL", { value: URL_real });
|
|
420
|
-
|
|
421
|
-
let url = urlInstance.href;
|
|
422
|
-
|
|
423
|
-
(
|
|
424
|
-
[
|
|
425
|
-
[getExtraQueryParams?.(), transformUrlBeforeRedirect],
|
|
426
|
-
[
|
|
427
|
-
extraQueryParams_fromLoginFn,
|
|
428
|
-
transformUrlBeforeRedirect_fromLoginFn
|
|
429
|
-
]
|
|
430
|
-
] as const
|
|
431
|
-
).forEach(([extraQueryParams, transformUrlBeforeRedirect]) => {
|
|
432
|
-
add_extra_query_params: {
|
|
433
|
-
if (extraQueryParams === undefined) {
|
|
434
|
-
break add_extra_query_params;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const url_obj = new URL_real(url);
|
|
438
|
-
|
|
439
|
-
for (const [name, value] of Object.entries(extraQueryParams)) {
|
|
440
|
-
url_obj.searchParams.set(name, value);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
url = url_obj.href;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
apply_transform_before_redirect: {
|
|
447
|
-
if (transformUrlBeforeRedirect === undefined) {
|
|
448
|
-
break apply_transform_before_redirect;
|
|
449
|
-
}
|
|
450
|
-
url = transformUrlBeforeRedirect(url);
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
return url;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
//@ts-expect-error
|
|
458
|
-
return target[prop];
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
Object.defineProperty(window, "URL", { value: URL });
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// NOTE: This is for the behavior when the use presses the back button on the login pages.
|
|
467
|
-
// This is what happens when the user gave up the login process.
|
|
468
|
-
// We want to that to redirect to the last public page.
|
|
469
|
-
const redirectMethod = (() => {
|
|
470
|
-
switch (rest.action) {
|
|
471
|
-
case "login":
|
|
472
|
-
return rest.doesCurrentHrefRequiresAuth ? "replace" : "assign";
|
|
473
|
-
case "go to auth server":
|
|
474
|
-
return "assign";
|
|
475
|
-
}
|
|
476
|
-
})();
|
|
477
|
-
|
|
478
|
-
log?.(`redirectMethod: ${redirectMethod}`);
|
|
479
|
-
|
|
480
|
-
const { extraQueryParams } = (() => {
|
|
481
|
-
const extraQueryParams: Record<string, string> = extraQueryParams_fromLoginFn ?? {};
|
|
482
|
-
|
|
483
|
-
read_query_params_added_by_transform_before_redirect: {
|
|
484
|
-
if (transformUrlBeforeRedirect_fromLoginFn === undefined) {
|
|
485
|
-
break read_query_params_added_by_transform_before_redirect;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
let url_afterTransform;
|
|
489
|
-
|
|
490
|
-
try {
|
|
491
|
-
url_afterTransform = transformUrlBeforeRedirect_fromLoginFn("https://dummy.com");
|
|
492
|
-
} catch {
|
|
493
|
-
break read_query_params_added_by_transform_before_redirect;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
for (const [name, value] of new URL(url_afterTransform).searchParams) {
|
|
497
|
-
extraQueryParams[name] = value;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
return { extraQueryParams };
|
|
502
|
-
})();
|
|
503
|
-
|
|
504
|
-
await oidcClientTsUserManager.signinRedirect({
|
|
505
|
-
state: id<StateData>({
|
|
506
|
-
context: "redirect",
|
|
507
|
-
redirectUrl,
|
|
508
|
-
extraQueryParams,
|
|
509
|
-
hasBeenProcessedByCallback: false,
|
|
510
|
-
configId,
|
|
511
|
-
action: "login",
|
|
512
|
-
redirectUrl_consentRequiredCase: (() => {
|
|
513
|
-
switch (rest.action) {
|
|
514
|
-
case "login":
|
|
515
|
-
return lastPublicUrl ?? homeAndCallbackUrl;
|
|
516
|
-
case "go to auth server":
|
|
517
|
-
return redirectUrl;
|
|
518
|
-
}
|
|
519
|
-
})()
|
|
520
|
-
}),
|
|
521
|
-
redirectMethod,
|
|
522
|
-
prompt: getIsPersistedLogoutState({ configId }) ? "consent" : undefined
|
|
523
|
-
});
|
|
524
|
-
return new Promise<never>(() => {});
|
|
525
|
-
};
|
|
347
|
+
const {
|
|
348
|
+
loginOrGoToAuthServer,
|
|
349
|
+
toCallBeforeReturningOidcLoggedIn,
|
|
350
|
+
toCallBeforeReturningOidcNotLoggedIn
|
|
351
|
+
} = createLoginOrGoToAuthServer({
|
|
352
|
+
configId,
|
|
353
|
+
oidcClientTsUserManager,
|
|
354
|
+
getExtraQueryParams,
|
|
355
|
+
transformUrlBeforeRedirect,
|
|
356
|
+
homeAndCallbackUrl,
|
|
357
|
+
log
|
|
358
|
+
});
|
|
526
359
|
|
|
527
360
|
const BROWSER_SESSION_NOT_FIRST_INIT_KEY = `oidc-spa.browser-session-not-first-init:${configId}`;
|
|
528
361
|
|
|
@@ -628,7 +461,6 @@ export async function createOidc_nonMemoized<
|
|
|
628
461
|
}
|
|
629
462
|
|
|
630
463
|
sessionStorage.removeItem(BROWSER_SESSION_NOT_FIRST_INIT_KEY);
|
|
631
|
-
clearPersistedLogoutState({ configId });
|
|
632
464
|
|
|
633
465
|
return {
|
|
634
466
|
oidcClientTsUser,
|
|
@@ -672,10 +504,41 @@ export async function createOidc_nonMemoized<
|
|
|
672
504
|
}
|
|
673
505
|
}
|
|
674
506
|
|
|
507
|
+
restore_from_session_storage: {
|
|
508
|
+
if (!isUserStorePersistent) {
|
|
509
|
+
break restore_from_session_storage;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
let oidcClientTsUser: OidcClientTsUser | null;
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
oidcClientTsUser = await oidcClientTsUserManager.getUser();
|
|
516
|
+
} catch {
|
|
517
|
+
// NOTE: Not sure if it can throw, but let's be safe.
|
|
518
|
+
oidcClientTsUser = null;
|
|
519
|
+
try {
|
|
520
|
+
await oidcClientTsUserManager.removeUser();
|
|
521
|
+
} catch {}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (oidcClientTsUser === null) {
|
|
525
|
+
break restore_from_session_storage;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
log?.("Restored the auth from ephemeral session storage");
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
oidcClientTsUser,
|
|
532
|
+
backFromAuthServer: undefined
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
675
536
|
restore_from_http_only_cookie: {
|
|
676
537
|
log?.("Trying to restore the auth from the http only cookie (silent signin with iframe)");
|
|
677
538
|
|
|
678
|
-
|
|
539
|
+
const persistedAuthState = getPersistedAuthState({ configId });
|
|
540
|
+
|
|
541
|
+
if (persistedAuthState === "explicitly logged out") {
|
|
679
542
|
log?.("Skipping silent signin with iframe, the user has logged out");
|
|
680
543
|
break restore_from_http_only_cookie;
|
|
681
544
|
}
|
|
@@ -687,7 +550,7 @@ export async function createOidc_nonMemoized<
|
|
|
687
550
|
getExtraTokenParams
|
|
688
551
|
});
|
|
689
552
|
|
|
690
|
-
assert(result_loginSilent.outcome !== "refresh token
|
|
553
|
+
assert(result_loginSilent.outcome !== "token refreshed using refresh token");
|
|
691
554
|
|
|
692
555
|
if (result_loginSilent.outcome === "failure") {
|
|
693
556
|
switch (result_loginSilent.cause) {
|
|
@@ -706,12 +569,13 @@ export async function createOidc_nonMemoized<
|
|
|
706
569
|
assert<Equals<typeof result_loginSilent.cause, never>>(false);
|
|
707
570
|
}
|
|
708
571
|
|
|
709
|
-
assert<Equals<typeof result_loginSilent.outcome, "
|
|
572
|
+
assert<Equals<typeof result_loginSilent.outcome, "got auth response from iframe">>();
|
|
710
573
|
|
|
711
574
|
const { authResponse } = result_loginSilent;
|
|
712
575
|
|
|
713
576
|
log?.("Silent signin auth response", authResponse);
|
|
714
577
|
|
|
578
|
+
const authResponse_error: string | undefined = authResponse["error"];
|
|
715
579
|
let oidcClientTsUser: OidcClientTsUser | undefined = undefined;
|
|
716
580
|
|
|
717
581
|
try {
|
|
@@ -728,24 +592,45 @@ export async function createOidc_nonMemoized<
|
|
|
728
592
|
});
|
|
729
593
|
}
|
|
730
594
|
|
|
731
|
-
{
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
595
|
+
if (authResponse_error === undefined) {
|
|
596
|
+
return error;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (oidcClientTsUser === undefined) {
|
|
601
|
+
if (
|
|
602
|
+
autoLogin ||
|
|
603
|
+
(persistedAuthState === "logged in" &&
|
|
604
|
+
(authResponse_error === "interaction_required" ||
|
|
605
|
+
authResponse_error === "login_required" ||
|
|
606
|
+
authResponse_error === "consent_required" ||
|
|
607
|
+
authResponse_error === "account_selection_required"))
|
|
608
|
+
) {
|
|
609
|
+
persistAuthState({ configId, state: undefined });
|
|
610
|
+
|
|
611
|
+
await loginOrGoToAuthServer({
|
|
612
|
+
action: "login",
|
|
613
|
+
doForceReloadOnBfCache: true,
|
|
614
|
+
redirectUrl: window.location.href,
|
|
615
|
+
doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: autoLogin,
|
|
616
|
+
extraQueryParams_local: undefined,
|
|
617
|
+
transformUrlBeforeRedirect_local: undefined,
|
|
618
|
+
doForceInteraction: false
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// NOTE: Never here
|
|
746
622
|
}
|
|
747
623
|
|
|
748
|
-
|
|
624
|
+
log?.(
|
|
625
|
+
[
|
|
626
|
+
`The auth server responded with: ${authResponse_error} `,
|
|
627
|
+
"login_required" === authResponse_error
|
|
628
|
+
? `(login_required just means that there's no active session for the user)`
|
|
629
|
+
: ""
|
|
630
|
+
].join("")
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
break restore_from_http_only_cookie;
|
|
749
634
|
}
|
|
750
635
|
|
|
751
636
|
log?.("Successful silent signed in");
|
|
@@ -829,118 +714,107 @@ export async function createOidc_nonMemoized<
|
|
|
829
714
|
}
|
|
830
715
|
};
|
|
831
716
|
|
|
832
|
-
|
|
833
|
-
|
|
717
|
+
not_loggedIn_case: {
|
|
718
|
+
if (!(resultOfLoginProcess instanceof Error) && resultOfLoginProcess !== undefined) {
|
|
719
|
+
break not_loggedIn_case;
|
|
720
|
+
}
|
|
834
721
|
|
|
835
|
-
const
|
|
722
|
+
const oidc_notLoggedIn: Oidc.NotLoggedIn = (() => {
|
|
723
|
+
if (resultOfLoginProcess instanceof Error) {
|
|
724
|
+
log?.("User not logged in and there was an initialization error");
|
|
836
725
|
|
|
837
|
-
|
|
838
|
-
error instanceof OidcInitializationError
|
|
839
|
-
? error
|
|
840
|
-
: new OidcInitializationError({
|
|
841
|
-
isAuthServerLikelyDown: false,
|
|
842
|
-
messageOrCause: error
|
|
843
|
-
});
|
|
726
|
+
const error = resultOfLoginProcess;
|
|
844
727
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
728
|
+
const initializationError =
|
|
729
|
+
error instanceof OidcInitializationError
|
|
730
|
+
? error
|
|
731
|
+
: new OidcInitializationError({
|
|
732
|
+
isAuthServerLikelyDown: false,
|
|
733
|
+
messageOrCause: error
|
|
734
|
+
});
|
|
848
735
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
`isAuthServerLikelyDown: ${initializationError.isAuthServerLikelyDown}`,
|
|
853
|
-
``,
|
|
854
|
-
initializationError.message
|
|
855
|
-
].join("\n")
|
|
856
|
-
);
|
|
736
|
+
if (autoLogin) {
|
|
737
|
+
throw initializationError;
|
|
738
|
+
}
|
|
857
739
|
|
|
858
|
-
|
|
740
|
+
console.error(
|
|
741
|
+
[
|
|
742
|
+
`oidc-spa Initialization Error: `,
|
|
743
|
+
`isAuthServerLikelyDown: ${initializationError.isAuthServerLikelyDown}`,
|
|
744
|
+
``,
|
|
745
|
+
initializationError.message
|
|
746
|
+
].join("\n")
|
|
747
|
+
);
|
|
859
748
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
749
|
+
return id<Oidc.NotLoggedIn>({
|
|
750
|
+
...common,
|
|
751
|
+
isUserLoggedIn: false,
|
|
752
|
+
login: async () => {
|
|
753
|
+
alert("Authentication is currently unavailable. Please try again later.");
|
|
754
|
+
return new Promise<never>(() => {});
|
|
755
|
+
},
|
|
756
|
+
initializationError
|
|
757
|
+
});
|
|
758
|
+
}
|
|
869
759
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
760
|
+
if (resultOfLoginProcess === undefined) {
|
|
761
|
+
log?.("User not logged in");
|
|
762
|
+
|
|
763
|
+
return id<Oidc.NotLoggedIn>({
|
|
764
|
+
...common,
|
|
765
|
+
isUserLoggedIn: false,
|
|
766
|
+
login: ({
|
|
767
|
+
doesCurrentHrefRequiresAuth,
|
|
768
|
+
extraQueryParams,
|
|
769
|
+
redirectUrl,
|
|
770
|
+
transformUrlBeforeRedirect
|
|
771
|
+
}) =>
|
|
772
|
+
loginOrGoToAuthServer({
|
|
773
|
+
action: "login",
|
|
774
|
+
doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack:
|
|
775
|
+
doesCurrentHrefRequiresAuth,
|
|
776
|
+
doForceReloadOnBfCache: false,
|
|
777
|
+
redirectUrl:
|
|
778
|
+
redirectUrl ?? postLoginRedirectUrl_default ?? window.location.href,
|
|
779
|
+
extraQueryParams_local: extraQueryParams,
|
|
780
|
+
transformUrlBeforeRedirect_local: transformUrlBeforeRedirect,
|
|
781
|
+
doForceInteraction:
|
|
782
|
+
getPersistedAuthState({ configId }) === "explicitly logged out"
|
|
783
|
+
}),
|
|
784
|
+
initializationError: undefined
|
|
785
|
+
});
|
|
786
|
+
}
|
|
873
787
|
|
|
874
|
-
|
|
875
|
-
|
|
788
|
+
assert<Equals<typeof resultOfLoginProcess, never>>(false);
|
|
789
|
+
})();
|
|
876
790
|
|
|
877
|
-
if (
|
|
878
|
-
|
|
879
|
-
await loginOrGoToAuthServer({
|
|
880
|
-
action: "login",
|
|
881
|
-
doesCurrentHrefRequiresAuth: true,
|
|
882
|
-
redirectUrl: postLoginRedirectUrl
|
|
883
|
-
});
|
|
884
|
-
// Never here
|
|
791
|
+
if (getPersistedAuthState({ configId }) !== "explicitly logged out") {
|
|
792
|
+
persistAuthState({ configId, state: undefined });
|
|
885
793
|
}
|
|
886
794
|
|
|
887
|
-
|
|
795
|
+
toCallBeforeReturningOidcNotLoggedIn();
|
|
888
796
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
isUserLoggedIn: false,
|
|
892
|
-
login: params => loginOrGoToAuthServer({ action: "login", ...params }),
|
|
893
|
-
initializationError: undefined
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
// @ts-expect-error: We know what we are doing.
|
|
897
|
-
return oidc;
|
|
797
|
+
// @ts-expect-error: We know what we're doing
|
|
798
|
+
return oidc_notLoggedIn;
|
|
898
799
|
}
|
|
899
800
|
|
|
900
801
|
log?.("User is logged in");
|
|
901
802
|
|
|
902
|
-
localStorage.setItem(USER_LOGGED_IN_KEY, "true");
|
|
903
|
-
|
|
904
803
|
let currentTokens = resultOfLoginProcess.tokens;
|
|
905
804
|
|
|
906
|
-
function getMsBeforeExpiration() {
|
|
907
|
-
// NOTE: In general the access token is supposed to have a shorter
|
|
908
|
-
// lifespan than the refresh token but we don't want to make any
|
|
909
|
-
// assumption here.
|
|
910
|
-
const tokenExpirationTime = Math.min(
|
|
911
|
-
currentTokens.accessTokenExpirationTime,
|
|
912
|
-
currentTokens.refreshTokenExpirationTime
|
|
913
|
-
);
|
|
914
|
-
|
|
915
|
-
const msBeforeExpiration = Math.min(
|
|
916
|
-
tokenExpirationTime - Date.now(),
|
|
917
|
-
// NOTE: We want to make sure we do not overflow the setTimeout
|
|
918
|
-
// that must be a 32 bit unsigned integer.
|
|
919
|
-
// This can happen if the tokenExpirationTime is more than 24.8 days in the future.
|
|
920
|
-
Math.pow(2, 31) - 1
|
|
921
|
-
);
|
|
922
|
-
|
|
923
|
-
if (msBeforeExpiration < 0) {
|
|
924
|
-
log?.("Token has already expired");
|
|
925
|
-
return 0;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
return msBeforeExpiration;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
805
|
const autoLogoutCountdownTickCallbacks = new Set<
|
|
932
806
|
(params: { secondsLeft: number | undefined }) => void
|
|
933
807
|
>();
|
|
934
808
|
|
|
935
809
|
const onTokenChanges = new Set<(tokens: Oidc.Tokens<DecodedIdToken>) => void>();
|
|
936
810
|
|
|
937
|
-
const
|
|
811
|
+
const oidc_loggedIn = id<Oidc.LoggedIn<DecodedIdToken>>({
|
|
938
812
|
...common,
|
|
939
813
|
isUserLoggedIn: true,
|
|
940
814
|
getTokens: () => currentTokens,
|
|
941
815
|
getTokens_next: async () => {
|
|
942
|
-
if (getMsBeforeExpiration() <=
|
|
943
|
-
await
|
|
816
|
+
if (getMsBeforeExpiration(currentTokens) <= MIN_RENEW_BEFORE_EXPIRE_MS) {
|
|
817
|
+
await oidc_loggedIn.renewTokens();
|
|
944
818
|
}
|
|
945
819
|
|
|
946
820
|
return currentTokens;
|
|
@@ -989,14 +863,21 @@ export async function createOidc_nonMemoized<
|
|
|
989
863
|
} catch (error) {
|
|
990
864
|
assert(is<Error>(error));
|
|
991
865
|
|
|
992
|
-
if (error.message
|
|
993
|
-
|
|
994
|
-
}
|
|
866
|
+
if (error.message === "No end session endpoint") {
|
|
867
|
+
log?.("No end session endpoint, managing logging state locally");
|
|
995
868
|
|
|
996
|
-
|
|
869
|
+
persistAuthState({ configId, state: "explicitly logged out" });
|
|
997
870
|
|
|
998
|
-
|
|
999
|
-
|
|
871
|
+
try {
|
|
872
|
+
await oidcClientTsUserManager.removeUser();
|
|
873
|
+
} catch {
|
|
874
|
+
// NOTE: Not sure if it can throw
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
window.location.href = postLogoutRedirectUrl;
|
|
878
|
+
} else {
|
|
879
|
+
throw error;
|
|
880
|
+
}
|
|
1000
881
|
}
|
|
1001
882
|
|
|
1002
883
|
return new Promise<never>(() => {});
|
|
@@ -1021,21 +902,53 @@ export async function createOidc_nonMemoized<
|
|
|
1021
902
|
let oidcClientTsUser: OidcClientTsUser;
|
|
1022
903
|
|
|
1023
904
|
switch (result_loginSilent.outcome) {
|
|
1024
|
-
case "refresh token
|
|
905
|
+
case "token refreshed using refresh token":
|
|
1025
906
|
{
|
|
1026
907
|
log?.("Refresh token used");
|
|
1027
908
|
oidcClientTsUser = result_loginSilent.oidcClientTsUser;
|
|
1028
909
|
}
|
|
1029
910
|
break;
|
|
1030
|
-
case "
|
|
911
|
+
case "got auth response from iframe":
|
|
1031
912
|
{
|
|
1032
913
|
const { authResponse } = result_loginSilent;
|
|
1033
914
|
|
|
1034
915
|
log?.("Tokens refresh using iframe", authResponse);
|
|
1035
916
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
917
|
+
const authResponse_error: string | undefined = authResponse["error"];
|
|
918
|
+
|
|
919
|
+
let oidcClientTsUser_scope: OidcClientTsUser | undefined = undefined;
|
|
920
|
+
|
|
921
|
+
try {
|
|
922
|
+
oidcClientTsUser_scope =
|
|
923
|
+
await oidcClientTsUserManager.signinRedirectCallback(
|
|
924
|
+
authResponseToUrl(authResponse)
|
|
925
|
+
);
|
|
926
|
+
} catch (error) {
|
|
927
|
+
assert(error instanceof Error);
|
|
928
|
+
|
|
929
|
+
if (authResponse_error === undefined) {
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
oidcClientTsUser_scope = undefined;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (oidcClientTsUser_scope === undefined) {
|
|
937
|
+
persistAuthState({ configId, state: undefined });
|
|
938
|
+
|
|
939
|
+
await loginOrGoToAuthServer({
|
|
940
|
+
action: "login",
|
|
941
|
+
redirectUrl: window.location.href,
|
|
942
|
+
doForceReloadOnBfCache: true,
|
|
943
|
+
extraQueryParams_local: undefined,
|
|
944
|
+
transformUrlBeforeRedirect_local: undefined,
|
|
945
|
+
doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: false,
|
|
946
|
+
doForceInteraction: false
|
|
947
|
+
});
|
|
948
|
+
assert(false);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
oidcClientTsUser = oidcClientTsUser_scope;
|
|
1039
952
|
}
|
|
1040
953
|
break;
|
|
1041
954
|
default:
|
|
@@ -1134,7 +1047,13 @@ export async function createOidc_nonMemoized<
|
|
|
1134
1047
|
|
|
1135
1048
|
return { unsubscribeFromAutoLogoutCountdown };
|
|
1136
1049
|
},
|
|
1137
|
-
goToAuthServer:
|
|
1050
|
+
goToAuthServer: ({ extraQueryParams, redirectUrl, transformUrlBeforeRedirect }) =>
|
|
1051
|
+
loginOrGoToAuthServer({
|
|
1052
|
+
action: "go to auth server",
|
|
1053
|
+
redirectUrl: redirectUrl ?? window.location.href,
|
|
1054
|
+
extraQueryParams_local: extraQueryParams,
|
|
1055
|
+
transformUrlBeforeRedirect_local: transformUrlBeforeRedirect
|
|
1056
|
+
}),
|
|
1138
1057
|
backFromAuthServer: resultOfLoginProcess.backFromAuthServer,
|
|
1139
1058
|
isNewBrowserSession: (() => {
|
|
1140
1059
|
if (sessionStorage.getItem(BROWSER_SESSION_NOT_FIRST_INIT_KEY) === null) {
|
|
@@ -1167,21 +1086,25 @@ export async function createOidc_nonMemoized<
|
|
|
1167
1086
|
}
|
|
1168
1087
|
|
|
1169
1088
|
(function scheduleRenew() {
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
// However there's no way to enforce the browser to redirect back to
|
|
1175
|
-
// the last public route if the user press back on the login page.
|
|
1176
|
-
// This is due to the fact that pushing to history only works if it's
|
|
1177
|
-
// triggered by a user interaction.
|
|
1178
|
-
const login_dueToExpiration = () =>
|
|
1179
|
-
loginOrGoToAuthServer({
|
|
1089
|
+
const login_dueToExpiration = () => {
|
|
1090
|
+
persistAuthState({ configId, state: undefined });
|
|
1091
|
+
|
|
1092
|
+
return loginOrGoToAuthServer({
|
|
1180
1093
|
action: "login",
|
|
1181
|
-
|
|
1094
|
+
redirectUrl: window.location.href,
|
|
1095
|
+
doForceReloadOnBfCache: true,
|
|
1096
|
+
extraQueryParams_local: undefined,
|
|
1097
|
+
transformUrlBeforeRedirect_local: undefined,
|
|
1098
|
+
// NOTE: Wether or not it's the preferred behavior, pushing to history
|
|
1099
|
+
// only works on user interaction so it have to be false
|
|
1100
|
+
doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: false,
|
|
1101
|
+
doForceInteraction: true
|
|
1182
1102
|
});
|
|
1103
|
+
};
|
|
1183
1104
|
|
|
1184
|
-
|
|
1105
|
+
const msBeforeExpiration = getMsBeforeExpiration(currentTokens);
|
|
1106
|
+
|
|
1107
|
+
if (msBeforeExpiration <= MIN_RENEW_BEFORE_EXPIRE_MS) {
|
|
1185
1108
|
// NOTE: We just got a new token that is about to expire. This means that
|
|
1186
1109
|
// the refresh token has reached it's max SSO time.
|
|
1187
1110
|
login_dueToExpiration();
|
|
@@ -1191,7 +1114,10 @@ export async function createOidc_nonMemoized<
|
|
|
1191
1114
|
// NOTE: We refresh the token 25 seconds before it expires.
|
|
1192
1115
|
// If the token expiration time is less than 25 seconds we refresh the token when
|
|
1193
1116
|
// only 1/10 of the token time is left.
|
|
1194
|
-
const renewMsBeforeExpires = Math.
|
|
1117
|
+
const renewMsBeforeExpires = Math.max(
|
|
1118
|
+
Math.min(25_000, msBeforeExpiration * 0.1),
|
|
1119
|
+
MIN_RENEW_BEFORE_EXPIRE_MS
|
|
1120
|
+
);
|
|
1195
1121
|
|
|
1196
1122
|
log?.(
|
|
1197
1123
|
[
|
|
@@ -1209,13 +1135,13 @@ export async function createOidc_nonMemoized<
|
|
|
1209
1135
|
);
|
|
1210
1136
|
|
|
1211
1137
|
try {
|
|
1212
|
-
await
|
|
1138
|
+
await oidc_loggedIn.renewTokens();
|
|
1213
1139
|
} catch {
|
|
1214
1140
|
await login_dueToExpiration();
|
|
1215
1141
|
}
|
|
1216
1142
|
}, msBeforeExpiration - renewMsBeforeExpires);
|
|
1217
1143
|
|
|
1218
|
-
const { unsubscribe: tokenChangeUnsubscribe } =
|
|
1144
|
+
const { unsubscribe: tokenChangeUnsubscribe } = oidc_loggedIn.subscribeToTokensChange(() => {
|
|
1219
1145
|
clearTimeout(timer);
|
|
1220
1146
|
tokenChangeUnsubscribe();
|
|
1221
1147
|
scheduleRenew();
|
|
@@ -1260,7 +1186,7 @@ export async function createOidc_nonMemoized<
|
|
|
1260
1186
|
);
|
|
1261
1187
|
|
|
1262
1188
|
if (secondsLeft === 0) {
|
|
1263
|
-
|
|
1189
|
+
oidc_loggedIn.logout(autoLogoutParams);
|
|
1264
1190
|
}
|
|
1265
1191
|
}
|
|
1266
1192
|
});
|
|
@@ -1287,5 +1213,17 @@ export async function createOidc_nonMemoized<
|
|
|
1287
1213
|
});
|
|
1288
1214
|
}
|
|
1289
1215
|
|
|
1290
|
-
|
|
1216
|
+
{
|
|
1217
|
+
if (getPersistedAuthState({ configId }) !== undefined) {
|
|
1218
|
+
persistAuthState({ configId, state: undefined });
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (!areThirdPartyCookiesAllowed) {
|
|
1222
|
+
persistAuthState({ configId, state: "logged in" });
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
toCallBeforeReturningOidcLoggedIn();
|
|
1227
|
+
|
|
1228
|
+
return oidc_loggedIn;
|
|
1291
1229
|
}
|