oidc-spa 8.6.10 → 8.6.11
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 +5 -2
- package/core/createOidc.js.map +1 -1
- package/core/diagnostic.d.ts +1 -0
- package/core/diagnostic.js +120 -39
- package/core/diagnostic.js.map +1 -1
- package/esm/core/createOidc.js +5 -2
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/diagnostic.d.ts +1 -0
- package/esm/core/diagnostic.js +120 -39
- package/esm/core/diagnostic.js.map +1 -1
- package/esm/tools/wildcardsMatch.d.ts +4 -0
- package/esm/tools/wildcardsMatch.js +11 -0
- package/esm/tools/wildcardsMatch.js.map +1 -0
- package/package.json +1 -1
- package/src/core/createOidc.ts +6 -1
- package/src/core/diagnostic.ts +149 -42
- package/src/tools/wildcardsMatch.ts +16 -0
- package/tools/wildcardsMatch.d.ts +4 -0
- package/tools/wildcardsMatch.js +14 -0
- package/tools/wildcardsMatch.js.map +1 -0
package/src/core/diagnostic.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { OidcInitializationError } from "./OidcInitializationError";
|
|
|
2
2
|
import { isKeycloak, createKeycloakUtils } from "../keycloak";
|
|
3
3
|
import { getIsValidRemoteJson } from "../tools/getIsValidRemoteJson";
|
|
4
4
|
import { WELL_KNOWN_PATH } from "./OidcMetadata";
|
|
5
|
+
import { assert } from "../tools/tsafe/assert";
|
|
6
|
+
import { getDoMatchWildcardsPattern } from "../tools/wildcardsMatch";
|
|
5
7
|
|
|
6
8
|
export async function createWellKnownOidcConfigurationEndpointUnreachableInitializationError(params: {
|
|
7
9
|
issuerUri: string;
|
|
@@ -90,8 +92,9 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
90
92
|
redirectUri: string;
|
|
91
93
|
issuerUri: string;
|
|
92
94
|
clientId: string;
|
|
95
|
+
authorizationEndpointUrl: string;
|
|
93
96
|
}): Promise<OidcInitializationError> {
|
|
94
|
-
const { redirectUri, issuerUri, clientId } = params;
|
|
97
|
+
const { redirectUri, issuerUri, clientId, authorizationEndpointUrl } = params;
|
|
95
98
|
|
|
96
99
|
check_if_well_known_endpoint_is_reachable: {
|
|
97
100
|
const isValid = await getIsValidRemoteJson(`${issuerUri}${WELL_KNOWN_PATH}`);
|
|
@@ -103,7 +106,8 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
103
106
|
return createWellKnownOidcConfigurationEndpointUnreachableInitializationError({ issuerUri });
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
// Investigate if framing was prevented by some header defined policies
|
|
110
|
+
{
|
|
107
111
|
const headersOrError = await fetch(redirectUri).then(
|
|
108
112
|
response => {
|
|
109
113
|
if (!response.ok) {
|
|
@@ -131,62 +135,165 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
131
135
|
|
|
132
136
|
const headers = headersOrError;
|
|
133
137
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const key = "Content-Security-Policy" as const;
|
|
138
|
+
content_security_policy_issue: {
|
|
139
|
+
const cspHeaderValue = headers["Content-Security-Policy"];
|
|
137
140
|
|
|
138
|
-
|
|
141
|
+
if (cspHeaderValue === null) {
|
|
142
|
+
break content_security_policy_issue;
|
|
143
|
+
}
|
|
139
144
|
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
const csp_parsed: Record<string, string[] | undefined> = Object.fromEntries(
|
|
146
|
+
cspHeaderValue
|
|
147
|
+
.split(";")
|
|
148
|
+
.filter(part => part !== "")
|
|
149
|
+
.map(statement => {
|
|
150
|
+
const [directive, ...values] = statement.split(" ");
|
|
151
|
+
assert(directive !== undefined);
|
|
152
|
+
assert(values.length !== 0);
|
|
153
|
+
return [directive, values];
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
frame_src_issue: {
|
|
158
|
+
const frameSrcValues = csp_parsed["frame-src"];
|
|
159
|
+
|
|
160
|
+
if (frameSrcValues === undefined) {
|
|
161
|
+
break frame_src_issue;
|
|
142
162
|
}
|
|
143
163
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
const hasIssue = (() => {
|
|
165
|
+
for (const frameSrcValue of frameSrcValues) {
|
|
166
|
+
if (frameSrcValue === "'none'") {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const origin_authorizationEndpoint = new URL(authorizationEndpointUrl).origin;
|
|
171
|
+
|
|
172
|
+
if (frameSrcValue === "'self'") {
|
|
173
|
+
const origin_app = new URL(location.href).origin;
|
|
174
|
+
|
|
175
|
+
if (origin_app === origin_authorizationEndpoint) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
getDoMatchWildcardsPattern({
|
|
182
|
+
candidate: origin_authorizationEndpoint,
|
|
183
|
+
stringWithWildcards: frameSrcValue
|
|
184
|
+
})
|
|
185
|
+
) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return true;
|
|
191
|
+
})();
|
|
192
|
+
|
|
193
|
+
if (!hasIssue) {
|
|
194
|
+
break frame_src_issue;
|
|
152
195
|
}
|
|
153
196
|
|
|
154
|
-
|
|
197
|
+
const recommendedValue = (() => {
|
|
198
|
+
const hostname_app = new URL(location.href).hostname;
|
|
199
|
+
const {
|
|
200
|
+
hostname: hostname_authorizationEndpoint,
|
|
201
|
+
origin: origin_authorizationEndpoint
|
|
202
|
+
} = new URL(authorizationEndpointUrl);
|
|
203
|
+
|
|
204
|
+
if (hostname_app === hostname_authorizationEndpoint) {
|
|
205
|
+
return "'self'";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const [lvl1, lvl2] = hostname_app.split(".").reverse();
|
|
209
|
+
|
|
210
|
+
if (!lvl2) {
|
|
211
|
+
return origin_authorizationEndpoint;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (hostname_authorizationEndpoint.endsWith(`.${lvl2}.${lvl1}`)) {
|
|
215
|
+
return `https://*.${lvl2}.${lvl1}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return origin_authorizationEndpoint;
|
|
219
|
+
})();
|
|
220
|
+
|
|
221
|
+
return new OidcInitializationError({
|
|
222
|
+
isAuthServerLikelyDown: false,
|
|
223
|
+
messageOrCause: [
|
|
224
|
+
`Session restoration via iframe failed due to the following HTTP header on GET ${redirectUri}:`,
|
|
225
|
+
`\nContent-Security-Policy “frame-src”: ${frameSrcValues.join("; ")}`,
|
|
226
|
+
`\nThis header prevents opening an iframe to ${authorizationEndpointUrl}.`,
|
|
227
|
+
`\nTo fix this:`,
|
|
228
|
+
`\n - Update your CSP to: frame-src ${[
|
|
229
|
+
...frameSrcValues.filter(v => v !== "'none'"),
|
|
230
|
+
recommendedValue
|
|
231
|
+
]}`,
|
|
232
|
+
`\n - OR remove the frame-src directive from your CSP`,
|
|
233
|
+
`\n - OR, if you cannot change your CSP, call bootstrapOidc/createOidc with sessionRestorationMethod: "full page redirect"`,
|
|
234
|
+
`\n\nMore info: https://docs.oidc-spa.dev/v/v8/resources/csp-configuration`
|
|
235
|
+
].join(" ")
|
|
236
|
+
});
|
|
155
237
|
}
|
|
156
238
|
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const header = headers[key];
|
|
239
|
+
frame_ancestor_issue: {
|
|
240
|
+
const frameAncestorsValues = csp_parsed["frame-ancestors"];
|
|
161
241
|
|
|
162
|
-
if (
|
|
163
|
-
break
|
|
242
|
+
if (frameAncestorsValues === undefined) {
|
|
243
|
+
break frame_ancestor_issue;
|
|
164
244
|
}
|
|
165
245
|
|
|
166
|
-
const
|
|
246
|
+
const hasIssue =
|
|
247
|
+
frameAncestorsValues.includes("'none'") || !frameAncestorsValues.includes("'self'");
|
|
167
248
|
|
|
168
|
-
if (!
|
|
169
|
-
break
|
|
249
|
+
if (!hasIssue) {
|
|
250
|
+
break frame_ancestor_issue;
|
|
170
251
|
}
|
|
171
252
|
|
|
172
|
-
return
|
|
253
|
+
return new OidcInitializationError({
|
|
254
|
+
isAuthServerLikelyDown: false,
|
|
255
|
+
messageOrCause: [
|
|
256
|
+
`Session restoration via iframe failed due to the following HTTP header on GET ${redirectUri}:`,
|
|
257
|
+
`\nContent-Security-Policy “frame-ancestors”: ${frameAncestorsValues.join(
|
|
258
|
+
"; "
|
|
259
|
+
)}`,
|
|
260
|
+
`\nThis header prevents your app from being iframed by itself.`,
|
|
261
|
+
`\nTo fix this:`,
|
|
262
|
+
`\n - Update your CSP to: frame-ancestors 'self'`,
|
|
263
|
+
`\n - OR remove the frame-ancestors directive from your CSP`,
|
|
264
|
+
`\n - OR, if you cannot modify your CSP, call bootstrapOidc/createOidc with sessionRestorationMethod: "full page redirect"`,
|
|
265
|
+
`\n\nMore info: https://docs.oidc-spa.dev/v/v8/resources/csp-configuration`
|
|
266
|
+
].join(" ")
|
|
267
|
+
});
|
|
173
268
|
}
|
|
269
|
+
}
|
|
174
270
|
|
|
175
|
-
|
|
176
|
-
|
|
271
|
+
x_frame_option_header_issue: {
|
|
272
|
+
const key = "X-Frame-Options" as const;
|
|
177
273
|
|
|
178
|
-
|
|
179
|
-
break iframe_blocked;
|
|
180
|
-
}
|
|
274
|
+
const value = headers[key];
|
|
181
275
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
276
|
+
if (value === null) {
|
|
277
|
+
break x_frame_option_header_issue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const hasFrameAncestorsNone = value.toLowerCase().includes("deny");
|
|
281
|
+
|
|
282
|
+
if (!hasFrameAncestorsNone) {
|
|
283
|
+
break x_frame_option_header_issue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return new OidcInitializationError({
|
|
287
|
+
isAuthServerLikelyDown: false,
|
|
288
|
+
messageOrCause: [
|
|
289
|
+
`Session restoration via iframe failed due to the following HTTP header on GET ${redirectUri}:`,
|
|
290
|
+
`\n${key}: ${value}`,
|
|
291
|
+
`\nThis header prevents your app from being framed by itself.`,
|
|
292
|
+
`\nTo fix this, remove the ${key} header and rely on Content-Security-Policy if you need to restrict framing.`,
|
|
293
|
+
`\n\nMore info: https://docs.oidc-spa.dev/v/v8/resources/csp-configuration`
|
|
294
|
+
].join(" ")
|
|
295
|
+
});
|
|
296
|
+
}
|
|
190
297
|
}
|
|
191
298
|
|
|
192
299
|
// Here we know that the server is not down and that the issuer_uri is correct
|
|
@@ -225,8 +332,8 @@ export async function createIframeTimeoutInitializationError(params: {
|
|
|
225
332
|
];
|
|
226
333
|
})(),
|
|
227
334
|
"\n\n",
|
|
228
|
-
|
|
229
|
-
|
|
335
|
+
`If nothing works, or if you see in the console a message mentioning 'refused to frame' there might be a problem with your CSP.`,
|
|
336
|
+
`Read more: https://docs.oidc-spa.dev/v/v8/resources/csp-configuration`
|
|
230
337
|
].join(" ")
|
|
231
338
|
});
|
|
232
339
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function getDoMatchWildcardsPattern(params: {
|
|
2
|
+
stringWithWildcards: string;
|
|
3
|
+
candidate: string;
|
|
4
|
+
}): boolean {
|
|
5
|
+
const { stringWithWildcards, candidate } = params;
|
|
6
|
+
|
|
7
|
+
if (!stringWithWildcards.includes("*")) {
|
|
8
|
+
return stringWithWildcards === candidate;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const escapedRegex = stringWithWildcards
|
|
12
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
13
|
+
.replace(/\\\*/g, ".*");
|
|
14
|
+
|
|
15
|
+
return new RegExp(`^${escapedRegex}$`).test(candidate);
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDoMatchWildcardsPattern = getDoMatchWildcardsPattern;
|
|
4
|
+
function getDoMatchWildcardsPattern(params) {
|
|
5
|
+
const { stringWithWildcards, candidate } = params;
|
|
6
|
+
if (!stringWithWildcards.includes("*")) {
|
|
7
|
+
return stringWithWildcards === candidate;
|
|
8
|
+
}
|
|
9
|
+
const escapedRegex = stringWithWildcards
|
|
10
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
11
|
+
.replace(/\\\*/g, ".*");
|
|
12
|
+
return new RegExp(`^${escapedRegex}$`).test(candidate);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=wildcardsMatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wildcardsMatch.js","sourceRoot":"","sources":["../src/tools/wildcardsMatch.ts"],"names":[],"mappings":";;AAAA,gEAeC;AAfD,SAAgB,0BAA0B,CAAC,MAG1C;IACG,MAAM,EAAE,mBAAmB,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAElD,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,OAAO,mBAAmB,KAAK,SAAS,CAAC;IAC7C,CAAC;IAED,MAAM,YAAY,GAAG,mBAAmB;SACnC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;SACtC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE5B,OAAO,IAAI,MAAM,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC3D,CAAC"}
|