directus 9.5.2 → 9.7.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/dist/app.js +3 -1
- package/dist/auth/drivers/ldap.d.ts +0 -1
- package/dist/auth/drivers/ldap.js +54 -60
- package/dist/auth/drivers/oauth2.js +3 -0
- package/dist/auth/drivers/openid.js +3 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +27 -5
- package/dist/cli/commands/schema/apply.d.ts +1 -0
- package/dist/cli/commands/schema/apply.js +9 -5
- package/dist/cli/index.js +1 -0
- package/dist/cli/utils/create-env/env-stub.liquid +1 -0
- package/dist/controllers/assets.js +9 -1
- package/dist/controllers/utils.js +18 -1
- package/dist/database/index.js +0 -3
- package/dist/database/migrations/20220303A-remove-default-project-color.d.ts +3 -0
- package/dist/database/migrations/20220303A-remove-default-project-color.js +22 -0
- package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
- package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
- package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
- package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +73 -0
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast.d.ts +1 -1
- package/dist/database/run-ast.js +48 -35
- package/dist/database/system-data/fields/collections.yaml +4 -4
- package/dist/database/system-data/fields/fields.yaml +8 -8
- package/dist/database/system-data/fields/files.yaml +2 -2
- package/dist/database/system-data/fields/panels.yaml +2 -2
- package/dist/database/system-data/fields/permissions.yaml +4 -4
- package/dist/database/system-data/fields/presets.yaml +3 -3
- package/dist/database/system-data/fields/relations.yaml +1 -1
- package/dist/database/system-data/fields/revisions.yaml +2 -2
- package/dist/database/system-data/fields/roles.yaml +4 -4
- package/dist/database/system-data/fields/settings.yaml +25 -6
- package/dist/database/system-data/fields/users.yaml +2 -2
- package/dist/database/system-data/fields/webhooks.yaml +4 -4
- package/dist/env.js +9 -2
- package/dist/exceptions/database/dialects/mysql.js +23 -17
- package/dist/extensions.js +0 -2
- package/dist/middleware/authenticate.d.ts +5 -3
- package/dist/middleware/authenticate.js +22 -39
- package/dist/middleware/respond.js +7 -28
- package/dist/server.js +5 -2
- package/dist/services/authorization.js +60 -1
- package/dist/services/collections.js +6 -6
- package/dist/services/fields.js +3 -3
- package/dist/services/files.js +69 -3
- package/dist/services/graphql.js +2 -2
- package/dist/services/import-export.d.ts +34 -0
- package/dist/services/import-export.js +270 -0
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.js +2 -1
- package/dist/services/items.js +1 -0
- package/dist/services/mail/templates/base.liquid +2 -2
- package/dist/services/payload.js +3 -3
- package/dist/services/permissions.js +10 -10
- package/dist/services/relations.js +7 -4
- package/dist/services/utils.js +10 -0
- package/dist/utils/apply-query.js +2 -24
- package/dist/utils/get-date-formatted.d.ts +1 -0
- package/dist/utils/get-date-formatted.js +14 -0
- package/dist/utils/get-local-type.js +2 -2
- package/dist/utils/get-permissions.js +1 -1
- package/dist/utils/get-schema.js +1 -1
- package/dist/utils/is-directus-jwt.js +4 -21
- package/dist/utils/jwt.d.ts +2 -0
- package/dist/utils/jwt.js +49 -0
- package/dist/utils/merge-permissions.js +18 -6
- package/example.env +1 -0
- package/package.json +17 -18
- package/dist/__mocks__/cache.d.ts +0 -6
- package/dist/__mocks__/cache.js +0 -8
- package/dist/services/import.d.ts +0 -13
- package/dist/services/import.js +0 -118
package/dist/app.js
CHANGED
|
@@ -111,9 +111,11 @@ async function createApp() {
|
|
|
111
111
|
// friendly. Ref #10806
|
|
112
112
|
upgradeInsecureRequests: null,
|
|
113
113
|
// These are required for MapLibre
|
|
114
|
+
// https://cdn.directus.io is required for images/videos in the official docs
|
|
114
115
|
workerSrc: ["'self'", 'blob:'],
|
|
115
116
|
childSrc: ["'self'", 'blob:'],
|
|
116
|
-
imgSrc: ["'self'", 'data:', 'blob:'],
|
|
117
|
+
imgSrc: ["'self'", 'data:', 'blob:', 'https://cdn.directus.io'],
|
|
118
|
+
mediaSrc: ["'self'", 'https://cdn.directus.io'],
|
|
117
119
|
connectSrc: ["'self'", 'https://*'],
|
|
118
120
|
},
|
|
119
121
|
}, (0, get_config_from_env_1.getConfigFromEnv)('CONTENT_SECURITY_POLICY_'))));
|
|
@@ -9,7 +9,6 @@ export declare class LDAPAuthDriver extends AuthDriver {
|
|
|
9
9
|
config: Record<string, any>;
|
|
10
10
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
11
11
|
private validateBindClient;
|
|
12
|
-
private fetchUserDn;
|
|
13
12
|
private fetchUserInfo;
|
|
14
13
|
private fetchUserGroups;
|
|
15
14
|
private fetchUserId;
|
|
@@ -44,7 +44,11 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
44
44
|
var _a;
|
|
45
45
|
super(options, config);
|
|
46
46
|
const { bindDn, bindPassword, userDn, provider, clientUrl } = config;
|
|
47
|
-
if (
|
|
47
|
+
if (bindDn === undefined ||
|
|
48
|
+
bindPassword === undefined ||
|
|
49
|
+
!userDn ||
|
|
50
|
+
!provider ||
|
|
51
|
+
(!clientUrl && !((_a = config.client) === null || _a === void 0 ? void 0 : _a.socketPath))) {
|
|
48
52
|
throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider });
|
|
49
53
|
}
|
|
50
54
|
const clientConfig = typeof config.client === 'object' ? config.client : {};
|
|
@@ -90,55 +94,38 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
90
94
|
});
|
|
91
95
|
res.on('end', (result) => {
|
|
92
96
|
if ((result === null || result === void 0 ? void 0 : result.status) === 0) {
|
|
93
|
-
// Handle edge case
|
|
97
|
+
// Handle edge case where authenticated bind user could not fetch their own DN
|
|
94
98
|
reject(new exceptions_1.UnexpectedResponseException('Failed to find bind user record'));
|
|
95
99
|
}
|
|
96
100
|
});
|
|
97
101
|
});
|
|
98
102
|
});
|
|
99
103
|
}
|
|
100
|
-
async
|
|
101
|
-
|
|
104
|
+
async fetchUserInfo(baseDn, filter, scope) {
|
|
105
|
+
let { firstNameAttribute, lastNameAttribute, mailAttribute } = this.config;
|
|
106
|
+
firstNameAttribute !== null && firstNameAttribute !== void 0 ? firstNameAttribute : (firstNameAttribute = 'givenName');
|
|
107
|
+
lastNameAttribute !== null && lastNameAttribute !== void 0 ? lastNameAttribute : (lastNameAttribute = 'sn');
|
|
108
|
+
mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : (mailAttribute = 'mail');
|
|
102
109
|
return new Promise((resolve, reject) => {
|
|
103
|
-
// Search for the user in LDAP by
|
|
104
|
-
this.bindClient.search(
|
|
105
|
-
filter
|
|
106
|
-
scope
|
|
110
|
+
// Search for the user in LDAP by filter
|
|
111
|
+
this.bindClient.search(baseDn, {
|
|
112
|
+
filter,
|
|
113
|
+
scope,
|
|
114
|
+
attributes: ['uid', firstNameAttribute, lastNameAttribute, mailAttribute, 'userAccountControl'],
|
|
107
115
|
}, (err, res) => {
|
|
108
116
|
if (err) {
|
|
109
117
|
reject(handleError(err));
|
|
110
118
|
return;
|
|
111
119
|
}
|
|
112
120
|
res.on('searchEntry', ({ object }) => {
|
|
113
|
-
|
|
114
|
-
});
|
|
115
|
-
res.on('error', (err) => {
|
|
116
|
-
reject(handleError(err));
|
|
117
|
-
});
|
|
118
|
-
res.on('end', () => {
|
|
119
|
-
resolve(undefined);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
async fetchUserInfo(userDn) {
|
|
125
|
-
const { mailAttribute } = this.config;
|
|
126
|
-
return new Promise((resolve, reject) => {
|
|
127
|
-
// Fetch user info in LDAP by domain component
|
|
128
|
-
this.bindClient.search(userDn, { attributes: ['givenName', 'sn', mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : 'mail', 'userAccountControl'] }, (err, res) => {
|
|
129
|
-
if (err) {
|
|
130
|
-
reject(handleError(err));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
res.on('searchEntry', ({ object }) => {
|
|
134
|
-
const email = object[mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : 'mail'];
|
|
121
|
+
var _a;
|
|
135
122
|
const user = {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
123
|
+
dn: object.dn,
|
|
124
|
+
uid: getEntryValue(object.uid),
|
|
125
|
+
firstName: getEntryValue(object[firstNameAttribute]),
|
|
126
|
+
lastName: getEntryValue(object[lastNameAttribute]),
|
|
127
|
+
email: getEntryValue(object[mailAttribute]),
|
|
128
|
+
userAccountControl: Number((_a = getEntryValue(object.userAccountControl)) !== null && _a !== void 0 ? _a : 0),
|
|
142
129
|
};
|
|
143
130
|
resolve(user);
|
|
144
131
|
});
|
|
@@ -151,18 +138,14 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
151
138
|
});
|
|
152
139
|
});
|
|
153
140
|
}
|
|
154
|
-
async fetchUserGroups(
|
|
155
|
-
const { groupDn, groupAttribute, groupScope } = this.config;
|
|
156
|
-
if (!groupDn) {
|
|
157
|
-
return Promise.resolve([]);
|
|
158
|
-
}
|
|
141
|
+
async fetchUserGroups(baseDn, filter, scope) {
|
|
159
142
|
return new Promise((resolve, reject) => {
|
|
160
143
|
let userGroups = [];
|
|
161
144
|
// Search for the user info in LDAP by group attribute
|
|
162
|
-
this.bindClient.search(
|
|
145
|
+
this.bindClient.search(baseDn, {
|
|
146
|
+
filter,
|
|
147
|
+
scope,
|
|
163
148
|
attributes: ['cn'],
|
|
164
|
-
filter: new ldapjs_1.EqualityFilter({ attribute: groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member', value: userDn }),
|
|
165
|
-
scope: groupScope !== null && groupScope !== void 0 ? groupScope : 'one',
|
|
166
149
|
}, (err, res) => {
|
|
167
150
|
if (err) {
|
|
168
151
|
reject(handleError(err));
|
|
@@ -199,28 +182,36 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
199
182
|
throw new exceptions_1.InvalidCredentialsException();
|
|
200
183
|
}
|
|
201
184
|
await this.validateBindClient();
|
|
202
|
-
const userDn =
|
|
203
|
-
|
|
185
|
+
const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute } = this.config;
|
|
186
|
+
const userInfo = await this.fetchUserInfo(userDn, new ldapjs_1.EqualityFilter({
|
|
187
|
+
attribute: userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn',
|
|
188
|
+
value: payload.identifier,
|
|
189
|
+
}), userScope !== null && userScope !== void 0 ? userScope : 'one');
|
|
190
|
+
if (!(userInfo === null || userInfo === void 0 ? void 0 : userInfo.dn)) {
|
|
204
191
|
throw new exceptions_1.InvalidCredentialsException();
|
|
205
192
|
}
|
|
206
|
-
const userId = await this.fetchUserId(userDn);
|
|
207
|
-
const userGroups = await this.fetchUserGroups(userDn);
|
|
208
193
|
let userRole;
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
194
|
+
if (groupDn) {
|
|
195
|
+
const userGroups = await this.fetchUserGroups(groupDn, new ldapjs_1.EqualityFilter({
|
|
196
|
+
attribute: groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member',
|
|
197
|
+
value: (groupAttribute === null || groupAttribute === void 0 ? void 0 : groupAttribute.toLowerCase()) === 'memberuid' && userInfo.uid ? userInfo.uid : userInfo.dn,
|
|
198
|
+
}), groupScope !== null && groupScope !== void 0 ? groupScope : 'one');
|
|
199
|
+
if (userGroups.length) {
|
|
200
|
+
userRole = await this.knex
|
|
201
|
+
.select('id')
|
|
202
|
+
.from('directus_roles')
|
|
203
|
+
.whereRaw(`LOWER(??) IN (${userGroups.map(() => '?')})`, [
|
|
204
|
+
'name',
|
|
205
|
+
...userGroups.map((group) => group.toLowerCase()),
|
|
206
|
+
])
|
|
207
|
+
.first();
|
|
208
|
+
}
|
|
218
209
|
}
|
|
210
|
+
const userId = await this.fetchUserId(userInfo.dn);
|
|
219
211
|
if (userId) {
|
|
220
212
|
await this.usersService.updateOne(userId, { role: (_a = userRole === null || userRole === void 0 ? void 0 : userRole.id) !== null && _a !== void 0 ? _a : null });
|
|
221
213
|
return userId;
|
|
222
214
|
}
|
|
223
|
-
const userInfo = await this.fetchUserInfo(userDn);
|
|
224
215
|
if (!userInfo) {
|
|
225
216
|
throw new exceptions_1.InvalidCredentialsException();
|
|
226
217
|
}
|
|
@@ -229,10 +220,10 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
229
220
|
first_name: userInfo.firstName,
|
|
230
221
|
last_name: userInfo.lastName,
|
|
231
222
|
email: userInfo.email,
|
|
232
|
-
external_identifier:
|
|
223
|
+
external_identifier: userInfo.dn,
|
|
233
224
|
role: userRole === null || userRole === void 0 ? void 0 : userRole.id,
|
|
234
225
|
});
|
|
235
|
-
return (await this.fetchUserId(
|
|
226
|
+
return (await this.fetchUserId(userInfo.dn));
|
|
236
227
|
}
|
|
237
228
|
async verify(user, password) {
|
|
238
229
|
if (!user.external_identifier || !password) {
|
|
@@ -282,6 +273,9 @@ const handleError = (e) => {
|
|
|
282
273
|
message: e.message,
|
|
283
274
|
});
|
|
284
275
|
};
|
|
276
|
+
const getEntryValue = (value) => {
|
|
277
|
+
return typeof value === 'object' ? value[0] : value;
|
|
278
|
+
};
|
|
285
279
|
function createLDAPAuthRouter(provider) {
|
|
286
280
|
const router = (0, express_1.Router)();
|
|
287
281
|
const loginSchema = joi_1.default.object({
|
|
@@ -18,6 +18,7 @@ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
|
|
|
18
18
|
const url_1 = require("../../utils/url");
|
|
19
19
|
const logger_1 = __importDefault(require("../../logger"));
|
|
20
20
|
const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
|
|
21
|
+
const get_config_from_env_1 = require("../../utils/get-config-from-env");
|
|
21
22
|
class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
22
23
|
constructor(options, config) {
|
|
23
24
|
super(options, config);
|
|
@@ -35,11 +36,13 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
35
36
|
userinfo_endpoint: profileUrl,
|
|
36
37
|
issuer: additionalConfig.provider,
|
|
37
38
|
});
|
|
39
|
+
const clientOptionsOverrides = (0, get_config_from_env_1.getConfigFromEnv)(`AUTH_${config.provider.toUpperCase()}_CLIENT_`, [`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`], 'underscore');
|
|
38
40
|
this.client = new issuer.Client({
|
|
39
41
|
client_id: clientId,
|
|
40
42
|
client_secret: clientSecret,
|
|
41
43
|
redirect_uris: [this.redirectUrl],
|
|
42
44
|
response_types: ['code'],
|
|
45
|
+
...clientOptionsOverrides,
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
48
|
generateCodeVerifier() {
|
|
@@ -18,6 +18,7 @@ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
|
|
|
18
18
|
const url_1 = require("../../utils/url");
|
|
19
19
|
const logger_1 = __importDefault(require("../../logger"));
|
|
20
20
|
const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
|
|
21
|
+
const get_config_from_env_1 = require("../../utils/get-config-from-env");
|
|
21
22
|
class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
22
23
|
constructor(options, config) {
|
|
23
24
|
super(options, config);
|
|
@@ -26,6 +27,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
26
27
|
throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
|
|
27
28
|
}
|
|
28
29
|
const redirectUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
|
|
30
|
+
const clientOptionsOverrides = (0, get_config_from_env_1.getConfigFromEnv)(`AUTH_${config.provider.toUpperCase()}_CLIENT_`, [`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`], 'underscore');
|
|
29
31
|
this.redirectUrl = redirectUrl.toString();
|
|
30
32
|
this.usersService = new services_1.UsersService({ knex: this.knex, schema: this.schema });
|
|
31
33
|
this.config = additionalConfig;
|
|
@@ -43,6 +45,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
43
45
|
client_secret: clientSecret,
|
|
44
46
|
redirect_uris: [this.redirectUrl],
|
|
45
47
|
response_types: ['code'],
|
|
48
|
+
...clientOptionsOverrides,
|
|
46
49
|
}));
|
|
47
50
|
})
|
|
48
51
|
.catch(reject);
|
package/dist/cache.d.ts
CHANGED
|
@@ -2,5 +2,8 @@ import Keyv from 'keyv';
|
|
|
2
2
|
export declare function getCache(): {
|
|
3
3
|
cache: Keyv | null;
|
|
4
4
|
systemCache: Keyv;
|
|
5
|
+
lockCache: Keyv;
|
|
5
6
|
};
|
|
6
|
-
export declare function flushCaches(): Promise<void>;
|
|
7
|
+
export declare function flushCaches(forced?: boolean): Promise<void>;
|
|
8
|
+
export declare function clearSystemCache(forced?: boolean): Promise<void>;
|
|
9
|
+
export declare function setSystemCache(key: string, value: any, ttl?: number): Promise<void>;
|
package/dist/cache.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.flushCaches = exports.getCache = void 0;
|
|
6
|
+
exports.setSystemCache = exports.clearSystemCache = exports.flushCaches = exports.getCache = void 0;
|
|
7
7
|
const keyv_1 = __importDefault(require("keyv"));
|
|
8
8
|
const ms_1 = __importDefault(require("ms"));
|
|
9
9
|
const env_1 = __importDefault(require("./env"));
|
|
@@ -12,6 +12,7 @@ const get_config_from_env_1 = require("./utils/get-config-from-env");
|
|
|
12
12
|
const validate_env_1 = require("./utils/validate-env");
|
|
13
13
|
let cache = null;
|
|
14
14
|
let systemCache = null;
|
|
15
|
+
let lockCache = null;
|
|
15
16
|
function getCache() {
|
|
16
17
|
if (env_1.default.CACHE_ENABLED === true && cache === null) {
|
|
17
18
|
(0, validate_env_1.validateEnv)(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']);
|
|
@@ -22,15 +23,36 @@ function getCache() {
|
|
|
22
23
|
systemCache = getKeyvInstance(undefined, '_system');
|
|
23
24
|
systemCache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
if (lockCache === null) {
|
|
27
|
+
lockCache = getKeyvInstance(undefined, '_lock');
|
|
28
|
+
lockCache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
|
|
29
|
+
}
|
|
30
|
+
return { cache, systemCache, lockCache };
|
|
26
31
|
}
|
|
27
32
|
exports.getCache = getCache;
|
|
28
|
-
async function flushCaches() {
|
|
29
|
-
const {
|
|
30
|
-
await (
|
|
33
|
+
async function flushCaches(forced) {
|
|
34
|
+
const { cache } = getCache();
|
|
35
|
+
await clearSystemCache(forced);
|
|
31
36
|
await (cache === null || cache === void 0 ? void 0 : cache.clear());
|
|
32
37
|
}
|
|
33
38
|
exports.flushCaches = flushCaches;
|
|
39
|
+
async function clearSystemCache(forced) {
|
|
40
|
+
const { systemCache, lockCache } = getCache();
|
|
41
|
+
// Flush system cache when forced or when system cache lock not set
|
|
42
|
+
if (forced || !(await lockCache.get('system-cache-lock'))) {
|
|
43
|
+
await lockCache.set('system-cache-lock', true, 10000);
|
|
44
|
+
await systemCache.clear();
|
|
45
|
+
await lockCache.delete('system-cache-lock');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.clearSystemCache = clearSystemCache;
|
|
49
|
+
async function setSystemCache(key, value, ttl) {
|
|
50
|
+
const { systemCache, lockCache } = getCache();
|
|
51
|
+
if (!(await lockCache.get('system-cache-lock'))) {
|
|
52
|
+
await systemCache.set(key, value, ttl);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.setSystemCache = setSystemCache;
|
|
34
56
|
function getKeyvInstance(ttl, namespaceSuffix) {
|
|
35
57
|
switch (env_1.default.CACHE_STORE) {
|
|
36
58
|
case 'redis':
|
|
@@ -63,7 +63,9 @@ async function apply(snapshotPath, options) {
|
|
|
63
63
|
database.destroy();
|
|
64
64
|
process.exit(0);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
const dryRun = (options === null || options === void 0 ? void 0 : options.dryRun) === true;
|
|
67
|
+
const promptForChanges = !dryRun && (options === null || options === void 0 ? void 0 : options.yes) !== true;
|
|
68
|
+
if (dryRun || promptForChanges) {
|
|
67
69
|
let message = '';
|
|
68
70
|
if (snapshotDiff.collections.length > 0) {
|
|
69
71
|
message += chalk_1.default.black.underline.bold('Collections:');
|
|
@@ -141,14 +143,16 @@ async function apply(snapshotPath, options) {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
}
|
|
146
|
+
message += 'The following changes will be applied:\n\n' + chalk_1.default.black(message);
|
|
147
|
+
if (dryRun) {
|
|
148
|
+
logger_1.default.info(message);
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
144
151
|
const { proceed } = await inquirer_1.default.prompt([
|
|
145
152
|
{
|
|
146
153
|
type: 'confirm',
|
|
147
154
|
name: 'proceed',
|
|
148
|
-
message:
|
|
149
|
-
chalk_1.default.black(message) +
|
|
150
|
-
'\n\n' +
|
|
151
|
-
'Would you like to continue?',
|
|
155
|
+
message: message + '\n\n' + 'Would you like to continue?',
|
|
152
156
|
},
|
|
153
157
|
]);
|
|
154
158
|
if (proceed === false) {
|
package/dist/cli/index.js
CHANGED
|
@@ -81,6 +81,7 @@ async function createCli() {
|
|
|
81
81
|
.command('apply')
|
|
82
82
|
.description('Apply a snapshot file to the current database')
|
|
83
83
|
.option('-y, --yes', `Assume "yes" as answer to all prompts and run non-interactively`)
|
|
84
|
+
.option('-d, --dry-run', 'Plan and log changes to be applied', false)
|
|
84
85
|
.argument('<path>', 'Path to snapshot file')
|
|
85
86
|
.action(apply_1.apply);
|
|
86
87
|
await emitter_1.default.emitInit('cli.after', { program });
|
|
@@ -14,6 +14,9 @@ const use_collection_1 = __importDefault(require("../middleware/use-collection")
|
|
|
14
14
|
const services_1 = require("../services");
|
|
15
15
|
const assets_1 = require("../types/assets");
|
|
16
16
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
17
|
+
const helmet_1 = __importDefault(require("helmet"));
|
|
18
|
+
const lodash_2 = require("lodash");
|
|
19
|
+
const get_config_from_env_1 = require("../utils/get-config-from-env");
|
|
17
20
|
const router = (0, express_1.Router)();
|
|
18
21
|
router.use((0, use_collection_1.default)('directus_files'));
|
|
19
22
|
router.get('/:pk',
|
|
@@ -88,7 +91,12 @@ router.get('/:pk',
|
|
|
88
91
|
return next();
|
|
89
92
|
throw new exceptions_1.InvalidQueryException(`Dynamic asset generation has been disabled for this project.`);
|
|
90
93
|
}
|
|
91
|
-
}),
|
|
94
|
+
}), helmet_1.default.contentSecurityPolicy((0, lodash_2.merge)({
|
|
95
|
+
useDefaults: false,
|
|
96
|
+
directives: {
|
|
97
|
+
defaultSrc: ['none'],
|
|
98
|
+
},
|
|
99
|
+
}, (0, get_config_from_env_1.getConfigFromEnv)('ASSETS_CONTENT_SECURITY_POLICY'))),
|
|
92
100
|
// Return file
|
|
93
101
|
(0, async_handler_1.default)(async (req, res) => {
|
|
94
102
|
var _a, _b;
|
|
@@ -95,12 +95,29 @@ router.post('/import/:collection', collection_exists_1.default, (0, async_handle
|
|
|
95
95
|
busboy.on('error', (err) => next(err));
|
|
96
96
|
req.pipe(busboy);
|
|
97
97
|
}));
|
|
98
|
+
router.post('/export/:collection', collection_exists_1.default, (0, async_handler_1.default)(async (req, res, next) => {
|
|
99
|
+
if (!req.body.query) {
|
|
100
|
+
throw new exceptions_1.InvalidPayloadException(`"query" is required.`);
|
|
101
|
+
}
|
|
102
|
+
if (!req.body.format) {
|
|
103
|
+
throw new exceptions_1.InvalidPayloadException(`"format" is required.`);
|
|
104
|
+
}
|
|
105
|
+
const service = new services_1.ExportService({
|
|
106
|
+
accountability: req.accountability,
|
|
107
|
+
schema: req.schema,
|
|
108
|
+
});
|
|
109
|
+
// We're not awaiting this, as it's supposed to run async in the background
|
|
110
|
+
service.exportToFile(req.params.collection, req.body.query, req.body.format, {
|
|
111
|
+
file: req.body.file,
|
|
112
|
+
});
|
|
113
|
+
return next();
|
|
114
|
+
}), respond_1.respond);
|
|
98
115
|
router.post('/cache/clear', (0, async_handler_1.default)(async (req, res) => {
|
|
99
116
|
var _a;
|
|
100
117
|
if (((_a = req.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true) {
|
|
101
118
|
throw new exceptions_1.ForbiddenException();
|
|
102
119
|
}
|
|
103
|
-
await (0, cache_1.flushCaches)();
|
|
120
|
+
await (0, cache_1.flushCaches)(true);
|
|
104
121
|
res.status(200).end();
|
|
105
122
|
}));
|
|
106
123
|
exports.default = router;
|
package/dist/database/index.js
CHANGED
|
@@ -235,9 +235,6 @@ exports.validateDatabaseExtensions = validateDatabaseExtensions;
|
|
|
235
235
|
async function validateDatabaseCharset(database) {
|
|
236
236
|
database = database !== null && database !== void 0 ? database : getDatabase();
|
|
237
237
|
if (getDatabaseClient(database) === 'mysql') {
|
|
238
|
-
if (env_1.default.DB_CHARSET) {
|
|
239
|
-
logger_1.default.warn(`Using custom DB_CHARSET "${env_1.default.DB_CHARSET}". Using a charset different from the database's default can cause problems in relationships. Omitting DB_CHARSET is strongly recommended.`);
|
|
240
|
-
}
|
|
241
238
|
const { collation } = await database.select(database.raw(`@@collation_database as collation`)).first();
|
|
242
239
|
const tables = await database('information_schema.tables')
|
|
243
240
|
.select({ name: 'TABLE_NAME', collation: 'TABLE_COLLATION' })
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.down = exports.up = void 0;
|
|
4
|
+
const helpers_1 = require("../helpers");
|
|
5
|
+
async function up(knex) {
|
|
6
|
+
const helper = (0, helpers_1.getHelpers)(knex).schema;
|
|
7
|
+
await helper.changeToString('directus_settings', 'project_color', {
|
|
8
|
+
nullable: true,
|
|
9
|
+
default: null,
|
|
10
|
+
length: 50,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
exports.up = up;
|
|
14
|
+
async function down(knex) {
|
|
15
|
+
const helper = (0, helpers_1.getHelpers)(knex).schema;
|
|
16
|
+
await helper.changeToString('directus_settings', 'project_color', {
|
|
17
|
+
nullable: true,
|
|
18
|
+
default: '#00C897',
|
|
19
|
+
length: 10,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
exports.down = down;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.down = exports.up = void 0;
|
|
4
|
+
async function up(knex) {
|
|
5
|
+
await knex.schema.alterTable('directus_settings', (table) => {
|
|
6
|
+
table.json('translation_strings');
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
exports.up = up;
|
|
10
|
+
async function down(knex) {
|
|
11
|
+
await knex.schema.alterTable('directus_settings', (table) => {
|
|
12
|
+
table.dropColumn('translation_strings');
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
exports.down = down;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.down = exports.up = void 0;
|
|
4
|
+
const utils_1 = require("@directus/shared/utils");
|
|
5
|
+
const lodash_1 = require("lodash");
|
|
6
|
+
async function up(knex) {
|
|
7
|
+
const fields = await knex
|
|
8
|
+
.select('id', 'special')
|
|
9
|
+
.from('directus_fields')
|
|
10
|
+
.whereNotNull('special')
|
|
11
|
+
.orWhere('special', '<>', '');
|
|
12
|
+
for (const { id, special } of fields) {
|
|
13
|
+
let parsedSpecial;
|
|
14
|
+
try {
|
|
15
|
+
parsedSpecial = (0, utils_1.toArray)(special);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (parsedSpecial && (0, lodash_1.isArray)(parsedSpecial)) {
|
|
21
|
+
let updateRequired = false;
|
|
22
|
+
parsedSpecial = parsedSpecial.map((special) => {
|
|
23
|
+
switch (special) {
|
|
24
|
+
case 'boolean':
|
|
25
|
+
case 'csv':
|
|
26
|
+
case 'json':
|
|
27
|
+
updateRequired = true;
|
|
28
|
+
return 'cast-' + special;
|
|
29
|
+
default:
|
|
30
|
+
return special;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
if (updateRequired) {
|
|
34
|
+
await knex('directus_fields').update({ special: parsedSpecial }).where({ id });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.up = up;
|
|
40
|
+
async function down(knex) {
|
|
41
|
+
const fields = await knex
|
|
42
|
+
.select('id', 'special')
|
|
43
|
+
.from('directus_fields')
|
|
44
|
+
.whereNotNull('special')
|
|
45
|
+
.orWhere('special', '<>', '');
|
|
46
|
+
for (const { id, special } of fields) {
|
|
47
|
+
let parsedSpecial;
|
|
48
|
+
try {
|
|
49
|
+
parsedSpecial = (0, utils_1.toArray)(special);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (parsedSpecial && (0, lodash_1.isArray)(parsedSpecial)) {
|
|
55
|
+
let updateRequired = false;
|
|
56
|
+
parsedSpecial = parsedSpecial.map((special) => {
|
|
57
|
+
switch (special) {
|
|
58
|
+
case 'cast-boolean':
|
|
59
|
+
case 'cast-csv':
|
|
60
|
+
case 'cast-json':
|
|
61
|
+
updateRequired = true;
|
|
62
|
+
return special.replace('cast-', '');
|
|
63
|
+
default:
|
|
64
|
+
return special;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (updateRequired) {
|
|
68
|
+
await knex('directus_fields').update({ special: parsedSpecial }).where({ id });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.down = down;
|
|
@@ -63,7 +63,7 @@ async function run(database, direction, log = true) {
|
|
|
63
63
|
await database.insert({ version: nextVersion.version, name: nextVersion.name }).into('directus_migrations');
|
|
64
64
|
}
|
|
65
65
|
async function down() {
|
|
66
|
-
const lastAppliedMigration = (0, lodash_1.orderBy)(completedMigrations, ['timestamp'], ['desc'])[0];
|
|
66
|
+
const lastAppliedMigration = (0, lodash_1.orderBy)(completedMigrations, ['timestamp', 'version'], ['desc', 'desc'])[0];
|
|
67
67
|
if (!lastAppliedMigration) {
|
|
68
68
|
throw Error('Nothing to downgrade');
|
|
69
69
|
}
|