oidc-spa 7.2.1 → 7.2.3
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/backend.js.map +1 -1
- package/core/AuthResponse.js.map +1 -1
- package/core/Oidc.js.map +1 -1
- package/core/OidcInitializationError.js.map +1 -1
- package/core/OidcMetadata.js.map +1 -1
- package/core/StateData.js.map +1 -1
- package/core/configId.js.map +1 -1
- package/core/createOidc.js +1 -1
- package/core/createOidc.js.map +1 -1
- package/core/diagnostic.js.map +1 -1
- package/core/evtIsUserActive.js.map +1 -1
- package/core/handleOidcCallback.js.map +1 -1
- package/core/iframeMessageProtection.js.map +1 -1
- package/core/index.js.map +1 -1
- package/core/initialLocationHref.js.map +1 -1
- package/core/isNewBrowserSession.js.map +1 -1
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/core/loginPropagationToOtherTabs.js.map +1 -1
- package/core/loginSilent.js.map +1 -1
- package/core/logoutPropagationToOtherTabs.js.map +1 -1
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/core/ongoingLoginOrRefreshProcesses.js.map +1 -1
- package/core/persistedAuthState.js.map +1 -1
- package/entrypoint.js.map +1 -1
- package/esm/core/AuthResponse.js +2 -2
- package/esm/core/AuthResponse.js.map +1 -1
- package/esm/core/Oidc.d.ts +1 -1
- package/esm/core/Oidc.js.map +1 -1
- package/esm/core/OidcInitializationError.js.map +1 -1
- package/esm/core/OidcMetadata.js +2 -2
- package/esm/core/OidcMetadata.js.map +1 -1
- package/esm/core/StateData.js +3 -3
- package/esm/core/StateData.js.map +1 -1
- package/esm/core/configId.js.map +1 -1
- package/esm/core/createOidc.d.ts +2 -2
- package/esm/core/createOidc.js +33 -33
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/diagnostic.d.ts +1 -1
- package/esm/core/diagnostic.js +4 -4
- package/esm/core/diagnostic.js.map +1 -1
- package/esm/core/evtIsUserActive.d.ts +1 -1
- package/esm/core/evtIsUserActive.js +5 -5
- package/esm/core/evtIsUserActive.js.map +1 -1
- package/esm/core/handleOidcCallback.d.ts +2 -2
- package/esm/core/handleOidcCallback.js +5 -5
- package/esm/core/handleOidcCallback.js.map +1 -1
- package/esm/core/iframeMessageProtection.d.ts +1 -1
- package/esm/core/iframeMessageProtection.js +3 -3
- package/esm/core/iframeMessageProtection.js.map +1 -1
- package/esm/core/index.d.ts +4 -4
- package/esm/core/index.js +4 -4
- package/esm/core/index.js.map +1 -1
- package/esm/core/initialLocationHref.js.map +1 -1
- package/esm/core/isNewBrowserSession.d.ts +1 -1
- package/esm/core/isNewBrowserSession.js.map +1 -1
- package/esm/core/loginOrGoToAuthServer.d.ts +2 -2
- package/esm/core/loginOrGoToAuthServer.js +6 -6
- package/esm/core/loginOrGoToAuthServer.js.map +1 -1
- package/esm/core/loginPropagationToOtherTabs.js +3 -3
- package/esm/core/loginPropagationToOtherTabs.js.map +1 -1
- package/esm/core/loginSilent.d.ts +2 -2
- package/esm/core/loginSilent.js +8 -8
- package/esm/core/loginSilent.js.map +1 -1
- package/esm/core/logoutPropagationToOtherTabs.js +3 -3
- package/esm/core/logoutPropagationToOtherTabs.js.map +1 -1
- package/esm/core/oidcClientTsUserToTokens.d.ts +2 -2
- package/esm/core/oidcClientTsUserToTokens.js +4 -4
- package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
- package/esm/core/ongoingLoginOrRefreshProcesses.js +3 -3
- package/esm/core/ongoingLoginOrRefreshProcesses.js.map +1 -1
- package/esm/core/persistedAuthState.js +2 -2
- package/esm/core/persistedAuthState.js.map +1 -1
- package/esm/entrypoint.js +3 -3
- package/esm/entrypoint.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.js +2 -2
- package/esm/index.js.map +1 -1
- package/esm/keycloak/index.d.ts +3 -3
- package/esm/keycloak/index.js +3 -3
- package/esm/keycloak/index.js.map +1 -1
- package/esm/keycloak/isKeycloak.js.map +1 -1
- package/esm/keycloak/keycloak-js/Keycloak.d.ts +1 -1
- package/esm/keycloak/keycloak-js/Keycloak.js +9 -9
- package/esm/keycloak/keycloak-js/Keycloak.js.map +1 -1
- package/esm/keycloak/keycloak-js/index.d.ts +2 -2
- package/esm/keycloak/keycloak-js/index.js +2 -2
- package/esm/keycloak/keycloak-js/index.js.map +1 -1
- package/esm/keycloak/keycloak-js/types.js.map +1 -1
- package/esm/keycloak/keycloakIssuerUriParsed.js +3 -3
- package/esm/keycloak/keycloakIssuerUriParsed.js.map +1 -1
- package/esm/keycloak/keycloakUtils.d.ts +1 -1
- package/esm/keycloak/keycloakUtils.js +3 -3
- package/esm/keycloak/keycloakUtils.js.map +1 -1
- package/esm/keycloak-js.d.ts +1 -1
- package/esm/keycloak-js.js +2 -2
- package/esm/keycloak-js.js.map +1 -1
- package/esm/mock/index.d.ts +1 -1
- package/esm/mock/index.js +2 -2
- package/esm/mock/index.js.map +1 -1
- package/esm/mock/oidc.d.ts +1 -1
- package/esm/mock/oidc.js +6 -6
- package/esm/mock/oidc.js.map +1 -1
- package/esm/mock/react.d.ts +8 -8
- package/esm/mock/react.js +3 -3
- package/esm/mock/react.js.map +1 -1
- package/esm/react/index.d.ts +1 -1
- package/esm/react/index.js +2 -2
- package/esm/react/index.js.map +1 -1
- package/esm/react/react.d.ts +2 -2
- package/esm/react/react.js +6 -6
- package/esm/react/react.js.map +1 -1
- package/esm/tools/Deferred.js.map +1 -1
- package/esm/tools/EphemeralSessionStorage.js +2 -2
- package/esm/tools/EphemeralSessionStorage.js.map +1 -1
- package/esm/tools/Evt.js +3 -3
- package/esm/tools/Evt.js.map +1 -1
- package/esm/tools/StatefulEvt.js.map +1 -1
- package/esm/tools/ValueOrAsyncGetter.js.map +1 -1
- package/esm/tools/asymmetricEncryption.js.map +1 -1
- package/esm/tools/base64.js.map +1 -1
- package/esm/tools/createObjectThatThrowsIfAccessed.js.map +1 -1
- package/esm/tools/decodeJwt.js.map +1 -1
- package/esm/tools/generateUrlSafeRandom.js.map +1 -1
- package/esm/tools/getDownlinkAndRtt.js +2 -2
- package/esm/tools/getDownlinkAndRtt.js.map +1 -1
- package/esm/tools/getIsOnline.js +2 -2
- package/esm/tools/getIsOnline.js.map +1 -1
- package/esm/tools/getIsValidRemoteJson.js.map +1 -1
- package/esm/tools/getPrUserInteraction.js +2 -2
- package/esm/tools/getPrUserInteraction.js.map +1 -1
- package/esm/tools/getUserEnvironmentInfo.js.map +1 -1
- package/esm/tools/haveSharedParentDomain.js.map +1 -1
- package/esm/tools/isDev.js.map +1 -1
- package/esm/tools/parseKeycloakIssuerUri.js +2 -2
- package/esm/tools/parseKeycloakIssuerUri.js.map +1 -1
- package/esm/tools/readExpirationTimeInJwt.js +3 -3
- package/esm/tools/readExpirationTimeInJwt.js.map +1 -1
- package/esm/tools/startCountdown.js +2 -2
- package/esm/tools/startCountdown.js.map +1 -1
- package/esm/tools/subscribeToUserInteraction.js +2 -2
- package/esm/tools/subscribeToUserInteraction.js.map +1 -1
- package/esm/tools/toFullyQualifiedUrl.js.map +1 -1
- package/esm/tools/toHumanReadableDuration.js.map +1 -1
- package/esm/tools/urlSearchParams.js.map +1 -1
- package/esm/tools/workerTimers.js +2 -2
- package/esm/tools/workerTimers.js.map +1 -1
- package/index.js.map +1 -1
- package/keycloak/index.js.map +1 -1
- package/keycloak/isKeycloak.js.map +1 -1
- package/keycloak/keycloak-js/Keycloak.js.map +1 -1
- package/keycloak/keycloak-js/index.js.map +1 -1
- package/keycloak/keycloak-js/types.js.map +1 -1
- package/keycloak/keycloakIssuerUriParsed.js.map +1 -1
- package/keycloak/keycloakUtils.js.map +1 -1
- package/keycloak-js.js.map +1 -1
- package/mock/index.js.map +1 -1
- package/mock/oidc.js.map +1 -1
- package/mock/react.js.map +1 -1
- package/package.json +11 -33
- package/react/index.js.map +1 -1
- package/react/react.js.map +1 -1
- package/src/backend.ts +391 -0
- package/src/core/AuthResponse.ts +26 -0
- package/src/core/Oidc.ts +140 -0
- package/src/core/OidcInitializationError.ts +19 -0
- package/src/core/OidcMetadata.ts +271 -0
- package/src/core/StateData.ts +118 -0
- package/src/core/configId.ts +3 -0
- package/src/core/createOidc.ts +1576 -0
- package/src/core/diagnostic.ts +267 -0
- package/src/core/evtIsUserActive.ts +108 -0
- package/src/core/handleOidcCallback.ts +321 -0
- package/src/core/iframeMessageProtection.ts +100 -0
- package/src/core/index.ts +4 -0
- package/src/core/initialLocationHref.ts +5 -0
- package/src/core/isNewBrowserSession.ts +37 -0
- package/src/core/loginOrGoToAuthServer.ts +324 -0
- package/src/core/loginPropagationToOtherTabs.ts +51 -0
- package/src/core/loginSilent.ts +242 -0
- package/src/core/logoutPropagationToOtherTabs.ts +53 -0
- package/src/core/oidcClientTsUserToTokens.ts +229 -0
- package/src/core/ongoingLoginOrRefreshProcesses.ts +47 -0
- package/src/core/persistedAuthState.ts +122 -0
- package/src/entrypoint.ts +69 -0
- package/src/index.ts +1 -0
- package/src/keycloak/index.ts +8 -0
- package/src/keycloak/isKeycloak.ts +23 -0
- package/src/keycloak/keycloak-js/Keycloak.ts +1097 -0
- package/src/keycloak/keycloak-js/index.ts +2 -0
- package/src/keycloak/keycloak-js/types.ts +442 -0
- package/src/keycloak/keycloakIssuerUriParsed.ts +29 -0
- package/src/keycloak/keycloakUtils.ts +90 -0
- package/src/keycloak-js.ts +1 -0
- package/src/mock/index.ts +1 -0
- package/src/mock/oidc.ts +211 -0
- package/src/mock/react.tsx +11 -0
- package/src/react/index.ts +1 -0
- package/src/react/react.tsx +476 -0
- package/src/tools/Deferred.ts +33 -0
- package/src/tools/EphemeralSessionStorage.ts +223 -0
- package/src/tools/Evt.ts +56 -0
- package/src/tools/StatefulEvt.ts +38 -0
- package/src/tools/ValueOrAsyncGetter.ts +1 -0
- package/src/tools/asymmetricEncryption.ts +184 -0
- package/src/tools/base64.ts +7 -0
- package/src/tools/createObjectThatThrowsIfAccessed.ts +40 -0
- package/src/tools/decodeJwt.ts +95 -0
- package/src/tools/generateUrlSafeRandom.ts +26 -0
- package/src/tools/getDownlinkAndRtt.ts +22 -0
- package/src/tools/getIsOnline.ts +20 -0
- package/src/tools/getIsValidRemoteJson.ts +18 -0
- package/src/tools/getPrUserInteraction.ts +27 -0
- package/src/tools/getUserEnvironmentInfo.ts +42 -0
- package/src/tools/haveSharedParentDomain.ts +13 -0
- package/src/tools/isDev.ts +30 -0
- package/src/tools/parseKeycloakIssuerUri.ts +49 -0
- package/src/tools/readExpirationTimeInJwt.ts +16 -0
- package/src/tools/startCountdown.ts +36 -0
- package/src/tools/subscribeToUserInteraction.ts +33 -0
- package/src/tools/toFullyQualifiedUrl.ts +58 -0
- package/src/tools/toHumanReadableDuration.ts +21 -0
- package/src/tools/urlSearchParams.ts +130 -0
- package/src/tools/workerTimers.ts +57 -0
- package/src/vendor/backend/evt.ts +2 -0
- package/src/vendor/backend/jsonwebtoken.ts +1 -0
- package/src/vendor/backend/node-fetch.ts +2 -0
- package/src/vendor/backend/node-jose.ts +1 -0
- package/src/vendor/backend/tsafe.ts +5 -0
- package/src/vendor/backend/zod.ts +1 -0
- package/src/vendor/frontend/oidc-client-ts.ts +1 -0
- package/src/vendor/frontend/tsafe.ts +6 -0
- package/src/vendor/frontend/worker-timers.ts +2 -0
- package/tools/Deferred.js.map +1 -1
- package/tools/EphemeralSessionStorage.js.map +1 -1
- package/tools/Evt.js.map +1 -1
- package/tools/StatefulEvt.js.map +1 -1
- package/tools/ValueOrAsyncGetter.js.map +1 -1
- package/tools/asymmetricEncryption.js.map +1 -1
- package/tools/base64.js.map +1 -1
- package/tools/createObjectThatThrowsIfAccessed.js.map +1 -1
- package/tools/decodeJwt.js.map +1 -1
- package/tools/generateUrlSafeRandom.js.map +1 -1
- package/tools/getDownlinkAndRtt.js.map +1 -1
- package/tools/getIsOnline.js.map +1 -1
- package/tools/getIsValidRemoteJson.js.map +1 -1
- package/tools/getPrUserInteraction.js.map +1 -1
- package/tools/getUserEnvironmentInfo.js.map +1 -1
- package/tools/haveSharedParentDomain.js.map +1 -1
- package/tools/isDev.js.map +1 -1
- package/tools/parseKeycloakIssuerUri.js.map +1 -1
- package/tools/readExpirationTimeInJwt.js.map +1 -1
- package/tools/startCountdown.js.map +1 -1
- package/tools/subscribeToUserInteraction.js.map +1 -1
- package/tools/toFullyQualifiedUrl.js.map +1 -1
- package/tools/toHumanReadableDuration.js.map +1 -1
- package/tools/urlSearchParams.js.map +1 -1
- package/tools/workerTimers.js.map +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { assert } from "../vendor/frontend/tsafe";
|
|
2
|
+
import { asymmetricEncrypt, asymmetricDecrypt, generateKeys } from "../tools/asymmetricEncryption";
|
|
3
|
+
import { type AuthResponse } from "./AuthResponse";
|
|
4
|
+
|
|
5
|
+
const sessionStorage_original = window.sessionStorage;
|
|
6
|
+
const setItem_real = Storage.prototype.setItem;
|
|
7
|
+
|
|
8
|
+
const SESSION_STORAGE_PREFIX = "oidc-spa_iframe_authResponse_publicKey_";
|
|
9
|
+
|
|
10
|
+
export function preventSessionStorageSetItemOfPublicKeyByThirdParty() {
|
|
11
|
+
const setItem_protected = function setItem(this: any, key: string, value: string): void {
|
|
12
|
+
if (this !== sessionStorage_original) {
|
|
13
|
+
return setItem_real.call(this, key, value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (key.startsWith(SESSION_STORAGE_PREFIX)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"Attack prevented by oidc-spa. You have malicious code running in your system"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return setItem_real.call(sessionStorage_original, key, value);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
const pd = Object.getOwnPropertyDescriptor(Storage.prototype, "setItem");
|
|
27
|
+
|
|
28
|
+
assert(pd !== undefined);
|
|
29
|
+
|
|
30
|
+
Object.defineProperty(Storage.prototype, "setItem", {
|
|
31
|
+
enumerable: pd.enumerable,
|
|
32
|
+
writable: pd.writable,
|
|
33
|
+
value: setItem_protected
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ENCRYPTED_AUTH_RESPONSES_PREFIX = "oidc-spa_encrypted_authResponse_";
|
|
39
|
+
|
|
40
|
+
function getSessionStorageKey(params: { stateUrlParamValue: string }) {
|
|
41
|
+
const { stateUrlParamValue } = params;
|
|
42
|
+
|
|
43
|
+
return `${SESSION_STORAGE_PREFIX}${stateUrlParamValue}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function initIframeMessageProtection(params: { stateUrlParamValue: string }) {
|
|
47
|
+
const { stateUrlParamValue } = params;
|
|
48
|
+
|
|
49
|
+
const { publicKey, privateKey } = await generateKeys();
|
|
50
|
+
|
|
51
|
+
const sessionStorageKey = getSessionStorageKey({ stateUrlParamValue });
|
|
52
|
+
|
|
53
|
+
setItem_real.call(sessionStorage, sessionStorageKey, publicKey);
|
|
54
|
+
|
|
55
|
+
function getIsEncryptedAuthResponse(params: { message: unknown }): boolean {
|
|
56
|
+
const { message } = params;
|
|
57
|
+
|
|
58
|
+
return typeof message === "string" && message.startsWith(ENCRYPTED_AUTH_RESPONSES_PREFIX);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function decodeEncryptedAuth(params: {
|
|
62
|
+
encryptedAuthResponse: string;
|
|
63
|
+
}): Promise<{ authResponse: AuthResponse }> {
|
|
64
|
+
const { encryptedAuthResponse } = params;
|
|
65
|
+
|
|
66
|
+
const { message: authResponse_str } = await asymmetricDecrypt({
|
|
67
|
+
encryptedMessage: encryptedAuthResponse.slice(ENCRYPTED_AUTH_RESPONSES_PREFIX.length),
|
|
68
|
+
privateKey
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const authResponse: AuthResponse = JSON.parse(authResponse_str);
|
|
72
|
+
|
|
73
|
+
return { authResponse };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function clearSessionStoragePublicKey() {
|
|
77
|
+
sessionStorage.removeItem(sessionStorageKey);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { getIsEncryptedAuthResponse, decodeEncryptedAuth, clearSessionStoragePublicKey };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function encryptAuthResponse(params: { authResponse: AuthResponse }) {
|
|
84
|
+
const { authResponse } = params;
|
|
85
|
+
|
|
86
|
+
const publicKey = sessionStorage.getItem(
|
|
87
|
+
getSessionStorageKey({ stateUrlParamValue: authResponse.state })
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
assert(publicKey !== null, "2293302");
|
|
91
|
+
|
|
92
|
+
const { encryptedMessage: encryptedMessage_withoutPrefix } = await asymmetricEncrypt({
|
|
93
|
+
publicKey,
|
|
94
|
+
message: JSON.stringify(authResponse)
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const encryptedMessage = `${ENCRYPTED_AUTH_RESPONSES_PREFIX}${encryptedMessage_withoutPrefix}`;
|
|
98
|
+
|
|
99
|
+
return { encryptedMessage };
|
|
100
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { NonPostableEvt } from "../tools/Evt";
|
|
2
|
+
|
|
3
|
+
export function createGetIsNewBrowserSession(params: {
|
|
4
|
+
configId: string;
|
|
5
|
+
evtUserNotLoggedIn: NonPostableEvt<void>;
|
|
6
|
+
}) {
|
|
7
|
+
const { configId, evtUserNotLoggedIn } = params;
|
|
8
|
+
|
|
9
|
+
const SESSION_STORAGE_KEY = `oidc-spa.subject-id:${configId}`;
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
const { unsubscribe } = evtUserNotLoggedIn.subscribe(() => {
|
|
13
|
+
unsubscribe();
|
|
14
|
+
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getIsNewBrowserSession(params: { subjectId: string }): boolean {
|
|
19
|
+
const { subjectId } = params;
|
|
20
|
+
|
|
21
|
+
const subjectId_sessionStorage = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
22
|
+
|
|
23
|
+
if (subjectId_sessionStorage === null) {
|
|
24
|
+
sessionStorage.setItem(SESSION_STORAGE_KEY, subjectId);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (subjectId !== subjectId_sessionStorage) {
|
|
29
|
+
sessionStorage.setItem(SESSION_STORAGE_KEY, subjectId);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { getIsNewBrowserSession };
|
|
37
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type { UserManager as OidcClientTsUserManager } from "../vendor/frontend/oidc-client-ts";
|
|
2
|
+
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
3
|
+
import { assert, type Equals, noUndefined } from "../vendor/frontend/tsafe";
|
|
4
|
+
import type { StateData } from "./StateData";
|
|
5
|
+
import type { NonPostableEvt } from "../tools/Evt";
|
|
6
|
+
import { createStatefulEvt } from "../tools/StatefulEvt";
|
|
7
|
+
import { Deferred } from "../tools/Deferred";
|
|
8
|
+
import { addOrUpdateSearchParam, getAllSearchParams } from "../tools/urlSearchParams";
|
|
9
|
+
|
|
10
|
+
const globalContext = {
|
|
11
|
+
evtHasLoginBeenCalled: createStatefulEvt(() => false)
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type Params = Params.Login | Params.GoToAuthServer;
|
|
15
|
+
|
|
16
|
+
namespace Params {
|
|
17
|
+
type Common = {
|
|
18
|
+
redirectUrl: string;
|
|
19
|
+
extraQueryParams_local: Record<string, string | undefined> | undefined;
|
|
20
|
+
transformUrlBeforeRedirect_local: ((url: string) => string) | undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type Login = Common & {
|
|
24
|
+
action: "login";
|
|
25
|
+
doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: boolean;
|
|
26
|
+
doForceReloadOnBfCache: boolean;
|
|
27
|
+
interaction:
|
|
28
|
+
| "ensure no interaction"
|
|
29
|
+
| "ensure interaction"
|
|
30
|
+
| "directly redirect if active session show login otherwise";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type GoToAuthServer = Common & {
|
|
34
|
+
action: "go to auth server";
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation() {
|
|
39
|
+
const dOut = new Deferred<void>();
|
|
40
|
+
|
|
41
|
+
const { unsubscribe } = globalContext.evtHasLoginBeenCalled.subscribe(hasLoginBeenCalled => {
|
|
42
|
+
if (!hasLoginBeenCalled) {
|
|
43
|
+
unsubscribe();
|
|
44
|
+
dOut.resolve();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return dOut.pr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createLoginOrGoToAuthServer(params: {
|
|
52
|
+
configId: string;
|
|
53
|
+
oidcClientTsUserManager: OidcClientTsUserManager;
|
|
54
|
+
transformUrlBeforeRedirect:
|
|
55
|
+
| ((params: { authorizationUrl: string; isSilent: boolean }) => string)
|
|
56
|
+
| undefined;
|
|
57
|
+
|
|
58
|
+
getExtraQueryParams:
|
|
59
|
+
| ((params: { isSilent: boolean; url: string }) => Record<string, string | undefined>)
|
|
60
|
+
| undefined;
|
|
61
|
+
|
|
62
|
+
getExtraTokenParams: (() => Record<string, string | undefined>) | undefined;
|
|
63
|
+
|
|
64
|
+
homeUrl: string;
|
|
65
|
+
evtIsUserLoggedIn: NonPostableEvt<boolean>;
|
|
66
|
+
log: typeof console.log | undefined;
|
|
67
|
+
}) {
|
|
68
|
+
const {
|
|
69
|
+
configId,
|
|
70
|
+
oidcClientTsUserManager,
|
|
71
|
+
|
|
72
|
+
transformUrlBeforeRedirect,
|
|
73
|
+
getExtraQueryParams,
|
|
74
|
+
|
|
75
|
+
getExtraTokenParams,
|
|
76
|
+
|
|
77
|
+
homeUrl,
|
|
78
|
+
evtIsUserLoggedIn,
|
|
79
|
+
log
|
|
80
|
+
} = params;
|
|
81
|
+
|
|
82
|
+
const LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN = `oidc-spa.login-redirect-initiated:${configId}`;
|
|
83
|
+
|
|
84
|
+
let lastPublicUrl: string | undefined = undefined;
|
|
85
|
+
|
|
86
|
+
async function loginOrGoToAuthServer(params: Params): Promise<never> {
|
|
87
|
+
const {
|
|
88
|
+
redirectUrl: redirectUrl_params,
|
|
89
|
+
extraQueryParams_local,
|
|
90
|
+
transformUrlBeforeRedirect_local,
|
|
91
|
+
...rest
|
|
92
|
+
} = params;
|
|
93
|
+
|
|
94
|
+
log?.(`Calling loginOrGoToAuthServer ${JSON.stringify(params, null, 2)}`);
|
|
95
|
+
|
|
96
|
+
login_specific_handling: {
|
|
97
|
+
if (rest.action !== "login") {
|
|
98
|
+
break login_specific_handling;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (globalContext.evtHasLoginBeenCalled.current) {
|
|
102
|
+
log?.("login() has already been called, ignoring the call");
|
|
103
|
+
return new Promise<never>(() => {});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
globalContext.evtHasLoginBeenCalled.current = true;
|
|
107
|
+
|
|
108
|
+
if (document.visibilityState !== "visible") {
|
|
109
|
+
rest.interaction === "ensure no interaction";
|
|
110
|
+
|
|
111
|
+
const dVisible = new Deferred<void>();
|
|
112
|
+
|
|
113
|
+
const onVisible = () => {
|
|
114
|
+
if (document.visibilityState !== "visible") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
118
|
+
dVisible.resolve();
|
|
119
|
+
};
|
|
120
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
121
|
+
|
|
122
|
+
await dVisible.pr;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
bf_cache_handling: {
|
|
126
|
+
if (rest.doForceReloadOnBfCache) {
|
|
127
|
+
window.removeEventListener("pageshow", () => {
|
|
128
|
+
location.reload();
|
|
129
|
+
});
|
|
130
|
+
break bf_cache_handling;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
localStorage.setItem(LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN, "true");
|
|
134
|
+
|
|
135
|
+
const callback = () => {
|
|
136
|
+
window.removeEventListener("pageshow", callback);
|
|
137
|
+
|
|
138
|
+
log?.(
|
|
139
|
+
"We came back from the login pages and the state of the app has been restored"
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (rest.doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack) {
|
|
143
|
+
if (lastPublicUrl !== undefined) {
|
|
144
|
+
log?.(`Loading last public route: ${lastPublicUrl}`);
|
|
145
|
+
window.location.href = lastPublicUrl;
|
|
146
|
+
} else {
|
|
147
|
+
log?.("We don't know the last public route, navigating back in history");
|
|
148
|
+
window.history.back();
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
log?.("The current page doesn't require auth...");
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
localStorage.getItem(LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN) === null
|
|
155
|
+
) {
|
|
156
|
+
log?.("but the user is now authenticated, reloading the page");
|
|
157
|
+
location.reload();
|
|
158
|
+
} else {
|
|
159
|
+
log?.("and the user doesn't seem to be authenticated, avoiding a reload");
|
|
160
|
+
globalContext.evtHasLoginBeenCalled.current = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
window.addEventListener("pageshow", callback);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const redirectUrl = toFullyQualifiedUrl({
|
|
170
|
+
urlish: redirectUrl_params,
|
|
171
|
+
doAssertNoQueryParams: false
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
log?.(`redirectUrl: ${redirectUrl}`);
|
|
175
|
+
|
|
176
|
+
const stateData: StateData = {
|
|
177
|
+
context: "redirect",
|
|
178
|
+
redirectUrl,
|
|
179
|
+
extraQueryParams: {},
|
|
180
|
+
hasBeenProcessedByCallback: false,
|
|
181
|
+
configId,
|
|
182
|
+
action: "login",
|
|
183
|
+
redirectUrl_consentRequiredCase: (() => {
|
|
184
|
+
switch (rest.action) {
|
|
185
|
+
case "login":
|
|
186
|
+
return lastPublicUrl ?? homeUrl;
|
|
187
|
+
case "go to auth server":
|
|
188
|
+
return redirectUrl;
|
|
189
|
+
}
|
|
190
|
+
})()
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const isSilent = rest.action === "login" && rest.interaction === "ensure no interaction";
|
|
194
|
+
|
|
195
|
+
const transformUrl_oidcClientTs = (url: string) => {
|
|
196
|
+
(
|
|
197
|
+
[
|
|
198
|
+
[
|
|
199
|
+
getExtraQueryParams,
|
|
200
|
+
transformUrlBeforeRedirect === undefined
|
|
201
|
+
? undefined
|
|
202
|
+
: (url: string) =>
|
|
203
|
+
transformUrlBeforeRedirect({
|
|
204
|
+
isSilent,
|
|
205
|
+
authorizationUrl: url
|
|
206
|
+
})
|
|
207
|
+
],
|
|
208
|
+
[extraQueryParams_local, transformUrlBeforeRedirect_local]
|
|
209
|
+
] as const
|
|
210
|
+
).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i, arr) => {
|
|
211
|
+
const url_before = i !== arr.length - 1 ? undefined : url;
|
|
212
|
+
|
|
213
|
+
add_extra_query_params: {
|
|
214
|
+
if (extraQueryParamsMaybeGetter === undefined) {
|
|
215
|
+
break add_extra_query_params;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const extraQueryParams =
|
|
219
|
+
typeof extraQueryParamsMaybeGetter === "function"
|
|
220
|
+
? extraQueryParamsMaybeGetter({ isSilent, url })
|
|
221
|
+
: extraQueryParamsMaybeGetter;
|
|
222
|
+
|
|
223
|
+
for (const [name, value] of Object.entries(extraQueryParams)) {
|
|
224
|
+
if (value === undefined) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
url = addOrUpdateSearchParam({
|
|
228
|
+
url,
|
|
229
|
+
name,
|
|
230
|
+
value,
|
|
231
|
+
encodeMethod: "www-form"
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
apply_transform_url: {
|
|
237
|
+
if (transformUrlBeforeRedirect === undefined) {
|
|
238
|
+
break apply_transform_url;
|
|
239
|
+
}
|
|
240
|
+
url = transformUrlBeforeRedirect(url);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
update_state: {
|
|
244
|
+
if (url_before === undefined) {
|
|
245
|
+
break update_state;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const paramValueByName_current = getAllSearchParams(url);
|
|
249
|
+
const paramValueByName_before = getAllSearchParams(url_before);
|
|
250
|
+
|
|
251
|
+
for (const [name, value_current] of Object.entries(paramValueByName_current)) {
|
|
252
|
+
const value_before: string | undefined = paramValueByName_before[name];
|
|
253
|
+
|
|
254
|
+
if (value_before === value_current) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
stateData.extraQueryParams[name] = value_current;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return url;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const redirectMethod = (() => {
|
|
267
|
+
switch (rest.action) {
|
|
268
|
+
case "login":
|
|
269
|
+
return rest.doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack
|
|
270
|
+
? "replace"
|
|
271
|
+
: "assign";
|
|
272
|
+
case "go to auth server":
|
|
273
|
+
return "assign";
|
|
274
|
+
}
|
|
275
|
+
})();
|
|
276
|
+
|
|
277
|
+
log?.(`redirectMethod: ${redirectMethod}`);
|
|
278
|
+
|
|
279
|
+
return oidcClientTsUserManager
|
|
280
|
+
.signinRedirect({
|
|
281
|
+
state: stateData,
|
|
282
|
+
redirectMethod,
|
|
283
|
+
prompt: (() => {
|
|
284
|
+
switch (rest.action) {
|
|
285
|
+
case "go to auth server":
|
|
286
|
+
return undefined;
|
|
287
|
+
case "login":
|
|
288
|
+
switch (rest.interaction) {
|
|
289
|
+
case "ensure no interaction":
|
|
290
|
+
return "none";
|
|
291
|
+
case "ensure interaction":
|
|
292
|
+
return "login";
|
|
293
|
+
case "directly redirect if active session show login otherwise":
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
assert<Equals<typeof rest.interaction, never>>;
|
|
297
|
+
}
|
|
298
|
+
assert<Equals<typeof rest, never>>;
|
|
299
|
+
})(),
|
|
300
|
+
transformUrl: transformUrl_oidcClientTs,
|
|
301
|
+
extraTokenParams:
|
|
302
|
+
getExtraTokenParams === undefined ? undefined : noUndefined(getExtraTokenParams())
|
|
303
|
+
})
|
|
304
|
+
.then(() => new Promise<never>(() => {}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const { unsubscribe } = evtIsUserLoggedIn.subscribe(isLoggedIn => {
|
|
308
|
+
unsubscribe();
|
|
309
|
+
|
|
310
|
+
if (isLoggedIn) {
|
|
311
|
+
localStorage.removeItem(LOCAL_STORAGE_KEY_TO_CLEAR_WHEN_USER_LOGGED_IN);
|
|
312
|
+
} else {
|
|
313
|
+
const realPushState = history.pushState.bind(history);
|
|
314
|
+
history.pushState = function pushState(...args) {
|
|
315
|
+
lastPublicUrl = window.location.href;
|
|
316
|
+
return realPushState(...args);
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
loginOrGoToAuthServer
|
|
323
|
+
};
|
|
324
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { assert, is } from "../vendor/frontend/tsafe";
|
|
2
|
+
import { Deferred } from "../tools/Deferred";
|
|
3
|
+
|
|
4
|
+
const globalContext = {
|
|
5
|
+
appInstanceId: Math.random().toString(36).slice(2)
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type Message = {
|
|
9
|
+
appInstanceId: string;
|
|
10
|
+
configId: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function getChannelName(params: { configId: string }) {
|
|
14
|
+
const { configId } = params;
|
|
15
|
+
return `oidc-spa:login-propagation:${configId}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function notifyOtherTabsOfLogin(params: { configId: string }) {
|
|
19
|
+
const { configId } = params;
|
|
20
|
+
|
|
21
|
+
const message: Message = {
|
|
22
|
+
configId,
|
|
23
|
+
appInstanceId: globalContext.appInstanceId
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
new BroadcastChannel(getChannelName({ configId })).postMessage(message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getPrOtherTabLogin(params: { configId: string }) {
|
|
30
|
+
const { configId } = params;
|
|
31
|
+
|
|
32
|
+
const dOtherTabLogin = new Deferred<void>();
|
|
33
|
+
|
|
34
|
+
const channel = new BroadcastChannel(getChannelName({ configId }));
|
|
35
|
+
|
|
36
|
+
channel.onmessage = ({ data: message }) => {
|
|
37
|
+
assert(is<Message>(message));
|
|
38
|
+
|
|
39
|
+
if (message.appInstanceId === globalContext.appInstanceId) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
channel.close();
|
|
44
|
+
|
|
45
|
+
dOtherTabLogin.resolve();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const prOtherTabLogin = dOtherTabLogin.pr;
|
|
49
|
+
|
|
50
|
+
return { prOtherTabLogin };
|
|
51
|
+
}
|