oidc-spa 8.4.7 → 8.5.1
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 +607 -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 +604 -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 +862 -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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { assert } from "../tools/tsafe/assert";
|
|
2
|
+
|
|
3
|
+
export type Params = {
|
|
4
|
+
freezeFetch?: boolean;
|
|
5
|
+
freezeXMLHttpRequest?: boolean;
|
|
6
|
+
freezeWebSocket?: boolean;
|
|
7
|
+
freezePromise?: boolean;
|
|
8
|
+
safeMode?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function handleTokenExfiltrationDefense_legacy(params: Params) {
|
|
12
|
+
const {
|
|
13
|
+
freezeFetch,
|
|
14
|
+
freezeXMLHttpRequest,
|
|
15
|
+
freezeWebSocket,
|
|
16
|
+
freezePromise,
|
|
17
|
+
safeMode = false
|
|
18
|
+
} = params;
|
|
19
|
+
|
|
20
|
+
const createWriteError = (target: string) =>
|
|
21
|
+
new Error(
|
|
22
|
+
[
|
|
23
|
+
`oidc-spa: Monkey patching of ${target} has been blocked for security reasons.`,
|
|
24
|
+
"You can disable this restriction by setting `safeMode: false` in `oidcEarlyInit()`",
|
|
25
|
+
"or in your Vite plugin configuration,",
|
|
26
|
+
"but please note this will reduce security.",
|
|
27
|
+
"If you believe this restriction is too strict, please open an issue at:",
|
|
28
|
+
"https://github.com/keycloakify/oidc-spa",
|
|
29
|
+
"We're still identifying real-world blockers and can safely add exceptions where needed.",
|
|
30
|
+
"For now, we prefer to err on the side of hardening rather than exposure."
|
|
31
|
+
].join(" ")
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
for (const name of [
|
|
35
|
+
"fetch",
|
|
36
|
+
"XMLHttpRequest",
|
|
37
|
+
"WebSocket",
|
|
38
|
+
"Headers",
|
|
39
|
+
"URLSearchParams",
|
|
40
|
+
"String",
|
|
41
|
+
"Object",
|
|
42
|
+
"Promise",
|
|
43
|
+
"Array",
|
|
44
|
+
"RegExp",
|
|
45
|
+
"TextEncoder",
|
|
46
|
+
"Uint8Array",
|
|
47
|
+
"Uint32Array",
|
|
48
|
+
"Response",
|
|
49
|
+
"Reflect",
|
|
50
|
+
"JSON",
|
|
51
|
+
"encodeURIComponent",
|
|
52
|
+
"decodeURIComponent",
|
|
53
|
+
"atob",
|
|
54
|
+
"btoa"
|
|
55
|
+
] as const) {
|
|
56
|
+
const doSkip = (() => {
|
|
57
|
+
switch (name) {
|
|
58
|
+
case "XMLHttpRequest":
|
|
59
|
+
if (freezeXMLHttpRequest !== undefined) {
|
|
60
|
+
return !freezeXMLHttpRequest;
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
case "fetch":
|
|
64
|
+
if (freezeFetch !== undefined) {
|
|
65
|
+
return !freezeFetch;
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case "WebSocket":
|
|
69
|
+
if (freezeWebSocket !== undefined) {
|
|
70
|
+
return !freezeWebSocket;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case "Promise":
|
|
74
|
+
if (freezePromise !== undefined) {
|
|
75
|
+
return !freezePromise;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return !safeMode;
|
|
81
|
+
})();
|
|
82
|
+
|
|
83
|
+
if (doSkip) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const original = window[name];
|
|
88
|
+
|
|
89
|
+
if (!original) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if ("prototype" in original) {
|
|
94
|
+
for (const propertyName of Object.getOwnPropertyNames(original.prototype)) {
|
|
95
|
+
if (name === "Object") {
|
|
96
|
+
if (
|
|
97
|
+
propertyName === "toString" ||
|
|
98
|
+
propertyName === "constructor" ||
|
|
99
|
+
propertyName === "valueOf"
|
|
100
|
+
) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (name === "Array") {
|
|
106
|
+
if (propertyName === "constructor" || propertyName === "concat") {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const pd = Object.getOwnPropertyDescriptor(original.prototype, propertyName);
|
|
112
|
+
|
|
113
|
+
assert(pd !== undefined);
|
|
114
|
+
|
|
115
|
+
if (!pd.configurable) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Object.defineProperty(original.prototype, propertyName, {
|
|
120
|
+
enumerable: pd.enumerable,
|
|
121
|
+
configurable: false,
|
|
122
|
+
...("value" in pd
|
|
123
|
+
? {
|
|
124
|
+
get: () => pd.value,
|
|
125
|
+
set: () => {
|
|
126
|
+
throw createWriteError(`window.${name}.prototype.${propertyName}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
: {
|
|
130
|
+
get: pd.get,
|
|
131
|
+
set:
|
|
132
|
+
pd.set ??
|
|
133
|
+
(() => {
|
|
134
|
+
throw createWriteError(`window.${name}.prototype.${propertyName}`);
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Object.defineProperty(window, name, {
|
|
142
|
+
configurable: false,
|
|
143
|
+
enumerable: true,
|
|
144
|
+
get: () => original,
|
|
145
|
+
set: () => {
|
|
146
|
+
throw createWriteError(`window.${name}`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (safeMode) {
|
|
152
|
+
for (const name of ["call", "apply", "bind"] as const) {
|
|
153
|
+
const original = Function.prototype[name];
|
|
154
|
+
|
|
155
|
+
Object.defineProperty(Function.prototype, name, {
|
|
156
|
+
configurable: false,
|
|
157
|
+
enumerable: true,
|
|
158
|
+
get: () => original,
|
|
159
|
+
set: () => {
|
|
160
|
+
throw createWriteError(`window.Function.prototype.${name})`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { assert } from "../tools/tsafe/assert";
|
|
2
|
+
|
|
3
|
+
let isTokenSubstitutionEnabled = false;
|
|
4
|
+
|
|
5
|
+
export function markTokenSubstitutionAdEnabled() {
|
|
6
|
+
isTokenSubstitutionEnabled = true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getIsTokenSubstitutionEnabled() {
|
|
10
|
+
return isTokenSubstitutionEnabled;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Tokens = {
|
|
14
|
+
accessToken: string;
|
|
15
|
+
idToken: string;
|
|
16
|
+
refreshToken?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const entries: {
|
|
20
|
+
configId: string;
|
|
21
|
+
tokens: Tokens;
|
|
22
|
+
id: number;
|
|
23
|
+
}[] = [];
|
|
24
|
+
|
|
25
|
+
let counter = Math.floor(Math.random() * 1_000_000) + 1_000_000;
|
|
26
|
+
|
|
27
|
+
export function getTokensPlaceholders(params: { configId: string; tokens: Tokens }): Tokens {
|
|
28
|
+
const { configId, tokens } = params;
|
|
29
|
+
|
|
30
|
+
assert(isTokenSubstitutionEnabled, "2934482");
|
|
31
|
+
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.configId !== configId) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
const index = entries.indexOf(entry);
|
|
39
|
+
|
|
40
|
+
if (index === -1) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
entries.splice(index, 1);
|
|
45
|
+
}, 30_000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const id = counter++;
|
|
49
|
+
|
|
50
|
+
const entry_new: (typeof entries)[number] = {
|
|
51
|
+
id,
|
|
52
|
+
configId,
|
|
53
|
+
tokens: {
|
|
54
|
+
accessToken: tokens.accessToken,
|
|
55
|
+
idToken: tokens.idToken,
|
|
56
|
+
refreshToken: tokens.refreshToken
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
entries.push(entry_new);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
accessToken: `access_token_placeholder_${id}`,
|
|
64
|
+
idToken: `id_token_placeholder_${id}`,
|
|
65
|
+
refreshToken: tokens.refreshToken === undefined ? undefined : `refresh_token_placeholder_${id}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function substitutePlaceholderByRealToken(text: string): string {
|
|
70
|
+
let text_modified = text;
|
|
71
|
+
|
|
72
|
+
for (const [tokenType, regExp] of [
|
|
73
|
+
["access_token", /access_token_placeholder_(\d+)/g],
|
|
74
|
+
["id_token", /id_token_placeholder_(\d+)/g],
|
|
75
|
+
["refresh_token", /refresh_token_placeholder_(\d+)/g]
|
|
76
|
+
] as const) {
|
|
77
|
+
text_modified = text_modified.replace(regExp, (...[, p1]) => {
|
|
78
|
+
const id = parseInt(p1);
|
|
79
|
+
|
|
80
|
+
const entry = entries.find(e => e.id === id);
|
|
81
|
+
|
|
82
|
+
if (!entry) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
[
|
|
85
|
+
"oidc-spa: Outdated token used to make a request.",
|
|
86
|
+
"Token should not be stored at the application level, when a token",
|
|
87
|
+
"is needed, it should be requested and used immediately."
|
|
88
|
+
].join(" ")
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
switch (tokenType) {
|
|
93
|
+
case "access_token":
|
|
94
|
+
return entry.tokens.accessToken;
|
|
95
|
+
case "id_token":
|
|
96
|
+
return entry.tokens.idToken;
|
|
97
|
+
case "refresh_token":
|
|
98
|
+
assert(entry.tokens.refreshToken !== undefined, "204392284");
|
|
99
|
+
return entry.tokens.refreshToken;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return text_modified;
|
|
105
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function getIsDomain(hostname: string): boolean {
|
|
2
|
+
// Reject IPv4
|
|
3
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Reject IPv6
|
|
8
|
+
if (hostname.includes(":")) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Must contain at least one dot (e.g., "example.com")
|
|
13
|
+
if (!hostname.includes(".")) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getIsLikelyDevServer } from "../tools/isLikelyDevServer";
|
|
2
|
+
import { getIsDomain } from "../tools/isDomain";
|
|
3
|
+
|
|
4
|
+
const MULTI_TENANT_DOMAINS = [
|
|
5
|
+
"vercel.app",
|
|
6
|
+
"netlify.app",
|
|
7
|
+
"github.io",
|
|
8
|
+
"pages.dev",
|
|
9
|
+
"web.app",
|
|
10
|
+
"firebaseapp.com",
|
|
11
|
+
"onrender.com",
|
|
12
|
+
"railway.app",
|
|
13
|
+
"fly.dev",
|
|
14
|
+
"herokuapp.com",
|
|
15
|
+
"amplifyapp.com",
|
|
16
|
+
"surge.sh",
|
|
17
|
+
"stackblitz.io",
|
|
18
|
+
"glitch.me",
|
|
19
|
+
"csb.app",
|
|
20
|
+
"codesandbox.io",
|
|
21
|
+
"repl.co",
|
|
22
|
+
"replit.dev",
|
|
23
|
+
"ondigitalocean.app",
|
|
24
|
+
"bubbleapps.io",
|
|
25
|
+
"wixsite.com",
|
|
26
|
+
"webflow.io",
|
|
27
|
+
"framer.app",
|
|
28
|
+
"deno.dev",
|
|
29
|
+
"azurestaticapps.net",
|
|
30
|
+
"run.app",
|
|
31
|
+
"cloudfront.net",
|
|
32
|
+
"qovery.io",
|
|
33
|
+
"northflank.app",
|
|
34
|
+
"cyclic.app",
|
|
35
|
+
"turso.io",
|
|
36
|
+
"koyeb.app",
|
|
37
|
+
"on.fleek.co",
|
|
38
|
+
"back4app.io"
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export function getIsHostnameAuthorized(params: {
|
|
42
|
+
allowedHostnames: string[];
|
|
43
|
+
hostname: string;
|
|
44
|
+
extendAuthorizationToParentDomain: boolean;
|
|
45
|
+
}) {
|
|
46
|
+
if (getIsLikelyDevServer()) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { hostname, allowedHostnames, extendAuthorizationToParentDomain } = params;
|
|
51
|
+
|
|
52
|
+
if (hostname === location.host) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (let allowedHost of allowedHostnames) {
|
|
57
|
+
allowedHost = allowedHost.toLocaleLowerCase();
|
|
58
|
+
|
|
59
|
+
if (allowedHost === hostname) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (allowedHost.startsWith("*") && hostname.endsWith(allowedHost.slice(1))) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!extendAuthorizationToParentDomain) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!getIsDomain(location.host) || !getIsDomain(hostname)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (MULTI_TENANT_DOMAINS.find(suffix => location.host.endsWith(`.${suffix}`)) !== undefined) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const trustedParentDomain = (() => {
|
|
81
|
+
const [s1, s2] = location.host.split(".").reverse();
|
|
82
|
+
|
|
83
|
+
return `${s2}.${s1}`;
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
if (hostname.endsWith(`.${trustedParentDomain}`)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
const origin = window.location.origin;
|
|
1
|
+
let isLikelyDevServer_cache: boolean | undefined = undefined;
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export function getIsLikelyDevServer(): boolean {
|
|
4
|
+
if (isLikelyDevServer_cache !== undefined) {
|
|
5
|
+
return isLikelyDevServer_cache;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
const isLikelyDevServer = (() => {
|
|
9
|
+
const origin = window.location.origin;
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
if (/^https?:\/\/localhost/.test(origin)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (/^https?:\/\/\[::\]/.test(origin)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (/^https?:\/\/127.0.0.1/.test(origin)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
})();
|
|
25
|
+
|
|
26
|
+
isLikelyDevServer_cache = isLikelyDevServer;
|
|
15
27
|
|
|
16
|
-
return
|
|
28
|
+
return isLikelyDevServer;
|
|
17
29
|
}
|
|
@@ -68,29 +68,66 @@ export function createHandleClientEntrypoint(params: {
|
|
|
68
68
|
|
|
69
69
|
entryResolution.watchFiles.forEach(file => pluginContext.addWatchFile(file));
|
|
70
70
|
|
|
71
|
-
const { freezeFetch, freezeXMLHttpRequest, freezeWebSocket, freezePromise, safeMode, ...rest } =
|
|
72
|
-
oidcSpaVitePluginParams ?? {};
|
|
73
|
-
|
|
74
|
-
assert<Equals<typeof rest, {}>>;
|
|
75
|
-
|
|
76
71
|
return [
|
|
77
72
|
`import { oidcEarlyInit } from "oidc-spa/entrypoint";`,
|
|
78
73
|
`const { shouldLoadApp } = oidcEarlyInit({`,
|
|
79
|
-
...
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
...(() => {
|
|
75
|
+
if ("enableTokenExfiltrationDefense" in oidcSpaVitePluginParams) {
|
|
76
|
+
const {
|
|
77
|
+
enableTokenExfiltrationDefense,
|
|
78
|
+
serviceWorkersAllowedHostnames,
|
|
79
|
+
resourceServersAllowedHostnames,
|
|
80
|
+
...rest
|
|
81
|
+
} = oidcSpaVitePluginParams ?? {};
|
|
82
|
+
|
|
83
|
+
assert<Equals<typeof rest, {}>>;
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
` enableTokenExfiltrationDefense: ${enableTokenExfiltrationDefense},`,
|
|
87
|
+
` resourceServersAllowedHostnames: ${JSON.stringify(
|
|
88
|
+
resourceServersAllowedHostnames
|
|
89
|
+
)},`,
|
|
90
|
+
` serviceWorkersAllowedHostnames: ${JSON.stringify(
|
|
91
|
+
serviceWorkersAllowedHostnames
|
|
92
|
+
)},`,
|
|
93
|
+
` BASE_URL: ${(() => {
|
|
94
|
+
switch (projectType) {
|
|
95
|
+
case "nuxt":
|
|
96
|
+
return "__NUXT__.config.app.baseURL";
|
|
97
|
+
default:
|
|
98
|
+
return `"${resolvedConfig.base}"`;
|
|
99
|
+
}
|
|
100
|
+
})()}`
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
freezeFetch,
|
|
106
|
+
freezeXMLHttpRequest,
|
|
107
|
+
freezeWebSocket,
|
|
108
|
+
freezePromise,
|
|
109
|
+
safeMode,
|
|
110
|
+
...rest
|
|
111
|
+
} = oidcSpaVitePluginParams ?? {};
|
|
112
|
+
|
|
113
|
+
assert<Equals<typeof rest, {}>>;
|
|
114
|
+
|
|
115
|
+
return [
|
|
116
|
+
` freezeFetch: ${freezeFetch},`,
|
|
117
|
+
` freezeXMLHttpRequest: ${freezeXMLHttpRequest},`,
|
|
118
|
+
` freezeWebSocket: ${freezeWebSocket},`,
|
|
119
|
+
` freezePromise: ${freezePromise},`,
|
|
120
|
+
` safeMode: ${safeMode},`,
|
|
121
|
+
` BASE_URL: ${(() => {
|
|
122
|
+
switch (projectType) {
|
|
123
|
+
case "nuxt":
|
|
124
|
+
return "__NUXT__.config.app.baseURL";
|
|
125
|
+
default:
|
|
126
|
+
return `"${resolvedConfig.base}"`;
|
|
127
|
+
}
|
|
128
|
+
})()}`
|
|
129
|
+
];
|
|
130
|
+
})(),
|
|
94
131
|
`});`,
|
|
95
132
|
``,
|
|
96
133
|
`if (shouldLoadApp) {`,
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import type { Plugin, TransformResult } from "vite";
|
|
2
2
|
import { assert } from "../tools/tsafe/assert";
|
|
3
|
-
import type {
|
|
4
|
-
import type { oidcEarlyInit } from "../entrypoint";
|
|
3
|
+
import type { ParamsOfEarlyInit, ParamsOfEarlyInit_legacy } from "../core/earlyInit";
|
|
5
4
|
import { createHandleClientEntrypoint } from "./handleClientEntrypoint";
|
|
6
5
|
import { createHandleServerEntrypoint } from "./handleServerEntrypoint";
|
|
7
6
|
import { manageOptimizedDeps } from "./manageOptimizedDeps";
|
|
8
7
|
import { transformCreateFileRoute } from "./transformTanstackRouterCreateFileRoute";
|
|
9
8
|
import { getProjectType, type ProjectType } from "./projectType";
|
|
10
9
|
|
|
11
|
-
export type OidcSpaVitePluginParams =
|
|
10
|
+
export type OidcSpaVitePluginParams =
|
|
11
|
+
| Omit<ParamsOfEarlyInit, "BASE_URL">
|
|
12
|
+
| Omit<ParamsOfEarlyInit_legacy, "BASE_URL">;
|
|
12
13
|
|
|
13
|
-
export function oidcSpa(
|
|
14
|
-
params: OidcSpaVitePluginParams = {
|
|
15
|
-
freezeFetch: true,
|
|
16
|
-
freezeXMLHttpRequest: true,
|
|
17
|
-
freezeWebSocket: true
|
|
18
|
-
}
|
|
19
|
-
) {
|
|
14
|
+
export function oidcSpa(params: OidcSpaVitePluginParams) {
|
|
20
15
|
let load_handleClientEntrypoint:
|
|
21
16
|
| ReturnType<typeof createHandleClientEntrypoint>["load_handleClientEntrypoint"]
|
|
22
17
|
| undefined = undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getIsDomain(hostname: string): boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getIsDomain = getIsDomain;
|
|
4
|
+
function getIsDomain(hostname) {
|
|
5
|
+
// Reject IPv4
|
|
6
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
// Reject IPv6
|
|
10
|
+
if (hostname.includes(":")) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// Must contain at least one dot (e.g., "example.com")
|
|
14
|
+
if (!hostname.includes(".")) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=isDomain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isDomain.js","sourceRoot":"","sources":["../src/tools/isDomain.ts"],"names":[],"mappings":";;AAAA,kCAiBC;AAjBD,SAAgB,WAAW,CAAC,QAAgB;IACxC,cAAc;IACd,IAAI,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,cAAc;IACd,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,sDAAsD;IACtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getIsHostnameAuthorized = getIsHostnameAuthorized;
|
|
4
|
+
const isLikelyDevServer_1 = require("../tools/isLikelyDevServer");
|
|
5
|
+
const isDomain_1 = require("../tools/isDomain");
|
|
6
|
+
const MULTI_TENANT_DOMAINS = [
|
|
7
|
+
"vercel.app",
|
|
8
|
+
"netlify.app",
|
|
9
|
+
"github.io",
|
|
10
|
+
"pages.dev",
|
|
11
|
+
"web.app",
|
|
12
|
+
"firebaseapp.com",
|
|
13
|
+
"onrender.com",
|
|
14
|
+
"railway.app",
|
|
15
|
+
"fly.dev",
|
|
16
|
+
"herokuapp.com",
|
|
17
|
+
"amplifyapp.com",
|
|
18
|
+
"surge.sh",
|
|
19
|
+
"stackblitz.io",
|
|
20
|
+
"glitch.me",
|
|
21
|
+
"csb.app",
|
|
22
|
+
"codesandbox.io",
|
|
23
|
+
"repl.co",
|
|
24
|
+
"replit.dev",
|
|
25
|
+
"ondigitalocean.app",
|
|
26
|
+
"bubbleapps.io",
|
|
27
|
+
"wixsite.com",
|
|
28
|
+
"webflow.io",
|
|
29
|
+
"framer.app",
|
|
30
|
+
"deno.dev",
|
|
31
|
+
"azurestaticapps.net",
|
|
32
|
+
"run.app",
|
|
33
|
+
"cloudfront.net",
|
|
34
|
+
"qovery.io",
|
|
35
|
+
"northflank.app",
|
|
36
|
+
"cyclic.app",
|
|
37
|
+
"turso.io",
|
|
38
|
+
"koyeb.app",
|
|
39
|
+
"on.fleek.co",
|
|
40
|
+
"back4app.io"
|
|
41
|
+
];
|
|
42
|
+
function getIsHostnameAuthorized(params) {
|
|
43
|
+
if ((0, isLikelyDevServer_1.getIsLikelyDevServer)()) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const { hostname, allowedHostnames, extendAuthorizationToParentDomain } = params;
|
|
47
|
+
if (hostname === location.host) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
for (let allowedHost of allowedHostnames) {
|
|
51
|
+
allowedHost = allowedHost.toLocaleLowerCase();
|
|
52
|
+
if (allowedHost === hostname) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (allowedHost.startsWith("*") && hostname.endsWith(allowedHost.slice(1))) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!extendAuthorizationToParentDomain) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (!(0, isDomain_1.getIsDomain)(location.host) || !(0, isDomain_1.getIsDomain)(hostname)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (MULTI_TENANT_DOMAINS.find(suffix => location.host.endsWith(`.${suffix}`)) !== undefined) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const trustedParentDomain = (() => {
|
|
69
|
+
const [s1, s2] = location.host.split(".").reverse();
|
|
70
|
+
return `${s2}.${s1}`;
|
|
71
|
+
})();
|
|
72
|
+
if (hostname.endsWith(`.${trustedParentDomain}`)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=isHostnameAuthorized.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isHostnameAuthorized.js","sourceRoot":"","sources":["../src/tools/isHostnameAuthorized.ts"],"names":[],"mappings":";;AAwCA,0DAkDC;AA1FD,kEAAkE;AAClE,gDAAgD;AAEhD,MAAM,oBAAoB,GAAG;IACzB,YAAY;IACZ,aAAa;IACb,WAAW;IACX,WAAW;IACX,SAAS;IACT,iBAAiB;IACjB,cAAc;IACd,aAAa;IACb,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,UAAU;IACV,eAAe;IACf,WAAW;IACX,SAAS;IACT,gBAAgB;IAChB,SAAS;IACT,YAAY;IACZ,oBAAoB;IACpB,eAAe;IACf,aAAa;IACb,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,qBAAqB;IACrB,SAAS;IACT,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,UAAU;IACV,WAAW;IACX,aAAa;IACb,aAAa;CAChB,CAAC;AAEF,SAAgB,uBAAuB,CAAC,MAIvC;IACG,IAAI,IAAA,wCAAoB,GAAE,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,iCAAiC,EAAE,GAAG,MAAM,CAAC;IAEjF,IAAI,QAAQ,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,KAAK,IAAI,WAAW,IAAI,gBAAgB,EAAE,CAAC;QACvC,WAAW,GAAG,WAAW,CAAC,iBAAiB,EAAE,CAAC;QAE9C,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,IAAI,CAAC,iCAAiC,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,CAAC,IAAA,sBAAW,EAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAA,sBAAW,EAAC,QAAQ,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,IAAI,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QAC1F,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE;QAC9B,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QAEpD,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;IACzB,CAAC,CAAC,EAAE,CAAC;IAEL,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,mBAAmB,EAAE,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC"}
|