@wbce-d9/api 12.5.9 → 12.5.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/dist/app.js +1 -0
- package/dist/auth/drivers/openid.js +19 -2
- package/dist/auth/drivers/openid.test.d.ts +1 -0
- package/dist/cli/utils/create-env/env-stub.liquid +3 -0
- package/dist/env.js +2 -0
- package/dist/operations/condition/index.js +3 -1
- package/dist/utils/is-redirect-allowed-on-login.js +13 -14
- package/package.json +8 -8
package/dist/app.js
CHANGED
|
@@ -109,6 +109,7 @@ export default async function createApp() {
|
|
|
109
109
|
if (env['HSTS_ENABLED']) {
|
|
110
110
|
app.use(helmet.hsts(getConfigFromEnv('HSTS_', ['HSTS_ENABLED'])));
|
|
111
111
|
}
|
|
112
|
+
app.use(helmet.crossOriginOpenerPolicy({ policy: env['CROSS_ORIGIN_OPENER_POLICY'] }));
|
|
112
113
|
await emitter.emitInit('app.before', { app });
|
|
113
114
|
await emitter.emitInit('middlewares.before', { app });
|
|
114
115
|
app.use(expressLogger);
|
|
@@ -17,6 +17,23 @@ import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
|
17
17
|
import { Url } from '../../utils/url.js';
|
|
18
18
|
import { isRedirectAllowedOnLogin } from '../../utils/is-redirect-allowed-on-login.js';
|
|
19
19
|
import { BaseOAuthDriver } from './baseoauth.js';
|
|
20
|
+
// Authorization-request parameters the caller must never control
|
|
21
|
+
const RESERVED_AUTH_PARAMS = new Set([
|
|
22
|
+
'prompt',
|
|
23
|
+
'redirect',
|
|
24
|
+
'scope',
|
|
25
|
+
'access_type',
|
|
26
|
+
'code_challenge',
|
|
27
|
+
'code_challenge_method',
|
|
28
|
+
'state',
|
|
29
|
+
'nonce',
|
|
30
|
+
'redirect_uri',
|
|
31
|
+
'client_id',
|
|
32
|
+
'response_type',
|
|
33
|
+
'response_mode',
|
|
34
|
+
'request',
|
|
35
|
+
'request_uri',
|
|
36
|
+
]);
|
|
20
37
|
export class OpenIDAuthDriver extends BaseOAuthDriver {
|
|
21
38
|
client;
|
|
22
39
|
redirectUrl;
|
|
@@ -75,6 +92,7 @@ export class OpenIDAuthDriver extends BaseOAuthDriver {
|
|
|
75
92
|
const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);
|
|
76
93
|
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
|
|
77
94
|
const params = {
|
|
95
|
+
...additionalParams,
|
|
78
96
|
scope: this.config['scope'] ?? 'openid profile email',
|
|
79
97
|
access_type: 'offline',
|
|
80
98
|
...paramsConfig,
|
|
@@ -84,7 +102,6 @@ export class OpenIDAuthDriver extends BaseOAuthDriver {
|
|
|
84
102
|
state: codeChallenge,
|
|
85
103
|
nonce: codeChallenge,
|
|
86
104
|
redirect_uri: this.redirectUrl,
|
|
87
|
-
...additionalParams,
|
|
88
105
|
};
|
|
89
106
|
if (prompt) {
|
|
90
107
|
params.prompt = 'consent';
|
|
@@ -155,7 +172,7 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
155
172
|
issuer: 'directus',
|
|
156
173
|
});
|
|
157
174
|
const additionalParams = Object.fromEntries(Object.entries(req.query)
|
|
158
|
-
.filter(([k, v]) => k
|
|
175
|
+
.filter(([k, v]) => RESERVED_AUTH_PARAMS.has(k) === false && typeof v === 'string')
|
|
159
176
|
.map(([k, v]) => [k, v]));
|
|
160
177
|
const authUrl = await provider.generateAuthUrl(codeVerifier, prompt, additionalParams);
|
|
161
178
|
const urlParams = new URL(authUrl);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -254,6 +254,9 @@ CORS_CREDENTIALS=true
|
|
|
254
254
|
# Value for the Access-Control-Max-Age header [18000]
|
|
255
255
|
CORS_MAX_AGE=18000
|
|
256
256
|
|
|
257
|
+
# Value for the Cross-Origin-Opener-Policy header: same-origin, same-origin-allow-popups or unsafe-none ["same-origin"]
|
|
258
|
+
# CROSS_ORIGIN_OPENER_POLICY="same-origin"
|
|
259
|
+
|
|
257
260
|
####################################################################################################
|
|
258
261
|
### Argon2
|
|
259
262
|
|
package/dist/env.js
CHANGED
|
@@ -63,6 +63,7 @@ const allowedEnvironmentVars = [
|
|
|
63
63
|
'IMPORT_IP_DENY_LIST',
|
|
64
64
|
'CONTENT_SECURITY_POLICY_.+',
|
|
65
65
|
'HSTS_.+',
|
|
66
|
+
'CROSS_ORIGIN_OPENER_POLICY',
|
|
66
67
|
// hashing
|
|
67
68
|
'HASH_.+',
|
|
68
69
|
'X_POWERED_BY_ENABLED',
|
|
@@ -283,6 +284,7 @@ const defaults = {
|
|
|
283
284
|
IP_TRUST_PROXY: true,
|
|
284
285
|
IP_CUSTOM_HEADER: false,
|
|
285
286
|
IMPORT_IP_DENY_LIST: ['0.0.0.0', '169.254.169.254'],
|
|
287
|
+
CROSS_ORIGIN_OPENER_POLICY: 'same-origin',
|
|
286
288
|
SERVE_APP: true,
|
|
287
289
|
SERVE_GRAPHQL: true,
|
|
288
290
|
RELATIONAL_BATCH_SIZE: 25000,
|
|
@@ -4,7 +4,9 @@ export default defineOperationApi({
|
|
|
4
4
|
handler: ({ filter }, { data }) => {
|
|
5
5
|
const errors = validatePayload(filter, data, { requireAll: true });
|
|
6
6
|
if (errors.length > 0) {
|
|
7
|
-
|
|
7
|
+
// Joi errors hold the validated data ($env, $accountability…) in _original, details[].context and in some
|
|
8
|
+
// messages: only keep the path and type, both derived from the filter, as webhook flows can return them
|
|
9
|
+
throw errors.flatMap((error) => error.details.map(({ path, type }) => ({ path, type })));
|
|
8
10
|
}
|
|
9
11
|
else {
|
|
10
12
|
return null;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { toArray } from '@wbce-d9/utils';
|
|
2
|
+
import { URL } from 'url';
|
|
2
3
|
import env from '../env.js';
|
|
3
4
|
import isUrlAllowed from './is-url-allowed.js';
|
|
4
|
-
|
|
5
|
+
// Sentinel origin that never resolves (RFC 6761): relative paths keep it,
|
|
6
|
+
// anything pointing elsewhere (`//x`, `/\x`, `https://x`) does not.
|
|
7
|
+
const RELATIVE_BASE = 'https://relative.invalid';
|
|
8
|
+
function isUrlValid(url, base) {
|
|
5
9
|
try {
|
|
6
|
-
new URL(
|
|
10
|
+
new URL(url, base);
|
|
7
11
|
return true;
|
|
8
12
|
}
|
|
9
13
|
catch (err) {
|
|
@@ -18,25 +22,20 @@ export function isRedirectAllowedOnLogin(redirect, provider) {
|
|
|
18
22
|
return true; // empty redirect
|
|
19
23
|
if (typeof redirect !== 'string')
|
|
20
24
|
return false; // invalid type
|
|
21
|
-
const publicUrl = env['PUBLIC_URL'];
|
|
22
25
|
if (!isUrlValid(redirect)) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
// domain without protocol `//example.com/test`
|
|
28
|
-
return false;
|
|
26
|
+
// relative path like `/admin/test`, allowed unless it resolves to another origin
|
|
27
|
+
return isUrlValid(redirect, RELATIVE_BASE) && new URL(redirect, RELATIVE_BASE).origin === RELATIVE_BASE;
|
|
29
28
|
}
|
|
30
|
-
const { protocol: redirectProtocol,
|
|
29
|
+
const { protocol: redirectProtocol, host: redirectHost } = new URL(redirect);
|
|
30
|
+
const publicUrl = env['PUBLIC_URL'];
|
|
31
31
|
const envKey = `AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`;
|
|
32
32
|
if (envKey in env) {
|
|
33
33
|
if (isUrlAllowed(redirect, [...toArray(env[envKey]), publicUrl]))
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
|
-
if (!isUrlValid(publicUrl))
|
|
36
|
+
if (!isUrlValid(publicUrl))
|
|
37
37
|
return false;
|
|
38
|
-
}
|
|
39
38
|
// allow redirects to the defined PUBLIC_URL
|
|
40
|
-
const { protocol: publicProtocol,
|
|
41
|
-
return
|
|
39
|
+
const { protocol: publicProtocol, host: publicHost } = new URL(publicUrl);
|
|
40
|
+
return redirectProtocol === publicProtocol && redirectHost === publicHost;
|
|
42
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wbce-d9/api",
|
|
3
|
-
"version": "12.5.
|
|
3
|
+
"version": "12.5.11",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -143,20 +143,20 @@
|
|
|
143
143
|
"tmp-promise": "3.0.3",
|
|
144
144
|
"tsx": "3.12.6",
|
|
145
145
|
"uuid-validate": "0.0.3",
|
|
146
|
+
"@wbce-d9/constants": "10.0.0",
|
|
146
147
|
"@wbce-d9/app": "11.0.0",
|
|
147
148
|
"@wbce-d9/exceptions": "10.0.0",
|
|
148
149
|
"@wbce-d9/specs": "9.26.5",
|
|
149
150
|
"@wbce-d9/schema": "10.0.0",
|
|
150
|
-
"@wbce-d9/extensions-sdk": "10.0.4",
|
|
151
151
|
"@wbce-d9/storage": "10.0.0",
|
|
152
|
-
"@wbce-d9/constants": "10.0.0",
|
|
153
|
-
"@wbce-d9/storage-driver-s3": "10.0.3",
|
|
154
|
-
"@wbce-d9/storage-driver-azure": "10.0.3",
|
|
155
|
-
"@wbce-d9/update-check": "10.0.4",
|
|
156
|
-
"@wbce-d9/utils": "10.0.3",
|
|
157
152
|
"@wbce-d9/storage-driver-gcs": "10.0.3",
|
|
158
153
|
"@wbce-d9/storage-driver-cloudinary": "10.0.4",
|
|
159
|
-
"@wbce-d9/storage-driver-
|
|
154
|
+
"@wbce-d9/storage-driver-azure": "10.0.3",
|
|
155
|
+
"@wbce-d9/storage-driver-local": "10.0.3",
|
|
156
|
+
"@wbce-d9/storage-driver-s3": "10.0.3",
|
|
157
|
+
"@wbce-d9/extensions-sdk": "10.0.4",
|
|
158
|
+
"@wbce-d9/update-check": "10.0.4",
|
|
159
|
+
"@wbce-d9/utils": "10.0.3"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
162
162
|
"@ngneat/falso": "6.4.0",
|