oidc-spa 8.4.8 → 8.5.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.
Files changed (68) hide show
  1. package/README.md +2 -5
  2. package/core/createOidc.js +3 -1
  3. package/core/createOidc.js.map +1 -1
  4. package/core/earlyInit.d.ts +45 -7
  5. package/core/earlyInit.js +69 -153
  6. package/core/earlyInit.js.map +1 -1
  7. package/core/oidcClientTsUserToTokens.d.ts +1 -0
  8. package/core/oidcClientTsUserToTokens.js +11 -1
  9. package/core/oidcClientTsUserToTokens.js.map +1 -1
  10. package/core/tokenExfiltrationDefense.d.ts +6 -0
  11. package/core/tokenExfiltrationDefense.js +616 -0
  12. package/core/tokenExfiltrationDefense.js.map +1 -0
  13. package/core/tokenExfiltrationDefense_legacy.d.ts +8 -0
  14. package/core/tokenExfiltrationDefense_legacy.js +133 -0
  15. package/core/tokenExfiltrationDefense_legacy.js.map +1 -0
  16. package/core/tokenPlaceholderSubstitution.d.ts +13 -0
  17. package/core/tokenPlaceholderSubstitution.js +79 -0
  18. package/core/tokenPlaceholderSubstitution.js.map +1 -0
  19. package/esm/core/createOidc.js +3 -1
  20. package/esm/core/createOidc.js.map +1 -1
  21. package/esm/core/earlyInit.d.ts +45 -7
  22. package/esm/core/earlyInit.js +69 -153
  23. package/esm/core/earlyInit.js.map +1 -1
  24. package/esm/core/oidcClientTsUserToTokens.d.ts +1 -0
  25. package/esm/core/oidcClientTsUserToTokens.js +11 -1
  26. package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
  27. package/esm/core/tokenExfiltrationDefense.d.ts +6 -0
  28. package/esm/core/tokenExfiltrationDefense.js +613 -0
  29. package/esm/core/tokenExfiltrationDefense.js.map +1 -0
  30. package/esm/core/tokenExfiltrationDefense_legacy.d.ts +8 -0
  31. package/esm/core/tokenExfiltrationDefense_legacy.js +130 -0
  32. package/esm/core/tokenExfiltrationDefense_legacy.js.map +1 -0
  33. package/esm/core/tokenPlaceholderSubstitution.d.ts +13 -0
  34. package/esm/core/tokenPlaceholderSubstitution.js +73 -0
  35. package/esm/core/tokenPlaceholderSubstitution.js.map +1 -0
  36. package/esm/tools/isDomain.d.ts +1 -0
  37. package/esm/tools/isDomain.js +16 -0
  38. package/esm/tools/isDomain.js.map +1 -0
  39. package/esm/tools/isHostnameAuthorized.d.ts +5 -0
  40. package/esm/tools/isHostnameAuthorized.js +74 -0
  41. package/esm/tools/isHostnameAuthorized.js.map +1 -0
  42. package/esm/tools/isLikelyDevServer.js +18 -10
  43. package/esm/tools/isLikelyDevServer.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/core/createOidc.ts +2 -0
  46. package/src/core/earlyInit.ts +138 -192
  47. package/src/core/oidcClientTsUserToTokens.ts +14 -0
  48. package/src/core/tokenExfiltrationDefense.ts +874 -0
  49. package/src/core/tokenExfiltrationDefense_legacy.ts +165 -0
  50. package/src/core/tokenPlaceholderSubstitution.ts +105 -0
  51. package/src/tools/isDomain.ts +18 -0
  52. package/src/tools/isHostnameAuthorized.ts +91 -0
  53. package/src/tools/isLikelyDevServer.ts +23 -11
  54. package/src/vite-plugin/handleClientEntrypoint.ts +57 -20
  55. package/src/vite-plugin/vite-plugin.ts +5 -10
  56. package/tools/isDomain.d.ts +1 -0
  57. package/tools/isDomain.js +19 -0
  58. package/tools/isDomain.js.map +1 -0
  59. package/tools/isHostnameAuthorized.d.ts +5 -0
  60. package/tools/isHostnameAuthorized.js +77 -0
  61. package/tools/isHostnameAuthorized.js.map +1 -0
  62. package/tools/isLikelyDevServer.js +18 -10
  63. package/tools/isLikelyDevServer.js.map +1 -1
  64. package/vite-plugin/handleClientEntrypoint.js +36 -17
  65. package/vite-plugin/handleClientEntrypoint.js.map +1 -1
  66. package/vite-plugin/vite-plugin.d.ts +3 -4
  67. package/vite-plugin/vite-plugin.js +1 -5
  68. package/vite-plugin/vite-plugin.js.map +1 -1
