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.
Files changed (82) hide show
  1. package/core/AuthResponse.d.ts +5 -0
  2. package/core/AuthResponse.js +25 -0
  3. package/core/AuthResponse.js.map +1 -1
  4. package/core/StateData.d.ts +2 -6
  5. package/core/StateData.js +0 -13
  6. package/core/StateData.js.map +1 -1
  7. package/core/createOidc.d.ts +2 -2
  8. package/core/createOidc.js +70 -19
  9. package/core/createOidc.js.map +1 -1
  10. package/core/diagnostic.js +3 -3
  11. package/core/earlyInit.d.ts +16 -0
  12. package/core/earlyInit.js +157 -0
  13. package/core/earlyInit.js.map +1 -0
  14. package/core/index.d.ts +0 -1
  15. package/core/index.js +1 -3
  16. package/core/index.js.map +1 -1
  17. package/core/loginOrGoToAuthServer.js +19 -6
  18. package/core/loginOrGoToAuthServer.js.map +1 -1
  19. package/entrypoint.d.ts +1 -7
  20. package/entrypoint.js +3 -46
  21. package/entrypoint.js.map +1 -1
  22. package/esm/core/AuthResponse.d.ts +5 -0
  23. package/esm/core/AuthResponse.js +23 -0
  24. package/esm/core/AuthResponse.js.map +1 -1
  25. package/esm/core/StateData.d.ts +2 -6
  26. package/esm/core/StateData.js +0 -12
  27. package/esm/core/StateData.js.map +1 -1
  28. package/esm/core/createOidc.d.ts +2 -2
  29. package/esm/core/createOidc.js +72 -21
  30. package/esm/core/createOidc.js.map +1 -1
  31. package/esm/core/diagnostic.js +3 -3
  32. package/esm/core/earlyInit.d.ts +16 -0
  33. package/esm/core/earlyInit.js +152 -0
  34. package/esm/core/earlyInit.js.map +1 -0
  35. package/esm/core/index.d.ts +0 -1
  36. package/esm/core/index.js +0 -1
  37. package/esm/core/index.js.map +1 -1
  38. package/esm/core/loginOrGoToAuthServer.js +19 -6
  39. package/esm/core/loginOrGoToAuthServer.js.map +1 -1
  40. package/esm/entrypoint.d.ts +1 -7
  41. package/esm/entrypoint.js +1 -45
  42. package/esm/entrypoint.js.map +1 -1
  43. package/esm/mock/oidc.js +15 -4
  44. package/esm/mock/oidc.js.map +1 -1
  45. package/esm/mock/react.d.ts +1 -1
  46. package/esm/mock/react.js +1 -1
  47. package/esm/react/react.d.ts +1 -1
  48. package/esm/react/react.js +2 -10
  49. package/esm/react/react.js.map +1 -1
  50. package/mock/oidc.js +15 -4
  51. package/mock/oidc.js.map +1 -1
  52. package/mock/react.d.ts +1 -1
  53. package/mock/react.js +1 -1
  54. package/package.json +1 -1
  55. package/react/react.d.ts +1 -1
  56. package/react/react.js +1 -9
  57. package/react/react.js.map +1 -1
  58. package/src/core/AuthResponse.ts +36 -0
  59. package/src/core/StateData.ts +2 -22
  60. package/src/core/createOidc.ts +108 -24
  61. package/src/core/diagnostic.ts +3 -3
  62. package/src/core/earlyInit.ts +213 -0
  63. package/src/core/index.ts +0 -1
  64. package/src/core/loginOrGoToAuthServer.ts +24 -6
  65. package/src/entrypoint.ts +1 -69
  66. package/src/mock/oidc.ts +15 -4
  67. package/src/mock/react.tsx +1 -1
  68. package/src/react/react.tsx +2 -18
  69. package/core/handleOidcCallback.d.ts +0 -13
  70. package/core/handleOidcCallback.js +0 -228
  71. package/core/handleOidcCallback.js.map +0 -1
  72. package/core/initialLocationHref.d.ts +0 -1
  73. package/core/initialLocationHref.js +0 -8
  74. package/core/initialLocationHref.js.map +0 -1
  75. package/esm/core/handleOidcCallback.d.ts +0 -13
  76. package/esm/core/handleOidcCallback.js +0 -223
  77. package/esm/core/handleOidcCallback.js.map +0 -1
  78. package/esm/core/initialLocationHref.d.ts +0 -1
  79. package/esm/core/initialLocationHref.js +0 -5
  80. package/esm/core/initialLocationHref.js.map +0 -1
  81. package/src/core/handleOidcCallback.ts +0 -318
  82. package/src/core/initialLocationHref.ts +0 -5
@@ -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 { type StateData, generateStateUrlParamValue, STATE_STORE_KEY_PREFIX } from "./StateData";
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 { authResponseToUrl } from "./AuthResponse";
23
- import { handleOidcCallback, retrieveRedirectAuthResponseAndStateData } from "./handleOidcCallback";
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/v7/resources/iframe-related-issues
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
- /** @see: https://docs.oidc-spa.dev/v/v7/usage */
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
- const authResponseAndStateData = retrieveRedirectAuthResponseAndStateData({ configId });
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
- if (authResponseAndStateData === undefined) {
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 { authResponse, stateData } = authResponseAndStateData;
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: initialLocationHref,
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 postLogoutRedirectUrl: string = (() => {
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
- redirectUrl: postLogoutRedirectUrl,
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 = postLogoutRedirectUrl;
1166
+ window.location.href = rootRelativePostLogoutRedirectUrl;
1083
1167
  } else {
1084
1168
  throw error;
1085
1169
  }
@@ -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/v7/resources/iframe-related-issues"
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/v7/providers-configuration/keycloak`
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/v7/providers-configuration/keycloak`
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
@@ -1,4 +1,3 @@
1
1
  export type { Oidc } from "./Oidc";
2
2
  export { createOidc, type ParamsOfCreateOidc } from "./createOidc";
3
3
  export { OidcInitializationError } from "./OidcInitializationError";
4
- export { handleOidcCallback } from "./handleOidcCallback";
@@ -178,21 +178,39 @@ export function createLoginOrGoToAuthServer(params: {
178
178
  doAssertNoQueryParams: false
179
179
  });
180
180
 
181
- log?.(`redirectUrl: ${redirectUrl}`);
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
- redirectUrl,
204
+ rootRelativeRedirectUrl,
186
205
  extraQueryParams: {},
187
- hasBeenProcessedByCallback: false,
188
206
  configId,
189
207
  action: "login",
190
- redirectUrl_consentRequiredCase: (() => {
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 redirectUrl;
213
+ return rootRelativeRedirectUrl;
196
214
  }
197
215
  })()
198
216
  };
package/src/entrypoint.ts CHANGED
@@ -1,69 +1 @@
1
- import {
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 { initialLocationHref } from "../core/initialLocationHref";
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: initialLocationHref,
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/v7/mock"
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/v7/mock"
162
+ "See https://docs.oidc-spa.dev/v/v8/mock"
152
163
  ].join("\n")
153
164
  }),
154
165
  issuedAtTime: Date.now(),
@@ -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/v7/mock */
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