oidc-spa 7.2.0 → 7.2.2
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 +1 -1
- 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,267 @@
|
|
|
1
|
+
import { OidcInitializationError } from "./OidcInitializationError";
|
|
2
|
+
import { isKeycloak, createKeycloakUtils } from "../keycloak";
|
|
3
|
+
import { getIsValidRemoteJson } from "../tools/getIsValidRemoteJson";
|
|
4
|
+
|
|
5
|
+
export async function createWellKnownOidcConfigurationEndpointUnreachableInitializationError(params: {
|
|
6
|
+
issuerUri: string;
|
|
7
|
+
}): Promise<OidcInitializationError> {
|
|
8
|
+
const { issuerUri } = params;
|
|
9
|
+
|
|
10
|
+
const WELL_KNOWN_PATH = "/.well-known/openid-configuration";
|
|
11
|
+
|
|
12
|
+
const commonFallbackMessagePart = [
|
|
13
|
+
`The OIDC server is either down or the issuerUri you provided is incorrect.`,
|
|
14
|
+
`You provided the issuerUri: ${issuerUri}`,
|
|
15
|
+
`Endpoint that couldn't be reached: ${issuerUri}${WELL_KNOWN_PATH}`
|
|
16
|
+
].join("\n");
|
|
17
|
+
|
|
18
|
+
if (!isKeycloak({ issuerUri })) {
|
|
19
|
+
return new OidcInitializationError({
|
|
20
|
+
messageOrCause: [
|
|
21
|
+
commonFallbackMessagePart,
|
|
22
|
+
``,
|
|
23
|
+
`If you happen to be using Keycloak, be aware that the issuerUri you provided doesn't match the expected shape.`,
|
|
24
|
+
`It should look like: https://<YOUR_KEYCLOAK_DOMAIN><KC_HTTP_RELATIVE_PATH>/realms/<YOUR_REALM>`,
|
|
25
|
+
`Unless configured otherwise the KC_HTTP_RELATIVE_PATH is '/' by default on recent version of Keycloak.`
|
|
26
|
+
].join("\n"),
|
|
27
|
+
isAuthServerLikelyDown: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const keycloakUtils = createKeycloakUtils({ issuerUri });
|
|
32
|
+
|
|
33
|
+
const getCandidateIssuerUri = (params: { kcHttpRelativePath: string | undefined }) => {
|
|
34
|
+
const { kcHttpRelativePath } = params;
|
|
35
|
+
|
|
36
|
+
return `${keycloakUtils.issuerUriParsed.origin}${
|
|
37
|
+
kcHttpRelativePath ?? ""
|
|
38
|
+
}/realms/${encodeURIComponent(keycloakUtils.issuerUriParsed.realm)}`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (keycloakUtils.issuerUriParsed.kcHttpRelativePath === undefined) {
|
|
42
|
+
const issuerUri_candidate = getCandidateIssuerUri({ kcHttpRelativePath: "/auth" });
|
|
43
|
+
|
|
44
|
+
const isValid = await getIsValidRemoteJson(`${issuerUri_candidate}${WELL_KNOWN_PATH}`);
|
|
45
|
+
|
|
46
|
+
if (isValid) {
|
|
47
|
+
return new OidcInitializationError({
|
|
48
|
+
messageOrCause: [
|
|
49
|
+
`Your Keycloak server is configured with KC_HTTP_RELATIVE_PATH=/auth`,
|
|
50
|
+
`The issuerUri you provided: ${issuerUri}`,
|
|
51
|
+
`The correct issuerUri is: ${issuerUri_candidate}`,
|
|
52
|
+
`(You are missing the /auth portion)`
|
|
53
|
+
].join("\n"),
|
|
54
|
+
isAuthServerLikelyDown: false
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
const issuerUri_candidate = getCandidateIssuerUri({ kcHttpRelativePath: undefined });
|
|
59
|
+
|
|
60
|
+
const isValid = await getIsValidRemoteJson(`${issuerUri_candidate}${WELL_KNOWN_PATH}`);
|
|
61
|
+
|
|
62
|
+
if (isValid) {
|
|
63
|
+
return new OidcInitializationError({
|
|
64
|
+
messageOrCause: [
|
|
65
|
+
`Your Keycloak server is configured with KC_HTTP_RELATIVE_PATH=/`,
|
|
66
|
+
`The issuerUri you provided: ${issuerUri}`,
|
|
67
|
+
`The correct issuerUri is: ${issuerUri_candidate}`,
|
|
68
|
+
`(You should remove the ${keycloakUtils.issuerUriParsed.kcHttpRelativePath} portion.)`
|
|
69
|
+
].join("\n"),
|
|
70
|
+
isAuthServerLikelyDown: false
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new OidcInitializationError({
|
|
76
|
+
messageOrCause: [
|
|
77
|
+
commonFallbackMessagePart,
|
|
78
|
+
``,
|
|
79
|
+
`Given the shape of the issuerUri you provided, it seems that you are using Keycloak.`,
|
|
80
|
+
`- Make sure the realm '${keycloakUtils.issuerUriParsed.realm}' exists.`,
|
|
81
|
+
`- Check the KC_HTTP_RELATIVE_PATH that you might have configured your keycloak server with.`,
|
|
82
|
+
` For example if you have KC_HTTP_RELATIVE_PATH=/xxx the issuerUri should be ${getCandidateIssuerUri(
|
|
83
|
+
{ kcHttpRelativePath: "/xxx" }
|
|
84
|
+
)}`
|
|
85
|
+
].join("\n"),
|
|
86
|
+
isAuthServerLikelyDown: true
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function createIframeTimeoutInitializationError(params: {
|
|
91
|
+
redirectUri: string;
|
|
92
|
+
issuerUri: string;
|
|
93
|
+
clientId: string;
|
|
94
|
+
noIframe: boolean;
|
|
95
|
+
}): Promise<OidcInitializationError> {
|
|
96
|
+
const { redirectUri, issuerUri, clientId, noIframe } = params;
|
|
97
|
+
|
|
98
|
+
iframe_blocked: {
|
|
99
|
+
if (noIframe) {
|
|
100
|
+
break iframe_blocked;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const headersOrError = await fetch(redirectUri).then(
|
|
104
|
+
response => {
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
return new Error(`${redirectUri} responded with a ${response.status} status code.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"Content-Security-Policy": response.headers.get("Content-Security-Policy"),
|
|
111
|
+
"X-Frame-Options": response.headers.get("X-Frame-Options")
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
(error: Error) => error
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (headersOrError instanceof Error) {
|
|
118
|
+
return new OidcInitializationError({
|
|
119
|
+
isAuthServerLikelyDown: false,
|
|
120
|
+
messageOrCause: new Error(
|
|
121
|
+
`Unexpected error while trying to diagnose why the silent sign-in process timed out.`,
|
|
122
|
+
// @ts-expect-error
|
|
123
|
+
{ cause: cspOrError }
|
|
124
|
+
)
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const headers = headersOrError;
|
|
129
|
+
|
|
130
|
+
let key_problem = (() => {
|
|
131
|
+
block: {
|
|
132
|
+
const key = "Content-Security-Policy" as const;
|
|
133
|
+
|
|
134
|
+
const header = headers[key];
|
|
135
|
+
|
|
136
|
+
if (header === null) {
|
|
137
|
+
break block;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const hasFrameAncestorsNone = header
|
|
141
|
+
.replace(/["']/g, "")
|
|
142
|
+
.replace(/\s+/g, " ")
|
|
143
|
+
.toLowerCase()
|
|
144
|
+
.includes("frame-ancestors none");
|
|
145
|
+
|
|
146
|
+
if (!hasFrameAncestorsNone) {
|
|
147
|
+
break block;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return key;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
block: {
|
|
154
|
+
const key = "X-Frame-Options" as const;
|
|
155
|
+
|
|
156
|
+
const header = headers[key];
|
|
157
|
+
|
|
158
|
+
if (header === null) {
|
|
159
|
+
break block;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const hasFrameAncestorsNone = header.toLowerCase().includes("deny");
|
|
163
|
+
|
|
164
|
+
if (!hasFrameAncestorsNone) {
|
|
165
|
+
break block;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return key;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return undefined;
|
|
172
|
+
})();
|
|
173
|
+
|
|
174
|
+
if (key_problem === undefined) {
|
|
175
|
+
break iframe_blocked;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return new OidcInitializationError({
|
|
179
|
+
isAuthServerLikelyDown: false,
|
|
180
|
+
messageOrCause: [
|
|
181
|
+
`${redirectUri} is currently served by your web server with the HTTP header \`${key_problem}: ${headers[key_problem]}\`.\n`,
|
|
182
|
+
"This header prevents the silent sign-in process from working.\n",
|
|
183
|
+
"Refer to this documentation page to fix this issue: https://docs.oidc-spa.dev/v/v7/resources/iframe-related-issues"
|
|
184
|
+
].join(" ")
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Here we know that the server is not down and that the issuer_uri is correct
|
|
189
|
+
// otherwise we would have had a fetch error earlier on the well-known endpoint.
|
|
190
|
+
// So this means that it's very likely a OIDC client misconfiguration.
|
|
191
|
+
// It could also be a very slow network but this risk is mitigated by the fact that we check
|
|
192
|
+
// for the network speed to adjust the timeout delay.
|
|
193
|
+
return new OidcInitializationError({
|
|
194
|
+
isAuthServerLikelyDown: false,
|
|
195
|
+
messageOrCause: [
|
|
196
|
+
`The silent sign-in process timed out.\n`,
|
|
197
|
+
`Based on the diagnostic performed by oidc-spa the more likely causes are:\n`,
|
|
198
|
+
`- Either the client ID "${clientId}" does not exist, or\n`,
|
|
199
|
+
`- You forgot to add the OIDC callback URL to the list of Valid Redirect URIs.\n`,
|
|
200
|
+
`Client ID: "${clientId}"\n`,
|
|
201
|
+
`Callback URL to add to the list of Valid Redirect URIs: "${redirectUri}"\n\n`,
|
|
202
|
+
...(() => {
|
|
203
|
+
if (!isKeycloak({ issuerUri })) {
|
|
204
|
+
return [
|
|
205
|
+
"Check the documentation of your OIDC server to learn how to configure the public client (Authorization Code Flow + PKCE) properly."
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const kc = createKeycloakUtils({ issuerUri });
|
|
210
|
+
|
|
211
|
+
return [
|
|
212
|
+
`It seems you are using Keycloak. Follow these steps to resolve the issue:\n\n`,
|
|
213
|
+
`1. Go to the Keycloak admin console: ${kc.adminConsoleUrl_master}\n`,
|
|
214
|
+
`2. Log in as an admin user.\n`,
|
|
215
|
+
`3. In the top left corner select the realm "${kc.issuerUriParsed.realm}".\n`,
|
|
216
|
+
`4. In the left menu, click on "Clients".\n`,
|
|
217
|
+
`5. Locate the client "${clientId}" in the list and click on it.\n`,
|
|
218
|
+
`6. Find "Valid Redirect URIs" and add "${redirectUri}" to the list.\n`,
|
|
219
|
+
`7. Save the changes.\n\n`,
|
|
220
|
+
`For more information, refer to the documentation: https://docs.oidc-spa.dev/v/v7/providers-configuration/keycloak`
|
|
221
|
+
];
|
|
222
|
+
})(),
|
|
223
|
+
"\n\n",
|
|
224
|
+
"If nothing works, you can try disabling the use of iframe: https://docs.oidc-spa.dev/resources/iframe-related-issues\n",
|
|
225
|
+
"with some OIDC provider it might solve the issue."
|
|
226
|
+
].join(" ")
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function createFailedToFetchTokenEndpointInitializationError(params: {
|
|
231
|
+
issuerUri: string;
|
|
232
|
+
clientId: string;
|
|
233
|
+
}) {
|
|
234
|
+
const { issuerUri, clientId } = params;
|
|
235
|
+
|
|
236
|
+
return new OidcInitializationError({
|
|
237
|
+
isAuthServerLikelyDown: false,
|
|
238
|
+
messageOrCause: [
|
|
239
|
+
"Failed to fetch the token endpoint.\n",
|
|
240
|
+
"This is usually due to a CORS issue.\n",
|
|
241
|
+
`Make sure you have added '${window.location.origin}' to the list of Web Origins`,
|
|
242
|
+
`in the '${clientId}' client configuration of your OIDC server.\n`,
|
|
243
|
+
"\n",
|
|
244
|
+
...(() => {
|
|
245
|
+
if (!isKeycloak({ issuerUri })) {
|
|
246
|
+
return [
|
|
247
|
+
"Check the documentation of your OIDC server to learn how to configure the public client (Authorization Code Flow + PKCE) properly."
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const kc = createKeycloakUtils({ issuerUri });
|
|
252
|
+
|
|
253
|
+
return [
|
|
254
|
+
`Since it seems that you are using Keycloak, here are the steps to follow:\n`,
|
|
255
|
+
`1. Go to the Keycloak admin console: ${kc.adminConsoleUrl_master}\n`,
|
|
256
|
+
`2. Log in as an admin user.\n`,
|
|
257
|
+
`3. In the top left corner select the realm "${kc.issuerUriParsed.realm}".\n`,
|
|
258
|
+
`4. In the left menu, click on "Clients".\n`,
|
|
259
|
+
`5. Find '${clientId}' in the list of clients and click on it.\n`,
|
|
260
|
+
`6. Find 'Web Origins' and add '${window.location.origin}' to the list.\n`,
|
|
261
|
+
`7. Save the changes.\n\n`,
|
|
262
|
+
`More info: https://docs.oidc-spa.dev/v/v7/providers-configuration/keycloak`
|
|
263
|
+
];
|
|
264
|
+
})()
|
|
265
|
+
].join(" ")
|
|
266
|
+
});
|
|
267
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createEvt, type NonPostableEvt } from "../tools/Evt";
|
|
2
|
+
import { subscribeToUserInteraction } from "../tools/subscribeToUserInteraction";
|
|
3
|
+
import { assert, is, id } from "../vendor/frontend/tsafe";
|
|
4
|
+
import { setTimeout, clearTimeout } from "../tools/workerTimers";
|
|
5
|
+
|
|
6
|
+
const globalContext = {
|
|
7
|
+
appInstanceId: Math.random().toString(36).slice(2),
|
|
8
|
+
evtIsUserActiveBySessionId: new Map<string, NonPostableEvt<boolean>>()
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function createEvtIsUserActive(params: {
|
|
12
|
+
configId: string;
|
|
13
|
+
sessionId: string | undefined;
|
|
14
|
+
}): NonPostableEvt<boolean> {
|
|
15
|
+
const { configId, sessionId } = params;
|
|
16
|
+
|
|
17
|
+
use_existing_instance: {
|
|
18
|
+
if (sessionId === undefined) {
|
|
19
|
+
break use_existing_instance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const evtIsUserActive = globalContext.evtIsUserActiveBySessionId.get(sessionId);
|
|
23
|
+
|
|
24
|
+
if (evtIsUserActive === undefined) {
|
|
25
|
+
break use_existing_instance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return evtIsUserActive;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { notifyOtherTabsOfUserInteraction, subscribeToUserInteractionOnOtherTabs } = (() => {
|
|
32
|
+
type Message = {
|
|
33
|
+
appInstanceId: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const channelName = `oidc-spa:user-interaction-tracker:${sessionId ?? configId}`;
|
|
37
|
+
|
|
38
|
+
function notifyOtherTabsOfUserInteraction() {
|
|
39
|
+
new BroadcastChannel(channelName).postMessage(
|
|
40
|
+
id<Message>({
|
|
41
|
+
appInstanceId: globalContext.appInstanceId
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function subscribeToUserInteractionOnOtherTabs(callback: () => void) {
|
|
47
|
+
const channel = new BroadcastChannel(channelName);
|
|
48
|
+
|
|
49
|
+
channel.onmessage = ({ data: message }) => {
|
|
50
|
+
assert(is<Message>(message));
|
|
51
|
+
|
|
52
|
+
if (message.appInstanceId === globalContext.appInstanceId) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
callback();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { notifyOtherTabsOfUserInteraction, subscribeToUserInteractionOnOtherTabs };
|
|
61
|
+
})();
|
|
62
|
+
|
|
63
|
+
const evtIsUserActive = createEvt<boolean>();
|
|
64
|
+
let isUserActive = true;
|
|
65
|
+
|
|
66
|
+
const scheduleSetInactive = () => {
|
|
67
|
+
const timer = setTimeout(() => {
|
|
68
|
+
assert(isUserActive, "011507");
|
|
69
|
+
isUserActive = false;
|
|
70
|
+
evtIsUserActive.post(isUserActive);
|
|
71
|
+
}, 5_000);
|
|
72
|
+
return () => {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
clearScheduledSetInactive = undefined;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let clearScheduledSetInactive: (() => void) | undefined = scheduleSetInactive();
|
|
79
|
+
|
|
80
|
+
const onUserActivity = (params: { isInteractionOnCurrentTab: boolean }) => {
|
|
81
|
+
const { isInteractionOnCurrentTab } = params;
|
|
82
|
+
|
|
83
|
+
clearScheduledSetInactive?.();
|
|
84
|
+
clearScheduledSetInactive = scheduleSetInactive();
|
|
85
|
+
|
|
86
|
+
if (isInteractionOnCurrentTab) {
|
|
87
|
+
notifyOtherTabsOfUserInteraction();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!isUserActive) {
|
|
91
|
+
isUserActive = true;
|
|
92
|
+
evtIsUserActive.post(isUserActive);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
subscribeToUserInteraction({
|
|
97
|
+
throttleMs: 1_000,
|
|
98
|
+
callback: () => onUserActivity({ isInteractionOnCurrentTab: true })
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
subscribeToUserInteractionOnOtherTabs(() => onUserActivity({ isInteractionOnCurrentTab: false }));
|
|
102
|
+
|
|
103
|
+
if (sessionId !== undefined) {
|
|
104
|
+
globalContext.evtIsUserActiveBySessionId.set(sessionId, evtIsUserActive);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return evtIsUserActive;
|
|
108
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getStateData,
|
|
3
|
+
markStateDataAsProcessedByCallback,
|
|
4
|
+
getIsStatQueryParamValue,
|
|
5
|
+
type StateData
|
|
6
|
+
} from "./StateData";
|
|
7
|
+
import { assert, id } from "../vendor/frontend/tsafe";
|
|
8
|
+
import type { AuthResponse } from "./AuthResponse";
|
|
9
|
+
import { initialLocationHref } from "./initialLocationHref";
|
|
10
|
+
import { encryptAuthResponse } from "./iframeMessageProtection";
|
|
11
|
+
|
|
12
|
+
const globalContext = {
|
|
13
|
+
previousCall: id<{ isHandled: boolean } | undefined>(undefined)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function handleOidcCallback(): { isHandled: boolean } {
|
|
17
|
+
if (globalContext.previousCall !== undefined) {
|
|
18
|
+
return globalContext.previousCall;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (globalContext.previousCall = handleOidcCallback_nonMemoized());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
|
|
25
|
+
const location_urlObj = new URL(initialLocationHref);
|
|
26
|
+
|
|
27
|
+
const stateUrlParamValue_wrap = (() => {
|
|
28
|
+
fragment: {
|
|
29
|
+
const stateUrlParamValue = new URLSearchParams(location_urlObj.hash.replace(/^#/, "")).get(
|
|
30
|
+
"state"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (stateUrlParamValue === null) {
|
|
34
|
+
break fragment;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!getIsStatQueryParamValue({ maybeStateUrlParamValue: stateUrlParamValue })) {
|
|
38
|
+
break fragment;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { stateUrlParamValue, isFragment: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
query: {
|
|
45
|
+
const stateUrlParamValue = location_urlObj.searchParams.get("state");
|
|
46
|
+
|
|
47
|
+
if (stateUrlParamValue === null) {
|
|
48
|
+
break query;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!getIsStatQueryParamValue({ maybeStateUrlParamValue: stateUrlParamValue })) {
|
|
52
|
+
break query;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
location_urlObj.searchParams.get("client_id") !== null &&
|
|
57
|
+
location_urlObj.searchParams.get("response_type") !== null &&
|
|
58
|
+
location_urlObj.searchParams.get("redirect_uri") !== null
|
|
59
|
+
) {
|
|
60
|
+
// NOTE: We are probably in a Keycloakify theme and oidc-spa was loaded by mistake.
|
|
61
|
+
break query;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { stateUrlParamValue, isFragment: false };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return undefined;
|
|
68
|
+
})();
|
|
69
|
+
|
|
70
|
+
if (stateUrlParamValue_wrap === undefined) {
|
|
71
|
+
const backForwardTracker = readBackForwardTracker();
|
|
72
|
+
|
|
73
|
+
if (backForwardTracker !== undefined) {
|
|
74
|
+
writeBackForwardTracker({
|
|
75
|
+
backForwardTracker: {
|
|
76
|
+
...backForwardTracker,
|
|
77
|
+
hasExitedCallback: true
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { isHandled: false };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isHandled = true;
|
|
86
|
+
|
|
87
|
+
const { stateUrlParamValue, isFragment } = stateUrlParamValue_wrap;
|
|
88
|
+
|
|
89
|
+
console.log = () => {};
|
|
90
|
+
console.warn = () => {};
|
|
91
|
+
console.error = () => {};
|
|
92
|
+
console.debug = () => {};
|
|
93
|
+
|
|
94
|
+
const stateData = getStateData({ stateUrlParamValue });
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
stateData === undefined ||
|
|
98
|
+
(stateData.context === "redirect" && stateData.hasBeenProcessedByCallback)
|
|
99
|
+
) {
|
|
100
|
+
const historyMethod: "back" | "forward" = (() => {
|
|
101
|
+
const backForwardTracker = readBackForwardTracker();
|
|
102
|
+
|
|
103
|
+
if (backForwardTracker === undefined) {
|
|
104
|
+
return "back";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!backForwardTracker.hasExitedCallback) {
|
|
108
|
+
return backForwardTracker.previousHistoryMethod;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
switch (backForwardTracker.previousHistoryMethod) {
|
|
112
|
+
case "back":
|
|
113
|
+
return "forward";
|
|
114
|
+
case "forward":
|
|
115
|
+
return "back";
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
|
|
119
|
+
writeBackForwardTracker({
|
|
120
|
+
backForwardTracker: {
|
|
121
|
+
previousHistoryMethod: historyMethod,
|
|
122
|
+
hasExitedCallback: false
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
reloadOnBfCacheNavigation();
|
|
128
|
+
|
|
129
|
+
window.history[historyMethod]();
|
|
130
|
+
|
|
131
|
+
// NOTE: This is a "better than nothing" approach.
|
|
132
|
+
// Under some circumstances it's possible to get stuck on this url
|
|
133
|
+
// if there is no "next" page in the history for example, navigating
|
|
134
|
+
// forward is a NoOp. So in that case it's better to reload the same route
|
|
135
|
+
// with just the authResponse removed from the url to avoid re-entering here.
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
const { protocol, host, pathname, hash } = window.location;
|
|
138
|
+
window.location.href = `${protocol}//${host}${pathname}${hash}`;
|
|
139
|
+
}, 350);
|
|
140
|
+
}, 0);
|
|
141
|
+
|
|
142
|
+
return { isHandled };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const authResponse: AuthResponse = { state: "" };
|
|
146
|
+
|
|
147
|
+
for (const [key, value] of isFragment
|
|
148
|
+
? new URLSearchParams(location_urlObj.hash.replace(/^#/, ""))
|
|
149
|
+
: location_urlObj.searchParams) {
|
|
150
|
+
authResponse[key] = value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
assert(authResponse.state !== "", "063965");
|
|
154
|
+
|
|
155
|
+
switch (stateData.context) {
|
|
156
|
+
case "iframe":
|
|
157
|
+
encryptAuthResponse({
|
|
158
|
+
authResponse
|
|
159
|
+
}).then(({ encryptedMessage }) => parent.postMessage(encryptedMessage, location.origin));
|
|
160
|
+
break;
|
|
161
|
+
case "redirect":
|
|
162
|
+
markStateDataAsProcessedByCallback({ stateUrlParamValue });
|
|
163
|
+
clearBackForwardTracker();
|
|
164
|
+
writeRedirectAuthResponses({
|
|
165
|
+
authResponses: [...readRedirectAuthResponses(), authResponse]
|
|
166
|
+
});
|
|
167
|
+
reloadOnBfCacheNavigation();
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
const href = (() => {
|
|
170
|
+
if (stateData.action === "login" && authResponse.error === "consent_required") {
|
|
171
|
+
return stateData.redirectUrl_consentRequiredCase;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return stateData.redirectUrl;
|
|
175
|
+
})();
|
|
176
|
+
|
|
177
|
+
location.href = href;
|
|
178
|
+
}, 0);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { isHandled };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const {
|
|
186
|
+
readRedirectAuthResponses,
|
|
187
|
+
writeRedirectAuthResponses,
|
|
188
|
+
moveRedirectAuthResponseFromSessionStorageToMemory
|
|
189
|
+
} = (() => {
|
|
190
|
+
const AUTH_RESPONSES_KEY = "oidc-spa:authResponses";
|
|
191
|
+
|
|
192
|
+
let authResponses_movedToMemoryFromSessionStorage: AuthResponse[] | undefined = undefined;
|
|
193
|
+
|
|
194
|
+
// NOTE: Here we note that we can re-write on session storage some auth response
|
|
195
|
+
// after earlyInit in retrieveRedirectAuthResponseAndStateData
|
|
196
|
+
// In situation where there are more than one client in the same app and we can't use iframe,
|
|
197
|
+
// we can have one client that has to redirect before the response has been dealt with.
|
|
198
|
+
// In most case it won't happen if the init sequence is deterministic but the client
|
|
199
|
+
// can be instantiated at any time really.
|
|
200
|
+
// So the move to memory of the response is fully effective only when theres one client.
|
|
201
|
+
function writeRedirectAuthResponses(params: { authResponses: AuthResponse[] }): void {
|
|
202
|
+
const { authResponses } = params;
|
|
203
|
+
|
|
204
|
+
authResponses_movedToMemoryFromSessionStorage = undefined;
|
|
205
|
+
|
|
206
|
+
if (authResponses.length === 0) {
|
|
207
|
+
sessionStorage.removeItem(AUTH_RESPONSES_KEY);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
sessionStorage.setItem(AUTH_RESPONSES_KEY, JSON.stringify(authResponses));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function readRedirectAuthResponses(): AuthResponse[] {
|
|
214
|
+
if (authResponses_movedToMemoryFromSessionStorage !== undefined) {
|
|
215
|
+
return authResponses_movedToMemoryFromSessionStorage;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const raw = sessionStorage.getItem(AUTH_RESPONSES_KEY);
|
|
219
|
+
|
|
220
|
+
if (raw === null) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return JSON.parse(raw);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function moveRedirectAuthResponseFromSessionStorageToMemory() {
|
|
228
|
+
const authResponses = readRedirectAuthResponses();
|
|
229
|
+
|
|
230
|
+
writeRedirectAuthResponses({ authResponses: [] });
|
|
231
|
+
|
|
232
|
+
authResponses_movedToMemoryFromSessionStorage = authResponses;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
writeRedirectAuthResponses,
|
|
237
|
+
readRedirectAuthResponses,
|
|
238
|
+
moveRedirectAuthResponseFromSessionStorageToMemory
|
|
239
|
+
};
|
|
240
|
+
})();
|
|
241
|
+
|
|
242
|
+
export { moveRedirectAuthResponseFromSessionStorageToMemory };
|
|
243
|
+
|
|
244
|
+
export function retrieveRedirectAuthResponseAndStateData(params: {
|
|
245
|
+
configId: string;
|
|
246
|
+
}): { authResponse: AuthResponse; stateData: StateData.Redirect } | undefined {
|
|
247
|
+
const { configId } = params;
|
|
248
|
+
|
|
249
|
+
const authResponses = readRedirectAuthResponses();
|
|
250
|
+
|
|
251
|
+
let authResponseAndStateData:
|
|
252
|
+
| { authResponse: AuthResponse; stateData: StateData.Redirect }
|
|
253
|
+
| undefined = undefined;
|
|
254
|
+
|
|
255
|
+
for (const authResponse of [...authResponses]) {
|
|
256
|
+
const stateData = getStateData({ stateUrlParamValue: authResponse.state });
|
|
257
|
+
|
|
258
|
+
if (stateData === undefined) {
|
|
259
|
+
// NOTE: We do not understand how this can happen but it can.
|
|
260
|
+
authResponses.splice(authResponses.indexOf(authResponse), 1);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
assert(stateData.context === "redirect", "474728");
|
|
265
|
+
|
|
266
|
+
if (stateData.configId !== configId) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
authResponses.splice(authResponses.indexOf(authResponse), 1);
|
|
271
|
+
|
|
272
|
+
authResponseAndStateData = { authResponse, stateData };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
writeRedirectAuthResponses({ authResponses });
|
|
276
|
+
|
|
277
|
+
return authResponseAndStateData;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function reloadOnBfCacheNavigation() {
|
|
281
|
+
const start = Date.now();
|
|
282
|
+
window.addEventListener("pageshow", () => {
|
|
283
|
+
const elapsed = Date.now() - start;
|
|
284
|
+
|
|
285
|
+
if (elapsed < 100) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
location.reload();
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { writeBackForwardTracker, readBackForwardTracker, clearBackForwardTracker } = (() => {
|
|
293
|
+
const BACK_NAVIGATION_TRACKER_KEY = "oidc-spa:callback-back-forward-tracker";
|
|
294
|
+
|
|
295
|
+
type BackForwardTracker = {
|
|
296
|
+
previousHistoryMethod: "back" | "forward";
|
|
297
|
+
hasExitedCallback: boolean;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
function writeBackForwardTracker(params: { backForwardTracker: BackForwardTracker }): void {
|
|
301
|
+
const { backForwardTracker } = params;
|
|
302
|
+
|
|
303
|
+
sessionStorage.setItem(BACK_NAVIGATION_TRACKER_KEY, JSON.stringify(backForwardTracker));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function readBackForwardTracker(): BackForwardTracker | undefined {
|
|
307
|
+
const raw = sessionStorage.getItem(BACK_NAVIGATION_TRACKER_KEY);
|
|
308
|
+
|
|
309
|
+
if (raw === null) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return JSON.parse(raw);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function clearBackForwardTracker(): void {
|
|
317
|
+
sessionStorage.removeItem(BACK_NAVIGATION_TRACKER_KEY);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { writeBackForwardTracker, readBackForwardTracker, clearBackForwardTracker };
|
|
321
|
+
})();
|