directus 9.3.0 → 9.4.0
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 +5 -3
- package/dist/auth/auth.d.ts +4 -6
- package/dist/auth/auth.js +5 -9
- package/dist/auth/drivers/ldap.d.ts +3 -3
- package/dist/auth/drivers/ldap.js +0 -2
- package/dist/auth/drivers/local.d.ts +2 -2
- package/dist/auth/drivers/local.js +5 -12
- package/dist/auth/drivers/oauth2.d.ts +3 -3
- package/dist/auth/drivers/oauth2.js +2 -3
- package/dist/auth/drivers/openid.d.ts +3 -3
- package/dist/auth/drivers/openid.js +2 -3
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +16 -2
- package/dist/controllers/shares.d.ts +2 -0
- package/dist/controllers/shares.js +212 -0
- package/dist/controllers/users.js +21 -9
- package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
- package/dist/database/migrations/20211211A-add-shares.js +37 -0
- package/dist/database/run-ast.js +5 -5
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -15
- package/dist/database/system-data/app-access-permissions/index.d.ts +1 -0
- package/dist/database/system-data/app-access-permissions/index.js +4 -2
- package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +17 -0
- package/dist/database/system-data/collections/collections.yaml +3 -0
- package/dist/database/system-data/fields/sessions.yaml +1 -1
- package/dist/database/system-data/fields/shares.yaml +73 -0
- package/dist/database/system-data/fields/users.yaml +1 -1
- package/dist/database/system-data/relations/relations.yaml +15 -0
- package/dist/extensions.js +5 -3
- package/dist/middleware/authenticate.js +5 -15
- package/dist/middleware/check-ip.js +9 -6
- package/dist/middleware/respond.js +4 -1
- package/dist/services/activity.d.ts +2 -1
- package/dist/services/activity.js +2 -2
- package/dist/services/authentication.d.ts +2 -7
- package/dist/services/authentication.js +81 -41
- package/dist/services/authorization.js +3 -3
- package/dist/services/collections.d.ts +1 -2
- package/dist/services/collections.js +2 -2
- package/dist/services/files.d.ts +2 -2
- package/dist/services/graphql.js +16 -5
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.d.ts +1 -15
- package/dist/services/notifications.d.ts +2 -2
- package/dist/services/permissions.d.ts +2 -2
- package/dist/services/roles.d.ts +2 -2
- package/dist/services/shares.d.ts +17 -0
- package/dist/services/shares.js +135 -0
- package/dist/services/specifications.js +1 -1
- package/dist/services/users.d.ts +2 -2
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/types/ast.d.ts +3 -3
- package/dist/types/auth.d.ts +31 -0
- package/dist/types/items.d.ts +14 -0
- package/dist/utils/apply-query.d.ts +0 -38
- package/dist/utils/apply-query.js +66 -67
- package/dist/utils/get-ast-from-query.js +3 -3
- package/dist/utils/get-permissions.js +15 -7
- package/dist/utils/get-relation-type.d.ts +1 -1
- package/dist/utils/get-relation-type.js +1 -1
- package/dist/utils/merge-permissions-for-share.d.ts +5 -0
- package/dist/utils/merge-permissions-for-share.js +116 -0
- package/dist/utils/merge-permissions.d.ts +13 -1
- package/dist/utils/merge-permissions.js +27 -19
- package/dist/utils/reduce-schema.d.ts +2 -2
- package/dist/utils/reduce-schema.js +7 -7
- package/dist/utils/user-name.js +3 -0
- package/package.json +12 -12
|
@@ -0,0 +1,37 @@
|
|
|
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.createTable('directus_shares', (table) => {
|
|
6
|
+
table.uuid('id').primary();
|
|
7
|
+
table.string('name');
|
|
8
|
+
table.string('collection', 64).references('collection').inTable('directus_collections').onDelete('CASCADE');
|
|
9
|
+
table.string('item');
|
|
10
|
+
table.uuid('role').references('id').inTable('directus_roles').onDelete('CASCADE');
|
|
11
|
+
table.string('password');
|
|
12
|
+
table.uuid('user_created').references('id').inTable('directus_users').onDelete('SET NULL');
|
|
13
|
+
table.timestamp('date_created').defaultTo(knex.fn.now());
|
|
14
|
+
table.timestamp('date_start');
|
|
15
|
+
table.timestamp('date_end');
|
|
16
|
+
table.integer('times_used').defaultTo(0);
|
|
17
|
+
table.integer('max_uses');
|
|
18
|
+
});
|
|
19
|
+
await knex.schema.alterTable('directus_sessions', (table) => {
|
|
20
|
+
table.dropColumn('data');
|
|
21
|
+
});
|
|
22
|
+
await knex.schema.alterTable('directus_sessions', (table) => {
|
|
23
|
+
table.uuid('user').nullable().alter();
|
|
24
|
+
table.uuid('share').references('id').inTable('directus_shares').onDelete('CASCADE');
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
exports.up = up;
|
|
28
|
+
async function down(knex) {
|
|
29
|
+
await knex.schema.alterTable('directus_sessions', (table) => {
|
|
30
|
+
table.uuid('user').notNullable().alter();
|
|
31
|
+
table.json('data');
|
|
32
|
+
table.dropForeign('share');
|
|
33
|
+
table.dropColumn('share');
|
|
34
|
+
});
|
|
35
|
+
await knex.schema.dropTable('directus_shares');
|
|
36
|
+
}
|
|
37
|
+
exports.down = down;
|
package/dist/database/run-ast.js
CHANGED
|
@@ -18,7 +18,7 @@ const helpers_1 = require("../database/helpers");
|
|
|
18
18
|
async function runAST(originalAST, schema, options) {
|
|
19
19
|
const ast = (0, lodash_1.cloneDeep)(originalAST);
|
|
20
20
|
const knex = (options === null || options === void 0 ? void 0 : options.knex) || (0, _1.default)();
|
|
21
|
-
if (ast.type === '
|
|
21
|
+
if (ast.type === 'a2o') {
|
|
22
22
|
const results = {};
|
|
23
23
|
for (const collection of ast.names) {
|
|
24
24
|
results[collection] = await run(collection, ast.children[collection], ast.query[collection]);
|
|
@@ -85,7 +85,7 @@ async function parseCurrentLevel(schema, collection, children, query) {
|
|
|
85
85
|
if (child.type === 'm2o') {
|
|
86
86
|
columnsToSelectInternal.push(child.fieldKey);
|
|
87
87
|
}
|
|
88
|
-
if (child.type === '
|
|
88
|
+
if (child.type === 'a2o') {
|
|
89
89
|
columnsToSelectInternal.push(child.relation.field);
|
|
90
90
|
columnsToSelectInternal.push(child.relation.meta.one_collection_field);
|
|
91
91
|
}
|
|
@@ -182,7 +182,7 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
|
|
|
182
182
|
nestedNode.query.union = [foreignField, foreignIds];
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
else if (nestedNode.type === '
|
|
185
|
+
else if (nestedNode.type === 'a2o') {
|
|
186
186
|
const keysPerCollection = {};
|
|
187
187
|
for (const parentItem of parentItems) {
|
|
188
188
|
const collection = parentItem[nestedNode.relation.meta.one_collection_field];
|
|
@@ -256,7 +256,7 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
|
|
|
256
256
|
parentItem[nestedNode.fieldKey] = itemChildren.length > 0 ? itemChildren : [];
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
-
else if (nestedNode.type === '
|
|
259
|
+
else if (nestedNode.type === 'a2o') {
|
|
260
260
|
for (const parentItem of parentItems) {
|
|
261
261
|
if (!((_a = nestedNode.relation.meta) === null || _a === void 0 ? void 0 : _a.one_collection_field)) {
|
|
262
262
|
parentItem[nestedNode.fieldKey] = null;
|
|
@@ -279,7 +279,7 @@ function removeTemporaryFields(schema, rawItem, ast, primaryKeyField, parentItem
|
|
|
279
279
|
var _a;
|
|
280
280
|
const rawItems = (0, lodash_1.cloneDeep)((0, utils_1.toArray)(rawItem));
|
|
281
281
|
const items = [];
|
|
282
|
-
if (ast.type === '
|
|
282
|
+
if (ast.type === 'a2o') {
|
|
283
283
|
const fields = {};
|
|
284
284
|
const nestedCollectionNodes = {};
|
|
285
285
|
for (const relatedCollection of ast.names) {
|
|
@@ -13,18 +13,6 @@
|
|
|
13
13
|
comment:
|
|
14
14
|
_nnull: true
|
|
15
15
|
|
|
16
|
-
- collection: directus_collections
|
|
17
|
-
action: read
|
|
18
|
-
|
|
19
|
-
- collection: directus_fields
|
|
20
|
-
action: read
|
|
21
|
-
|
|
22
|
-
- collection: directus_permissions
|
|
23
|
-
action: read
|
|
24
|
-
permissions:
|
|
25
|
-
role:
|
|
26
|
-
_eq: $CURRENT_ROLE
|
|
27
|
-
|
|
28
16
|
- collection: directus_presets
|
|
29
17
|
action: read
|
|
30
18
|
permissions:
|
|
@@ -60,9 +48,6 @@
|
|
|
60
48
|
user:
|
|
61
49
|
_eq: $CURRENT_USER
|
|
62
50
|
|
|
63
|
-
- collection: directus_relations
|
|
64
|
-
action: read
|
|
65
|
-
|
|
66
51
|
- collection: directus_roles
|
|
67
52
|
action: read
|
|
68
53
|
permissions:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.appAccessMinimalPermissions = void 0;
|
|
3
|
+
exports.appAccessMinimalPermissions = exports.schemaPermissions = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const require_yaml_1 = require("../../../utils/require-yaml");
|
|
6
6
|
const defaults = {
|
|
@@ -11,5 +11,7 @@ const defaults = {
|
|
|
11
11
|
fields: ['*'],
|
|
12
12
|
system: true,
|
|
13
13
|
};
|
|
14
|
+
const schemaPermissionsRaw = (0, require_yaml_1.requireYAML)(require.resolve('./schema-access-permissions.yaml'));
|
|
14
15
|
const permissions = (0, require_yaml_1.requireYAML)(require.resolve('./app-access-permissions.yaml'));
|
|
15
|
-
exports.
|
|
16
|
+
exports.schemaPermissions = schemaPermissionsRaw.map((row) => (0, lodash_1.merge)({}, defaults, row));
|
|
17
|
+
exports.appAccessMinimalPermissions = [...exports.schemaPermissions, ...permissions].map((row) => (0, lodash_1.merge)({}, defaults, row));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# NOTE: Activity/collections/fields/presets/relations/revisions will have an extra hardcoded filter
|
|
2
|
+
# to filter out collections you don't have read access
|
|
3
|
+
|
|
4
|
+
- collection: directus_collections
|
|
5
|
+
action: read
|
|
6
|
+
|
|
7
|
+
- collection: directus_fields
|
|
8
|
+
action: read
|
|
9
|
+
|
|
10
|
+
- collection: directus_permissions
|
|
11
|
+
action: read
|
|
12
|
+
permissions:
|
|
13
|
+
role:
|
|
14
|
+
_eq: $CURRENT_ROLE
|
|
15
|
+
|
|
16
|
+
- collection: directus_relations
|
|
17
|
+
action: read
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
table: directus_shares
|
|
2
|
+
|
|
3
|
+
fields:
|
|
4
|
+
- field: id
|
|
5
|
+
special: uuid
|
|
6
|
+
readonly: true
|
|
7
|
+
hidden: true
|
|
8
|
+
|
|
9
|
+
- field: name
|
|
10
|
+
|
|
11
|
+
- field: collection
|
|
12
|
+
width: half
|
|
13
|
+
hidden: true
|
|
14
|
+
|
|
15
|
+
- field: item
|
|
16
|
+
width: half
|
|
17
|
+
hidden: true
|
|
18
|
+
|
|
19
|
+
- field: role
|
|
20
|
+
interface: select-dropdown-m2o
|
|
21
|
+
width: half
|
|
22
|
+
options:
|
|
23
|
+
template: '{{name}}'
|
|
24
|
+
filter:
|
|
25
|
+
admin_access:
|
|
26
|
+
_eq: false
|
|
27
|
+
|
|
28
|
+
- field: password
|
|
29
|
+
special: hash,conceal
|
|
30
|
+
interface: input-hash
|
|
31
|
+
options:
|
|
32
|
+
iconRight: lock
|
|
33
|
+
masked: true
|
|
34
|
+
width: half
|
|
35
|
+
|
|
36
|
+
- field: date_start
|
|
37
|
+
width: half
|
|
38
|
+
|
|
39
|
+
- field: date_end
|
|
40
|
+
width: half
|
|
41
|
+
|
|
42
|
+
- field: max_uses
|
|
43
|
+
width: half
|
|
44
|
+
|
|
45
|
+
- field: times_used
|
|
46
|
+
width: half
|
|
47
|
+
readonly: true
|
|
48
|
+
|
|
49
|
+
- field: date_created
|
|
50
|
+
special: date-created
|
|
51
|
+
width: half
|
|
52
|
+
readonly: true
|
|
53
|
+
conditions:
|
|
54
|
+
- name: notCreatedYet
|
|
55
|
+
rule:
|
|
56
|
+
id:
|
|
57
|
+
_null: true
|
|
58
|
+
hidden: true
|
|
59
|
+
|
|
60
|
+
- field: user_created
|
|
61
|
+
special: user-created
|
|
62
|
+
interface: select-dropdown-m2o
|
|
63
|
+
width: half
|
|
64
|
+
display: user
|
|
65
|
+
options:
|
|
66
|
+
template: '{{avatar.$thumbnail}} {{first_name}} {{last_name}}'
|
|
67
|
+
readonly: true
|
|
68
|
+
conditions:
|
|
69
|
+
- name: notCreatedYet
|
|
70
|
+
rule:
|
|
71
|
+
id:
|
|
72
|
+
_null: true
|
|
73
|
+
hidden: true
|
|
@@ -12,6 +12,9 @@ defaults:
|
|
|
12
12
|
sort_field: null
|
|
13
13
|
|
|
14
14
|
data:
|
|
15
|
+
- many_collection: directus_collections
|
|
16
|
+
many_field: group
|
|
17
|
+
one_collection: directus_collections
|
|
15
18
|
- many_collection: directus_users
|
|
16
19
|
many_field: role
|
|
17
20
|
one_collection: directus_roles
|
|
@@ -73,6 +76,9 @@ data:
|
|
|
73
76
|
- many_collection: directus_sessions
|
|
74
77
|
many_field: user
|
|
75
78
|
one_collection: directus_users
|
|
79
|
+
- many_collection: directus_sessions
|
|
80
|
+
many_field: share
|
|
81
|
+
one_collection: directus_shares
|
|
76
82
|
- many_collection: directus_settings
|
|
77
83
|
many_field: storage_default_folder
|
|
78
84
|
one_collection: directus_folders
|
|
@@ -88,3 +94,12 @@ data:
|
|
|
88
94
|
- many_collection: directus_notifications
|
|
89
95
|
many_field: sender
|
|
90
96
|
one_collection: directus_users
|
|
97
|
+
- many_collection: directus_shares
|
|
98
|
+
many_field: role
|
|
99
|
+
one_collection: directus_roles
|
|
100
|
+
- many_collection: directus_shares
|
|
101
|
+
many_field: collection
|
|
102
|
+
one_collection: directus_collections
|
|
103
|
+
- many_collection: directus_shares
|
|
104
|
+
many_field: user_created
|
|
105
|
+
one_collection: directus_users
|
package/dist/extensions.js
CHANGED
|
@@ -44,6 +44,7 @@ const plugin_virtual_1 = __importDefault(require("@rollup/plugin-virtual"));
|
|
|
44
44
|
const plugin_alias_1 = __importDefault(require("@rollup/plugin-alias"));
|
|
45
45
|
const url_1 = require("./utils/url");
|
|
46
46
|
const get_module_default_1 = __importDefault(require("./utils/get-module-default"));
|
|
47
|
+
const lodash_1 = require("lodash");
|
|
47
48
|
let extensionManager;
|
|
48
49
|
function getExtensionManager() {
|
|
49
50
|
if (extensionManager) {
|
|
@@ -141,12 +142,13 @@ class ExtensionManager {
|
|
|
141
142
|
return bundles;
|
|
142
143
|
}
|
|
143
144
|
async getSharedDepsMapping(deps) {
|
|
144
|
-
const appDir = await fs_extra_1.default.readdir(path_1.default.join((0, node_1.resolvePackage)('@directus/app'), 'dist'));
|
|
145
|
+
const appDir = await fs_extra_1.default.readdir(path_1.default.join((0, node_1.resolvePackage)('@directus/app'), 'dist', 'assets'));
|
|
145
146
|
const depsMapping = {};
|
|
146
147
|
for (const dep of deps) {
|
|
147
|
-
const
|
|
148
|
+
const depRegex = new RegExp(`${(0, lodash_1.escapeRegExp)(dep.replace(/\//g, '_'))}\\.[0-9a-f]{8}\\.entry\\.js`);
|
|
149
|
+
const depName = appDir.find((file) => depRegex.test(file));
|
|
148
150
|
if (depName) {
|
|
149
|
-
const depUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', depName);
|
|
151
|
+
const depUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'assets', depName);
|
|
150
152
|
depsMapping[dep] = depUrl.toString({ rootRelative: true });
|
|
151
153
|
}
|
|
152
154
|
else {
|
|
@@ -58,22 +58,12 @@ const authenticate = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
58
58
|
throw err;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.from('directus_users')
|
|
64
|
-
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
65
|
-
.where({
|
|
66
|
-
'directus_users.id': payload.id,
|
|
67
|
-
status: 'active',
|
|
68
|
-
})
|
|
69
|
-
.first();
|
|
70
|
-
if (!user) {
|
|
71
|
-
throw new exceptions_1.InvalidCredentialsException();
|
|
72
|
-
}
|
|
61
|
+
req.accountability.share = payload.share;
|
|
62
|
+
req.accountability.share_scope = payload.share_scope;
|
|
73
63
|
req.accountability.user = payload.id;
|
|
74
|
-
req.accountability.role =
|
|
75
|
-
req.accountability.admin =
|
|
76
|
-
req.accountability.app =
|
|
64
|
+
req.accountability.role = payload.role;
|
|
65
|
+
req.accountability.admin = payload.admin_access === true || payload.admin_access == 1;
|
|
66
|
+
req.accountability.app = payload.app_access === true || payload.app_access == 1;
|
|
77
67
|
}
|
|
78
68
|
else {
|
|
79
69
|
// Try finding the user with the provided token
|
|
@@ -7,13 +7,16 @@ exports.checkIP = void 0;
|
|
|
7
7
|
const database_1 = __importDefault(require("../database"));
|
|
8
8
|
const exceptions_1 = require("../exceptions");
|
|
9
9
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
10
|
-
exports.checkIP = (0, async_handler_1.default)(async (req,
|
|
10
|
+
exports.checkIP = (0, async_handler_1.default)(async (req, _res, next) => {
|
|
11
11
|
const database = (0, database_1.default)();
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const query = database.select('ip_access').from('directus_roles');
|
|
13
|
+
if (req.accountability.role) {
|
|
14
|
+
query.where({ id: req.accountability.role });
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
query.whereNull('id');
|
|
18
|
+
}
|
|
19
|
+
const role = await query.first();
|
|
17
20
|
const ipAllowlist = ((role === null || role === void 0 ? void 0 : role.ip_access) || '').split(',').filter((ip) => ip);
|
|
18
21
|
if (ipAllowlist.length > 0 && ipAllowlist.includes(req.accountability.ip) === false)
|
|
19
22
|
throw new exceptions_1.InvalidIPException();
|
|
@@ -77,9 +77,12 @@ exports.respond = (0, async_handler_1.default)(async (req, res) => {
|
|
|
77
77
|
if (Buffer.isBuffer(res.locals.payload)) {
|
|
78
78
|
return res.end(res.locals.payload);
|
|
79
79
|
}
|
|
80
|
-
else {
|
|
80
|
+
else if (res.locals.payload) {
|
|
81
81
|
return res.json(res.locals.payload);
|
|
82
82
|
}
|
|
83
|
+
else {
|
|
84
|
+
return res.status(204).end();
|
|
85
|
+
}
|
|
83
86
|
});
|
|
84
87
|
function getDateFormatted() {
|
|
85
88
|
const date = new Date();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractServiceOptions, PrimaryKey, Item } from '../types';
|
|
2
|
-
import { ItemsService
|
|
2
|
+
import { ItemsService } from './items';
|
|
3
|
+
import { MutationOptions } from '../types';
|
|
3
4
|
import { NotificationsService } from './notifications';
|
|
4
5
|
import { UsersService } from './users';
|
|
5
6
|
export declare class ActivityService extends ItemsService {
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ActivityService = void 0;
|
|
7
7
|
const types_1 = require("../types");
|
|
8
|
-
const
|
|
8
|
+
const items_1 = require("./items");
|
|
9
9
|
const notifications_1 = require("./notifications");
|
|
10
10
|
const users_1 = require("./users");
|
|
11
11
|
const authorization_1 = require("./authorization");
|
|
@@ -16,7 +16,7 @@ const user_name_1 = require("../utils/user-name");
|
|
|
16
16
|
const lodash_1 = require("lodash");
|
|
17
17
|
const env_1 = __importDefault(require("../env"));
|
|
18
18
|
const uuid_validate_1 = __importDefault(require("uuid-validate"));
|
|
19
|
-
class ActivityService extends
|
|
19
|
+
class ActivityService extends items_1.ItemsService {
|
|
20
20
|
constructor(options) {
|
|
21
21
|
super('directus_activity', options);
|
|
22
22
|
this.notificationsService = new notifications_1.NotificationsService({ schema: this.schema });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Knex } from 'knex';
|
|
2
2
|
import { ActivityService } from './activity';
|
|
3
|
-
import { AbstractServiceOptions, SchemaOverview } from '../types';
|
|
3
|
+
import { AbstractServiceOptions, SchemaOverview, LoginResult } from '../types';
|
|
4
4
|
import { Accountability } from '@directus/shared/types';
|
|
5
5
|
export declare class AuthenticationService {
|
|
6
6
|
knex: Knex;
|
|
@@ -14,12 +14,7 @@ export declare class AuthenticationService {
|
|
|
14
14
|
* Password is optional to allow usage of this function within the SSO flow and extensions. Make sure
|
|
15
15
|
* to handle password existence checks elsewhere
|
|
16
16
|
*/
|
|
17
|
-
login(providerName: string | undefined, payload: Record<string, any>, otp?: string): Promise<
|
|
18
|
-
accessToken: any;
|
|
19
|
-
refreshToken: any;
|
|
20
|
-
expires: any;
|
|
21
|
-
id?: any;
|
|
22
|
-
}>;
|
|
17
|
+
login(providerName: string | undefined, payload: Record<string, any>, otp?: string): Promise<LoginResult>;
|
|
23
18
|
refresh(refreshToken: string): Promise<Record<string, any>>;
|
|
24
19
|
logout(refreshToken: string): Promise<void>;
|
|
25
20
|
verifyPassword(userID: string, password: string): Promise<void>;
|
|
@@ -21,7 +21,6 @@ const settings_1 = require("./settings");
|
|
|
21
21
|
const lodash_1 = require("lodash");
|
|
22
22
|
const perf_hooks_1 = require("perf_hooks");
|
|
23
23
|
const stall_1 = require("../utils/stall");
|
|
24
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
25
24
|
const loginAttemptsLimiter = (0, rate_limiter_1.createRateLimiter)({ duration: 0 });
|
|
26
25
|
class AuthenticationService {
|
|
27
26
|
constructor(options) {
|
|
@@ -42,10 +41,11 @@ class AuthenticationService {
|
|
|
42
41
|
const timeStart = perf_hooks_1.performance.now();
|
|
43
42
|
const provider = (0, auth_1.getAuthProvider)(providerName);
|
|
44
43
|
const user = await this.knex
|
|
45
|
-
.select('id', 'first_name', 'last_name', 'email', 'password', 'status', 'role', 'tfa_secret', 'provider', 'external_identifier', 'auth_data')
|
|
46
|
-
.from('directus_users')
|
|
47
|
-
.
|
|
48
|
-
.
|
|
44
|
+
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'r.admin_access', 'r.app_access', 'u.tfa_secret', 'u.provider', 'u.external_identifier', 'u.auth_data')
|
|
45
|
+
.from('directus_users as u')
|
|
46
|
+
.innerJoin('directus_roles as r', 'u.role', 'r.id')
|
|
47
|
+
.where('u.id', await provider.getUserID((0, lodash_1.cloneDeep)(payload)))
|
|
48
|
+
.andWhere('u.provider', providerName)
|
|
49
49
|
.first();
|
|
50
50
|
const updatedPayload = await emitter_1.default.emitFilter('auth.login', payload, {
|
|
51
51
|
status: 'pending',
|
|
@@ -98,9 +98,8 @@ class AuthenticationService {
|
|
|
98
98
|
await loginAttemptsLimiter.set(user.id, 0, 0);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
let sessionData = null;
|
|
102
101
|
try {
|
|
103
|
-
|
|
102
|
+
await provider.login((0, lodash_1.clone)(user), (0, lodash_1.cloneDeep)(updatedPayload));
|
|
104
103
|
}
|
|
105
104
|
catch (e) {
|
|
106
105
|
emitStatus('fail');
|
|
@@ -123,6 +122,9 @@ class AuthenticationService {
|
|
|
123
122
|
}
|
|
124
123
|
const tokenPayload = {
|
|
125
124
|
id: user.id,
|
|
125
|
+
role: user.role,
|
|
126
|
+
app_access: user.app_access,
|
|
127
|
+
admin_access: user.admin_access,
|
|
126
128
|
};
|
|
127
129
|
const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
|
|
128
130
|
status: 'pending',
|
|
@@ -146,7 +148,6 @@ class AuthenticationService {
|
|
|
146
148
|
expires: refreshTokenExpiration,
|
|
147
149
|
ip: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.ip,
|
|
148
150
|
user_agent: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.userAgent,
|
|
149
|
-
data: sessionData && JSON.stringify(sessionData),
|
|
150
151
|
});
|
|
151
152
|
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
|
|
152
153
|
if (this.accountability) {
|
|
@@ -177,33 +178,80 @@ class AuthenticationService {
|
|
|
177
178
|
throw new exceptions_1.InvalidCredentialsException();
|
|
178
179
|
}
|
|
179
180
|
const record = await this.knex
|
|
180
|
-
.select(
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
.select({
|
|
182
|
+
session_expires: 's.expires',
|
|
183
|
+
user_id: 'u.id',
|
|
184
|
+
user_first_name: 'u.first_name',
|
|
185
|
+
user_last_name: 'u.last_name',
|
|
186
|
+
user_email: 'u.email',
|
|
187
|
+
user_password: 'u.password',
|
|
188
|
+
user_status: 'u.status',
|
|
189
|
+
user_provider: 'u.provider',
|
|
190
|
+
user_external_identifier: 'u.external_identifier',
|
|
191
|
+
user_auth_data: 'u.auth_data',
|
|
192
|
+
role_id: 'r.id',
|
|
193
|
+
role_admin_access: 'r.admin_access',
|
|
194
|
+
role_app_access: 'r.app_access',
|
|
195
|
+
share_id: 'd.id',
|
|
196
|
+
share_item: 'd.item',
|
|
197
|
+
share_role: 'd.role',
|
|
198
|
+
share_collection: 'd.collection',
|
|
199
|
+
share_start: 'd.date_start',
|
|
200
|
+
share_end: 'd.date_end',
|
|
201
|
+
share_times_used: 'd.times_used',
|
|
202
|
+
share_max_uses: 'd.max_uses',
|
|
203
|
+
})
|
|
204
|
+
.from('directus_sessions AS s')
|
|
205
|
+
.leftJoin('directus_users AS u', 's.user', 'u.id')
|
|
206
|
+
.leftJoin('directus_shares AS d', 's.share', 'd.id')
|
|
207
|
+
.joinRaw('LEFT JOIN directus_roles AS r ON r.id IN (u.role, d.role)')
|
|
183
208
|
.where('s.token', refreshToken)
|
|
209
|
+
.andWhere('s.expires', '>=', this.knex.fn.now())
|
|
210
|
+
.andWhere((subQuery) => {
|
|
211
|
+
subQuery.whereNull('d.date_end').orWhere('d.date_end', '>=', this.knex.fn.now());
|
|
212
|
+
})
|
|
213
|
+
.andWhere((subQuery) => {
|
|
214
|
+
subQuery.whereNull('d.date_start').orWhere('d.date_start', '<=', this.knex.fn.now());
|
|
215
|
+
})
|
|
184
216
|
.first();
|
|
185
|
-
if (!record || record.
|
|
217
|
+
if (!record || (!record.share_id && !record.user_id)) {
|
|
186
218
|
throw new exceptions_1.InvalidCredentialsException();
|
|
187
219
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
220
|
+
if (record.user_id) {
|
|
221
|
+
const provider = (0, auth_1.getAuthProvider)(record.user_provider);
|
|
222
|
+
await provider.refresh({
|
|
223
|
+
id: record.user_id,
|
|
224
|
+
first_name: record.user_first_name,
|
|
225
|
+
last_name: record.user_last_name,
|
|
226
|
+
email: record.user_email,
|
|
227
|
+
password: record.user_password,
|
|
228
|
+
status: record.user_status,
|
|
229
|
+
provider: record.user_provider,
|
|
230
|
+
external_identifier: record.user_external_identifier,
|
|
231
|
+
auth_data: record.user_auth_data,
|
|
232
|
+
role: record.role_id,
|
|
233
|
+
app_access: record.role_app_access,
|
|
234
|
+
admin_access: record.role_admin_access,
|
|
235
|
+
});
|
|
197
236
|
}
|
|
198
|
-
const provider = (0, auth_1.getAuthProvider)(user.provider);
|
|
199
|
-
const newSessionData = await provider.refresh((0, lodash_1.clone)(user), sessionData);
|
|
200
237
|
const tokenPayload = {
|
|
201
|
-
id:
|
|
238
|
+
id: record.user_id,
|
|
239
|
+
role: record.role_id,
|
|
240
|
+
app_access: record.role_app_access,
|
|
241
|
+
admin_access: record.role_admin_access,
|
|
202
242
|
};
|
|
243
|
+
if (record.share_id) {
|
|
244
|
+
tokenPayload.share = record.share_id;
|
|
245
|
+
tokenPayload.role = record.share_role;
|
|
246
|
+
tokenPayload.share_scope = {
|
|
247
|
+
collection: record.share_collection,
|
|
248
|
+
item: record.share_item,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
203
251
|
const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
|
|
204
252
|
status: 'pending',
|
|
205
|
-
user:
|
|
206
|
-
provider:
|
|
253
|
+
user: record.user_id,
|
|
254
|
+
provider: record.user_provider,
|
|
207
255
|
type: 'refresh',
|
|
208
256
|
}, {
|
|
209
257
|
database: this.knex,
|
|
@@ -220,37 +268,29 @@ class AuthenticationService {
|
|
|
220
268
|
.update({
|
|
221
269
|
token: newRefreshToken,
|
|
222
270
|
expires: refreshTokenExpiration,
|
|
223
|
-
data: newSessionData && JSON.stringify(newSessionData),
|
|
224
271
|
})
|
|
225
272
|
.where({ token: refreshToken });
|
|
226
|
-
|
|
273
|
+
if (record.user_id) {
|
|
274
|
+
await this.knex('directus_users').update({ last_access: new Date() }).where({ id: record.user_id });
|
|
275
|
+
}
|
|
227
276
|
return {
|
|
228
277
|
accessToken,
|
|
229
278
|
refreshToken: newRefreshToken,
|
|
230
279
|
expires: (0, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
|
|
231
|
-
id:
|
|
280
|
+
id: record.user_id,
|
|
232
281
|
};
|
|
233
282
|
}
|
|
234
283
|
async logout(refreshToken) {
|
|
235
284
|
const record = await this.knex
|
|
236
|
-
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'u.provider', 'u.external_identifier', 'u.auth_data'
|
|
285
|
+
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'u.provider', 'u.external_identifier', 'u.auth_data')
|
|
237
286
|
.from('directus_sessions as s')
|
|
238
287
|
.innerJoin('directus_users as u', 's.user', 'u.id')
|
|
239
288
|
.where('s.token', refreshToken)
|
|
240
289
|
.first();
|
|
241
290
|
if (record) {
|
|
242
|
-
|
|
243
|
-
const user = (0, lodash_1.omit)(record, 'data');
|
|
244
|
-
if (typeof sessionData === 'string') {
|
|
245
|
-
try {
|
|
246
|
-
sessionData = JSON.parse(sessionData);
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
logger_1.default.warn(`Session data isn't valid JSON: ${sessionData}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
291
|
+
const user = record;
|
|
252
292
|
const provider = (0, auth_1.getAuthProvider)(user.provider);
|
|
253
|
-
await provider.logout((0, lodash_1.clone)(user)
|
|
293
|
+
await provider.logout((0, lodash_1.clone)(user));
|
|
254
294
|
await this.knex.delete().from('directus_sessions').where('token', refreshToken);
|
|
255
295
|
}
|
|
256
296
|
}
|
|
@@ -41,7 +41,7 @@ class AuthorizationService {
|
|
|
41
41
|
*/
|
|
42
42
|
function getCollectionsFromAST(ast) {
|
|
43
43
|
const collections = [];
|
|
44
|
-
if (ast.type === '
|
|
44
|
+
if (ast.type === 'a2o') {
|
|
45
45
|
collections.push(...ast.names.map((name) => ({ collection: name, field: ast.fieldKey })));
|
|
46
46
|
for (const children of Object.values(ast.children)) {
|
|
47
47
|
for (const nestedNode of children) {
|
|
@@ -67,7 +67,7 @@ class AuthorizationService {
|
|
|
67
67
|
function validateFields(ast) {
|
|
68
68
|
var _a, _b, _c;
|
|
69
69
|
if (ast.type !== 'field') {
|
|
70
|
-
if (ast.type === '
|
|
70
|
+
if (ast.type === 'a2o') {
|
|
71
71
|
for (const [collection, children] of Object.entries(ast.children)) {
|
|
72
72
|
checkFields(collection, children, (_b = (_a = ast.query) === null || _a === void 0 ? void 0 : _a[collection]) === null || _b === void 0 ? void 0 : _b.aggregate);
|
|
73
73
|
}
|
|
@@ -106,7 +106,7 @@ class AuthorizationService {
|
|
|
106
106
|
}
|
|
107
107
|
function applyFilters(ast, accountability) {
|
|
108
108
|
if (ast.type !== 'field') {
|
|
109
|
-
if (ast.type === '
|
|
109
|
+
if (ast.type === 'a2o') {
|
|
110
110
|
const collections = Object.keys(ast.children);
|
|
111
111
|
for (const collection of collections) {
|
|
112
112
|
updateFilterQuery(collection, ast.query[collection]);
|