directus 9.15.0 → 9.16.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/__utils__/items-utils.d.ts +2 -0
- package/dist/__utils__/items-utils.js +36 -0
- package/dist/__utils__/schemas.d.ts +13 -0
- package/dist/__utils__/schemas.js +304 -0
- package/dist/__utils__/snapshots.d.ts +5 -0
- package/dist/__utils__/snapshots.js +897 -0
- package/dist/app.js +1 -0
- package/dist/cli/index.test.d.ts +1 -0
- package/dist/cli/index.test.js +58 -0
- package/dist/cli/utils/create-env/env-stub.liquid +2 -2
- package/dist/controllers/assets.js +20 -16
- package/dist/controllers/files.test.d.ts +1 -0
- package/dist/controllers/files.test.js +49 -0
- package/dist/controllers/server.js +0 -1
- package/dist/database/migrations/run.test.d.ts +1 -0
- package/dist/database/migrations/run.test.js +92 -0
- package/dist/env.js +8 -0
- package/dist/env.test.d.ts +8 -0
- package/dist/env.test.js +39 -0
- package/dist/flows.js +2 -1
- package/dist/middleware/authenticate.test.d.ts +1 -0
- package/dist/middleware/authenticate.test.js +174 -0
- package/dist/middleware/extract-token.test.d.ts +1 -0
- package/dist/middleware/extract-token.test.js +60 -0
- package/dist/operations/exec/index.d.ts +5 -0
- package/dist/operations/exec/index.js +26 -0
- package/dist/operations/exec/index.test.d.ts +1 -0
- package/dist/operations/exec/index.test.js +95 -0
- package/dist/operations/notification/index.js +9 -6
- package/dist/operations/request/index.js +22 -3
- package/dist/services/files.js +3 -2
- package/dist/services/files.test.d.ts +1 -0
- package/dist/services/files.test.js +53 -0
- package/dist/services/flows.js +4 -0
- package/dist/services/graphql/index.d.ts +2 -2
- package/dist/services/graphql/index.js +33 -38
- package/dist/services/items.js +83 -39
- package/dist/services/items.test.d.ts +1 -0
- package/dist/services/items.test.js +765 -0
- package/dist/services/payload.d.ts +7 -4
- package/dist/services/payload.js +35 -8
- package/dist/services/payload.test.d.ts +1 -0
- package/dist/services/payload.test.js +94 -0
- package/dist/services/server.js +5 -3
- package/dist/services/specifications.test.d.ts +1 -0
- package/dist/services/specifications.test.js +96 -0
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-query.js +8 -3
- package/dist/utils/apply-snapshot.js +15 -0
- package/dist/utils/apply-snapshot.test.d.ts +1 -0
- package/dist/utils/apply-snapshot.test.js +305 -0
- package/dist/utils/calculate-field-depth.test.d.ts +1 -0
- package/dist/utils/calculate-field-depth.test.js +76 -0
- package/dist/utils/filter-items.test.d.ts +1 -0
- package/dist/utils/filter-items.test.js +60 -0
- package/dist/utils/get-cache-key.test.d.ts +1 -0
- package/dist/utils/get-cache-key.test.js +53 -0
- package/dist/utils/get-column-path.test.d.ts +1 -0
- package/dist/utils/get-column-path.test.js +136 -0
- package/dist/utils/get-config-from-env.test.d.ts +1 -0
- package/dist/utils/get-config-from-env.test.js +19 -0
- package/dist/utils/get-graphql-type.d.ts +1 -1
- package/dist/utils/get-graphql-type.js +4 -1
- package/dist/utils/get-os-info.d.ts +9 -0
- package/dist/utils/get-os-info.js +47 -0
- package/dist/utils/get-relation-info.test.d.ts +1 -0
- package/dist/utils/get-relation-info.test.js +88 -0
- package/dist/utils/get-relation-type.test.d.ts +1 -0
- package/dist/utils/get-relation-type.test.js +69 -0
- package/dist/utils/get-string-byte-size.test.d.ts +1 -0
- package/dist/utils/get-string-byte-size.test.js +8 -0
- package/dist/utils/is-directus-jwt.test.d.ts +1 -0
- package/dist/utils/is-directus-jwt.test.js +26 -0
- package/dist/utils/jwt.test.d.ts +1 -0
- package/dist/utils/jwt.test.js +36 -0
- package/dist/utils/merge-permissions.test.d.ts +1 -0
- package/dist/utils/merge-permissions.test.js +80 -0
- package/dist/utils/validate-keys.test.d.ts +1 -0
- package/dist/utils/validate-keys.test.js +97 -0
- package/package.json +11 -11
package/dist/app.js
CHANGED
|
@@ -190,6 +190,7 @@ async function createApp() {
|
|
|
190
190
|
if (env_1.default.RATE_LIMITER_ENABLED === true) {
|
|
191
191
|
app.use(rate_limiter_1.default);
|
|
192
192
|
}
|
|
193
|
+
app.get('/server/ping', (req, res) => res.send('pong'));
|
|
193
194
|
app.use(authenticate_1.default);
|
|
194
195
|
app.use(check_ip_1.checkIP);
|
|
195
196
|
app.use(sanitize_query_1.default);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
jest.mock('../../src/env', () => ({
|
|
5
|
+
...jest.requireActual('../../src/env').default,
|
|
6
|
+
EXTENSIONS_PATH: '',
|
|
7
|
+
SERVE_APP: false,
|
|
8
|
+
DB_CLIENT: 'pg',
|
|
9
|
+
DB_HOST: 'localhost',
|
|
10
|
+
DB_PORT: 5432,
|
|
11
|
+
DB_DATABASE: 'directus',
|
|
12
|
+
DB_USER: 'postgres',
|
|
13
|
+
DB_PASSWORD: 'psql1234',
|
|
14
|
+
}));
|
|
15
|
+
jest.mock('@directus/shared/utils/node/get-extensions', () => ({
|
|
16
|
+
getPackageExtensions: jest.fn(() => Promise.resolve([])),
|
|
17
|
+
getLocalExtensions: jest.fn(() => Promise.resolve([customCliExtension])),
|
|
18
|
+
}));
|
|
19
|
+
jest.mock(`/hooks/custom-cli/index.js`, () => customCliHook, { virtual: true });
|
|
20
|
+
const customCliExtension = {
|
|
21
|
+
path: `/hooks/custom-cli`,
|
|
22
|
+
name: 'custom-cli',
|
|
23
|
+
type: 'hook',
|
|
24
|
+
entrypoint: 'index.js',
|
|
25
|
+
local: true,
|
|
26
|
+
};
|
|
27
|
+
const beforeHook = jest.fn();
|
|
28
|
+
const afterAction = jest.fn();
|
|
29
|
+
const afterHook = jest.fn(({ program }) => {
|
|
30
|
+
program.command('custom').action(afterAction);
|
|
31
|
+
});
|
|
32
|
+
const customCliHook = ({ init }) => {
|
|
33
|
+
init('cli.before', beforeHook);
|
|
34
|
+
init('cli.after', afterHook);
|
|
35
|
+
};
|
|
36
|
+
const writeOut = jest.fn();
|
|
37
|
+
const writeErr = jest.fn();
|
|
38
|
+
const setup = async () => {
|
|
39
|
+
const program = await (0, index_1.createCli)();
|
|
40
|
+
program.exitOverride();
|
|
41
|
+
program.configureOutput({ writeOut, writeErr });
|
|
42
|
+
return program;
|
|
43
|
+
};
|
|
44
|
+
beforeEach(jest.clearAllMocks);
|
|
45
|
+
describe('cli hooks', () => {
|
|
46
|
+
test('should call hooks before and after creating the cli', async () => {
|
|
47
|
+
const program = await setup();
|
|
48
|
+
expect(beforeHook).toHaveBeenCalledTimes(1);
|
|
49
|
+
expect(beforeHook).toHaveBeenCalledWith({ event: 'cli.before', program });
|
|
50
|
+
expect(afterHook).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(afterHook).toHaveBeenCalledWith({ event: 'cli.after', program });
|
|
52
|
+
});
|
|
53
|
+
test('should be able to add a custom cli command', async () => {
|
|
54
|
+
const program = await setup();
|
|
55
|
+
program.parseAsync(['custom'], { from: 'user' });
|
|
56
|
+
expect(afterAction).toHaveBeenCalledTimes(1);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -296,10 +296,10 @@ EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
|
296
296
|
## Email (Sendmail Transport)
|
|
297
297
|
|
|
298
298
|
# What new line style to use in sendmail ["unix"]
|
|
299
|
-
EMAIL_SENDMAIL_NEW_LINE="unix"
|
|
299
|
+
# EMAIL_SENDMAIL_NEW_LINE="unix"
|
|
300
300
|
|
|
301
301
|
# Path to your sendmail executable ["/usr/sbin/sendmail"]
|
|
302
|
-
EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
302
|
+
# EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
303
303
|
|
|
304
304
|
## Email (SMTP Transport)
|
|
305
305
|
# EMAIL_SMTP_HOST="localhost"
|
|
@@ -148,27 +148,31 @@ router.get('/:pk/:filename?',
|
|
|
148
148
|
res.setHeader('Content-Length', stat.size);
|
|
149
149
|
return res.end();
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
let isDataSent = false;
|
|
152
|
+
stream.on('data', (chunk) => {
|
|
153
|
+
isDataSent = true;
|
|
154
|
+
res.write(chunk);
|
|
155
|
+
});
|
|
152
156
|
stream.on('end', () => {
|
|
153
|
-
res.
|
|
157
|
+
res.end();
|
|
154
158
|
});
|
|
155
159
|
stream.on('error', (e) => {
|
|
156
160
|
logger_1.default.error(e, `Couldn't stream file ${file.id} to the client`);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
if (!isDataSent) {
|
|
162
|
+
res.removeHeader('Content-Type');
|
|
163
|
+
res.removeHeader('Content-Disposition');
|
|
164
|
+
res.removeHeader('Cache-Control');
|
|
165
|
+
res.status(500).json({
|
|
166
|
+
errors: [
|
|
167
|
+
{
|
|
168
|
+
message: 'An unexpected error occurred.',
|
|
169
|
+
extensions: {
|
|
170
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
171
|
+
},
|
|
168
172
|
},
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
172
176
|
});
|
|
173
177
|
}));
|
|
174
178
|
exports.default = router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
jest.mock('../../src/cache');
|
|
8
|
+
jest.mock('../../src/database');
|
|
9
|
+
jest.mock('../../src/utils/validate-env');
|
|
10
|
+
const files_1 = require("./files");
|
|
11
|
+
const invalid_payload_1 = require("../exceptions/invalid-payload");
|
|
12
|
+
const stream_1 = require("stream");
|
|
13
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
14
|
+
describe('multipartHandler', () => {
|
|
15
|
+
it(`Errors out if request doesn't contain any files to upload`, () => {
|
|
16
|
+
const fakeForm = new form_data_1.default();
|
|
17
|
+
fakeForm.append('field', 'test');
|
|
18
|
+
const req = {
|
|
19
|
+
headers: fakeForm.getHeaders(),
|
|
20
|
+
is: jest.fn().mockReturnValue(true),
|
|
21
|
+
body: fakeForm.getBuffer(),
|
|
22
|
+
params: {},
|
|
23
|
+
pipe: (input) => stream.pipe(input),
|
|
24
|
+
};
|
|
25
|
+
const stream = new stream_1.PassThrough();
|
|
26
|
+
stream.push(fakeForm.getBuffer());
|
|
27
|
+
(0, files_1.multipartHandler)(req, {}, (err) => {
|
|
28
|
+
expect(err.message).toBe('No files where included in the body');
|
|
29
|
+
expect(err).toBeInstanceOf(invalid_payload_1.InvalidPayloadException);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it(`Errors out if uploaded file doesn't include the filename`, () => {
|
|
33
|
+
const fakeForm = new form_data_1.default();
|
|
34
|
+
fakeForm.append('file', Buffer.from('<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 243 266" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g id="Calligraphy"><path d="M67.097,135.868c0,3.151 0.598,14.121 -11.586,14.121c-17.076,0 -15.95,-12.947 -15.95,-12.947c0,-2.521 4.597,-5.638 4.597,-7.318c0,-0.63 0.041,-3.519 -2.27,-3.519c-5.671,0 -5.671,10.083 -5.671,10.083c0,0 0.419,15.336 19.116,15.336c20.205,0 30.04,-23.712 30.04,-30.88c0,-18.197 -51.112,-27.701 -51.112,-57.949c0,1.575 -2.205,-13.864 14.18,-13.864c28.358,0 44.426,42.536 44.426,71.524c0,28.988 -16.699,55.455 -16.699,55.455c0,0 33.4,-25.837 33.4,-76.25c0,-70.264 -46.003,-69.634 -46.003,-69.634c-4.792,0 -7.602,-0.241 -28.398,20.555c-20.797,20.797 -17.646,29.83 -17.646,29.83c0,31.93 49.576,32.35 49.576,55.457Z" style="fill-rule:nonzero;"/><path d="M241.886,174.861c-1.602,-9.142 -15.448,-9.916 -22.675,-9.682c-0.7,-0.003 -1.172,0.02 -1.327,0.03c-8.804,0.01 -19.314,4.179 -33.072,13.115c-3.554,2.308 -7.19,4.847 -10.902,7.562c-6.979,-31.39 -13.852,-63.521 -28.033,-63.521c20.415,-20.119 22.19,-16.272 22.19,-39.054c0,-11.244 14.498,-21.35 14.498,-21.35l-0.296,-2.024c-19.193,5.304 -37.307,-8.577 -42.2,-12.755c5.375,-9.663 9.584,-12.565 9.584,-12.565c1.891,-20.377 15.965,-27.31 15.965,-27.31c1.681,-4.201 6.092,-7.142 6.092,-7.142c-70.162,22.267 -54.247,189.298 -54.247,189.298c-0.475,-55.91 5.238,-92.242 11.977,-115.55c9.094,8.248 24.425,11.765 24.425,11.765c-7.396,3.55 -5.324,12.13 -5.324,19.527c0,7.397 -3.848,10.651 -3.848,10.651l-21.893,22.782c17.043,0.294 23.638,31.657 30.689,63.056c-2.548,2.042 -5.125,4.12 -7.728,6.219c-16.396,13.223 -33.351,26.897 -50.266,37.354c-19.086,11.797 -35.151,17.533 -49.116,17.533c-25.25,0 -44.118,-24.368 -44.118,-46.154c0,-9.838 3.227,-17.831 5.935,-22.805c2.935,-5.39 5.911,-8.503 5.967,-8.561c0.001,0 0.001,0 0.001,-0.001l-0.013,-0.012c1.803,-1.885 4.841,-5.181 10.423,-5.181c20.715,0 27.475,40.776 55.603,40.776c24.857,0 31.834,-20.497 37.286,-31.399c0,0 -8.94,11.12 -21.587,11.12c-27.038,0 -35.323,-40.557 -55.166,-40.557c-13.41,0 -22.743,15.506 -31.029,27.281c0,0 0.018,-0.001 0.048,-0.003c-1.02,1.415 -2.214,3.233 -3.41,5.425c-2.847,5.21 -6.239,13.587 -6.239,23.917c0,22.816 19.801,48.334 46.299,48.334c14.381,0 30.822,-5.84 50.262,-17.858c17.033,-10.529 34.04,-24.246 50.489,-37.511c2.309,-1.862 4.607,-3.715 6.891,-5.549c6.952,30.814 14.606,60.912 33.278,60.912c14.794,0 26.923,-25.445 26.923,-25.445c-7.987,7.101 -13.313,5.621 -13.313,5.621c-13.139,0.379 -19.937,-27.594 -26.48,-56.931c16.455,-12.099 31.46,-20.829 43.488,-20.829l0.072,-0.003c0.082,-0.005 5.246,-0.305 9.957,1.471c-2.95,1.636 -4.947,4.782 -4.947,8.394c0,5.299 4.296,9.594 9.594,9.594c5.298,0 9.594,-4.295 9.594,-9.594c0,-0.826 -0.104,-1.627 -0.301,-2.391Z" style="fill-rule:nonzero;"/></g></svg>'));
|
|
35
|
+
const req = {
|
|
36
|
+
headers: fakeForm.getHeaders(),
|
|
37
|
+
is: jest.fn().mockReturnValue(true),
|
|
38
|
+
body: fakeForm.getBuffer(),
|
|
39
|
+
params: {},
|
|
40
|
+
pipe: (input) => stream.pipe(input),
|
|
41
|
+
};
|
|
42
|
+
const stream = new stream_1.PassThrough();
|
|
43
|
+
stream.push(fakeForm.getBuffer());
|
|
44
|
+
(0, files_1.multipartHandler)(req, {}, (err) => {
|
|
45
|
+
expect(err.message).toBe('File is missing filename');
|
|
46
|
+
expect(err).toBeInstanceOf(invalid_payload_1.InvalidPayloadException);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -36,7 +36,6 @@ router.get('/specs/graphql/:scope?', (0, async_handler_1.default)(async (req, re
|
|
|
36
36
|
res.attachment(filename);
|
|
37
37
|
res.send(result);
|
|
38
38
|
}));
|
|
39
|
-
router.get('/ping', (req, res) => res.send('pong'));
|
|
40
39
|
router.get('/info', (0, async_handler_1.default)(async (req, res, next) => {
|
|
41
40
|
const service = new services_1.ServerService({
|
|
42
41
|
accountability: req.accountability,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const knex_1 = __importDefault(require("knex"));
|
|
7
|
+
const knex_mock_client_1 = require("knex-mock-client");
|
|
8
|
+
const run_1 = __importDefault(require("./run"));
|
|
9
|
+
describe('run', () => {
|
|
10
|
+
let db;
|
|
11
|
+
let tracker;
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
db = (0, knex_1.default)({ client: knex_mock_client_1.MockClient });
|
|
14
|
+
tracker = (0, knex_mock_client_1.getTracker)();
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
tracker.reset();
|
|
18
|
+
});
|
|
19
|
+
describe('when passed the argument up', () => {
|
|
20
|
+
it('returns "Nothing To Upgrade" if no directus_migrations', async () => {
|
|
21
|
+
tracker.on.select('directus_migrations').response(['Empty']);
|
|
22
|
+
await (0, run_1.default)(db, 'up').catch((e) => {
|
|
23
|
+
expect(e).toBeInstanceOf(Error);
|
|
24
|
+
expect(e.message).toBe('Nothing to upgrade');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it('returns "Method implemented in the dialect driver" if no directus_migrations', async () => {
|
|
28
|
+
tracker.on.select('directus_migrations').response([]);
|
|
29
|
+
await (0, run_1.default)(db, 'up').catch((e) => {
|
|
30
|
+
expect(e).toBeInstanceOf(Error);
|
|
31
|
+
expect(e.message).toBe('Method implemented in the dialect driver');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it('returns undefined if the migration is successful', async () => {
|
|
35
|
+
tracker.on.select('directus_migrations').response([
|
|
36
|
+
{
|
|
37
|
+
version: '20201028A',
|
|
38
|
+
name: 'Remove Collection Foreign Keys',
|
|
39
|
+
timestamp: '2021-11-27 11:36:56.471595-05',
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
tracker.on.delete('directus_relations').response([]);
|
|
43
|
+
tracker.on.insert('directus_migrations').response(['Remove System Relations', '20201029A']);
|
|
44
|
+
expect(await (0, run_1.default)(db, 'up')).toBe(undefined);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('when passed the argument down', () => {
|
|
48
|
+
it('returns "Nothing To downgrade" if no valid directus_migrations', async () => {
|
|
49
|
+
tracker.on.select('directus_migrations').response(['Empty']);
|
|
50
|
+
await (0, run_1.default)(db, 'down').catch((e) => {
|
|
51
|
+
expect(e).toBeInstanceOf(Error);
|
|
52
|
+
expect(e.message).toBe(`Couldn't find migration`);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it('returns "Method implemented in the dialect driver" if no directus_migrations', async () => {
|
|
56
|
+
tracker.on.select('directus_migrations').response([]);
|
|
57
|
+
await (0, run_1.default)(db, 'down').catch((e) => {
|
|
58
|
+
expect(e).toBeInstanceOf(Error);
|
|
59
|
+
expect(e.message).toBe('Nothing to downgrade');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it(`returns "Couldn't find migration" if an invalid migration object is supplied`, async () => {
|
|
63
|
+
tracker.on.select('directus_migrations').response([
|
|
64
|
+
{
|
|
65
|
+
version: '202018129A',
|
|
66
|
+
name: 'Fake Migration',
|
|
67
|
+
timestamp: '2020-00-32 11:36:56.471595-05',
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
await (0, run_1.default)(db, 'down').catch((e) => {
|
|
71
|
+
expect(e).toBeInstanceOf(Error);
|
|
72
|
+
expect(e.message).toBe(`Couldn't find migration`);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('when passed the argument latest', () => {
|
|
77
|
+
it('returns "Nothing To downgrade" if no valid directus_migrations', async () => {
|
|
78
|
+
tracker.on.select('directus_migrations').response(['Empty']);
|
|
79
|
+
await (0, run_1.default)(db, 'latest').catch((e) => {
|
|
80
|
+
expect(e).toBeInstanceOf(Error);
|
|
81
|
+
expect(e.message).toBe(`Method implemented in the dialect driver`);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
it('returns "Method implemented in the dialect driver" if no directus_migrations', async () => {
|
|
85
|
+
tracker.on.select('directus_migrations').response([]);
|
|
86
|
+
await (0, run_1.default)(db, 'latest').catch((e) => {
|
|
87
|
+
expect(e).toBeInstanceOf(Error);
|
|
88
|
+
expect(e.message).toBe('Method implemented in the dialect driver');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
package/dist/env.js
CHANGED
|
@@ -138,6 +138,13 @@ const allowedEnvironmentVars = [
|
|
|
138
138
|
// extensions
|
|
139
139
|
'EXTENSIONS_PATH',
|
|
140
140
|
'EXTENSIONS_AUTO_RELOAD',
|
|
141
|
+
// messenger
|
|
142
|
+
'MESSENGER_STORE',
|
|
143
|
+
'MESSENGER_NAMESPACE',
|
|
144
|
+
'MESSENGER_REDIS',
|
|
145
|
+
'MESSENGER_REDIS_HOST',
|
|
146
|
+
'MESSENGER_REDIS_PORT',
|
|
147
|
+
'MESSENGER_REDIS_PASSWORD',
|
|
141
148
|
// emails
|
|
142
149
|
'EMAIL_FROM',
|
|
143
150
|
'EMAIL_TRANSPORT',
|
|
@@ -226,6 +233,7 @@ const defaults = {
|
|
|
226
233
|
EXPORT_BATCH_SIZE: 5000,
|
|
227
234
|
FILE_METADATA_ALLOW_LIST: 'ifd0.Make,ifd0.Model,exif.FNumber,exif.ExposureTime,exif.FocalLength,exif.ISO',
|
|
228
235
|
GRAPHQL_INTROSPECTION: true,
|
|
236
|
+
FLOWS_EXEC_ALLOWED_MODULES: false,
|
|
229
237
|
};
|
|
230
238
|
// Allows us to force certain environment variable into a type, instead of relying
|
|
231
239
|
// on the auto-parsed type in processValues. ref #3705
|
package/dist/env.test.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const testEnv = {
|
|
3
|
+
NUMBER: '1234',
|
|
4
|
+
NUMBER_CAST_AS_STRING: 'string:1234',
|
|
5
|
+
REGEX: 'regex:\\.example\\.com$',
|
|
6
|
+
CSV: 'one,two,three,four',
|
|
7
|
+
CSV_CAST_AS_STRING: 'string:one,two,three,four',
|
|
8
|
+
MULTIPLE: 'array:string:https://example.com,regex:\\.example2\\.com$',
|
|
9
|
+
};
|
|
10
|
+
describe('env processed values', () => {
|
|
11
|
+
const originalEnv = process.env;
|
|
12
|
+
let env;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.resetModules();
|
|
15
|
+
process.env = { ...testEnv };
|
|
16
|
+
env = jest.requireActual('../src/env').default;
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
process.env = originalEnv;
|
|
20
|
+
});
|
|
21
|
+
test('Number value should be a number', () => {
|
|
22
|
+
expect(env.NUMBER).toStrictEqual(1234);
|
|
23
|
+
});
|
|
24
|
+
test('Number value casted as string should be a string', () => {
|
|
25
|
+
expect(env.NUMBER_CAST_AS_STRING).toStrictEqual('1234');
|
|
26
|
+
});
|
|
27
|
+
test('Value casted as regex', () => {
|
|
28
|
+
expect(env.REGEX).toBeInstanceOf(RegExp);
|
|
29
|
+
});
|
|
30
|
+
test('CSV value should be an array', () => {
|
|
31
|
+
expect(env.CSV).toStrictEqual(['one', 'two', 'three', 'four']);
|
|
32
|
+
});
|
|
33
|
+
test('CSV value casted as string should be a string', () => {
|
|
34
|
+
expect(env.CSV_CAST_AS_STRING).toStrictEqual('one,two,three,four');
|
|
35
|
+
});
|
|
36
|
+
test('Multiple type cast', () => {
|
|
37
|
+
expect(env.MULTIPLE).toStrictEqual(['https://example.com', /\.example2\.com$/]);
|
|
38
|
+
});
|
|
39
|
+
});
|
package/dist/flows.js
CHANGED
|
@@ -124,11 +124,12 @@ class FlowManager {
|
|
|
124
124
|
const flows = await flowsService.readByQuery({
|
|
125
125
|
filter: { status: { _eq: 'active' } },
|
|
126
126
|
fields: ['*', 'operations.*'],
|
|
127
|
+
limit: -1,
|
|
127
128
|
});
|
|
128
129
|
const flowTrees = flows.map((flow) => (0, construct_flow_tree_1.constructFlowTree)(flow));
|
|
129
130
|
for (const flow of flowTrees) {
|
|
130
131
|
if (flow.trigger === 'event') {
|
|
131
|
-
const events = (_d = (_c = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.scope) === null || _b === void 0 ? void 0 : _b.map((scope) => {
|
|
132
|
+
const events = (_d = (_c = (_b = (0, utils_1.toArray)((_a = flow.options) === null || _a === void 0 ? void 0 : _a.scope)) === null || _b === void 0 ? void 0 : _b.map((scope) => {
|
|
132
133
|
var _a, _b, _c;
|
|
133
134
|
if (['items.create', 'items.update', 'items.delete'].includes(scope)) {
|
|
134
135
|
return ((_c = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.collections) === null || _b === void 0 ? void 0 : _b.map((collection) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../../src/types/express.d.ts';
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const database_1 = __importDefault(require("../database"));
|
|
9
|
+
const emitter_1 = __importDefault(require("../emitter"));
|
|
10
|
+
const env_1 = __importDefault(require("../env"));
|
|
11
|
+
const exceptions_1 = require("../exceptions");
|
|
12
|
+
const authenticate_1 = require("./authenticate");
|
|
13
|
+
require("../../src/types/express.d.ts");
|
|
14
|
+
jest.mock('../../src/database');
|
|
15
|
+
jest.mock('../../src/env', () => ({
|
|
16
|
+
SECRET: 'test',
|
|
17
|
+
}));
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.resetAllMocks();
|
|
20
|
+
});
|
|
21
|
+
test('Short-circuits when authenticate filter is used', async () => {
|
|
22
|
+
const req = {
|
|
23
|
+
ip: '127.0.0.1',
|
|
24
|
+
get: jest.fn(),
|
|
25
|
+
};
|
|
26
|
+
const res = {};
|
|
27
|
+
const next = jest.fn();
|
|
28
|
+
const customAccountability = { admin: true };
|
|
29
|
+
jest.spyOn(emitter_1.default, 'emitFilter').mockResolvedValue(customAccountability);
|
|
30
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
31
|
+
expect(req.accountability).toEqual(customAccountability);
|
|
32
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
test('Uses default public accountability when no token is given', async () => {
|
|
35
|
+
const req = {
|
|
36
|
+
ip: '127.0.0.1',
|
|
37
|
+
get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)),
|
|
38
|
+
};
|
|
39
|
+
const res = {};
|
|
40
|
+
const next = jest.fn();
|
|
41
|
+
jest.spyOn(emitter_1.default, 'emitFilter').mockImplementation((_, payload) => payload);
|
|
42
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
43
|
+
expect(req.accountability).toEqual({
|
|
44
|
+
user: null,
|
|
45
|
+
role: null,
|
|
46
|
+
admin: false,
|
|
47
|
+
app: false,
|
|
48
|
+
ip: '127.0.0.1',
|
|
49
|
+
userAgent: 'fake-user-agent',
|
|
50
|
+
});
|
|
51
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
test('Sets accountability to payload contents if valid token is passed', async () => {
|
|
54
|
+
const userID = '3fac3c02-607f-4438-8d6e-6b8b25109b52';
|
|
55
|
+
const roleID = '38269fc6-6eb6-475a-93cb-479d97f73039';
|
|
56
|
+
const share = 'ca0ad005-f4ad-4bfe-b428-419ee8784790';
|
|
57
|
+
const shareScope = {
|
|
58
|
+
collection: 'articles',
|
|
59
|
+
item: 15,
|
|
60
|
+
};
|
|
61
|
+
const appAccess = true;
|
|
62
|
+
const adminAccess = false;
|
|
63
|
+
const token = jsonwebtoken_1.default.sign({
|
|
64
|
+
id: userID,
|
|
65
|
+
role: roleID,
|
|
66
|
+
app_access: appAccess,
|
|
67
|
+
admin_access: adminAccess,
|
|
68
|
+
share,
|
|
69
|
+
share_scope: shareScope,
|
|
70
|
+
}, env_1.default.SECRET, { issuer: 'directus' });
|
|
71
|
+
const req = {
|
|
72
|
+
ip: '127.0.0.1',
|
|
73
|
+
get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)),
|
|
74
|
+
token,
|
|
75
|
+
};
|
|
76
|
+
const res = {};
|
|
77
|
+
const next = jest.fn();
|
|
78
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
79
|
+
expect(req.accountability).toEqual({
|
|
80
|
+
user: userID,
|
|
81
|
+
role: roleID,
|
|
82
|
+
app: appAccess,
|
|
83
|
+
admin: adminAccess,
|
|
84
|
+
share,
|
|
85
|
+
share_scope: shareScope,
|
|
86
|
+
ip: '127.0.0.1',
|
|
87
|
+
userAgent: 'fake-user-agent',
|
|
88
|
+
});
|
|
89
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
90
|
+
// Test with 1/0 instead or true/false
|
|
91
|
+
next.mockClear();
|
|
92
|
+
req.token = jsonwebtoken_1.default.sign({
|
|
93
|
+
id: userID,
|
|
94
|
+
role: roleID,
|
|
95
|
+
app_access: 1,
|
|
96
|
+
admin_access: 0,
|
|
97
|
+
share,
|
|
98
|
+
share_scope: shareScope,
|
|
99
|
+
}, env_1.default.SECRET, { issuer: 'directus' });
|
|
100
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
101
|
+
expect(req.accountability).toEqual({
|
|
102
|
+
user: userID,
|
|
103
|
+
role: roleID,
|
|
104
|
+
app: appAccess,
|
|
105
|
+
admin: adminAccess,
|
|
106
|
+
share,
|
|
107
|
+
share_scope: shareScope,
|
|
108
|
+
ip: '127.0.0.1',
|
|
109
|
+
userAgent: 'fake-user-agent',
|
|
110
|
+
});
|
|
111
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
test('Throws InvalidCredentialsException when static token is used, but user does not exist', async () => {
|
|
114
|
+
jest.mocked(database_1.default).mockReturnValue({
|
|
115
|
+
select: jest.fn().mockReturnThis(),
|
|
116
|
+
from: jest.fn().mockReturnThis(),
|
|
117
|
+
leftJoin: jest.fn().mockReturnThis(),
|
|
118
|
+
where: jest.fn().mockReturnThis(),
|
|
119
|
+
first: jest.fn().mockResolvedValue(undefined),
|
|
120
|
+
});
|
|
121
|
+
const req = {
|
|
122
|
+
ip: '127.0.0.1',
|
|
123
|
+
get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)),
|
|
124
|
+
token: 'static-token',
|
|
125
|
+
};
|
|
126
|
+
const res = {};
|
|
127
|
+
const next = jest.fn();
|
|
128
|
+
expect((0, authenticate_1.handler)(req, res, next)).rejects.toEqual(new exceptions_1.InvalidCredentialsException());
|
|
129
|
+
expect(next).toHaveBeenCalledTimes(0);
|
|
130
|
+
});
|
|
131
|
+
test('Sets accountability to user information when static token is used', async () => {
|
|
132
|
+
const req = {
|
|
133
|
+
ip: '127.0.0.1',
|
|
134
|
+
get: jest.fn((string) => (string === 'user-agent' ? 'fake-user-agent' : null)),
|
|
135
|
+
token: 'static-token',
|
|
136
|
+
};
|
|
137
|
+
const res = {};
|
|
138
|
+
const next = jest.fn();
|
|
139
|
+
const testUser = { id: 'test-id', role: 'test-role', admin_access: true, app_access: false };
|
|
140
|
+
const expectedAccountability = {
|
|
141
|
+
user: testUser.id,
|
|
142
|
+
role: testUser.role,
|
|
143
|
+
app: testUser.app_access,
|
|
144
|
+
admin: testUser.admin_access,
|
|
145
|
+
ip: '127.0.0.1',
|
|
146
|
+
userAgent: 'fake-user-agent',
|
|
147
|
+
};
|
|
148
|
+
jest.mocked(database_1.default).mockReturnValue({
|
|
149
|
+
select: jest.fn().mockReturnThis(),
|
|
150
|
+
from: jest.fn().mockReturnThis(),
|
|
151
|
+
leftJoin: jest.fn().mockReturnThis(),
|
|
152
|
+
where: jest.fn().mockReturnThis(),
|
|
153
|
+
first: jest.fn().mockResolvedValue(testUser),
|
|
154
|
+
});
|
|
155
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
156
|
+
expect(req.accountability).toEqual(expectedAccountability);
|
|
157
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
158
|
+
// Test for 0 / 1 instead of false / true
|
|
159
|
+
next.mockClear();
|
|
160
|
+
testUser.admin_access = 1;
|
|
161
|
+
testUser.app_access = 0;
|
|
162
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
163
|
+
expect(req.accountability).toEqual(expectedAccountability);
|
|
164
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
165
|
+
// Test for "1" / "0" instead of true / false
|
|
166
|
+
next.mockClear();
|
|
167
|
+
testUser.admin_access = '0';
|
|
168
|
+
testUser.app_access = '1';
|
|
169
|
+
expectedAccountability.admin = false;
|
|
170
|
+
expectedAccountability.app = true;
|
|
171
|
+
await (0, authenticate_1.handler)(req, res, next);
|
|
172
|
+
expect(req.accountability).toEqual(expectedAccountability);
|
|
173
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
174
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../../src/types/express.d.ts';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const extract_token_1 = __importDefault(require("../../src/middleware/extract-token"));
|
|
7
|
+
require("../../src/types/express.d.ts");
|
|
8
|
+
let mockRequest;
|
|
9
|
+
let mockResponse;
|
|
10
|
+
const nextFunction = jest.fn();
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockRequest = {};
|
|
13
|
+
mockResponse = {};
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
test('Token from query', () => {
|
|
17
|
+
mockRequest = {
|
|
18
|
+
query: {
|
|
19
|
+
access_token: 'test',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
(0, extract_token_1.default)(mockRequest, mockResponse, nextFunction);
|
|
23
|
+
expect(mockRequest.token).toBe('test');
|
|
24
|
+
expect(nextFunction).toBeCalledTimes(1);
|
|
25
|
+
});
|
|
26
|
+
test('Token from Authorization header (capitalized)', () => {
|
|
27
|
+
mockRequest = {
|
|
28
|
+
headers: {
|
|
29
|
+
authorization: 'Bearer test',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
(0, extract_token_1.default)(mockRequest, mockResponse, nextFunction);
|
|
33
|
+
expect(mockRequest.token).toBe('test');
|
|
34
|
+
expect(nextFunction).toBeCalledTimes(1);
|
|
35
|
+
});
|
|
36
|
+
test('Token from Authorization header (lowercase)', () => {
|
|
37
|
+
mockRequest = {
|
|
38
|
+
headers: {
|
|
39
|
+
authorization: 'bearer test',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
(0, extract_token_1.default)(mockRequest, mockResponse, nextFunction);
|
|
43
|
+
expect(mockRequest.token).toBe('test');
|
|
44
|
+
expect(nextFunction).toBeCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
test('Ignore the token if authorization header is too many parts', () => {
|
|
47
|
+
mockRequest = {
|
|
48
|
+
headers: {
|
|
49
|
+
authorization: 'bearer test what another one',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
(0, extract_token_1.default)(mockRequest, mockResponse, nextFunction);
|
|
53
|
+
expect(mockRequest.token).toBeNull();
|
|
54
|
+
expect(nextFunction).toBeCalledTimes(1);
|
|
55
|
+
});
|
|
56
|
+
test('Null if no token passed', () => {
|
|
57
|
+
(0, extract_token_1.default)(mockRequest, mockResponse, nextFunction);
|
|
58
|
+
expect(mockRequest.token).toBeNull();
|
|
59
|
+
expect(nextFunction).toBeCalledTimes(1);
|
|
60
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@directus/shared/utils");
|
|
4
|
+
const vm2_1 = require("vm2");
|
|
5
|
+
exports.default = (0, utils_1.defineOperationApi)({
|
|
6
|
+
id: 'exec',
|
|
7
|
+
handler: async ({ code }, { data, env }) => {
|
|
8
|
+
const allowedModules = env.FLOWS_EXEC_ALLOWED_MODULES ? (0, utils_1.toArray)(env.FLOWS_EXEC_ALLOWED_MODULES) : [];
|
|
9
|
+
const opts = {
|
|
10
|
+
eval: false,
|
|
11
|
+
wasm: false,
|
|
12
|
+
};
|
|
13
|
+
if (allowedModules.length > 0) {
|
|
14
|
+
opts.require = {
|
|
15
|
+
external: {
|
|
16
|
+
modules: allowedModules,
|
|
17
|
+
transitive: false,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const vm = new vm2_1.NodeVM(opts);
|
|
22
|
+
const script = new vm2_1.VMScript(code).compile();
|
|
23
|
+
const fn = await vm.run(script);
|
|
24
|
+
return await fn(data);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|