oidc-spa 7.3.1 → 8.0.0
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/core/AuthResponse.d.ts +5 -0
- package/core/AuthResponse.js +25 -0
- package/core/AuthResponse.js.map +1 -1
- package/core/StateData.d.ts +2 -6
- package/core/StateData.js +0 -13
- package/core/StateData.js.map +1 -1
- package/core/createOidc.d.ts +2 -2
- package/core/createOidc.js +70 -19
- package/core/createOidc.js.map +1 -1
- package/core/diagnostic.js +3 -3
- package/core/earlyInit.d.ts +16 -0
- package/core/earlyInit.js +157 -0
- package/core/earlyInit.js.map +1 -0
- package/core/index.d.ts +0 -1
- package/core/index.js +1 -3
- package/core/index.js.map +1 -1
- package/core/loginOrGoToAuthServer.js +19 -6
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/entrypoint.d.ts +1 -7
- package/entrypoint.js +3 -46
- package/entrypoint.js.map +1 -1
- package/esm/core/AuthResponse.d.ts +5 -0
- package/esm/core/AuthResponse.js +23 -0
- package/esm/core/AuthResponse.js.map +1 -1
- package/esm/core/StateData.d.ts +2 -6
- package/esm/core/StateData.js +0 -12
- package/esm/core/StateData.js.map +1 -1
- package/esm/core/createOidc.d.ts +2 -2
- package/esm/core/createOidc.js +72 -21
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/diagnostic.js +3 -3
- package/esm/core/earlyInit.d.ts +16 -0
- package/esm/core/earlyInit.js +152 -0
- package/esm/core/earlyInit.js.map +1 -0
- package/esm/core/index.d.ts +0 -1
- package/esm/core/index.js +0 -1
- package/esm/core/index.js.map +1 -1
- package/esm/core/loginOrGoToAuthServer.js +19 -6
- package/esm/core/loginOrGoToAuthServer.js.map +1 -1
- package/esm/entrypoint.d.ts +1 -7
- package/esm/entrypoint.js +1 -45
- package/esm/entrypoint.js.map +1 -1
- package/esm/mock/oidc.js +15 -4
- package/esm/mock/oidc.js.map +1 -1
- package/esm/mock/react.d.ts +1 -1
- package/esm/mock/react.js +1 -1
- package/esm/react/react.d.ts +1 -1
- package/esm/react/react.js +2 -10
- package/esm/react/react.js.map +1 -1
- package/mock/oidc.js +15 -4
- package/mock/oidc.js.map +1 -1
- package/mock/react.d.ts +1 -1
- package/mock/react.js +1 -1
- package/package.json +1 -1
- package/react/react.d.ts +1 -1
- package/react/react.js +1 -9
- package/react/react.js.map +1 -1
- package/src/core/AuthResponse.ts +36 -0
- package/src/core/StateData.ts +2 -22
- package/src/core/createOidc.ts +108 -24
- package/src/core/diagnostic.ts +3 -3
- package/src/core/earlyInit.ts +213 -0
- package/src/core/index.ts +0 -1
- package/src/core/loginOrGoToAuthServer.ts +24 -6
- package/src/entrypoint.ts +1 -69
- package/src/mock/oidc.ts +15 -4
- package/src/mock/react.tsx +1 -1
- package/src/react/react.tsx +2 -18
- package/core/handleOidcCallback.d.ts +0 -13
- package/core/handleOidcCallback.js +0 -228
- package/core/handleOidcCallback.js.map +0 -1
- package/core/initialLocationHref.d.ts +0 -1
- package/core/initialLocationHref.js +0 -8
- package/core/initialLocationHref.js.map +0 -1
- package/esm/core/handleOidcCallback.d.ts +0 -13
- package/esm/core/handleOidcCallback.js +0 -223
- package/esm/core/handleOidcCallback.js.map +0 -1
- package/esm/core/initialLocationHref.d.ts +0 -1
- package/esm/core/initialLocationHref.js +0 -5
- package/esm/core/initialLocationHref.js.map +0 -1
- package/src/core/handleOidcCallback.ts +0 -318
- package/src/core/initialLocationHref.ts +0 -5
package/src/core/createOidc.ts
CHANGED
|
@@ -13,14 +13,24 @@ import { createStartCountdown } from "../tools/startCountdown";
|
|
|
13
13
|
import { toHumanReadableDuration } from "../tools/toHumanReadableDuration";
|
|
14
14
|
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
15
15
|
import { OidcInitializationError } from "./OidcInitializationError";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type StateData,
|
|
18
|
+
generateStateUrlParamValue,
|
|
19
|
+
STATE_STORE_KEY_PREFIX,
|
|
20
|
+
getStateData
|
|
21
|
+
} from "./StateData";
|
|
17
22
|
import { notifyOtherTabsOfLogout, getPrOtherTabLogout } from "./logoutPropagationToOtherTabs";
|
|
18
23
|
import { notifyOtherTabsOfLogin, getPrOtherTabLogin } from "./loginPropagationToOtherTabs";
|
|
19
24
|
import { getConfigId } from "./configId";
|
|
20
25
|
import { oidcClientTsUserToTokens } from "./oidcClientTsUserToTokens";
|
|
21
26
|
import { loginSilent } from "./loginSilent";
|
|
22
|
-
import {
|
|
23
|
-
|
|
27
|
+
import {
|
|
28
|
+
authResponseToUrl,
|
|
29
|
+
getPersistedRedirectAuthResponses,
|
|
30
|
+
setPersistedRedirectAuthResponses,
|
|
31
|
+
type AuthResponse
|
|
32
|
+
} from "./AuthResponse";
|
|
33
|
+
import { getRootRelativeOriginalLocationHref, getRedirectAuthResponse } from "./earlyInit";
|
|
24
34
|
import { getPersistedAuthState, persistAuthState } from "./persistedAuthState";
|
|
25
35
|
import type { Oidc } from "./Oidc";
|
|
26
36
|
import { createEvt } from "../tools/Evt";
|
|
@@ -34,7 +44,6 @@ import {
|
|
|
34
44
|
startLoginOrRefreshProcess,
|
|
35
45
|
waitForAllOtherOngoingLoginOrRefreshProcessesToComplete
|
|
36
46
|
} from "./ongoingLoginOrRefreshProcesses";
|
|
37
|
-
import { initialLocationHref } from "./initialLocationHref";
|
|
38
47
|
import { createGetIsNewBrowserSession } from "./isNewBrowserSession";
|
|
39
48
|
import { getIsOnline } from "../tools/getIsOnline";
|
|
40
49
|
import { isKeycloak } from "../keycloak/isKeycloak";
|
|
@@ -135,7 +144,7 @@ export type ParamsOfCreateOidc<
|
|
|
135
144
|
/**
|
|
136
145
|
* Default: false
|
|
137
146
|
*
|
|
138
|
-
* See: https://docs.oidc-spa.dev/v/
|
|
147
|
+
* See: https://docs.oidc-spa.dev/v/v8/resources/iframe-related-issues
|
|
139
148
|
*/
|
|
140
149
|
noIframe?: boolean;
|
|
141
150
|
|
|
@@ -180,7 +189,21 @@ const globalContext = {
|
|
|
180
189
|
evtRequestToPersistTokens: createEvt<{ configIdOfInstancePostingTheRequest: string }>()
|
|
181
190
|
};
|
|
182
191
|
|
|
183
|
-
|
|
192
|
+
globalContext.evtRequestToPersistTokens.subscribe(() => {
|
|
193
|
+
const { authResponse } = getRedirectAuthResponse();
|
|
194
|
+
|
|
195
|
+
if (authResponse === undefined) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { authResponses } = getPersistedRedirectAuthResponses();
|
|
200
|
+
|
|
201
|
+
setPersistedRedirectAuthResponses({
|
|
202
|
+
authResponses: [...authResponses, authResponse]
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/** @see: https://docs.oidc-spa.dev/v/v8/usage */
|
|
184
207
|
export async function createOidc<
|
|
185
208
|
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
|
|
186
209
|
AutoLogin extends boolean = false
|
|
@@ -340,14 +363,6 @@ export async function createOidc_nonMemoized<
|
|
|
340
363
|
)}`
|
|
341
364
|
);
|
|
342
365
|
|
|
343
|
-
{
|
|
344
|
-
const { isHandled } = handleOidcCallback();
|
|
345
|
-
|
|
346
|
-
if (isHandled) {
|
|
347
|
-
await new Promise<never>(() => {});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
366
|
const stateUrlParamValue_instance = generateStateUrlParamValue();
|
|
352
367
|
|
|
353
368
|
const canUseIframe = (() => {
|
|
@@ -476,13 +491,81 @@ export async function createOidc_nonMemoized<
|
|
|
476
491
|
}
|
|
477
492
|
> => {
|
|
478
493
|
handle_redirect_auth_response: {
|
|
479
|
-
|
|
494
|
+
let stateDataAndAuthResponse:
|
|
495
|
+
| { stateData: StateData.Redirect; authResponse: AuthResponse }
|
|
496
|
+
| undefined = undefined;
|
|
497
|
+
|
|
498
|
+
get_stateData_and_authResponse: {
|
|
499
|
+
from_memory: {
|
|
500
|
+
const { authResponse, clearAuthResponse } = getRedirectAuthResponse();
|
|
501
|
+
|
|
502
|
+
if (authResponse === undefined) {
|
|
503
|
+
break from_memory;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const stateData = getStateData({ stateUrlParamValue: authResponse.state });
|
|
507
|
+
|
|
508
|
+
if (stateData === undefined) {
|
|
509
|
+
clearAuthResponse();
|
|
510
|
+
break from_memory;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (stateData.configId !== configId) {
|
|
514
|
+
break from_memory;
|
|
515
|
+
}
|
|
480
516
|
|
|
481
|
-
|
|
517
|
+
assert(stateData.context === "redirect", "3229492");
|
|
518
|
+
|
|
519
|
+
clearAuthResponse();
|
|
520
|
+
|
|
521
|
+
stateDataAndAuthResponse = { stateData, authResponse };
|
|
522
|
+
|
|
523
|
+
break get_stateData_and_authResponse;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// from storage, this is for race condition in multiple instance
|
|
527
|
+
// setup where one instance would need to redirect before
|
|
528
|
+
// the authResponse in memory had the chance to be processed.
|
|
529
|
+
// This can only happen if:
|
|
530
|
+
// 1) There are multiple oidc instances in the App.
|
|
531
|
+
// 2) They are instantiated in a non deterministic order.
|
|
532
|
+
// 3) We can't use iframe
|
|
533
|
+
// We practically never persist the auth response and do it only in session
|
|
534
|
+
// an ephemeral session storage, when we know it's gonna be required.
|
|
535
|
+
{
|
|
536
|
+
const { authResponses } = getPersistedRedirectAuthResponses();
|
|
537
|
+
|
|
538
|
+
for (const authResponse of authResponses) {
|
|
539
|
+
const stateData = getStateData({ stateUrlParamValue: authResponse.state });
|
|
540
|
+
|
|
541
|
+
if (stateData === undefined) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (stateData.configId !== configId) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
assert(stateData.context === "redirect", "35935591");
|
|
550
|
+
|
|
551
|
+
setPersistedRedirectAuthResponses({
|
|
552
|
+
authResponses: authResponses.filter(
|
|
553
|
+
authResponse_i => authResponse_i !== authResponse
|
|
554
|
+
)
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
stateDataAndAuthResponse = { stateData, authResponse };
|
|
558
|
+
|
|
559
|
+
break get_stateData_and_authResponse;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (stateDataAndAuthResponse === undefined) {
|
|
482
565
|
break handle_redirect_auth_response;
|
|
483
566
|
}
|
|
484
567
|
|
|
485
|
-
const {
|
|
568
|
+
const { stateData, authResponse } = stateDataAndAuthResponse;
|
|
486
569
|
|
|
487
570
|
switch (stateData.action) {
|
|
488
571
|
case "login":
|
|
@@ -583,6 +666,8 @@ export async function createOidc_nonMemoized<
|
|
|
583
666
|
return undefined;
|
|
584
667
|
}
|
|
585
668
|
break;
|
|
669
|
+
default:
|
|
670
|
+
assert<Equals<typeof stateData, never>>(false);
|
|
586
671
|
}
|
|
587
672
|
}
|
|
588
673
|
|
|
@@ -761,7 +846,7 @@ export async function createOidc_nonMemoized<
|
|
|
761
846
|
await loginOrGoToAuthServer({
|
|
762
847
|
action: "login",
|
|
763
848
|
doForceReloadOnBfCache: true,
|
|
764
|
-
redirectUrl:
|
|
849
|
+
redirectUrl: getRootRelativeOriginalLocationHref(),
|
|
765
850
|
// NOTE: Wether or not it's the preferred behavior, pushing to history
|
|
766
851
|
// only works on user interaction so it have to be false
|
|
767
852
|
doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: false,
|
|
@@ -1023,7 +1108,7 @@ export async function createOidc_nonMemoized<
|
|
|
1023
1108
|
|
|
1024
1109
|
globalContext.hasLogoutBeenCalled = true;
|
|
1025
1110
|
|
|
1026
|
-
const
|
|
1111
|
+
const rootRelativePostLogoutRedirectUrl: string = (() => {
|
|
1027
1112
|
switch (params.redirectTo) {
|
|
1028
1113
|
case "current page":
|
|
1029
1114
|
return window.location.href;
|
|
@@ -1035,7 +1120,7 @@ export async function createOidc_nonMemoized<
|
|
|
1035
1120
|
doAssertNoQueryParams: false
|
|
1036
1121
|
});
|
|
1037
1122
|
}
|
|
1038
|
-
})();
|
|
1123
|
+
})().slice(window.location.origin.length);
|
|
1039
1124
|
|
|
1040
1125
|
await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
|
|
1041
1126
|
prUnlock: new Promise<never>(() => {})
|
|
@@ -1050,11 +1135,10 @@ export async function createOidc_nonMemoized<
|
|
|
1050
1135
|
|
|
1051
1136
|
try {
|
|
1052
1137
|
await oidcClientTsUserManager.signoutRedirect({
|
|
1053
|
-
state: id<StateData>({
|
|
1138
|
+
state: id<StateData.Redirect>({
|
|
1054
1139
|
configId,
|
|
1055
1140
|
context: "redirect",
|
|
1056
|
-
|
|
1057
|
-
hasBeenProcessedByCallback: false,
|
|
1141
|
+
rootRelativeRedirectUrl: rootRelativePostLogoutRedirectUrl,
|
|
1058
1142
|
action: "logout",
|
|
1059
1143
|
sessionId
|
|
1060
1144
|
}),
|
|
@@ -1079,7 +1163,7 @@ export async function createOidc_nonMemoized<
|
|
|
1079
1163
|
sessionId
|
|
1080
1164
|
});
|
|
1081
1165
|
|
|
1082
|
-
window.location.href =
|
|
1166
|
+
window.location.href = rootRelativePostLogoutRedirectUrl;
|
|
1083
1167
|
} else {
|
|
1084
1168
|
throw error;
|
|
1085
1169
|
}
|
package/src/core/diagnostic.ts
CHANGED
|
@@ -180,7 +180,7 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
180
180
|
messageOrCause: [
|
|
181
181
|
`${redirectUri} is currently served by your web server with the HTTP header \`${key_problem}: ${headers[key_problem]}\`.\n`,
|
|
182
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/
|
|
183
|
+
"Refer to this documentation page to fix this issue: https://docs.oidc-spa.dev/v/v8/resources/iframe-related-issues"
|
|
184
184
|
].join(" ")
|
|
185
185
|
});
|
|
186
186
|
}
|
|
@@ -217,7 +217,7 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
217
217
|
`5. Locate the client "${clientId}" in the list and click on it.\n`,
|
|
218
218
|
`6. Find "Valid Redirect URIs" and add "${redirectUri}" to the list.\n`,
|
|
219
219
|
`7. Save the changes.\n\n`,
|
|
220
|
-
`For more information, refer to the documentation: https://docs.oidc-spa.dev/v/
|
|
220
|
+
`For more information, refer to the documentation: https://docs.oidc-spa.dev/v/v8/providers-configuration/keycloak`
|
|
221
221
|
];
|
|
222
222
|
})(),
|
|
223
223
|
"\n\n",
|
|
@@ -259,7 +259,7 @@ export async function createFailedToFetchTokenEndpointInitializationError(params
|
|
|
259
259
|
`5. Find '${clientId}' in the list of clients and click on it.\n`,
|
|
260
260
|
`6. Find 'Web Origins' and add '${window.location.origin}' to the list.\n`,
|
|
261
261
|
`7. Save the changes.\n\n`,
|
|
262
|
-
`More info: https://docs.oidc-spa.dev/v/
|
|
262
|
+
`More info: https://docs.oidc-spa.dev/v/v8/providers-configuration/keycloak`
|
|
263
263
|
];
|
|
264
264
|
})()
|
|
265
265
|
].join(" ")
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { getStateData, getIsStatQueryParamValue } from "./StateData";
|
|
2
|
+
import { assert, type Equals } from "../vendor/frontend/tsafe";
|
|
3
|
+
import type { AuthResponse } from "./AuthResponse";
|
|
4
|
+
import {
|
|
5
|
+
encryptAuthResponse,
|
|
6
|
+
preventSessionStorageSetItemOfPublicKeyByThirdParty
|
|
7
|
+
} from "./iframeMessageProtection";
|
|
8
|
+
|
|
9
|
+
let hasEarlyInitBeenCalled = false;
|
|
10
|
+
|
|
11
|
+
export function oidcEarlyInit(params: {
|
|
12
|
+
freezeFetch: boolean;
|
|
13
|
+
freezeXMLHttpRequest: boolean;
|
|
14
|
+
// NOTE: Made optional just to avoid breaking change.
|
|
15
|
+
// Will be made mandatory next major.
|
|
16
|
+
freezeWebSocket?: boolean;
|
|
17
|
+
}) {
|
|
18
|
+
if (hasEarlyInitBeenCalled) {
|
|
19
|
+
throw new Error("oidc-spa: oidcEarlyInit() Should be called only once");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hasEarlyInitBeenCalled = true;
|
|
23
|
+
|
|
24
|
+
const { freezeFetch, freezeXMLHttpRequest, freezeWebSocket = false } = params ?? {};
|
|
25
|
+
|
|
26
|
+
const { shouldLoadApp } = handleOidcCallback();
|
|
27
|
+
|
|
28
|
+
if (shouldLoadApp) {
|
|
29
|
+
if (freezeXMLHttpRequest) {
|
|
30
|
+
const XMLHttpRequest_trusted = globalThis.XMLHttpRequest;
|
|
31
|
+
|
|
32
|
+
Object.freeze(XMLHttpRequest_trusted.prototype);
|
|
33
|
+
Object.freeze(XMLHttpRequest_trusted);
|
|
34
|
+
|
|
35
|
+
Object.defineProperty(globalThis, "XMLHttpRequest", {
|
|
36
|
+
configurable: false,
|
|
37
|
+
writable: false,
|
|
38
|
+
enumerable: true,
|
|
39
|
+
value: XMLHttpRequest_trusted
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (freezeFetch) {
|
|
44
|
+
const fetch_trusted = globalThis.fetch;
|
|
45
|
+
|
|
46
|
+
Object.freeze(fetch_trusted);
|
|
47
|
+
|
|
48
|
+
Object.defineProperty(globalThis, "fetch", {
|
|
49
|
+
configurable: false,
|
|
50
|
+
writable: false,
|
|
51
|
+
enumerable: true,
|
|
52
|
+
value: fetch_trusted
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (freezeWebSocket) {
|
|
57
|
+
const WebSocket_trusted = globalThis.WebSocket;
|
|
58
|
+
|
|
59
|
+
Object.freeze(WebSocket_trusted.prototype);
|
|
60
|
+
Object.freeze(WebSocket_trusted);
|
|
61
|
+
|
|
62
|
+
Object.defineProperty(globalThis, "WebSocket", {
|
|
63
|
+
configurable: false,
|
|
64
|
+
writable: false,
|
|
65
|
+
enumerable: true,
|
|
66
|
+
value: WebSocket_trusted
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
preventSessionStorageSetItemOfPublicKeyByThirdParty();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { shouldLoadApp };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let redirectAuthResponse: AuthResponse | undefined = undefined;
|
|
77
|
+
|
|
78
|
+
export function getRedirectAuthResponse():
|
|
79
|
+
| { authResponse: AuthResponse; clearAuthResponse: () => void }
|
|
80
|
+
| { authResponse: undefined; clearAuthResponse?: never } {
|
|
81
|
+
if (!hasEarlyInitBeenCalled) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
[
|
|
84
|
+
"oidc-spa setup error.",
|
|
85
|
+
"oidcEarlyInit() wasn't called.",
|
|
86
|
+
"In newer version, using oidc-spa/entrypoint is no longer optional."
|
|
87
|
+
].join(" ")
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return redirectAuthResponse === undefined
|
|
91
|
+
? { authResponse: undefined }
|
|
92
|
+
: {
|
|
93
|
+
authResponse: redirectAuthResponse,
|
|
94
|
+
clearAuthResponse: () => {
|
|
95
|
+
redirectAuthResponse = undefined;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let rootRelativeOriginalLocationHref: string | undefined = undefined;
|
|
101
|
+
|
|
102
|
+
export function getRootRelativeOriginalLocationHref() {
|
|
103
|
+
assert(rootRelativeOriginalLocationHref !== undefined, "033292");
|
|
104
|
+
return rootRelativeOriginalLocationHref;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handleOidcCallback(): { shouldLoadApp: boolean } {
|
|
108
|
+
const location_urlObj = new URL(window.location.href);
|
|
109
|
+
|
|
110
|
+
const locationHrefAssessment = (() => {
|
|
111
|
+
fragment: {
|
|
112
|
+
const stateUrlParamValue = new URLSearchParams(location_urlObj.hash.replace(/^#/, "")).get(
|
|
113
|
+
"state"
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (stateUrlParamValue === null) {
|
|
117
|
+
break fragment;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!getIsStatQueryParamValue({ maybeStateUrlParamValue: stateUrlParamValue })) {
|
|
121
|
+
break fragment;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { hasAuthResponseInUrl: true, responseMode: "fragment" } as const;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
query: {
|
|
128
|
+
const stateUrlParamValue = location_urlObj.searchParams.get("state");
|
|
129
|
+
|
|
130
|
+
if (stateUrlParamValue === null) {
|
|
131
|
+
break query;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!getIsStatQueryParamValue({ maybeStateUrlParamValue: stateUrlParamValue })) {
|
|
135
|
+
break query;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
location_urlObj.searchParams.get("client_id") !== null &&
|
|
140
|
+
location_urlObj.searchParams.get("response_type") !== null &&
|
|
141
|
+
location_urlObj.searchParams.get("redirect_uri") !== null
|
|
142
|
+
) {
|
|
143
|
+
// NOTE: We are probably in a Keycloakify theme and oidc-spa was loaded by mistake.
|
|
144
|
+
break query;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { hasAuthResponseInUrl: true, responseMode: "query" } as const;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { hasAuthResponseInUrl: false } as const;
|
|
151
|
+
})();
|
|
152
|
+
|
|
153
|
+
if (!locationHrefAssessment.hasAuthResponseInUrl) {
|
|
154
|
+
rootRelativeOriginalLocationHref = location_urlObj.href.slice(location_urlObj.origin.length);
|
|
155
|
+
return { shouldLoadApp: true };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
rootRelativeOriginalLocationHref = location_urlObj.pathname;
|
|
159
|
+
|
|
160
|
+
const { authResponse } = (() => {
|
|
161
|
+
const authResponse: AuthResponse = { state: "" };
|
|
162
|
+
|
|
163
|
+
const searchParams = (() => {
|
|
164
|
+
switch (locationHrefAssessment.responseMode) {
|
|
165
|
+
case "fragment":
|
|
166
|
+
return new URLSearchParams(location_urlObj.hash.replace(/^#/, ""));
|
|
167
|
+
case "query":
|
|
168
|
+
return location_urlObj.searchParams;
|
|
169
|
+
default:
|
|
170
|
+
assert<Equals<typeof locationHrefAssessment, never>>(false);
|
|
171
|
+
}
|
|
172
|
+
})();
|
|
173
|
+
|
|
174
|
+
for (const [key, value] of searchParams) {
|
|
175
|
+
authResponse[key] = value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
assert(authResponse.state !== "", "063965");
|
|
179
|
+
|
|
180
|
+
return { authResponse };
|
|
181
|
+
})();
|
|
182
|
+
|
|
183
|
+
const stateData = getStateData({ stateUrlParamValue: authResponse.state });
|
|
184
|
+
|
|
185
|
+
if (stateData === undefined) {
|
|
186
|
+
history.replaceState({}, "", rootRelativeOriginalLocationHref);
|
|
187
|
+
return { shouldLoadApp: true };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
switch (stateData.context) {
|
|
191
|
+
case "iframe":
|
|
192
|
+
encryptAuthResponse({
|
|
193
|
+
authResponse
|
|
194
|
+
}).then(({ encryptedMessage }) => parent.postMessage(encryptedMessage, location.origin));
|
|
195
|
+
return { shouldLoadApp: false };
|
|
196
|
+
case "redirect": {
|
|
197
|
+
redirectAuthResponse = authResponse;
|
|
198
|
+
|
|
199
|
+
const rootRelativeRedirectUrl = (() => {
|
|
200
|
+
if (stateData.action === "login" && authResponse.error === "consent_required") {
|
|
201
|
+
return stateData.rootRelativeRedirectUrl_consentRequiredCase;
|
|
202
|
+
}
|
|
203
|
+
return stateData.rootRelativeRedirectUrl;
|
|
204
|
+
})();
|
|
205
|
+
|
|
206
|
+
history.replaceState({}, "", rootRelativeRedirectUrl);
|
|
207
|
+
|
|
208
|
+
return { shouldLoadApp: true };
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
assert<Equals<typeof stateData, never>>(false);
|
|
212
|
+
}
|
|
213
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -178,21 +178,39 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
178
178
|
doAssertNoQueryParams: false
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
{
|
|
182
|
+
const redirectUrl_obj = new URL(redirectUrl);
|
|
183
|
+
const redirectUrl_originAndPath = `${redirectUrl_obj.origin}${redirectUrl_obj.pathname}`;
|
|
184
|
+
|
|
185
|
+
if (!redirectUrl_originAndPath.startsWith(homeUrl)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
[
|
|
188
|
+
`oidc-spa: redirect target ${redirectUrl_originAndPath} is outside of your application.`,
|
|
189
|
+
`The homeUrl that you have provided defines the root where your app is hosted: ${homeUrl}.\n`,
|
|
190
|
+
`This usually means one of the following:\n`,
|
|
191
|
+
`1) The homeUrl is not set correctly. It must be the actual hosting root (for Vite, typically \`import.meta.env.BASE_URL\`).\n`,
|
|
192
|
+
`2) You are trying to redirect outside of your application, which is not allowed by OIDC.`
|
|
193
|
+
].join(" ")
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const rootRelativeRedirectUrl = redirectUrl.slice(window.location.origin.length);
|
|
199
|
+
|
|
200
|
+
log?.(`redirectUrl: ${rootRelativeRedirectUrl}`);
|
|
182
201
|
|
|
183
202
|
const stateData: StateData = {
|
|
184
203
|
context: "redirect",
|
|
185
|
-
|
|
204
|
+
rootRelativeRedirectUrl,
|
|
186
205
|
extraQueryParams: {},
|
|
187
|
-
hasBeenProcessedByCallback: false,
|
|
188
206
|
configId,
|
|
189
207
|
action: "login",
|
|
190
|
-
|
|
208
|
+
rootRelativeRedirectUrl_consentRequiredCase: (() => {
|
|
191
209
|
switch (rest.action) {
|
|
192
210
|
case "login":
|
|
193
|
-
return lastPublicUrl ?? homeUrl;
|
|
211
|
+
return (lastPublicUrl ?? homeUrl).slice(window.location.origin.length);
|
|
194
212
|
case "go to auth server":
|
|
195
|
-
return
|
|
213
|
+
return rootRelativeRedirectUrl;
|
|
196
214
|
}
|
|
197
215
|
})()
|
|
198
216
|
};
|
package/src/entrypoint.ts
CHANGED
|
@@ -1,69 +1 @@
|
|
|
1
|
-
|
|
2
|
-
handleOidcCallback,
|
|
3
|
-
moveRedirectAuthResponseFromSessionStorageToMemory
|
|
4
|
-
} from "./core/handleOidcCallback";
|
|
5
|
-
import { preventSessionStorageSetItemOfPublicKeyByThirdParty } from "./core/iframeMessageProtection";
|
|
6
|
-
|
|
7
|
-
export function oidcEarlyInit(params: {
|
|
8
|
-
freezeFetch: boolean;
|
|
9
|
-
freezeXMLHttpRequest: boolean;
|
|
10
|
-
// NOTE: Made optional just to avoid breaking change.
|
|
11
|
-
// Will be made mandatory next major.
|
|
12
|
-
freezeWebSocket?: boolean;
|
|
13
|
-
}) {
|
|
14
|
-
const { freezeFetch, freezeXMLHttpRequest, freezeWebSocket = false } = params ?? {};
|
|
15
|
-
|
|
16
|
-
const { isHandled } = handleOidcCallback();
|
|
17
|
-
|
|
18
|
-
const shouldLoadApp = !isHandled;
|
|
19
|
-
|
|
20
|
-
if (shouldLoadApp) {
|
|
21
|
-
moveRedirectAuthResponseFromSessionStorageToMemory();
|
|
22
|
-
|
|
23
|
-
if (freezeXMLHttpRequest) {
|
|
24
|
-
const XMLHttpRequest_trusted = globalThis.XMLHttpRequest;
|
|
25
|
-
|
|
26
|
-
Object.freeze(XMLHttpRequest_trusted.prototype);
|
|
27
|
-
Object.freeze(XMLHttpRequest_trusted);
|
|
28
|
-
|
|
29
|
-
Object.defineProperty(globalThis, "XMLHttpRequest", {
|
|
30
|
-
configurable: false,
|
|
31
|
-
writable: false,
|
|
32
|
-
enumerable: true,
|
|
33
|
-
value: XMLHttpRequest_trusted
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (freezeFetch) {
|
|
38
|
-
const fetch_trusted = globalThis.fetch;
|
|
39
|
-
|
|
40
|
-
Object.freeze(fetch_trusted.prototype);
|
|
41
|
-
Object.freeze(fetch_trusted);
|
|
42
|
-
|
|
43
|
-
Object.defineProperty(globalThis, "fetch", {
|
|
44
|
-
configurable: false,
|
|
45
|
-
writable: false,
|
|
46
|
-
enumerable: true,
|
|
47
|
-
value: fetch_trusted
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (freezeWebSocket) {
|
|
52
|
-
const WebSocket_trusted = globalThis.WebSocket;
|
|
53
|
-
|
|
54
|
-
Object.freeze(WebSocket_trusted.prototype);
|
|
55
|
-
Object.freeze(WebSocket_trusted);
|
|
56
|
-
|
|
57
|
-
Object.defineProperty(globalThis, "WebSocket", {
|
|
58
|
-
configurable: false,
|
|
59
|
-
writable: false,
|
|
60
|
-
enumerable: true,
|
|
61
|
-
value: WebSocket_trusted
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
preventSessionStorageSetItemOfPublicKeyByThirdParty();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return { shouldLoadApp };
|
|
69
|
-
}
|
|
1
|
+
export { oidcEarlyInit } from "./core/earlyInit";
|
package/src/mock/oidc.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createObjectThatThrowsIfAccessed } from "../tools/createObjectThatThrow
|
|
|
3
3
|
import { id } from "../vendor/frontend/tsafe";
|
|
4
4
|
import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
|
|
5
5
|
import { getSearchParam, addOrUpdateSearchParam } from "../tools/urlSearchParams";
|
|
6
|
-
import {
|
|
6
|
+
import { getRootRelativeOriginalLocationHref } from "../core/earlyInit";
|
|
7
7
|
|
|
8
8
|
export type ParamsOfCreateMockOidc<
|
|
9
9
|
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
|
|
@@ -28,6 +28,8 @@ export type ParamsOfCreateMockOidc<
|
|
|
28
28
|
|
|
29
29
|
const URL_SEARCH_PARAM_NAME = "isUserLoggedIn";
|
|
30
30
|
|
|
31
|
+
const locationHref_moduleEvalTime = location.href;
|
|
32
|
+
|
|
31
33
|
export async function createMockOidc<
|
|
32
34
|
DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
|
|
33
35
|
AutoLogin extends boolean = false
|
|
@@ -45,7 +47,16 @@ export async function createMockOidc<
|
|
|
45
47
|
|
|
46
48
|
const isUserLoggedIn = (() => {
|
|
47
49
|
const { wasPresent, value } = getSearchParam({
|
|
48
|
-
url:
|
|
50
|
+
url: toFullyQualifiedUrl({
|
|
51
|
+
urlish: (() => {
|
|
52
|
+
try {
|
|
53
|
+
return getRootRelativeOriginalLocationHref();
|
|
54
|
+
} catch {
|
|
55
|
+
return locationHref_moduleEvalTime;
|
|
56
|
+
}
|
|
57
|
+
})(),
|
|
58
|
+
doAssertNoQueryParams: false
|
|
59
|
+
}),
|
|
49
60
|
name: URL_SEARCH_PARAM_NAME
|
|
50
61
|
});
|
|
51
62
|
|
|
@@ -140,7 +151,7 @@ export async function createMockOidc<
|
|
|
140
151
|
createObjectThatThrowsIfAccessed<DecodedIdToken>({
|
|
141
152
|
debugMessage: [
|
|
142
153
|
"You haven't provided a mocked decodedIdToken",
|
|
143
|
-
"See https://docs.oidc-spa.dev/v/
|
|
154
|
+
"See https://docs.oidc-spa.dev/v/v8/mock"
|
|
144
155
|
].join("\n")
|
|
145
156
|
}),
|
|
146
157
|
decodedIdToken_original:
|
|
@@ -148,7 +159,7 @@ export async function createMockOidc<
|
|
|
148
159
|
createObjectThatThrowsIfAccessed<Oidc.Tokens.DecodedIdToken_base>({
|
|
149
160
|
debugMessage: [
|
|
150
161
|
"You haven't provided a mocked decodedIdToken_original",
|
|
151
|
-
"See https://docs.oidc-spa.dev/v/
|
|
162
|
+
"See https://docs.oidc-spa.dev/v/v8/mock"
|
|
152
163
|
].join("\n")
|
|
153
164
|
}),
|
|
154
165
|
issuedAtTime: Date.now(),
|
package/src/mock/react.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { createOidcReactApi_dependencyInjection } from "../react/react";
|
|
|
2
2
|
import { createMockOidc, type ParamsOfCreateMockOidc } from "./oidc";
|
|
3
3
|
import type { ValueOrAsyncGetter } from "../tools/ValueOrAsyncGetter";
|
|
4
4
|
|
|
5
|
-
/** @see: https://docs.oidc-spa.dev/v/
|
|
5
|
+
/** @see: https://docs.oidc-spa.dev/v/v8/mock */
|
|
6
6
|
export function createMockReactOidc<
|
|
7
7
|
DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
|
|
8
8
|
AutoLogin extends boolean = false
|