oidc-spa 8.6.10 → 8.6.12
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/core/loginOrGoToAuthServer.js +4 -3
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/core/loginSilent.js +17 -3
- package/core/loginSilent.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/core/loginOrGoToAuthServer.js +4 -3
- package/esm/core/loginOrGoToAuthServer.js.map +1 -1
- package/esm/core/loginSilent.js +17 -3
- package/esm/core/loginSilent.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/core/loginOrGoToAuthServer.ts +5 -3
- package/src/core/loginSilent.ts +22 -3
- 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
|
}
|
|
@@ -353,18 +353,20 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
353
353
|
})
|
|
354
354
|
.then(
|
|
355
355
|
() => new Promise<never>(() => {}),
|
|
356
|
-
|
|
356
|
+
error => {
|
|
357
|
+
assert(error instanceof Error, "393430");
|
|
358
|
+
|
|
357
359
|
if (error.message.includes("Crypto.subtle is available only in secure contexts")) {
|
|
358
360
|
throw new Error(
|
|
359
361
|
[
|
|
360
362
|
`oidc-spa: ${error.message}.`,
|
|
361
|
-
"
|
|
363
|
+
"\nTo fix this error see:",
|
|
362
364
|
"https://docs.oidc-spa.dev/v/v8/resources/fixing-crypto.subtle-is-available-only-in-secure-contexts-https"
|
|
363
365
|
].join(" ")
|
|
364
366
|
);
|
|
365
367
|
}
|
|
366
368
|
|
|
367
|
-
assert(false,
|
|
369
|
+
assert(false, `This is a bug in oidc-spa, please report: ${error.message}`);
|
|
368
370
|
}
|
|
369
371
|
);
|
|
370
372
|
}
|
package/src/core/loginSilent.ts
CHANGED
|
@@ -197,9 +197,28 @@ export async function loginSilent(params: {
|
|
|
197
197
|
oidcClientTsUser
|
|
198
198
|
});
|
|
199
199
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
error => {
|
|
201
|
+
assert(error instanceof Error);
|
|
202
|
+
|
|
203
|
+
if (error.message.includes("IFrame timed out without a response")) {
|
|
204
|
+
// NOTE: This is the expected successful outcome
|
|
205
|
+
// oidc-spa is handling the iframe's response
|
|
206
|
+
// oidc-client-ts never sees it. By design.
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (error.message.includes("Crypto.subtle is available only in secure contexts")) {
|
|
211
|
+
clearTimeouts({ wasSuccess: false });
|
|
212
|
+
throw new Error(
|
|
213
|
+
[
|
|
214
|
+
`oidc-spa: ${error.message}.`,
|
|
215
|
+
"\nTo fix this error see:",
|
|
216
|
+
"https://docs.oidc-spa.dev/v/v8/resources/fixing-crypto.subtle-is-available-only-in-secure-contexts-https"
|
|
217
|
+
].join(" ")
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
assert(false, `This is a bug in oidc-spa, please report: ${error.message}`);
|
|
203
222
|
}
|
|
204
223
|
);
|
|
205
224
|
|
|
@@ -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"}
|