directus 9.16.1 → 9.17.2
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 +7 -7
- package/dist/auth/drivers/ldap.js +1 -0
- package/dist/auth/drivers/local.js +6 -0
- package/dist/auth/drivers/oauth2.js +1 -0
- package/dist/auth/drivers/openid.js +1 -0
- package/dist/cli/utils/create-env/env-stub.liquid +8 -1
- package/dist/controllers/activity.js +1 -0
- package/dist/controllers/auth.js +6 -9
- package/dist/database/helpers/schema/dialects/sqlite.d.ts +2 -2
- package/dist/database/helpers/schema/dialects/sqlite.js +2 -2
- package/dist/database/helpers/schema/types.d.ts +2 -2
- package/dist/database/helpers/schema/types.js +2 -2
- package/dist/database/migrations/20220826A-add-origin-to-accountability.d.ts +3 -0
- package/dist/database/migrations/20220826A-add-origin-to-accountability.js +21 -0
- package/dist/database/system-data/fields/activity.yaml +6 -0
- package/dist/database/system-data/fields/sessions.yaml +2 -0
- package/dist/env.js +8 -0
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +16 -3
- package/dist/flows.js +27 -17
- package/dist/mailer.js +7 -0
- package/dist/middleware/authenticate.d.ts +1 -1
- package/dist/middleware/authenticate.js +1 -0
- package/dist/middleware/authenticate.test.js +44 -4
- package/dist/middleware/validate-batch.d.ts +1 -2
- package/dist/middleware/validate-batch.js +10 -13
- package/dist/middleware/validate-batch.test.d.ts +1 -0
- package/dist/middleware/validate-batch.test.js +82 -0
- package/dist/operations/request/index.js +2 -2
- package/dist/operations/transform/index.d.ts +1 -1
- package/dist/operations/transform/index.js +1 -1
- package/dist/services/authentication.js +13 -3
- package/dist/services/fields.js +11 -3
- package/dist/services/files.js +2 -2
- package/dist/services/graphql/index.js +45 -37
- package/dist/services/items.js +15 -3
- package/dist/services/payload.js +28 -4
- package/dist/services/relations.d.ts +2 -0
- package/dist/services/relations.js +14 -0
- package/dist/services/server.js +5 -4
- package/dist/services/shares.js +2 -1
- package/dist/utils/async-handler.d.ts +2 -6
- package/dist/utils/async-handler.js +1 -13
- package/dist/utils/async-handler.test.d.ts +1 -0
- package/dist/utils/async-handler.test.js +18 -0
- package/package.json +14 -11
package/dist/app.js
CHANGED
|
@@ -135,6 +135,13 @@ async function createApp() {
|
|
|
135
135
|
await emitter_1.default.emitInit('app.before', { app });
|
|
136
136
|
await emitter_1.default.emitInit('middlewares.before', { app });
|
|
137
137
|
app.use(logger_1.expressLogger);
|
|
138
|
+
app.use((_req, res, next) => {
|
|
139
|
+
res.setHeader('X-Powered-By', 'Directus');
|
|
140
|
+
next();
|
|
141
|
+
});
|
|
142
|
+
if (env_1.default.CORS_ENABLED === true) {
|
|
143
|
+
app.use(cors_1.default);
|
|
144
|
+
}
|
|
138
145
|
app.use((req, res, next) => {
|
|
139
146
|
express_1.default.json({
|
|
140
147
|
limit: env_1.default.MAX_PAYLOAD_SIZE,
|
|
@@ -147,13 +154,6 @@ async function createApp() {
|
|
|
147
154
|
});
|
|
148
155
|
app.use((0, cookie_parser_1.default)());
|
|
149
156
|
app.use(extract_token_1.default);
|
|
150
|
-
app.use((_req, res, next) => {
|
|
151
|
-
res.setHeader('X-Powered-By', 'Directus');
|
|
152
|
-
next();
|
|
153
|
-
});
|
|
154
|
-
if (env_1.default.CORS_ENABLED === true) {
|
|
155
|
-
app.use(cors_1.default);
|
|
156
|
-
}
|
|
157
157
|
app.get('/', (_req, res, next) => {
|
|
158
158
|
if (env_1.default.ROOT_REDIRECT) {
|
|
159
159
|
res.redirect(env_1.default.ROOT_REDIRECT);
|
|
@@ -302,6 +302,7 @@ function createLDAPAuthRouter(provider) {
|
|
|
302
302
|
const accountability = {
|
|
303
303
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
304
304
|
userAgent: req.get('user-agent'),
|
|
305
|
+
origin: req.get('origin'),
|
|
305
306
|
role: null,
|
|
306
307
|
};
|
|
307
308
|
const authenticationService = new services_1.AuthenticationService({
|
|
@@ -15,6 +15,8 @@ const env_1 = __importDefault(require("../../env"));
|
|
|
15
15
|
const respond_1 = require("../../middleware/respond");
|
|
16
16
|
const constants_1 = require("../../constants");
|
|
17
17
|
const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
|
|
18
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
19
|
+
const stall_1 = require("../../utils/stall");
|
|
18
20
|
class LocalAuthDriver extends auth_1.AuthDriver {
|
|
19
21
|
async getUserID(payload) {
|
|
20
22
|
if (!payload.email) {
|
|
@@ -50,9 +52,12 @@ function createLocalAuthRouter(provider) {
|
|
|
50
52
|
}).unknown();
|
|
51
53
|
router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
|
|
52
54
|
var _a;
|
|
55
|
+
const STALL_TIME = env_1.default.LOGIN_STALL_TIME;
|
|
56
|
+
const timeStart = perf_hooks_1.performance.now();
|
|
53
57
|
const accountability = {
|
|
54
58
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
55
59
|
userAgent: req.get('user-agent'),
|
|
60
|
+
origin: req.get('origin'),
|
|
56
61
|
role: null,
|
|
57
62
|
};
|
|
58
63
|
const authenticationService = new services_1.AuthenticationService({
|
|
@@ -61,6 +66,7 @@ function createLocalAuthRouter(provider) {
|
|
|
61
66
|
});
|
|
62
67
|
const { error } = userLoginSchema.validate(req.body);
|
|
63
68
|
if (error) {
|
|
69
|
+
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
64
70
|
throw new exceptions_1.InvalidPayloadException(error.message);
|
|
65
71
|
}
|
|
66
72
|
const mode = req.body.mode || 'json';
|
|
@@ -211,6 +211,10 @@ REFRESH_TOKEN_COOKIE_NAME="directus_refresh_token"
|
|
|
211
211
|
# Which domain to use for the refresh cookie. Useful for development mode.
|
|
212
212
|
# REFRESH_TOKEN_COOKIE_DOMAIN
|
|
213
213
|
|
|
214
|
+
# The duration in milliseconds that a login request will be stalled for,
|
|
215
|
+
# and it should be greater than the time taken for a login request with an invalid password [500]
|
|
216
|
+
# LOGIN_STALL_TIME=500
|
|
217
|
+
|
|
214
218
|
# Whether or not to enable the CORS headers [false]
|
|
215
219
|
CORS_ENABLED=true
|
|
216
220
|
|
|
@@ -288,7 +292,7 @@ EXTENSIONS_AUTO_RELOAD=false
|
|
|
288
292
|
EMAIL_FROM="no-reply@directus.io"
|
|
289
293
|
|
|
290
294
|
# What to use to send emails. One of
|
|
291
|
-
# sendmail, smtp, mailgun, ses.
|
|
295
|
+
# sendmail, smtp, mailgun, sendgrid, ses.
|
|
292
296
|
EMAIL_TRANSPORT="sendmail"
|
|
293
297
|
EMAIL_SENDMAIL_NEW_LINE="unix"
|
|
294
298
|
EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
@@ -315,3 +319,6 @@ EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
|
315
319
|
## Email (Mailgun Transport)
|
|
316
320
|
# EMAIL_MAILGUN_API_KEY="key-1234123412341234"
|
|
317
321
|
# EMAIL_MAILGUN_DOMAIN="a domain name from https://app.mailgun.com/app/sending/domains"
|
|
322
|
+
|
|
323
|
+
## Email (SendGrid Transport)
|
|
324
|
+
# EMAIL_SENDGRID_API_KEY="key-1234123412341234"
|
|
@@ -75,6 +75,7 @@ router.post('/comment', (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
75
75
|
user: (_a = req.accountability) === null || _a === void 0 ? void 0 : _a.user,
|
|
76
76
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
77
77
|
user_agent: req.get('user-agent'),
|
|
78
|
+
origin: req.get('origin'),
|
|
78
79
|
});
|
|
79
80
|
try {
|
|
80
81
|
const record = await service.readOne(primaryKey, req.sanitizedQuery);
|
package/dist/controllers/auth.js
CHANGED
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const express_1 = require("express");
|
|
7
|
-
const ms_1 = __importDefault(require("ms"));
|
|
8
7
|
const env_1 = __importDefault(require("../env"));
|
|
9
8
|
const exceptions_1 = require("../exceptions");
|
|
10
9
|
const respond_1 = require("../middleware/respond");
|
|
@@ -15,6 +14,7 @@ const logger_1 = __importDefault(require("../logger"));
|
|
|
15
14
|
const drivers_1 = require("../auth/drivers");
|
|
16
15
|
const constants_1 = require("../constants");
|
|
17
16
|
const get_ip_from_req_1 = require("../utils/get-ip-from-req");
|
|
17
|
+
const constants_2 = require("../constants");
|
|
18
18
|
const router = (0, express_1.Router)();
|
|
19
19
|
const authProviders = (0, get_auth_providers_1.getAuthProviders)();
|
|
20
20
|
for (const authProvider of authProviders) {
|
|
@@ -43,10 +43,10 @@ if (!env_1.default.AUTH_DISABLE_DEFAULT) {
|
|
|
43
43
|
router.use('/login', (0, drivers_1.createLocalAuthRouter)(constants_1.DEFAULT_AUTH_PROVIDER));
|
|
44
44
|
}
|
|
45
45
|
router.post('/refresh', (0, async_handler_1.default)(async (req, res, next) => {
|
|
46
|
-
var _a;
|
|
47
46
|
const accountability = {
|
|
48
47
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
49
48
|
userAgent: req.get('user-agent'),
|
|
49
|
+
origin: req.get('origin'),
|
|
50
50
|
role: null,
|
|
51
51
|
};
|
|
52
52
|
const authenticationService = new services_1.AuthenticationService({
|
|
@@ -66,13 +66,7 @@ router.post('/refresh', (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
66
66
|
payload.data.refresh_token = refreshToken;
|
|
67
67
|
}
|
|
68
68
|
if (mode === 'cookie') {
|
|
69
|
-
res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken,
|
|
70
|
-
httpOnly: true,
|
|
71
|
-
domain: env_1.default.REFRESH_TOKEN_COOKIE_DOMAIN,
|
|
72
|
-
maxAge: (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL),
|
|
73
|
-
secure: (_a = env_1.default.REFRESH_TOKEN_COOKIE_SECURE) !== null && _a !== void 0 ? _a : false,
|
|
74
|
-
sameSite: env_1.default.REFRESH_TOKEN_COOKIE_SAME_SITE || 'strict',
|
|
75
|
-
});
|
|
69
|
+
res.cookie(env_1.default.REFRESH_TOKEN_COOKIE_NAME, refreshToken, constants_2.COOKIE_OPTIONS);
|
|
76
70
|
}
|
|
77
71
|
res.locals.payload = payload;
|
|
78
72
|
return next();
|
|
@@ -82,6 +76,7 @@ router.post('/logout', (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
82
76
|
const accountability = {
|
|
83
77
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
84
78
|
userAgent: req.get('user-agent'),
|
|
79
|
+
origin: req.get('origin'),
|
|
85
80
|
role: null,
|
|
86
81
|
};
|
|
87
82
|
const authenticationService = new services_1.AuthenticationService({
|
|
@@ -110,6 +105,7 @@ router.post('/password/request', (0, async_handler_1.default)(async (req, res, n
|
|
|
110
105
|
const accountability = {
|
|
111
106
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
112
107
|
userAgent: req.get('user-agent'),
|
|
108
|
+
origin: req.get('origin'),
|
|
113
109
|
role: null,
|
|
114
110
|
};
|
|
115
111
|
const service = new services_1.UsersService({ accountability, schema: req.schema });
|
|
@@ -137,6 +133,7 @@ router.post('/password/reset', (0, async_handler_1.default)(async (req, res, nex
|
|
|
137
133
|
const accountability = {
|
|
138
134
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
139
135
|
userAgent: req.get('user-agent'),
|
|
136
|
+
origin: req.get('origin'),
|
|
140
137
|
role: null,
|
|
141
138
|
};
|
|
142
139
|
const service = new services_1.UsersService({ accountability, schema: req.schema });
|
|
@@ -3,14 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SchemaHelperSQLite = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
class SchemaHelperSQLite extends types_1.SchemaHelper {
|
|
6
|
-
async
|
|
6
|
+
async preColumnChange() {
|
|
7
7
|
const foreignCheckStatus = (await this.knex.raw('PRAGMA foreign_keys'))[0].foreign_keys === 1;
|
|
8
8
|
if (foreignCheckStatus) {
|
|
9
9
|
await this.knex.raw('PRAGMA foreign_keys = OFF');
|
|
10
10
|
}
|
|
11
11
|
return foreignCheckStatus;
|
|
12
12
|
}
|
|
13
|
-
async
|
|
13
|
+
async postColumnChange() {
|
|
14
14
|
await this.knex.raw('PRAGMA foreign_keys = ON');
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -11,7 +11,7 @@ export declare abstract class SchemaHelper extends DatabaseHelper {
|
|
|
11
11
|
changeNullable(table: string, column: string, nullable: boolean): Promise<void>;
|
|
12
12
|
changeToType(table: string, column: string, type: typeof KNEX_TYPES[number], options?: Options): Promise<void>;
|
|
13
13
|
protected changeToTypeByCopy(table: string, column: string, type: typeof KNEX_TYPES[number], options: Options): Promise<void>;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
preColumnChange(): Promise<boolean>;
|
|
15
|
+
postColumnChange(): Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
export {};
|
|
@@ -56,10 +56,10 @@ class SchemaHelper extends types_1.DatabaseHelper {
|
|
|
56
56
|
await this.changeNullable(table, column, options.nullable);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
async
|
|
59
|
+
async preColumnChange() {
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
|
-
async
|
|
62
|
+
async postColumnChange() {
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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_activity', (table) => {
|
|
6
|
+
table.string('origin').nullable();
|
|
7
|
+
});
|
|
8
|
+
await knex.schema.alterTable('directus_sessions', (table) => {
|
|
9
|
+
table.string('origin').nullable();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
exports.up = up;
|
|
13
|
+
async function down(knex) {
|
|
14
|
+
await knex.schema.alterTable('directus_activity', (table) => {
|
|
15
|
+
table.dropColumn('origin');
|
|
16
|
+
});
|
|
17
|
+
await knex.schema.alterTable('directus_sessions', (table) => {
|
|
18
|
+
table.dropColumn('origin');
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
exports.down = down;
|
package/dist/env.js
CHANGED
|
@@ -42,6 +42,7 @@ const allowedEnvironmentVars = [
|
|
|
42
42
|
'REFRESH_TOKEN_COOKIE_SECURE',
|
|
43
43
|
'REFRESH_TOKEN_COOKIE_SAME_SITE',
|
|
44
44
|
'REFRESH_TOKEN_COOKIE_NAME',
|
|
45
|
+
'LOGIN_STALL_TIME',
|
|
45
46
|
'PASSWORD_RESET_URL_ALLOW_LIST',
|
|
46
47
|
'USER_INVITE_URL_ALLOW_LIST',
|
|
47
48
|
'IP_TRUST_PROXY',
|
|
@@ -79,6 +80,7 @@ const allowedEnvironmentVars = [
|
|
|
79
80
|
'CACHE_REDIS_PASSWORD',
|
|
80
81
|
'CACHE_MEMCACHE',
|
|
81
82
|
'CACHE_VALUE_MAX_SIZE',
|
|
83
|
+
'CACHE_HEALTHCHECK_THRESHOLD',
|
|
82
84
|
// storage
|
|
83
85
|
'STORAGE_LOCATIONS',
|
|
84
86
|
'STORAGE_.+_DRIVER',
|
|
@@ -95,6 +97,7 @@ const allowedEnvironmentVars = [
|
|
|
95
97
|
'STORAGE_.+_ENDPOINT',
|
|
96
98
|
'STORAGE_.+_KEY_FILENAME',
|
|
97
99
|
'STORAGE_.+_BUCKET',
|
|
100
|
+
'STORAGE_.+_HEALTHCHECK_THRESHOLD',
|
|
98
101
|
// metadata
|
|
99
102
|
'FILE_METADATA_ALLOW_LIST',
|
|
100
103
|
// assets
|
|
@@ -151,6 +154,7 @@ const allowedEnvironmentVars = [
|
|
|
151
154
|
'EMAIL_VERIFY_SETUP',
|
|
152
155
|
'EMAIL_SENDMAIL_NEW_LINE',
|
|
153
156
|
'EMAIL_SENDMAIL_PATH',
|
|
157
|
+
'EMAIL_SMTP_NAME',
|
|
154
158
|
'EMAIL_SMTP_HOST',
|
|
155
159
|
'EMAIL_SMTP_PORT',
|
|
156
160
|
'EMAIL_SMTP_USER',
|
|
@@ -161,6 +165,7 @@ const allowedEnvironmentVars = [
|
|
|
161
165
|
'EMAIL_MAILGUN_API_KEY',
|
|
162
166
|
'EMAIL_MAILGUN_DOMAIN',
|
|
163
167
|
'EMAIL_MAILGUN_HOST',
|
|
168
|
+
'EMAIL_SENDGRID_API_KEY',
|
|
164
169
|
'EMAIL_SES_CREDENTIALS__ACCESS_KEY_ID',
|
|
165
170
|
'EMAIL_SES_CREDENTIALS__SECRET_ACCESS_KEY',
|
|
166
171
|
'EMAIL_SES_REGION',
|
|
@@ -172,6 +177,8 @@ const allowedEnvironmentVars = [
|
|
|
172
177
|
// limits & optimization
|
|
173
178
|
'RELATIONAL_BATCH_SIZE',
|
|
174
179
|
'EXPORT_BATCH_SIZE',
|
|
180
|
+
// flows
|
|
181
|
+
'FLOWS_EXEC_ALLOWED_MODULES',
|
|
175
182
|
].map((name) => new RegExp(`^${name}$`));
|
|
176
183
|
const acceptedEnvTypes = ['string', 'number', 'regex', 'array', 'json'];
|
|
177
184
|
const defaults = {
|
|
@@ -194,6 +201,7 @@ const defaults = {
|
|
|
194
201
|
REFRESH_TOKEN_COOKIE_SECURE: false,
|
|
195
202
|
REFRESH_TOKEN_COOKIE_SAME_SITE: 'lax',
|
|
196
203
|
REFRESH_TOKEN_COOKIE_NAME: 'directus_refresh_token',
|
|
204
|
+
LOGIN_STALL_TIME: 500,
|
|
197
205
|
ROOT_REDIRECT: './admin',
|
|
198
206
|
CORS_ENABLED: false,
|
|
199
207
|
CORS_ORIGIN: false,
|
package/dist/extensions.d.ts
CHANGED
package/dist/extensions.js
CHANGED
|
@@ -78,19 +78,27 @@ class ExtensionManager {
|
|
|
78
78
|
this.reloadQueue = new job_queue_1.JobQueue();
|
|
79
79
|
}
|
|
80
80
|
async initialize(options = {}) {
|
|
81
|
+
const prevOptions = this.options;
|
|
81
82
|
this.options = {
|
|
82
83
|
...defaultOptions,
|
|
83
84
|
...options,
|
|
84
85
|
};
|
|
85
|
-
this.
|
|
86
|
+
if (!prevOptions.watch && this.options.watch) {
|
|
87
|
+
this.initializeWatcher();
|
|
88
|
+
}
|
|
89
|
+
else if (prevOptions.watch && !this.options.watch) {
|
|
90
|
+
await this.closeWatcher();
|
|
91
|
+
}
|
|
86
92
|
if (!this.isLoaded) {
|
|
87
93
|
await this.load();
|
|
88
|
-
this.updateWatchedExtensions(this.extensions);
|
|
89
94
|
const loadedExtensions = this.getExtensionsList();
|
|
90
95
|
if (loadedExtensions.length > 0) {
|
|
91
96
|
logger_1.default.info(`Loaded extensions: ${loadedExtensions.join(', ')}`);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
99
|
+
if (!prevOptions.watch && this.options.watch) {
|
|
100
|
+
this.updateWatchedExtensions(this.extensions);
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
103
|
reload() {
|
|
96
104
|
this.reloadQueue.enqueue(async () => {
|
|
@@ -158,7 +166,7 @@ class ExtensionManager {
|
|
|
158
166
|
this.isLoaded = false;
|
|
159
167
|
}
|
|
160
168
|
initializeWatcher() {
|
|
161
|
-
if (
|
|
169
|
+
if (!this.watcher) {
|
|
162
170
|
logger_1.default.info('Watching extensions for changes...');
|
|
163
171
|
const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_OR_HYBRID_EXTENSION_TYPES).flatMap((type) => {
|
|
164
172
|
const typeDir = path_1.default.posix.join(path_1.default.relative('.', env_1.default.EXTENSIONS_PATH).split(path_1.default.sep).join(path_1.default.posix.sep), (0, utils_1.pluralize)(type));
|
|
@@ -175,6 +183,11 @@ class ExtensionManager {
|
|
|
175
183
|
.on('unlink', () => this.reload());
|
|
176
184
|
}
|
|
177
185
|
}
|
|
186
|
+
async closeWatcher() {
|
|
187
|
+
if (this.watcher) {
|
|
188
|
+
await this.watcher.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
178
191
|
updateWatchedExtensions(added, removed = []) {
|
|
179
192
|
if (this.watcher) {
|
|
180
193
|
const toPackageExtensionPaths = (extensions) => extensions
|
package/dist/flows.js
CHANGED
|
@@ -119,7 +119,7 @@ class FlowManager {
|
|
|
119
119
|
return handler(data, context);
|
|
120
120
|
}
|
|
121
121
|
async load() {
|
|
122
|
-
var _a, _b, _c, _d
|
|
122
|
+
var _a, _b, _c, _d;
|
|
123
123
|
const flowsService = new services_1.FlowsService({ knex: (0, database_1.default)(), schema: await (0, get_schema_1.getSchema)() });
|
|
124
124
|
const flows = await flowsService.readByQuery({
|
|
125
125
|
filter: { status: { _eq: 'active' } },
|
|
@@ -129,19 +129,23 @@ class FlowManager {
|
|
|
129
129
|
const flowTrees = flows.map((flow) => (0, construct_flow_tree_1.constructFlowTree)(flow));
|
|
130
130
|
for (const flow of flowTrees) {
|
|
131
131
|
if (flow.trigger === 'event') {
|
|
132
|
-
const events = (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
132
|
+
const events = ((_a = flow.options) === null || _a === void 0 ? void 0 : _a.scope)
|
|
133
|
+
? (0, utils_1.toArray)(flow.options.scope)
|
|
134
|
+
.map((scope) => {
|
|
135
|
+
var _a, _b, _c;
|
|
136
|
+
if (['items.create', 'items.update', 'items.delete'].includes(scope)) {
|
|
137
|
+
return ((_c = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.collections) === null || _b === void 0 ? void 0 : _b.map((collection) => {
|
|
138
|
+
if (collection.startsWith('directus_')) {
|
|
139
|
+
const action = scope.split('.')[1];
|
|
140
|
+
return collection.substring(9) + '.' + action;
|
|
141
|
+
}
|
|
142
|
+
return `${collection}.${scope}`;
|
|
143
|
+
})) !== null && _c !== void 0 ? _c : []);
|
|
144
|
+
}
|
|
145
|
+
return scope;
|
|
146
|
+
})
|
|
147
|
+
.flat()
|
|
148
|
+
: [];
|
|
145
149
|
if (flow.options.type === 'filter') {
|
|
146
150
|
const handler = (payload, meta, context) => this.executeFlow(flow, { payload, ...meta }, {
|
|
147
151
|
accountability: context.accountability,
|
|
@@ -196,9 +200,9 @@ class FlowManager {
|
|
|
196
200
|
return this.executeFlow(flow, data, context);
|
|
197
201
|
}
|
|
198
202
|
};
|
|
199
|
-
const method = (
|
|
203
|
+
const method = (_c = (_b = flow.options) === null || _b === void 0 ? void 0 : _b.method) !== null && _c !== void 0 ? _c : 'GET';
|
|
200
204
|
// Default return to $last for webhooks
|
|
201
|
-
flow.options.return = (
|
|
205
|
+
flow.options.return = (_d = flow.options.return) !== null && _d !== void 0 ? _d : '$last';
|
|
202
206
|
this.webhookFlowHandlers[`${method}-${flow.id}`] = handler;
|
|
203
207
|
}
|
|
204
208
|
else if (flow.trigger === 'manual') {
|
|
@@ -254,7 +258,7 @@ class FlowManager {
|
|
|
254
258
|
this.isLoaded = false;
|
|
255
259
|
}
|
|
256
260
|
async executeFlow(flow, data = null, context = {}) {
|
|
257
|
-
var _a, _b, _c, _d, _e, _f;
|
|
261
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
258
262
|
const database = (_a = context.database) !== null && _a !== void 0 ? _a : (0, database_1.default)();
|
|
259
263
|
const schema = (_b = context.schema) !== null && _b !== void 0 ? _b : (await (0, get_schema_1.getSchema)({ database }));
|
|
260
264
|
const keyedData = {
|
|
@@ -263,11 +267,13 @@ class FlowManager {
|
|
|
263
267
|
[ACCOUNTABILITY_KEY]: (_c = context === null || context === void 0 ? void 0 : context.accountability) !== null && _c !== void 0 ? _c : null,
|
|
264
268
|
};
|
|
265
269
|
let nextOperation = flow.operation;
|
|
270
|
+
let lastOperationStatus = 'unknown';
|
|
266
271
|
const steps = [];
|
|
267
272
|
while (nextOperation !== null) {
|
|
268
273
|
const { successor, data, status, options } = await this.executeOperation(nextOperation, keyedData, context);
|
|
269
274
|
keyedData[nextOperation.key] = data;
|
|
270
275
|
keyedData[LAST_KEY] = data;
|
|
276
|
+
lastOperationStatus = status;
|
|
271
277
|
steps.push({ operation: nextOperation.id, key: nextOperation.key, status, options });
|
|
272
278
|
nextOperation = successor;
|
|
273
279
|
}
|
|
@@ -283,6 +289,7 @@ class FlowManager {
|
|
|
283
289
|
collection: 'directus_flows',
|
|
284
290
|
ip: (_e = accountability === null || accountability === void 0 ? void 0 : accountability.ip) !== null && _e !== void 0 ? _e : null,
|
|
285
291
|
user_agent: (_f = accountability === null || accountability === void 0 ? void 0 : accountability.userAgent) !== null && _f !== void 0 ? _f : null,
|
|
292
|
+
origin: (_g = accountability === null || accountability === void 0 ? void 0 : accountability.origin) !== null && _g !== void 0 ? _g : null,
|
|
286
293
|
item: flow.id,
|
|
287
294
|
});
|
|
288
295
|
if (flow.accountability === 'all') {
|
|
@@ -301,6 +308,9 @@ class FlowManager {
|
|
|
301
308
|
});
|
|
302
309
|
}
|
|
303
310
|
}
|
|
311
|
+
if (flow.trigger === 'event' && flow.options.type === 'filter' && lastOperationStatus === 'reject') {
|
|
312
|
+
throw keyedData[LAST_KEY];
|
|
313
|
+
}
|
|
304
314
|
if (flow.options.return === '$all') {
|
|
305
315
|
return keyedData;
|
|
306
316
|
}
|
package/dist/mailer.js
CHANGED
|
@@ -37,6 +37,7 @@ function getMailer() {
|
|
|
37
37
|
}
|
|
38
38
|
const tls = (0, get_config_from_env_1.getConfigFromEnv)('EMAIL_SMTP_TLS_');
|
|
39
39
|
transporter = nodemailer_1.default.createTransport({
|
|
40
|
+
name: env_1.default.EMAIL_SMTP_NAME,
|
|
40
41
|
pool: env_1.default.EMAIL_SMTP_POOL,
|
|
41
42
|
host: env_1.default.EMAIL_SMTP_HOST,
|
|
42
43
|
port: env_1.default.EMAIL_SMTP_PORT,
|
|
@@ -56,6 +57,12 @@ function getMailer() {
|
|
|
56
57
|
host: env_1.default.EMAIL_MAILGUN_HOST || 'api.mailgun.net',
|
|
57
58
|
}));
|
|
58
59
|
}
|
|
60
|
+
else if (transportName === 'sendgrid') {
|
|
61
|
+
const sg = require('nodemailer-sendgrid');
|
|
62
|
+
transporter = nodemailer_1.default.createTransport(sg({
|
|
63
|
+
apiKey: env_1.default.EMAIL_SENDGRID_API_KEY,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
59
66
|
else {
|
|
60
67
|
logger_1.default.warn('Illegal transport given for email. Check the EMAIL_TRANSPORT env var.');
|
|
61
68
|
}
|
|
@@ -3,5 +3,5 @@ import { NextFunction, Request, Response } from 'express';
|
|
|
3
3
|
* Verify the passed JWT and assign the user ID and role to `req`
|
|
4
4
|
*/
|
|
5
5
|
export declare const handler: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
6
|
-
declare const _default:
|
|
6
|
+
declare const _default: (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: NextFunction) => Promise<void>;
|
|
7
7
|
export default _default;
|
|
@@ -24,6 +24,7 @@ const handler = async (req, res, next) => {
|
|
|
24
24
|
app: false,
|
|
25
25
|
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
26
26
|
userAgent: req.get('user-agent'),
|
|
27
|
+
origin: req.get('origin'),
|
|
27
28
|
};
|
|
28
29
|
const database = (0, database_1.default)();
|
|
29
30
|
const customAccountability = await emitter_1.default.emitFilter('authenticate', defaultAccountability, {
|
|
@@ -34,7 +34,16 @@ test('Short-circuits when authenticate filter is used', async () => {
|
|
|
34
34
|
test('Uses default public accountability when no token is given', async () => {
|
|
35
35
|
const req = {
|
|
36
36
|
ip: '127.0.0.1',
|
|
37
|
-
get: jest.fn((string) =>
|
|
37
|
+
get: jest.fn((string) => {
|
|
38
|
+
switch (string) {
|
|
39
|
+
case 'user-agent':
|
|
40
|
+
return 'fake-user-agent';
|
|
41
|
+
case 'origin':
|
|
42
|
+
return 'fake-origin';
|
|
43
|
+
default:
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
38
47
|
};
|
|
39
48
|
const res = {};
|
|
40
49
|
const next = jest.fn();
|
|
@@ -47,6 +56,7 @@ test('Uses default public accountability when no token is given', async () => {
|
|
|
47
56
|
app: false,
|
|
48
57
|
ip: '127.0.0.1',
|
|
49
58
|
userAgent: 'fake-user-agent',
|
|
59
|
+
origin: 'fake-origin',
|
|
50
60
|
});
|
|
51
61
|
expect(next).toHaveBeenCalledTimes(1);
|
|
52
62
|
});
|
|
@@ -70,7 +80,16 @@ test('Sets accountability to payload contents if valid token is passed', async (
|
|
|
70
80
|
}, env_1.default.SECRET, { issuer: 'directus' });
|
|
71
81
|
const req = {
|
|
72
82
|
ip: '127.0.0.1',
|
|
73
|
-
get: jest.fn((string) =>
|
|
83
|
+
get: jest.fn((string) => {
|
|
84
|
+
switch (string) {
|
|
85
|
+
case 'user-agent':
|
|
86
|
+
return 'fake-user-agent';
|
|
87
|
+
case 'origin':
|
|
88
|
+
return 'fake-origin';
|
|
89
|
+
default:
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
74
93
|
token,
|
|
75
94
|
};
|
|
76
95
|
const res = {};
|
|
@@ -85,6 +104,7 @@ test('Sets accountability to payload contents if valid token is passed', async (
|
|
|
85
104
|
share_scope: shareScope,
|
|
86
105
|
ip: '127.0.0.1',
|
|
87
106
|
userAgent: 'fake-user-agent',
|
|
107
|
+
origin: 'fake-origin',
|
|
88
108
|
});
|
|
89
109
|
expect(next).toHaveBeenCalledTimes(1);
|
|
90
110
|
// Test with 1/0 instead or true/false
|
|
@@ -107,6 +127,7 @@ test('Sets accountability to payload contents if valid token is passed', async (
|
|
|
107
127
|
share_scope: shareScope,
|
|
108
128
|
ip: '127.0.0.1',
|
|
109
129
|
userAgent: 'fake-user-agent',
|
|
130
|
+
origin: 'fake-origin',
|
|
110
131
|
});
|
|
111
132
|
expect(next).toHaveBeenCalledTimes(1);
|
|
112
133
|
});
|
|
@@ -120,7 +141,16 @@ test('Throws InvalidCredentialsException when static token is used, but user doe
|
|
|
120
141
|
});
|
|
121
142
|
const req = {
|
|
122
143
|
ip: '127.0.0.1',
|
|
123
|
-
get: jest.fn((string) =>
|
|
144
|
+
get: jest.fn((string) => {
|
|
145
|
+
switch (string) {
|
|
146
|
+
case 'user-agent':
|
|
147
|
+
return 'fake-user-agent';
|
|
148
|
+
case 'origin':
|
|
149
|
+
return 'fake-origin';
|
|
150
|
+
default:
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
124
154
|
token: 'static-token',
|
|
125
155
|
};
|
|
126
156
|
const res = {};
|
|
@@ -131,7 +161,16 @@ test('Throws InvalidCredentialsException when static token is used, but user doe
|
|
|
131
161
|
test('Sets accountability to user information when static token is used', async () => {
|
|
132
162
|
const req = {
|
|
133
163
|
ip: '127.0.0.1',
|
|
134
|
-
get: jest.fn((string) =>
|
|
164
|
+
get: jest.fn((string) => {
|
|
165
|
+
switch (string) {
|
|
166
|
+
case 'user-agent':
|
|
167
|
+
return 'fake-user-agent';
|
|
168
|
+
case 'origin':
|
|
169
|
+
return 'fake-origin';
|
|
170
|
+
default:
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
135
174
|
token: 'static-token',
|
|
136
175
|
};
|
|
137
176
|
const res = {};
|
|
@@ -144,6 +183,7 @@ test('Sets accountability to user information when static token is used', async
|
|
|
144
183
|
admin: testUser.admin_access,
|
|
145
184
|
ip: '127.0.0.1',
|
|
146
185
|
userAgent: 'fake-user-agent',
|
|
186
|
+
origin: 'fake-origin',
|
|
147
187
|
};
|
|
148
188
|
jest.mocked(database_1.default).mockReturnValue({
|
|
149
189
|
select: jest.fn().mockReturnThis(),
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const validateBatch: (scope: 'read' | 'update' | 'delete') => RequestHandler;
|
|
1
|
+
export declare const validateBatch: (scope: 'read' | 'update' | 'delete') => (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: import("express").Response<any, Record<string, any>>, next: import("express").NextFunction) => Promise<void>;
|