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.
- package/README.md +2 -5
- package/core/createOidc.js +3 -1
- package/core/createOidc.js.map +1 -1
- package/core/earlyInit.d.ts +45 -7
- package/core/earlyInit.js +69 -153
- package/core/earlyInit.js.map +1 -1
- package/core/oidcClientTsUserToTokens.d.ts +1 -0
- package/core/oidcClientTsUserToTokens.js +11 -1
- package/core/oidcClientTsUserToTokens.js.map +1 -1
- package/core/tokenExfiltrationDefense.d.ts +6 -0
- package/core/tokenExfiltrationDefense.js +616 -0
- package/core/tokenExfiltrationDefense.js.map +1 -0
- package/core/tokenExfiltrationDefense_legacy.d.ts +8 -0
- package/core/tokenExfiltrationDefense_legacy.js +133 -0
- package/core/tokenExfiltrationDefense_legacy.js.map +1 -0
- package/core/tokenPlaceholderSubstitution.d.ts +13 -0
- package/core/tokenPlaceholderSubstitution.js +79 -0
- package/core/tokenPlaceholderSubstitution.js.map +1 -0
- package/esm/core/createOidc.js +3 -1
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/earlyInit.d.ts +45 -7
- package/esm/core/earlyInit.js +69 -153
- package/esm/core/earlyInit.js.map +1 -1
- package/esm/core/oidcClientTsUserToTokens.d.ts +1 -0
- package/esm/core/oidcClientTsUserToTokens.js +11 -1
- package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
- package/esm/core/tokenExfiltrationDefense.d.ts +6 -0
- package/esm/core/tokenExfiltrationDefense.js +613 -0
- package/esm/core/tokenExfiltrationDefense.js.map +1 -0
- package/esm/core/tokenExfiltrationDefense_legacy.d.ts +8 -0
- package/esm/core/tokenExfiltrationDefense_legacy.js +130 -0
- package/esm/core/tokenExfiltrationDefense_legacy.js.map +1 -0
- package/esm/core/tokenPlaceholderSubstitution.d.ts +13 -0
- package/esm/core/tokenPlaceholderSubstitution.js +73 -0
- package/esm/core/tokenPlaceholderSubstitution.js.map +1 -0
- package/esm/tools/isDomain.d.ts +1 -0
- package/esm/tools/isDomain.js +16 -0
- package/esm/tools/isDomain.js.map +1 -0
- package/esm/tools/isHostnameAuthorized.d.ts +5 -0
- package/esm/tools/isHostnameAuthorized.js +74 -0
- package/esm/tools/isHostnameAuthorized.js.map +1 -0
- package/esm/tools/isLikelyDevServer.js +18 -10
- package/esm/tools/isLikelyDevServer.js.map +1 -1
- package/package.json +1 -1
- package/src/core/createOidc.ts +2 -0
- package/src/core/earlyInit.ts +138 -192
- package/src/core/oidcClientTsUserToTokens.ts +14 -0
- package/src/core/tokenExfiltrationDefense.ts +874 -0
- package/src/core/tokenExfiltrationDefense_legacy.ts +165 -0
- package/src/core/tokenPlaceholderSubstitution.ts +105 -0
- package/src/tools/isDomain.ts +18 -0
- package/src/tools/isHostnameAuthorized.ts +91 -0
- package/src/tools/isLikelyDevServer.ts +23 -11
- package/src/vite-plugin/handleClientEntrypoint.ts +57 -20
- package/src/vite-plugin/vite-plugin.ts +5 -10
- package/tools/isDomain.d.ts +1 -0
- package/tools/isDomain.js +19 -0
- package/tools/isDomain.js.map +1 -0
- package/tools/isHostnameAuthorized.d.ts +5 -0
- package/tools/isHostnameAuthorized.js +77 -0
- package/tools/isHostnameAuthorized.js.map +1 -0
- package/tools/isLikelyDevServer.js +18 -10
- package/tools/isLikelyDevServer.js.map +1 -1
- package/vite-plugin/handleClientEntrypoint.js +36 -17
- package/vite-plugin/handleClientEntrypoint.js.map +1 -1
- package/vite-plugin/vite-plugin.d.ts +3 -4
- package/vite-plugin/vite-plugin.js +1 -5
- package/vite-plugin/vite-plugin.js.map +1 -1
package/src/core/earlyInit.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
get: () => original,
|
|
166
|
-
set: () => {
|
|
167
|
-
throw createWriteError(`window.${name}`);
|
|
168
|
-
}
|
|
120
|
+
enableTokenExfiltrationDefense({
|
|
121
|
+
resourceServersAllowedHostnames,
|
|
122
|
+
serviceWorkersAllowedHostnames
|
|
169
123
|
});
|
|
170
124
|
}
|
|
171
125
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
126
|
+
{
|
|
127
|
+
const _MessageEvent_prototype_data_get = (() => {
|
|
128
|
+
const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
|
|
175
129
|
|
|
176
|
-
|
|
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
|
-
|
|
188
|
-
const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
|
|
132
|
+
const { get } = pd;
|
|
189
133
|
|
|
190
|
-
|
|
134
|
+
assert(get !== undefined);
|
|
191
135
|
|
|
192
|
-
|
|
136
|
+
return get;
|
|
137
|
+
})();
|
|
193
138
|
|
|
194
|
-
|
|
139
|
+
const _MessageEvent_prototype_origin_get = (() => {
|
|
140
|
+
const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "origin");
|
|
195
141
|
|
|
196
|
-
|
|
197
|
-
})();
|
|
142
|
+
assert(pd !== undefined);
|
|
198
143
|
|
|
199
|
-
|
|
200
|
-
const pd = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "origin");
|
|
144
|
+
const { get } = pd;
|
|
201
145
|
|
|
202
|
-
|
|
146
|
+
assert(get !== undefined);
|
|
203
147
|
|
|
204
|
-
|
|
148
|
+
return get;
|
|
149
|
+
})();
|
|
205
150
|
|
|
206
|
-
|
|
151
|
+
const _Event_prototype_stopImmediatePropagation_value =
|
|
152
|
+
Event.prototype.stopImmediatePropagation;
|
|
207
153
|
|
|
208
|
-
|
|
209
|
-
})();
|
|
154
|
+
const origin = window.location.origin;
|
|
210
155
|
|
|
211
|
-
|
|
156
|
+
window.addEventListener(
|
|
157
|
+
"message",
|
|
158
|
+
event => {
|
|
159
|
+
if (_MessageEvent_prototype_origin_get.call(event) !== origin) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
212
162
|
|
|
213
|
-
|
|
163
|
+
const eventData: unknown = _MessageEvent_prototype_data_get.call(event);
|
|
214
164
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (_MessageEvent_prototype_origin_get.call(event) !== origin) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
165
|
+
if (typeof eventData !== "string") {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
221
168
|
|
|
222
|
-
|
|
169
|
+
if (!eventData.startsWith(IFRAME_MESSAGE_PREFIX)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
223
172
|
|
|
224
|
-
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
173
|
+
_Event_prototype_stopImmediatePropagation_value.call(event);
|
|
227
174
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
175
|
+
const authResponse: AuthResponse = JSON.parse(
|
|
176
|
+
eventData.slice(IFRAME_MESSAGE_PREFIX.length)
|
|
177
|
+
);
|
|
231
178
|
|
|
232
|
-
|
|
179
|
+
(evtIframeAuthResponse ??= createEvt()).post(authResponse);
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
capture: true,
|
|
183
|
+
once: false,
|
|
184
|
+
passive: false
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
233
188
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
);
|
|
189
|
+
{
|
|
190
|
+
const { BASE_URL } = params;
|
|
237
191
|
|
|
238
|
-
|
|
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 &&
|