@@ -5,19 +5,65 @@ import { setBASE_URL } from "./BASE_URL";
5
5
  import { resolvePrShouldLoadApp } from "./prShouldLoadApp";
6
6
  import { isBrowser } from "../tools/isBrowser";
7
7
  import { createEvt, type Evt } from "../tools/Evt";
8
+ import {
9
+ handleTokenExfiltrationDefense_legacy,
10
+ type Params as Params_handleTokenExfiltrationDefense_legacy
11
+ } from "./tokenExfiltrationDefense_legacy";
12
+ import { enableTokenExfiltrationDefense } from "./tokenExfiltrationDefense";
8
13
 
9
14
  let hasEarlyInitBeenCalled = false;
10
15
 
11
16
  const IFRAME_MESSAGE_PREFIX = "oidc-spa:cross-window-messaging:";
12
17
 
13
- export function oidcEarlyInit(params: {
14
- freezeFetch?: boolean;
15
- freezeXMLHttpRequest?: boolean;
16
- freezeWebSocket?: boolean;
17
- freezePromise?: boolean;
18
- safeMode?: boolean;
18
+ export type ParamsOfEarlyInit_legacy = Params_handleTokenExfiltrationDefense_legacy & {
19
19
  BASE_URL?: string;
20
- }) {
20
+ };
21
+
22
+ export type ParamsOfEarlyInit = {
23
+ /**
24
+ * Base path of where is deployed the webapp
25
+ * usually `import.meta.env.BASE_URL`
26
+ * if omitted, can be provided to createOidc()
27
+ */
28
+ BASE_URL?: string;
29
+
30
+ enableTokenExfiltrationDefense: boolean;
31
+ /**
32
+ * Only when enableTokenExfiltrationDefense: true
33
+ *
34
+ * Example ["vault.domain2.net", "minio.domain2.net", "*.lab.domain3.net"]
35
+ * Note that any domains first party relative to where your app
36
+ * is deployed will be automatically allowed.
37
+ *
38
+ * So for example if your app is deployed under:
39
+ * dashboard.my-company.com
40
+ * Authed request to the following domains will automatically be allowed (examples):
41
+ * - minio.my-company.com
42
+ * - minio.dashboard.my-company.com
43
+ * - my-company.com
44
+ *
45
+ * BUT there is an exception to the rule. If your app is deployed under free default domain
46
+ * provided by known hosting platform like
47
+ * - xxx.vercel.com
48
+ * - xxx.netlify.com
49
+ * - xxx.github.com
50
+ * - xxx.pages.dev (firebase)
51
+ * - xxx.web.app (firebase)
52
+ * - ...
53
+ *
54
+ * We we won't allow request to parent domain since those are multi tenant.
55
+ *
56
+ * Also, all filtering will be disabled when the app is ran with the dev server, so under:
57
+ * - localhost
58
+ * - 127.0.0.1
59
+ * - [::]
60
+ * */
61
+ resourceServersAllowedHostnames?: string[];
62
+
63
+ serviceWorkersAllowedHostnames?: string[];
64
+ };
65
+
66
+ export function oidcEarlyInit(params: ParamsOfEarlyInit | ParamsOfEarlyInit_legacy) {
21
67
  if (hasEarlyInitBeenCalled) {
22
68
  throw new Error("oidc-spa: oidcEarlyInit() Should be called only once");
23
69
  }
@@ -28,224 +74,124 @@ export function oidcEarlyInit(params: {
28
74
  return { shouldLoadApp: true };
29
75
  }
30
76
 
31
- const {
32
- freezeFetch,
33
- freezeXMLHttpRequest,
34
- freezeWebSocket,
35
- freezePromise,
36
- safeMode = false,
37
- BASE_URL
38
- } = params;
39
-
40
77
  const { shouldLoadApp } = handleOidcCallback();
41
78
 
42
79
  if (shouldLoadApp) {
43
- const createWriteError = (target: string) =>
44
- new Error(
45
- [
46
- `oidc-spa: Monkey patching of ${target} has been blocked for security reasons.`,
47
- "You can disable this restriction by setting `safeMode: false` in `oidcEarlyInit()`",
48
- "or in your Vite plugin configuration,",
49
- "but please note this will reduce security.",
50
- "If you believe this restriction is too strict, please open an issue at:",
51
- "https://github.com/keycloakify/oidc-spa",
52
- "We're still identifying real-world blockers and can safely add exceptions where needed.",
53
- "For now, we prefer to err on the side of hardening rather than exposure."
54
- ].join(" ")
55
- );
56
-
57
- for (const name of [
58
- "fetch",
59
- "XMLHttpRequest",
60
- "WebSocket",
61
- "Headers",
62
- "URLSearchParams",
63
- "String",
64
- "Object",
65
- "Promise",
66
- "Array",
67
- "RegExp",
68
- "TextEncoder",
69
- "Uint8Array",
70
- "Uint32Array",
71
- "Response",
72
- "Reflect",
73
- "JSON",
74
- "encodeURIComponent",
75
- "decodeURIComponent",
76
- "atob",
77
- "btoa"
78
- ] as const) {
79
- const doSkip = (() => {
80
- switch (name) {
81
- case "XMLHttpRequest":
82
- if (freezeXMLHttpRequest !== undefined) {
83
- return !freezeXMLHttpRequest;
84
- }
85
- break;
86
- case "fetch":
87
- if (freezeFetch !== undefined) {
88
- return !freezeFetch;
89
- }
90
- break;
91
- case "WebSocket":
92
- if (freezeWebSocket !== undefined) {
93
- return !freezeWebSocket;
94
- }
95
- break;
96
- case "Promise":
97
- if (freezePromise !== undefined) {
98
- return !freezePromise;
99
- }
100
- break;
101
- }
102
-
103
- return !safeMode;
104
- })();
105
-
106
- if (doSkip) {
107
- continue;
80
+ token_exfiltration_defense: {
81
+ if (!("enableTokenExfiltrationDefense" in params)) {
82
+ handleTokenExfiltrationDefense_legacy({
83
+ freezeFetch: params.freezeFetch,
84
+ freezeXMLHttpRequest: params.freezeXMLHttpRequest,
85
+ freezeWebSocket: params.freezeWebSocket,
86
+ freezePromise: params.freezePromise,
87
+ safeMode: params.safeMode
88
+ });
89
+ break token_exfiltration_defense;
108
90
  }
109
91
 
110
- const original = window[name];
111
-
112
- if ("prototype" in original) {
113
- for (const propertyName of Object.getOwnPropertyNames(original.prototype)) {
114
- if (name === "Object") {
115
- if (
116
- propertyName === "toString" ||
117
- propertyName === "constructor" ||
118
- propertyName === "valueOf"
119
- ) {
120
- continue;
121
- }
122
- }
123
-
124
- if (name === "Array") {
125
- if (propertyName === "constructor" || propertyName === "concat") {
126
- continue;
127
- }
128
- }
129
-
130
- const pd = Object.getOwnPropertyDescriptor(original.prototype, propertyName);
131
-
132
- assert(pd !== undefined);
133
-
134
- if (!pd.configurable) {
135
- continue;
136
- }
92
+ const {
93
+ enableTokenExfiltrationDefense: doEnableTokenExfiltrationDefense,
94
+ resourceServersAllowedHostnames,
95
+ serviceWorkersAllowedHostnames
96
+ } = params;
97
+
98
+ if (!doEnableTokenExfiltrationDefense) {
99
+ if (resourceServersAllowedHostnames !== undefined) {
100
+ console.warn(
101
+ [
102
+ "oidc-spa: resourceServersAllowedHostnames is ignored when",
103
+ "enableTokenExfiltrationDefense is set to false."
104
+ ].join(" ")
105
+ );
106
+ }
137
107
 
138
- Object.defineProperty(original.prototype, propertyName, {
139
- enumerable: pd.enumerable,
140
- configurable: false,
141
- ...("value" in pd
142
- ? {
143
- get: () => pd.value,
144
- set: () => {
145
- throw createWriteError(`window.${name}.prototype.${propertyName}`);
146
- }
147
- }
148
- : {
149
- get: pd.get,
150
- set:
151
- pd.set ??
152
- (() => {
153
- throw createWriteError(
154
- `window.${name}.prototype.${propertyName}`
155
- );
156
- })
157
- })
158
- });
108
+ if (serviceWorkersAllowedHostnames !== undefined) {
109
+ console.warn(
110
+ [
111
+ "oidc-spa: serviceWorkersAllowedHostnames is ignored when",
112
+ "enableTokenExfiltrationDefense is set to false."
113
+ ].join(" ")
114
+ );
159
115
  }
116
+
117
+ break token_exfiltration_defense;
160
118
  }
161
119
 
162
- Object.defineProperty(window, name, {
163
- configurable: false,
164
- enumerable: true,
165
- get: () => original,
166
- set: () => {
167
- throw createWriteError(`window.${name}`);
168
- }
120
+ enableTokenExfiltrationDefense({
121
+ resourceServersAllowedHostnames,
122
+ serviceWorkersAllowedHostnames
169
123
  });
170
124
  }
171
125
 
172
- if (safeMode) {
173
- for (const name of ["call", "apply", "bind"] as const) {
174
- const original = Function.prototype[name];
126
+ {
127
+ const _MessageEvent_prototype_data_get = (() => {
128
+ const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
175
129
 
176
- Object.defineProperty(Function.prototype, name, {
177
- configurable: false,
178
- enumerable: true,
179
- get: () => original,
180
- set: () => {
181
- throw createWriteError(`window.Function.prototype.${name});`);
182
- }
183
- });
184
- }
185
- }
130
+ assert(pd !== undefined);
186
131
 
187
- const _MessageEvent_prototype_data_get = (() => {
188
- const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
132
+ const { get } = pd;
189
133
 
190
- assert(pd !== undefined);
134
+ assert(get !== undefined);
191
135
 
192
- const { get } = pd;
136
+ return get;
137
+ })();
193
138
 
194
- assert(get !== undefined);
139
+ const _MessageEvent_prototype_origin_get = (() => {
140
+ const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "origin");
195
141
 
196
- return get;
197
- })();
142
+ assert(pd !== undefined);
198
143
 
199
- const _MessageEvent_prototype_origin_get = (() => {
200
- const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "origin");
144
+ const { get } = pd;
201
145
 
202
- assert(pd !== undefined);
146
+ assert(get !== undefined);
203
147
 
204
- const { get } = pd;
148
+ return get;
149
+ })();
205
150
 
206
- assert(get !== undefined);
151
+ const _Event_prototype_stopImmediatePropagation_value =
152
+ Event.prototype.stopImmediatePropagation;
207
153
 
208
- return get;
209
- })();
154
+ const origin = window.location.origin;
210
155
 
211
- const _Event_prototype_stopImmediatePropagation_value = Event.prototype.stopImmediatePropagation;
156
+ window.addEventListener(
157
+ "message",
158
+ event => {
159
+ if (_MessageEvent_prototype_origin_get.call(event) !== origin) {
160
+ return;
161
+ }
212
162
 
213
- const origin = window.location.origin;
163
+ const eventData: unknown = _MessageEvent_prototype_data_get.call(event);
214
164
 
215
- window.addEventListener(
216
- "message",
217
- event => {
218
- if (_MessageEvent_prototype_origin_get.call(event) !== origin) {
219
- return;
220
- }
165
+ if (typeof eventData !== "string") {
166
+ return;
167
+ }
221
168
 
222
- const eventData: unknown = _MessageEvent_prototype_data_get.call(event);
169
+ if (!eventData.startsWith(IFRAME_MESSAGE_PREFIX)) {
170
+ return;
171
+ }
223
172
 
224
- if (typeof eventData !== "string") {
225
- return;
226
- }
173
+ _Event_prototype_stopImmediatePropagation_value.call(event);
227
174
 
228
- if (!eventData.startsWith(IFRAME_MESSAGE_PREFIX)) {
229
- return;
230
- }
175
+ const authResponse: AuthResponse = JSON.parse(
176
+ eventData.slice(IFRAME_MESSAGE_PREFIX.length)
177
+ );
231
178
 
232
- _Event_prototype_stopImmediatePropagation_value.call(event);
179
+ (evtIframeAuthResponse ??= createEvt()).post(authResponse);
180
+ },
181
+ {
182
+ capture: true,
183
+ once: false,
184
+ passive: false
185
+ }
186
+ );
187
+ }
233
188
 
234
- const authResponse: AuthResponse = JSON.parse(
235
- eventData.slice(IFRAME_MESSAGE_PREFIX.length)
236
- );
189
+ {
190
+ const { BASE_URL } = params;
237
191
 
238
- (evtIframeAuthResponse ??= createEvt()).post(authResponse);
239
- },
240
- {
241
- capture: true,
242
- once: false,
243
- passive: false
192
+ if (BASE_URL !== undefined) {
193
+ setBASE_URL({ BASE_URL });
244
194
  }
245
- );
246
-
247
- if (BASE_URL !== undefined) {
248
- setBASE_URL({ BASE_URL });
249
195
  }
250
196
  }
251
197
 
@@ -5,8 +5,10 @@ import { readExpirationTimeInJwt } from "../tools/readExpirationTimeInJwt";
5
5
  import { decodeJwt } from "../tools/decodeJwt";
6
6
  import type { Oidc } from "./Oidc";
7
7
  import { INFINITY_TIME } from "../tools/INFINITY_TIME";
8
+ import { getIsTokenSubstitutionEnabled, getTokensPlaceholders } from "./tokenPlaceholderSubstitution";
8
9
 
9
10
  export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, unknown>>(params: {
11
+ configId: string;
10
12
  oidcClientTsUser: OidcClientTsUser;
11
13
  decodedIdTokenSchema?: {
12
14
  parse: (decodedIdToken_original: Oidc.Tokens.DecodedIdToken_OidcCoreSpec) => DecodedIdToken;
@@ -16,6 +18,7 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
16
18
  log: typeof console.log | undefined;
17
19
  }): Oidc.Tokens<DecodedIdToken> {
18
20
  const {
21
+ configId,
19
22
  oidcClientTsUser,
20
23
  decodedIdTokenSchema,
21
24
  __unsafe_useIdTokenAsAccessToken,
@@ -226,6 +229,17 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
226
229
  })()
227
230
  });
228
231
 
232
+ if (getIsTokenSubstitutionEnabled()) {
233
+ const placeholders = getTokensPlaceholders({
234
+ configId,
235
+ tokens
236
+ });
237
+
238
+ tokens.accessToken = placeholders.accessToken;
239
+ tokens.idToken = placeholders.idToken;
240
+ tokens.refreshToken = placeholders.refreshToken;
241
+ }
242
+
229
243
  if (
230
244
  isFirstInit &&
231
245
  tokens.hasRefreshToken &&