directus 9.23.3 → 9.23.4
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 +12 -12
- package/dist/auth/drivers/ldap.js +22 -22
- package/dist/auth/drivers/local.js +7 -7
- package/dist/auth/drivers/oauth2.js +27 -25
- package/dist/auth/drivers/openid.js +32 -30
- package/dist/auth/drivers/saml.js +10 -10
- package/dist/auth.js +4 -3
- package/dist/cache.js +16 -11
- package/dist/cli/commands/bootstrap/index.js +5 -4
- package/dist/cli/utils/create-db-connection.js +1 -1
- package/dist/cli/utils/create-env/index.js +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +6 -5
- package/dist/controllers/activity.js +9 -9
- package/dist/controllers/assets.js +19 -18
- package/dist/controllers/auth.js +13 -13
- package/dist/controllers/collections.js +10 -10
- package/dist/controllers/dashboards.js +9 -9
- package/dist/controllers/extensions.js +3 -3
- package/dist/controllers/fields.js +16 -16
- package/dist/controllers/files.js +16 -15
- package/dist/controllers/flows.js +11 -11
- package/dist/controllers/folders.js +9 -9
- package/dist/controllers/graphql.js +6 -6
- package/dist/controllers/items.js +17 -17
- package/dist/controllers/notifications.js +9 -9
- package/dist/controllers/operations.js +9 -9
- package/dist/controllers/panels.js +9 -9
- package/dist/controllers/permissions.js +9 -9
- package/dist/controllers/presets.js +9 -9
- package/dist/controllers/relations.js +10 -10
- package/dist/controllers/revisions.js +3 -3
- package/dist/controllers/roles.js +9 -9
- package/dist/controllers/schema.js +5 -5
- package/dist/controllers/server.js +7 -7
- package/dist/controllers/settings.js +2 -2
- package/dist/controllers/shares.js +13 -13
- package/dist/controllers/users.js +16 -16
- package/dist/controllers/utils.js +5 -5
- package/dist/controllers/webhooks.js +9 -9
- package/dist/database/helpers/fn/types.d.ts +0 -1
- package/dist/database/helpers/fn/types.js +0 -2
- package/dist/database/helpers/index.d.ts +3 -3
- package/dist/database/index.js +5 -5
- package/dist/database/migrations/20210805B-change-image-metadata-structure.js +15 -15
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast.js +4 -4
- package/dist/database/system-data/collections/index.js +2 -2
- package/dist/database/system-data/fields/index.js +3 -3
- package/dist/env.js +1 -1
- package/dist/extensions.js +10 -10
- package/dist/flows.js +33 -31
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +32 -32
- package/dist/mailer.js +16 -16
- package/dist/messenger.js +4 -4
- package/dist/middleware/authenticate.js +1 -1
- package/dist/middleware/cache.js +11 -11
- package/dist/middleware/collection-exists.js +3 -3
- package/dist/middleware/cors.js +7 -7
- package/dist/middleware/error-handler.js +2 -2
- package/dist/middleware/extract-token.js +2 -2
- package/dist/middleware/graphql.js +12 -6
- package/dist/middleware/rate-limiter-global.js +5 -5
- package/dist/middleware/rate-limiter-ip.js +2 -2
- package/dist/middleware/respond.js +16 -16
- package/dist/middleware/sanitize-query.js +1 -1
- package/dist/operations/exec/index.js +2 -2
- package/dist/rate-limiter.js +1 -1
- package/dist/request/validate-ip.js +2 -2
- package/dist/server.js +4 -4
- package/dist/services/activity.js +14 -14
- package/dist/services/assets.js +6 -6
- package/dist/services/authentication.js +9 -9
- package/dist/services/collections.js +9 -9
- package/dist/services/fields.js +5 -5
- package/dist/services/files.js +12 -12
- package/dist/services/graphql/index.js +100 -98
- package/dist/services/import-export.js +6 -6
- package/dist/services/items.js +6 -6
- package/dist/services/mail/index.js +5 -5
- package/dist/services/meta.js +1 -0
- package/dist/services/notifications.js +4 -4
- package/dist/services/revisions.js +3 -3
- package/dist/services/roles.js +5 -5
- package/dist/services/server.js +27 -27
- package/dist/services/shares.js +9 -9
- package/dist/services/specifications.js +5 -3
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +24 -27
- package/dist/storage/register-locations.js +1 -1
- package/dist/utils/apply-query.js +2 -1
- package/dist/utils/dynamic-import.js +1 -1
- package/dist/utils/generate-hash.js +1 -1
- package/dist/utils/get-ast-from-query.js +1 -1
- package/dist/utils/get-auth-providers.js +1 -1
- package/dist/utils/get-cache-headers.js +3 -3
- package/dist/utils/get-collection-from-alias.js +1 -0
- package/dist/utils/get-default-value.js +1 -1
- package/dist/utils/get-ip-from-req.js +2 -2
- package/dist/utils/get-permissions.js +11 -11
- package/dist/utils/get-schema.js +2 -2
- package/dist/utils/is-url-allowed.js +5 -2
- package/dist/utils/sanitize-query.js +26 -26
- package/dist/utils/should-skip-cache.js +2 -2
- package/dist/utils/track.js +16 -16
- package/dist/utils/validate-query.js +1 -1
- package/dist/utils/validate-storage.js +8 -8
- package/dist/webhooks.js +2 -2
- package/package.json +13 -13
- package/dist/utils/redact-header-cookies.d.ts +0 -1
- package/dist/utils/redact-header-cookies.js +0 -11
- /package/dist/{utils/redact-header-cookies.test.d.ts → logger.test.d.ts} +0 -0
package/dist/services/items.js
CHANGED
|
@@ -205,7 +205,7 @@ class ItemsService {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
if (this.cache && env_1.default
|
|
208
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
209
209
|
await this.cache.clear();
|
|
210
210
|
}
|
|
211
211
|
return primaryKey;
|
|
@@ -242,7 +242,7 @@ class ItemsService {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
if (this.cache && env_1.default
|
|
245
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
246
246
|
await this.cache.clear();
|
|
247
247
|
}
|
|
248
248
|
return primaryKeys;
|
|
@@ -381,7 +381,7 @@ class ItemsService {
|
|
|
381
381
|
});
|
|
382
382
|
}
|
|
383
383
|
finally {
|
|
384
|
-
if (this.cache && env_1.default
|
|
384
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
385
385
|
await this.cache.clear();
|
|
386
386
|
}
|
|
387
387
|
}
|
|
@@ -506,7 +506,7 @@ class ItemsService {
|
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
508
|
});
|
|
509
|
-
if (this.cache && env_1.default
|
|
509
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
510
510
|
await this.cache.clear();
|
|
511
511
|
}
|
|
512
512
|
if (opts?.emitEvents !== false) {
|
|
@@ -581,7 +581,7 @@ class ItemsService {
|
|
|
581
581
|
}
|
|
582
582
|
return primaryKeys;
|
|
583
583
|
});
|
|
584
|
-
if (this.cache && env_1.default
|
|
584
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
585
585
|
await this.cache.clear();
|
|
586
586
|
}
|
|
587
587
|
return primaryKeys;
|
|
@@ -648,7 +648,7 @@ class ItemsService {
|
|
|
648
648
|
})));
|
|
649
649
|
}
|
|
650
650
|
});
|
|
651
|
-
if (this.cache && env_1.default
|
|
651
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
652
652
|
await this.cache.clear();
|
|
653
653
|
}
|
|
654
654
|
if (opts?.emitEvents !== false) {
|
|
@@ -14,7 +14,7 @@ const logger_1 = __importDefault(require("../../logger"));
|
|
|
14
14
|
const mailer_1 = __importDefault(require("../../mailer"));
|
|
15
15
|
const url_1 = require("../../utils/url");
|
|
16
16
|
const liquidEngine = new liquidjs_1.Liquid({
|
|
17
|
-
root: [path_1.default.resolve(env_1.default
|
|
17
|
+
root: [path_1.default.resolve(env_1.default['EXTENSIONS_PATH'], 'templates'), path_1.default.resolve(__dirname, 'templates')],
|
|
18
18
|
extname: '.liquid',
|
|
19
19
|
});
|
|
20
20
|
class MailService {
|
|
@@ -27,7 +27,7 @@ class MailService {
|
|
|
27
27
|
this.accountability = opts.accountability || null;
|
|
28
28
|
this.knex = opts?.knex || (0, database_1.default)();
|
|
29
29
|
this.mailer = (0, mailer_1.default)();
|
|
30
|
-
if (env_1.default
|
|
30
|
+
if (env_1.default['EMAIL_VERIFY_SETUP']) {
|
|
31
31
|
this.mailer.verify((error) => {
|
|
32
32
|
if (error) {
|
|
33
33
|
logger_1.default.warn(`Email connection failed:`);
|
|
@@ -40,7 +40,7 @@ class MailService {
|
|
|
40
40
|
const { template, ...emailOptions } = options;
|
|
41
41
|
let { html } = options;
|
|
42
42
|
const defaultTemplateData = await this.getDefaultTemplateData();
|
|
43
|
-
const from = `${defaultTemplateData.projectName} <${options.from || env_1.default
|
|
43
|
+
const from = `${defaultTemplateData.projectName} <${options.from || env_1.default['EMAIL_FROM']}>`;
|
|
44
44
|
if (template) {
|
|
45
45
|
let templateData = template.data;
|
|
46
46
|
templateData = {
|
|
@@ -60,7 +60,7 @@ class MailService {
|
|
|
60
60
|
return info;
|
|
61
61
|
}
|
|
62
62
|
async renderTemplate(template, variables) {
|
|
63
|
-
const customTemplatePath = path_1.default.resolve(env_1.default
|
|
63
|
+
const customTemplatePath = path_1.default.resolve(env_1.default['EXTENSIONS_PATH'], 'templates', template + '.liquid');
|
|
64
64
|
const systemTemplatePath = path_1.default.join(__dirname, 'templates', template + '.liquid');
|
|
65
65
|
const templatePath = (await fs_extra_1.default.pathExists(customTemplatePath)) ? customTemplatePath : systemTemplatePath;
|
|
66
66
|
if ((await fs_extra_1.default.pathExists(templatePath)) === false) {
|
|
@@ -82,7 +82,7 @@ class MailService {
|
|
|
82
82
|
projectUrl: projectInfo?.project_url || '',
|
|
83
83
|
};
|
|
84
84
|
function getProjectLogoURL(logoID) {
|
|
85
|
-
const projectLogoUrl = new url_1.Url(env_1.default
|
|
85
|
+
const projectLogoUrl = new url_1.Url(env_1.default['PUBLIC_URL']);
|
|
86
86
|
if (logoID) {
|
|
87
87
|
projectLogoUrl.addPath('assets', logoID);
|
|
88
88
|
}
|
package/dist/services/meta.js
CHANGED
|
@@ -36,16 +36,16 @@ class NotificationsService extends items_1.ItemsService {
|
|
|
36
36
|
const user = await this.usersService.readOne(data.recipient, {
|
|
37
37
|
fields: ['id', 'email', 'email_notifications', 'role.app_access'],
|
|
38
38
|
});
|
|
39
|
-
const manageUserAccountUrl = new url_1.Url(env_1.default
|
|
39
|
+
const manageUserAccountUrl = new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'users', user['id']).toString();
|
|
40
40
|
const html = data.message ? (0, md_1.md)(data.message) : '';
|
|
41
|
-
if (user
|
|
41
|
+
if (user['email'] && user['email_notifications'] === true) {
|
|
42
42
|
try {
|
|
43
43
|
await this.mailService.send({
|
|
44
44
|
template: {
|
|
45
45
|
name: 'base',
|
|
46
|
-
data: user
|
|
46
|
+
data: user['role']?.app_access ? { url: manageUserAccountUrl, html } : { html },
|
|
47
47
|
},
|
|
48
|
-
to: user
|
|
48
|
+
to: user['email'],
|
|
49
49
|
subject: data.subject,
|
|
50
50
|
});
|
|
51
51
|
}
|
|
@@ -11,14 +11,14 @@ class RevisionsService extends index_1.ItemsService {
|
|
|
11
11
|
const revision = await super.readOne(pk);
|
|
12
12
|
if (!revision)
|
|
13
13
|
throw new exceptions_1.ForbiddenException();
|
|
14
|
-
if (!revision
|
|
14
|
+
if (!revision['data'])
|
|
15
15
|
throw new exceptions_1.InvalidPayloadException(`Revision doesn't contain data to revert to`);
|
|
16
|
-
const service = new index_1.ItemsService(revision
|
|
16
|
+
const service = new index_1.ItemsService(revision['collection'], {
|
|
17
17
|
accountability: this.accountability,
|
|
18
18
|
knex: this.knex,
|
|
19
19
|
schema: this.schema,
|
|
20
20
|
});
|
|
21
|
-
await service.updateOne(revision
|
|
21
|
+
await service.updateOne(revision['item'], revision['data']);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
exports.RevisionsService = RevisionsService;
|
package/dist/services/roles.js
CHANGED
|
@@ -29,10 +29,10 @@ class RolesService extends items_1.ItemsService {
|
|
|
29
29
|
// The users that will now be in this new non-admin role
|
|
30
30
|
let userKeys = [];
|
|
31
31
|
if (Array.isArray(users)) {
|
|
32
|
-
userKeys = users.map((user) => (typeof user === 'string' ? user : user
|
|
32
|
+
userKeys = users.map((user) => (typeof user === 'string' ? user : user['id'])).filter((id) => id);
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
userKeys = users.update.map((user) => user
|
|
35
|
+
userKeys = users.update.map((user) => user['id']).filter((id) => id);
|
|
36
36
|
}
|
|
37
37
|
const usersThatWereInRoleBefore = (await this.knex.select('id').from('directus_users').where('role', '=', key)).map((user) => user.id);
|
|
38
38
|
const usersThatAreRemoved = usersThatWereInRoleBefore.filter((id) => Array.isArray(users) ? userKeys.includes(id) === false : users.delete.includes(id) === true);
|
|
@@ -58,7 +58,7 @@ class RolesService extends items_1.ItemsService {
|
|
|
58
58
|
async updateOne(key, data, opts) {
|
|
59
59
|
try {
|
|
60
60
|
if ('users' in data) {
|
|
61
|
-
await this.checkForOtherAdminUsers(key, data
|
|
61
|
+
await this.checkForOtherAdminUsers(key, data['users']);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
@@ -69,7 +69,7 @@ class RolesService extends items_1.ItemsService {
|
|
|
69
69
|
async updateBatch(data, opts) {
|
|
70
70
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
71
71
|
const keys = data.map((item) => item[primaryKeyField]);
|
|
72
|
-
const setsToNoAdmin = data.some((item) => item
|
|
72
|
+
const setsToNoAdmin = data.some((item) => item['admin_access'] === false);
|
|
73
73
|
try {
|
|
74
74
|
if (setsToNoAdmin) {
|
|
75
75
|
await this.checkForOtherAdminRoles(keys);
|
|
@@ -82,7 +82,7 @@ class RolesService extends items_1.ItemsService {
|
|
|
82
82
|
}
|
|
83
83
|
async updateMany(keys, data, opts) {
|
|
84
84
|
try {
|
|
85
|
-
if ('admin_access' in data && data
|
|
85
|
+
if ('admin_access' in data && data['admin_access'] === false) {
|
|
86
86
|
await this.checkForOtherAdminRoles(keys);
|
|
87
87
|
}
|
|
88
88
|
}
|
package/dist/services/server.js
CHANGED
|
@@ -70,40 +70,40 @@ class ServerService {
|
|
|
70
70
|
'custom_css',
|
|
71
71
|
],
|
|
72
72
|
});
|
|
73
|
-
info
|
|
73
|
+
info['project'] = projectInfo;
|
|
74
74
|
if (this.accountability?.user) {
|
|
75
|
-
if (env_1.default
|
|
76
|
-
info
|
|
77
|
-
points: env_1.default
|
|
78
|
-
duration: env_1.default
|
|
75
|
+
if (env_1.default['RATE_LIMITER_ENABLED']) {
|
|
76
|
+
info['rateLimit'] = {
|
|
77
|
+
points: env_1.default['RATE_LIMITER_POINTS'],
|
|
78
|
+
duration: env_1.default['RATE_LIMITER_DURATION'],
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
info
|
|
82
|
+
info['rateLimit'] = false;
|
|
83
83
|
}
|
|
84
|
-
if (env_1.default
|
|
85
|
-
info
|
|
86
|
-
points: env_1.default
|
|
87
|
-
duration: env_1.default
|
|
84
|
+
if (env_1.default['RATE_LIMITER_GLOBAL_ENABLED']) {
|
|
85
|
+
info['rateLimitGlobal'] = {
|
|
86
|
+
points: env_1.default['RATE_LIMITER_GLOBAL_POINTS'],
|
|
87
|
+
duration: env_1.default['RATE_LIMITER_GLOBAL_DURATION'],
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
info
|
|
91
|
+
info['rateLimitGlobal'] = false;
|
|
92
92
|
}
|
|
93
|
-
info
|
|
94
|
-
execAllowedModules: env_1.default
|
|
93
|
+
info['flows'] = {
|
|
94
|
+
execAllowedModules: env_1.default['FLOWS_EXEC_ALLOWED_MODULES'] ? (0, utils_1.toArray)(env_1.default['FLOWS_EXEC_ALLOWED_MODULES']) : [],
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
if (this.accountability?.admin === true) {
|
|
98
98
|
const { osType, osVersion } = (0, get_os_info_1.getOSInfo)();
|
|
99
|
-
info
|
|
99
|
+
info['directus'] = {
|
|
100
100
|
version: package_json_1.version,
|
|
101
101
|
};
|
|
102
|
-
info
|
|
102
|
+
info['node'] = {
|
|
103
103
|
version: process.versions.node,
|
|
104
104
|
uptime: Math.round(process.uptime()),
|
|
105
105
|
};
|
|
106
|
-
info
|
|
106
|
+
info['os'] = {
|
|
107
107
|
type: osType,
|
|
108
108
|
version: osVersion,
|
|
109
109
|
uptime: Math.round(os_1.default.uptime()),
|
|
@@ -118,7 +118,7 @@ class ServerService {
|
|
|
118
118
|
const data = {
|
|
119
119
|
status: 'ok',
|
|
120
120
|
releaseId: package_json_1.version,
|
|
121
|
-
serviceId: env_1.default
|
|
121
|
+
serviceId: env_1.default['KEY'],
|
|
122
122
|
checks: (0, lodash_1.merge)(...(await Promise.all([
|
|
123
123
|
testDatabase(),
|
|
124
124
|
testCache(),
|
|
@@ -153,7 +153,7 @@ class ServerService {
|
|
|
153
153
|
}
|
|
154
154
|
async function testDatabase() {
|
|
155
155
|
const database = (0, database_1.default)();
|
|
156
|
-
const client = env_1.default
|
|
156
|
+
const client = env_1.default['DB_CLIENT'];
|
|
157
157
|
const checks = {};
|
|
158
158
|
// Response time
|
|
159
159
|
// ----------------------------------------------------------------------------------------
|
|
@@ -163,7 +163,7 @@ class ServerService {
|
|
|
163
163
|
componentType: 'datastore',
|
|
164
164
|
observedUnit: 'ms',
|
|
165
165
|
observedValue: 0,
|
|
166
|
-
threshold: env_1.default
|
|
166
|
+
threshold: env_1.default['DB_HEALTHCHECK_THRESHOLD'] ? +env_1.default['DB_HEALTHCHECK_THRESHOLD'] : 150,
|
|
167
167
|
},
|
|
168
168
|
];
|
|
169
169
|
const startTime = perf_hooks_1.performance.now();
|
|
@@ -197,7 +197,7 @@ class ServerService {
|
|
|
197
197
|
return checks;
|
|
198
198
|
}
|
|
199
199
|
async function testCache() {
|
|
200
|
-
if (env_1.default
|
|
200
|
+
if (env_1.default['CACHE_ENABLED'] !== true) {
|
|
201
201
|
return {};
|
|
202
202
|
}
|
|
203
203
|
const { cache } = (0, cache_1.getCache)();
|
|
@@ -208,7 +208,7 @@ class ServerService {
|
|
|
208
208
|
componentType: 'cache',
|
|
209
209
|
observedValue: 0,
|
|
210
210
|
observedUnit: 'ms',
|
|
211
|
-
threshold: env_1.default
|
|
211
|
+
threshold: env_1.default['CACHE_HEALTHCHECK_THRESHOLD'] ? +env_1.default['CACHE_HEALTHCHECK_THRESHOLD'] : 150,
|
|
212
212
|
},
|
|
213
213
|
],
|
|
214
214
|
};
|
|
@@ -232,7 +232,7 @@ class ServerService {
|
|
|
232
232
|
return checks;
|
|
233
233
|
}
|
|
234
234
|
async function testRateLimiter() {
|
|
235
|
-
if (env_1.default
|
|
235
|
+
if (env_1.default['RATE_LIMITER_ENABLED'] !== true) {
|
|
236
236
|
return {};
|
|
237
237
|
}
|
|
238
238
|
const checks = {
|
|
@@ -242,7 +242,7 @@ class ServerService {
|
|
|
242
242
|
componentType: 'ratelimiter',
|
|
243
243
|
observedValue: 0,
|
|
244
244
|
observedUnit: 'ms',
|
|
245
|
-
threshold: env_1.default
|
|
245
|
+
threshold: env_1.default['RATE_LIMITER_HEALTHCHECK_THRESHOLD'] ? +env_1.default['RATE_LIMITER_HEALTHCHECK_THRESHOLD'] : 150,
|
|
246
246
|
},
|
|
247
247
|
],
|
|
248
248
|
};
|
|
@@ -266,7 +266,7 @@ class ServerService {
|
|
|
266
266
|
return checks;
|
|
267
267
|
}
|
|
268
268
|
async function testRateLimiterGlobal() {
|
|
269
|
-
if (env_1.default
|
|
269
|
+
if (env_1.default['RATE_LIMITER_GLOBAL_ENABLED'] !== true) {
|
|
270
270
|
return {};
|
|
271
271
|
}
|
|
272
272
|
const checks = {
|
|
@@ -276,8 +276,8 @@ class ServerService {
|
|
|
276
276
|
componentType: 'ratelimiter',
|
|
277
277
|
observedValue: 0,
|
|
278
278
|
observedUnit: 'ms',
|
|
279
|
-
threshold: env_1.default
|
|
280
|
-
? +env_1.default
|
|
279
|
+
threshold: env_1.default['RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD']
|
|
280
|
+
? +env_1.default['RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD']
|
|
281
281
|
: 150,
|
|
282
282
|
},
|
|
283
283
|
],
|
|
@@ -305,7 +305,7 @@ class ServerService {
|
|
|
305
305
|
async function testStorage() {
|
|
306
306
|
const storage = await (0, storage_1.getStorage)();
|
|
307
307
|
const checks = {};
|
|
308
|
-
for (const location of (0, utils_1.toArray)(env_1.default
|
|
308
|
+
for (const location of (0, utils_1.toArray)(env_1.default['STORAGE_LOCATIONS'])) {
|
|
309
309
|
const disk = storage.location(location);
|
|
310
310
|
const envThresholdKey = `STORAGE_${location}_HEALTHCHECK_THRESHOLD`.toUpperCase();
|
|
311
311
|
checks[`storage:${location}:responseTime`] = [
|
package/dist/services/shares.js
CHANGED
|
@@ -27,7 +27,7 @@ class SharesService extends items_1.ItemsService {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
async createOne(data, opts) {
|
|
30
|
-
await this.authorizationService.checkAccess('share', data
|
|
30
|
+
await this.authorizationService.checkAccess('share', data['collection'], data['item']);
|
|
31
31
|
return super.createOne(data, opts);
|
|
32
32
|
}
|
|
33
33
|
async login(payload) {
|
|
@@ -45,7 +45,7 @@ class SharesService extends items_1.ItemsService {
|
|
|
45
45
|
share_password: 'password',
|
|
46
46
|
})
|
|
47
47
|
.from('directus_shares')
|
|
48
|
-
.where('id', payload
|
|
48
|
+
.where('id', payload['share'])
|
|
49
49
|
.andWhere((subQuery) => {
|
|
50
50
|
subQuery.whereNull('date_end').orWhere('date_end', '>=', new Date());
|
|
51
51
|
})
|
|
@@ -59,7 +59,7 @@ class SharesService extends items_1.ItemsService {
|
|
|
59
59
|
if (!record) {
|
|
60
60
|
throw new exceptions_1.InvalidCredentialsException();
|
|
61
61
|
}
|
|
62
|
-
if (record.share_password && !(await argon2_1.default.verify(record.share_password, payload
|
|
62
|
+
if (record.share_password && !(await argon2_1.default.verify(record.share_password, payload['password']))) {
|
|
63
63
|
throw new exceptions_1.InvalidCredentialsException();
|
|
64
64
|
}
|
|
65
65
|
await this.knex('directus_shares')
|
|
@@ -75,12 +75,12 @@ class SharesService extends items_1.ItemsService {
|
|
|
75
75
|
collection: record.share_collection,
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
|
-
const accessToken = jsonwebtoken_1.default.sign(tokenPayload, env_1.default
|
|
79
|
-
expiresIn: env_1.default
|
|
78
|
+
const accessToken = jsonwebtoken_1.default.sign(tokenPayload, env_1.default['SECRET'], {
|
|
79
|
+
expiresIn: env_1.default['ACCESS_TOKEN_TTL'],
|
|
80
80
|
issuer: 'directus',
|
|
81
81
|
});
|
|
82
82
|
const refreshToken = nanoid(64);
|
|
83
|
-
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
83
|
+
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default['REFRESH_TOKEN_TTL'], 0));
|
|
84
84
|
await this.knex('directus_sessions').insert({
|
|
85
85
|
token: refreshToken,
|
|
86
86
|
expires: refreshTokenExpiration,
|
|
@@ -93,7 +93,7 @@ class SharesService extends items_1.ItemsService {
|
|
|
93
93
|
return {
|
|
94
94
|
accessToken,
|
|
95
95
|
refreshToken,
|
|
96
|
-
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
96
|
+
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default['ACCESS_TOKEN_TTL']),
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
@@ -115,9 +115,9 @@ class SharesService extends items_1.ItemsService {
|
|
|
115
115
|
const message = `
|
|
116
116
|
Hello!
|
|
117
117
|
|
|
118
|
-
${(0, user_name_1.userName)(userInfo)} has invited you to view an item in ${share
|
|
118
|
+
${(0, user_name_1.userName)(userInfo)} has invited you to view an item in ${share['collection']}.
|
|
119
119
|
|
|
120
|
-
[Open](${new url_1.Url(env_1.default
|
|
120
|
+
[Open](${new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'shared', payload.share).toString()})
|
|
121
121
|
`;
|
|
122
122
|
for (const email of payload.emails) {
|
|
123
123
|
await mailService.send({
|
|
@@ -74,7 +74,7 @@ class OASSpecsService {
|
|
|
74
74
|
},
|
|
75
75
|
servers: [
|
|
76
76
|
{
|
|
77
|
-
url: env_1.default
|
|
77
|
+
url: env_1.default['PUBLIC_URL'],
|
|
78
78
|
description: 'Your current Directus instance.',
|
|
79
79
|
},
|
|
80
80
|
],
|
|
@@ -168,7 +168,7 @@ class OASSpecsService {
|
|
|
168
168
|
paths[`/items/${collection}`][method] = (0, lodash_1.mergeWith)((0, lodash_1.cloneDeep)(listBase[method]), {
|
|
169
169
|
description: listBase[method].description.replace('item', collection + ' item'),
|
|
170
170
|
tags: [tag.name],
|
|
171
|
-
parameters: 'parameters' in listBase ? this.filterCollectionFromParams(listBase
|
|
171
|
+
parameters: 'parameters' in listBase ? this.filterCollectionFromParams(listBase.parameters) : [],
|
|
172
172
|
operationId: `${this.getActionForMethod(method)}${tag.name}`,
|
|
173
173
|
requestBody: ['get', 'delete'].includes(method)
|
|
174
174
|
? undefined
|
|
@@ -213,6 +213,7 @@ class OASSpecsService {
|
|
|
213
213
|
}, (obj, src) => {
|
|
214
214
|
if (Array.isArray(obj))
|
|
215
215
|
return obj.concat(src);
|
|
216
|
+
return undefined;
|
|
216
217
|
});
|
|
217
218
|
}
|
|
218
219
|
if (detailBase[method]) {
|
|
@@ -220,7 +221,7 @@ class OASSpecsService {
|
|
|
220
221
|
description: detailBase[method].description.replace('item', collection + ' item'),
|
|
221
222
|
tags: [tag.name],
|
|
222
223
|
operationId: `${this.getActionForMethod(method)}Single${tag.name}`,
|
|
223
|
-
parameters: 'parameters' in detailBase ? this.filterCollectionFromParams(detailBase
|
|
224
|
+
parameters: 'parameters' in detailBase ? this.filterCollectionFromParams(detailBase.parameters) : [],
|
|
224
225
|
requestBody: ['get', 'delete'].includes(method)
|
|
225
226
|
? undefined
|
|
226
227
|
: {
|
|
@@ -252,6 +253,7 @@ class OASSpecsService {
|
|
|
252
253
|
}, (obj, src) => {
|
|
253
254
|
if (Array.isArray(obj))
|
|
254
255
|
return obj.concat(src);
|
|
256
|
+
return undefined;
|
|
255
257
|
});
|
|
256
258
|
}
|
|
257
259
|
}
|
package/dist/services/users.d.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Knex } from 'knex';
|
|
1
|
+
import type { Query } from '@directus/shared/types';
|
|
3
2
|
import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types';
|
|
4
3
|
import { ItemsService } from './items';
|
|
5
4
|
export declare class UsersService extends ItemsService {
|
|
6
|
-
knex: Knex;
|
|
7
|
-
accountability: Accountability | null;
|
|
8
|
-
schema: SchemaOverview;
|
|
9
5
|
constructor(options: AbstractServiceOptions);
|
|
10
6
|
/**
|
|
11
7
|
* User email has to be unique case-insensitive. This is an additional check to make sure that
|
package/dist/services/users.js
CHANGED
|
@@ -20,9 +20,6 @@ const items_1 = require("./items");
|
|
|
20
20
|
const mail_1 = require("./mail");
|
|
21
21
|
const settings_1 = require("./settings");
|
|
22
22
|
class UsersService extends items_1.ItemsService {
|
|
23
|
-
knex;
|
|
24
|
-
accountability;
|
|
25
|
-
schema;
|
|
26
23
|
constructor(options) {
|
|
27
24
|
super('directus_users', options);
|
|
28
25
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -131,8 +128,8 @@ class UsersService extends items_1.ItemsService {
|
|
|
131
128
|
* Create multiple new users
|
|
132
129
|
*/
|
|
133
130
|
async createMany(data, opts) {
|
|
134
|
-
const emails = data
|
|
135
|
-
const passwords = data
|
|
131
|
+
const emails = data['map']((payload) => payload['email']).filter((email) => email);
|
|
132
|
+
const passwords = data['map']((payload) => payload['password']).filter((password) => password);
|
|
136
133
|
try {
|
|
137
134
|
if (emails.length) {
|
|
138
135
|
await this.checkUniqueEmails(emails);
|
|
@@ -182,44 +179,44 @@ class UsersService extends items_1.ItemsService {
|
|
|
182
179
|
*/
|
|
183
180
|
async updateMany(keys, data, opts) {
|
|
184
181
|
try {
|
|
185
|
-
if (data
|
|
186
|
-
// data
|
|
187
|
-
const roleId = data
|
|
182
|
+
if (data['role']) {
|
|
183
|
+
// data['role'] will be an object with id with GraphQL mutations
|
|
184
|
+
const roleId = data['role']?.id ?? data['role'];
|
|
188
185
|
const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
|
|
189
186
|
if (!newRole?.admin_access) {
|
|
190
187
|
await this.checkRemainingAdminExistence(keys);
|
|
191
188
|
}
|
|
192
189
|
}
|
|
193
|
-
if (data
|
|
190
|
+
if (data['status'] !== undefined && data['status'] !== 'active') {
|
|
194
191
|
await this.checkRemainingActiveAdmin(keys);
|
|
195
192
|
}
|
|
196
|
-
if (data
|
|
193
|
+
if (data['email']) {
|
|
197
194
|
if (keys.length > 1) {
|
|
198
195
|
throw new record_not_unique_1.RecordNotUniqueException('email', {
|
|
199
196
|
collection: 'directus_users',
|
|
200
197
|
field: 'email',
|
|
201
|
-
invalid: data
|
|
198
|
+
invalid: data['email'],
|
|
202
199
|
});
|
|
203
200
|
}
|
|
204
|
-
await this.checkUniqueEmails([data
|
|
201
|
+
await this.checkUniqueEmails([data['email']], keys[0]);
|
|
205
202
|
}
|
|
206
|
-
if (data
|
|
207
|
-
await this.checkPasswordPolicy([data
|
|
203
|
+
if (data['password']) {
|
|
204
|
+
await this.checkPasswordPolicy([data['password']]);
|
|
208
205
|
}
|
|
209
|
-
if (data
|
|
206
|
+
if (data['tfa_secret'] !== undefined) {
|
|
210
207
|
throw new exceptions_2.InvalidPayloadException(`You can't change the "tfa_secret" value manually.`);
|
|
211
208
|
}
|
|
212
|
-
if (data
|
|
209
|
+
if (data['provider'] !== undefined) {
|
|
213
210
|
if (this.accountability && this.accountability.admin !== true) {
|
|
214
211
|
throw new exceptions_2.InvalidPayloadException(`You can't change the "provider" value manually.`);
|
|
215
212
|
}
|
|
216
|
-
data
|
|
213
|
+
data['auth_data'] = null;
|
|
217
214
|
}
|
|
218
|
-
if (data
|
|
215
|
+
if (data['external_identifier'] !== undefined) {
|
|
219
216
|
if (this.accountability && this.accountability.admin !== true) {
|
|
220
217
|
throw new exceptions_2.InvalidPayloadException(`You can't change the "external_identifier" value manually.`);
|
|
221
218
|
}
|
|
222
|
-
data
|
|
219
|
+
data['auth_data'] = null;
|
|
223
220
|
}
|
|
224
221
|
}
|
|
225
222
|
catch (err) {
|
|
@@ -266,7 +263,7 @@ class UsersService extends items_1.ItemsService {
|
|
|
266
263
|
async inviteUser(email, role, url, subject) {
|
|
267
264
|
const opts = {};
|
|
268
265
|
try {
|
|
269
|
-
if (url && (0, is_url_allowed_1.default)(url, env_1.default
|
|
266
|
+
if (url && (0, is_url_allowed_1.default)(url, env_1.default['USER_INVITE_URL_ALLOW_LIST']) === false) {
|
|
270
267
|
throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to invite users.`);
|
|
271
268
|
}
|
|
272
269
|
}
|
|
@@ -280,9 +277,9 @@ class UsersService extends items_1.ItemsService {
|
|
|
280
277
|
});
|
|
281
278
|
for (const email of emails) {
|
|
282
279
|
const payload = { email, scope: 'invite' };
|
|
283
|
-
const token = jsonwebtoken_1.default.sign(payload, env_1.default
|
|
280
|
+
const token = jsonwebtoken_1.default.sign(payload, env_1.default['SECRET'], { expiresIn: '7d', issuer: 'directus' });
|
|
284
281
|
const subjectLine = subject ?? "You've been invited";
|
|
285
|
-
const inviteURL = url ? new url_1.Url(url) : new url_1.Url(env_1.default
|
|
282
|
+
const inviteURL = url ? new url_1.Url(url) : new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'accept-invite');
|
|
286
283
|
inviteURL.setQuery('token', token);
|
|
287
284
|
// Create user first to verify uniqueness
|
|
288
285
|
await this.createOne({ email, role, status: 'invited' }, opts);
|
|
@@ -300,7 +297,7 @@ class UsersService extends items_1.ItemsService {
|
|
|
300
297
|
}
|
|
301
298
|
}
|
|
302
299
|
async acceptInvite(token, password) {
|
|
303
|
-
const { email, scope } = jsonwebtoken_1.default.verify(token, env_1.default
|
|
300
|
+
const { email, scope } = jsonwebtoken_1.default.verify(token, env_1.default['SECRET'], { issuer: 'directus' });
|
|
304
301
|
if (scope !== 'invite')
|
|
305
302
|
throw new exceptions_2.ForbiddenException();
|
|
306
303
|
const user = await this.knex.select('id', 'status').from('directus_users').where({ email }).first();
|
|
@@ -326,7 +323,7 @@ class UsersService extends items_1.ItemsService {
|
|
|
326
323
|
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
327
324
|
throw new exceptions_2.ForbiddenException();
|
|
328
325
|
}
|
|
329
|
-
if (url && (0, is_url_allowed_1.default)(url, env_1.default
|
|
326
|
+
if (url && (0, is_url_allowed_1.default)(url, env_1.default['PASSWORD_RESET_URL_ALLOW_LIST']) === false) {
|
|
330
327
|
throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`);
|
|
331
328
|
}
|
|
332
329
|
const mailService = new mail_1.MailService({
|
|
@@ -335,10 +332,10 @@ class UsersService extends items_1.ItemsService {
|
|
|
335
332
|
accountability: this.accountability,
|
|
336
333
|
});
|
|
337
334
|
const payload = { email, scope: 'password-reset', hash: (0, utils_1.getSimpleHash)('' + user.password) };
|
|
338
|
-
const token = jsonwebtoken_1.default.sign(payload, env_1.default
|
|
335
|
+
const token = jsonwebtoken_1.default.sign(payload, env_1.default['SECRET'], { expiresIn: '1d', issuer: 'directus' });
|
|
339
336
|
const acceptURL = url
|
|
340
337
|
? new url_1.Url(url).setQuery('token', token).toString()
|
|
341
|
-
: new url_1.Url(env_1.default
|
|
338
|
+
: new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'reset-password').setQuery('token', token);
|
|
342
339
|
const subjectLine = subject ? subject : 'Password Reset Request';
|
|
343
340
|
await mailService.send({
|
|
344
341
|
to: email,
|
|
@@ -354,7 +351,7 @@ class UsersService extends items_1.ItemsService {
|
|
|
354
351
|
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
355
352
|
}
|
|
356
353
|
async resetPassword(token, password) {
|
|
357
|
-
const { email, scope, hash } = jsonwebtoken_1.default.verify(token, env_1.default
|
|
354
|
+
const { email, scope, hash } = jsonwebtoken_1.default.verify(token, env_1.default['SECRET'], { issuer: 'directus' });
|
|
358
355
|
if (scope !== 'password-reset' || !hash)
|
|
359
356
|
throw new exceptions_2.ForbiddenException();
|
|
360
357
|
const opts = {};
|
|
@@ -6,7 +6,7 @@ const env_1 = require("../env");
|
|
|
6
6
|
const get_config_from_env_1 = require("../utils/get-config-from-env");
|
|
7
7
|
const registerLocations = async (storage) => {
|
|
8
8
|
const env = (0, env_1.getEnv)();
|
|
9
|
-
const locations = (0, utils_1.toArray)(env
|
|
9
|
+
const locations = (0, utils_1.toArray)(env['STORAGE_LOCATIONS']);
|
|
10
10
|
locations.forEach((location) => {
|
|
11
11
|
location = location.trim();
|
|
12
12
|
const driverConfig = (0, get_config_from_env_1.getConfigFromEnv)(`STORAGE_${location.toUpperCase()}_`);
|
|
@@ -171,6 +171,7 @@ function applySort(knex, schema, rootQuery, rootSort, collection, aliasMap, retu
|
|
|
171
171
|
// Clears the order if any, eg: from MSSQL offset
|
|
172
172
|
rootQuery.clear('order');
|
|
173
173
|
rootQuery.orderBy(sortRecords);
|
|
174
|
+
return undefined;
|
|
174
175
|
}
|
|
175
176
|
exports.applySort = applySort;
|
|
176
177
|
function applyLimit(knex, rootQuery, limit) {
|
|
@@ -337,7 +338,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap)
|
|
|
337
338
|
}
|
|
338
339
|
if (operator === '_nempty' || (operator === '_empty' && compareValue === false)) {
|
|
339
340
|
dbQuery[logical].andWhere((query) => {
|
|
340
|
-
query.whereNotNull(key).
|
|
341
|
+
query.whereNotNull(key).andWhere(key, '!=', '');
|
|
341
342
|
});
|
|
342
343
|
}
|
|
343
344
|
// The following fields however, require a value to be run. If no value is passed, we
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dynamicImport = void 0;
|
|
4
4
|
const dynamicImport = async (mod) => {
|
|
5
|
-
return process.env
|
|
5
|
+
return process.env['VITEST'] ? await import(mod) : require(mod);
|
|
6
6
|
};
|
|
7
7
|
exports.dynamicImport = dynamicImport;
|
|
@@ -10,7 +10,7 @@ function generateHash(stringToHash) {
|
|
|
10
10
|
const argon2HashConfigOptions = (0, get_config_from_env_1.getConfigFromEnv)('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/directus/directus/discussions/7670#discussioncomment-1255805
|
|
11
11
|
// associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata
|
|
12
12
|
'associatedData' in argon2HashConfigOptions &&
|
|
13
|
-
(argon2HashConfigOptions
|
|
13
|
+
(argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']));
|
|
14
14
|
return argon2_1.default.hash(stringToHash, argon2HashConfigOptions);
|
|
15
15
|
}
|
|
16
16
|
exports.generateHash = generateHash;
|