oidc-spa 8.6.6 → 8.6.8
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/createOidc.js +1 -1
- package/core/tokenExfiltrationDefense.js +174 -143
- package/core/tokenExfiltrationDefense.js.map +1 -1
- package/esm/core/createOidc.js +1 -1
- package/esm/core/tokenExfiltrationDefense.js +174 -143
- package/esm/core/tokenExfiltrationDefense.js.map +1 -1
- package/esm/tools/isLikelyDevServer.js +5 -0
- package/esm/tools/isLikelyDevServer.js.map +1 -1
- package/package.json +1 -1
- package/src/core/tokenExfiltrationDefense.ts +196 -156
- package/src/tools/isLikelyDevServer.ts +8 -0
- package/tools/isLikelyDevServer.js +5 -0
- package/tools/isLikelyDevServer.js.map +1 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
substitutePlaceholderByRealToken
|
|
5
5
|
} from "./tokenPlaceholderSubstitution";
|
|
6
6
|
import { getIsHostnameAuthorized } from "../tools/isHostnameAuthorized";
|
|
7
|
+
import { getIsLikelyDevServer } from "../tools/isLikelyDevServer";
|
|
7
8
|
|
|
8
9
|
type Params = {
|
|
9
10
|
resourceServersAllowedHostnames: string[] | undefined;
|
|
@@ -33,237 +34,271 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
33
34
|
const { resourceServersAllowedHostnames } = params;
|
|
34
35
|
|
|
35
36
|
const fetch_actual = window.fetch;
|
|
37
|
+
//@ts-expect-error
|
|
38
|
+
const fetchLater_actual: typeof fetch_actual | undefined = window.fetchLater;
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
const
|
|
40
|
+
const createFetchOrFetchLater = (params: { isFetchLater: boolean }) => {
|
|
41
|
+
const { isFetchLater } = params;
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
const fn: typeof fetch_actual = async (input, init) => {
|
|
44
|
+
const request = input instanceof Request ? input : new Request(input, init);
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
let didSubstitute = false;
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
const url_before = `${request.url}`;
|
|
48
|
+
let url: string;
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
{
|
|
51
|
+
const url_before = `${request.url}`;
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
url = substitutePlaceholderByRealToken(url_before);
|
|
54
|
+
|
|
55
|
+
if (url !== url_before) {
|
|
56
|
+
didSubstitute = true;
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
|
-
}
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
prevent_fetching_of_hashed_js_assets: {
|
|
61
|
+
const { pathname } = new URL(url, window.location.href);
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
if (!viteHashedJsAssetPathRegExp.test(pathname)) {
|
|
64
|
+
break prevent_fetching_of_hashed_js_assets;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error("oidc-spa: Blocked request to hashed js static asset.");
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const headers = new Headers();
|
|
71
|
+
request.headers.forEach((value, key) => {
|
|
72
|
+
const nextValue = substitutePlaceholderByRealToken(value);
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
if (nextValue !== value) {
|
|
75
|
+
didSubstitute = true;
|
|
76
|
+
}
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
78
|
+
headers.set(key, nextValue);
|
|
79
|
+
});
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
});
|
|
81
|
+
let body: BodyInit | undefined;
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
handle_body: {
|
|
84
|
+
from_init: {
|
|
85
|
+
if (!init) {
|
|
86
|
+
break from_init;
|
|
87
|
+
}
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
break from_init;
|
|
81
|
-
}
|
|
89
|
+
if (!init.body) {
|
|
90
|
+
break from_init;
|
|
91
|
+
}
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
if (input instanceof Request && input.body !== null) {
|
|
94
|
+
break from_init;
|
|
95
|
+
}
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
97
|
+
if (typeof init.body === "string") {
|
|
98
|
+
body = substitutePlaceholderByRealToken(init.body);
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
if (init.body !== body) {
|
|
101
|
+
didSubstitute = true;
|
|
102
|
+
}
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
didSubstitute = true;
|
|
104
|
+
break handle_body;
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
if (init.body instanceof URLSearchParams) {
|
|
108
|
+
let didUrlSearchParamsSubstitute = false;
|
|
109
|
+
const next = new URLSearchParams();
|
|
110
|
+
|
|
111
|
+
init.body.forEach((value, key) => {
|
|
112
|
+
const nextValue = substitutePlaceholderByRealToken(value);
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
if (nextValue !== value) {
|
|
115
|
+
didUrlSearchParamsSubstitute = true;
|
|
116
|
+
}
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
next.append(key, nextValue);
|
|
119
|
+
});
|
|
107
120
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
121
|
+
if (didUrlSearchParamsSubstitute) {
|
|
122
|
+
didSubstitute = true;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
next.
|
|
113
|
-
});
|
|
125
|
+
body = didUrlSearchParamsSubstitute ? next : init.body;
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
didSubstitute = true;
|
|
127
|
+
break handle_body;
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
body
|
|
130
|
+
if (init.body instanceof FormData) {
|
|
131
|
+
let didFormDataSubstitute = false;
|
|
132
|
+
const next = new FormData();
|
|
120
133
|
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
init.body.forEach((value, key) => {
|
|
135
|
+
if (typeof value === "string") {
|
|
136
|
+
const nextValue = substitutePlaceholderByRealToken(value);
|
|
123
137
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
138
|
+
if (nextValue !== value) {
|
|
139
|
+
didFormDataSubstitute = true;
|
|
140
|
+
}
|
|
127
141
|
|
|
128
|
-
|
|
129
|
-
if (typeof value === "string") {
|
|
130
|
-
const nextValue = substitutePlaceholderByRealToken(value);
|
|
142
|
+
next.append(key, nextValue);
|
|
131
143
|
|
|
132
|
-
|
|
133
|
-
didFormDataSubstitute = true;
|
|
144
|
+
return;
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
next.append(key,
|
|
147
|
+
next.append(key, value);
|
|
148
|
+
});
|
|
137
149
|
|
|
138
|
-
|
|
150
|
+
if (didFormDataSubstitute) {
|
|
151
|
+
didSubstitute = true;
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
next.
|
|
142
|
-
});
|
|
154
|
+
body = didFormDataSubstitute ? next : init.body;
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
didSubstitute = true;
|
|
156
|
+
break handle_body;
|
|
146
157
|
}
|
|
147
158
|
|
|
148
|
-
body
|
|
159
|
+
if (init.body instanceof Blob) {
|
|
160
|
+
break from_init;
|
|
161
|
+
}
|
|
149
162
|
|
|
163
|
+
body = init.body;
|
|
150
164
|
break handle_body;
|
|
151
165
|
}
|
|
152
166
|
|
|
153
|
-
if (
|
|
154
|
-
|
|
167
|
+
if (request.body === null) {
|
|
168
|
+
body = undefined;
|
|
169
|
+
break handle_body;
|
|
155
170
|
}
|
|
156
171
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
172
|
+
const shouldInspectBody = (() => {
|
|
173
|
+
let ct = headers.get("Content-Type");
|
|
160
174
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
175
|
+
if (ct === null) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
165
178
|
|
|
166
|
-
|
|
167
|
-
let ct = headers.get("Content-Type");
|
|
179
|
+
ct = ct.toLocaleLowerCase();
|
|
168
180
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
if (
|
|
182
|
+
!ct.startsWith("application/json") &&
|
|
183
|
+
!ct.startsWith("application/x-www-form-urlencoded")
|
|
184
|
+
) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
172
187
|
|
|
173
|
-
|
|
188
|
+
const len_str = headers.get("Content-Length");
|
|
174
189
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
if (len_str === null) {
|
|
191
|
+
// NOTE: This will have performance implications for large bodies
|
|
192
|
+
// but we have no other way to know the size
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const len = parseInt(len_str, 10);
|
|
181
197
|
|
|
182
|
-
|
|
198
|
+
if (!Number.isFinite(len) || len > 100_000) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
183
201
|
|
|
184
|
-
if (len_str === null) {
|
|
185
|
-
// NOTE: This will have performance implications for large bodies
|
|
186
|
-
// but we have no other way to know the size
|
|
187
202
|
return true;
|
|
203
|
+
})();
|
|
204
|
+
|
|
205
|
+
if (!shouldInspectBody) {
|
|
206
|
+
body = request.body;
|
|
207
|
+
break handle_body;
|
|
188
208
|
}
|
|
189
209
|
|
|
190
|
-
const
|
|
210
|
+
const bodyText = await request.clone().text();
|
|
211
|
+
const nextBodyText = substitutePlaceholderByRealToken(bodyText);
|
|
191
212
|
|
|
192
|
-
if (
|
|
193
|
-
|
|
213
|
+
if (nextBodyText !== bodyText) {
|
|
214
|
+
didSubstitute = true;
|
|
194
215
|
}
|
|
195
216
|
|
|
196
|
-
|
|
197
|
-
})();
|
|
198
|
-
|
|
199
|
-
if (!shouldInspectBody) {
|
|
200
|
-
body = request.body;
|
|
201
|
-
break handle_body;
|
|
217
|
+
body = nextBodyText;
|
|
202
218
|
}
|
|
203
219
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
didSubstitute = true;
|
|
209
|
-
}
|
|
220
|
+
block_authed_request_to_unauthorized_hostnames: {
|
|
221
|
+
if (!didSubstitute) {
|
|
222
|
+
break block_authed_request_to_unauthorized_hostnames;
|
|
223
|
+
}
|
|
210
224
|
|
|
211
|
-
|
|
212
|
-
}
|
|
225
|
+
const { hostname } = new URL(url, window.location.href);
|
|
213
226
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
227
|
+
if (
|
|
228
|
+
getIsHostnameAuthorized({
|
|
229
|
+
allowedHostnames: resourceServersAllowedHostnames,
|
|
230
|
+
extendAuthorizationToParentDomain: true,
|
|
231
|
+
hostname
|
|
232
|
+
})
|
|
233
|
+
) {
|
|
234
|
+
break block_authed_request_to_unauthorized_hostnames;
|
|
235
|
+
}
|
|
218
236
|
|
|
219
|
-
|
|
237
|
+
throw new Error(
|
|
238
|
+
[
|
|
239
|
+
`oidc-spa: Blocked authed request to ${hostname}.`,
|
|
240
|
+
`To authorize this request add "${hostname}" to`,
|
|
241
|
+
"`resourceServersAllowedHostnames`."
|
|
242
|
+
].join(" ")
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const nextInit: RequestInit = {
|
|
247
|
+
method: request.method,
|
|
248
|
+
headers,
|
|
249
|
+
body,
|
|
250
|
+
mode: request.mode,
|
|
251
|
+
credentials: request.credentials,
|
|
252
|
+
cache: request.cache,
|
|
253
|
+
redirect: request.redirect,
|
|
254
|
+
referrer: request.referrer,
|
|
255
|
+
referrerPolicy: request.referrerPolicy,
|
|
256
|
+
integrity: request.integrity,
|
|
257
|
+
keepalive: request.keepalive,
|
|
258
|
+
signal: request.signal
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
{
|
|
262
|
+
//@ts-expect-error
|
|
263
|
+
const duplex = init?.duplex ?? (input instanceof Request ? input.duplex : undefined);
|
|
220
264
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
hostname
|
|
226
|
-
})
|
|
227
|
-
) {
|
|
228
|
-
break block_authed_request_to_unauthorized_hostnames;
|
|
265
|
+
if (duplex !== undefined) {
|
|
266
|
+
//@ts-expect-error
|
|
267
|
+
nextInit.duplex = duplex;
|
|
268
|
+
}
|
|
229
269
|
}
|
|
230
270
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"`resourceServersAllowedHostnames`."
|
|
236
|
-
].join(" ")
|
|
237
|
-
);
|
|
238
|
-
}
|
|
271
|
+
fetch_later: {
|
|
272
|
+
if (!isFetchLater) {
|
|
273
|
+
break fetch_later;
|
|
274
|
+
}
|
|
239
275
|
|
|
240
|
-
|
|
241
|
-
method: request.method,
|
|
242
|
-
headers,
|
|
243
|
-
body,
|
|
244
|
-
mode: request.mode,
|
|
245
|
-
credentials: request.credentials,
|
|
246
|
-
cache: request.cache,
|
|
247
|
-
redirect: request.redirect,
|
|
248
|
-
referrer: request.referrer,
|
|
249
|
-
referrerPolicy: request.referrerPolicy,
|
|
250
|
-
integrity: request.integrity,
|
|
251
|
-
keepalive: request.keepalive,
|
|
252
|
-
signal: request.signal
|
|
253
|
-
};
|
|
276
|
+
assert(fetchLater_actual !== undefined);
|
|
254
277
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
278
|
+
const activateAfter =
|
|
279
|
+
//@ts-expect-error
|
|
280
|
+
init?.activateAfter ?? (input instanceof Request ? input.activateAfter : undefined);
|
|
258
281
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
282
|
+
if (activateAfter !== undefined) {
|
|
283
|
+
//@ts-expect-error
|
|
284
|
+
nextInit.activateAfter = activateAfter;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return fetchLater_actual(url, nextInit);
|
|
262
288
|
}
|
|
263
|
-
}
|
|
264
289
|
|
|
265
|
-
|
|
290
|
+
return fetch_actual(url, nextInit);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return fn;
|
|
266
294
|
};
|
|
295
|
+
|
|
296
|
+
window.fetch = createFetchOrFetchLater({ isFetchLater: false });
|
|
297
|
+
// @ts-expect-error
|
|
298
|
+
if (window.fetchLater) {
|
|
299
|
+
// @ts-expect-error
|
|
300
|
+
window.fetchLater = createFetchOrFetchLater({ isFetchLater: true });
|
|
301
|
+
}
|
|
267
302
|
}
|
|
268
303
|
|
|
269
304
|
function patchXMLHttpRequestApiToSubstituteTokenPlaceholder(params: {
|
|
@@ -330,6 +365,11 @@ function patchXMLHttpRequestApiToSubstituteTokenPlaceholder(params: {
|
|
|
330
365
|
XMLHttpRequest.prototype.send = function send(body) {
|
|
331
366
|
const state = stateByInstance.get(this);
|
|
332
367
|
|
|
368
|
+
// NOTE: Vite's dev server instantiates a websocket before earlyInit runs.
|
|
369
|
+
if (state === undefined && getIsLikelyDevServer()) {
|
|
370
|
+
return send_actual.call(this, body);
|
|
371
|
+
}
|
|
372
|
+
|
|
333
373
|
assert(state !== undefined, "32323484");
|
|
334
374
|
|
|
335
375
|
let nextBody = body;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
import { isBrowser } from "./isBrowser";
|
|
2
|
+
|
|
1
3
|
let isLikelyDevServer_cache: boolean | undefined = undefined;
|
|
2
4
|
|
|
5
|
+
const hasRefreshReg = isBrowser && "$RefreshReg$" in window;
|
|
6
|
+
|
|
3
7
|
export function getIsLikelyDevServer(): boolean {
|
|
4
8
|
if (isLikelyDevServer_cache !== undefined) {
|
|
5
9
|
return isLikelyDevServer_cache;
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
const isLikelyDevServer = (() => {
|
|
13
|
+
if (hasRefreshReg) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
const origin = window.location.origin;
|
|
10
18
|
|
|
11
19
|
if (/^https?:\/\/localhost/.test(origin)) {
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getIsLikelyDevServer = getIsLikelyDevServer;
|
|
4
|
+
const isBrowser_1 = require("./isBrowser");
|
|
4
5
|
let isLikelyDevServer_cache = undefined;
|
|
6
|
+
const hasRefreshReg = isBrowser_1.isBrowser && "$RefreshReg$" in window;
|
|
5
7
|
function getIsLikelyDevServer() {
|
|
6
8
|
if (isLikelyDevServer_cache !== undefined) {
|
|
7
9
|
return isLikelyDevServer_cache;
|
|
8
10
|
}
|
|
9
11
|
const isLikelyDevServer = (() => {
|
|
12
|
+
if (hasRefreshReg) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
10
15
|
const origin = window.location.origin;
|
|
11
16
|
if (/^https?:\/\/localhost/.test(origin)) {
|
|
12
17
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isLikelyDevServer.js","sourceRoot":"","sources":["../src/tools/isLikelyDevServer.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"isLikelyDevServer.js","sourceRoot":"","sources":["../src/tools/isLikelyDevServer.ts"],"names":[],"mappings":";;AAMA,oDA8BC;AApCD,2CAAwC;AAExC,IAAI,uBAAuB,GAAwB,SAAS,CAAC;AAE7D,MAAM,aAAa,GAAG,qBAAS,IAAI,cAAc,IAAI,MAAM,CAAC;AAE5D,SAAgB,oBAAoB;IAChC,IAAI,uBAAuB,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,uBAAuB,CAAC;IACnC,CAAC;IAED,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE;QAC5B,IAAI,aAAa,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAEtC,IAAI,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC,CAAC,EAAE,CAAC;IAEL,uBAAuB,GAAG,iBAAiB,CAAC;IAE5C,OAAO,iBAAiB,CAAC;AAC7B,CAAC"}
|