directus 9.0.1 → 9.1.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 +2 -0
- package/dist/auth/drivers/ldap.js +9 -13
- package/dist/auth/drivers/oauth2.d.ts +1 -1
- package/dist/auth/drivers/oauth2.js +2 -2
- package/dist/auth/drivers/openid.d.ts +1 -1
- package/dist/auth/drivers/openid.js +8 -2
- package/dist/controllers/notifications.d.ts +2 -0
- package/dist/controllers/notifications.js +147 -0
- package/dist/database/migrations/20211118A-add-notifications.d.ts +3 -0
- package/dist/database/migrations/20211118A-add-notifications.js +28 -0
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +14 -0
- package/dist/database/system-data/collections/collections.yaml +2 -0
- package/dist/database/system-data/fields/notifications.yaml +12 -0
- package/dist/database/system-data/fields/users.yaml +5 -0
- package/dist/database/system-data/relations/relations.yaml +6 -0
- package/dist/middleware/get-permissions.js +3 -102
- package/dist/services/activity.d.ts +7 -5
- package/dist/services/activity.js +82 -3
- package/dist/services/collections.js +12 -1
- package/dist/services/graphql.js +3 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/mail/index.js +2 -2
- package/dist/services/mail/templates/base.liquid +153 -85
- package/dist/services/mail/templates/password-reset.liquid +3 -2
- package/dist/services/mail/templates/user-invitation.liquid +4 -4
- package/dist/services/notifications.d.ts +12 -0
- package/dist/services/notifications.js +41 -0
- package/dist/services/users.js +1 -0
- package/dist/types/collection.d.ts +1 -0
- package/dist/utils/get-local-type.js +12 -7
- package/dist/utils/get-permissions.d.ts +3 -0
- package/dist/utils/get-permissions.js +106 -0
- package/dist/utils/md.d.ts +4 -0
- package/dist/utils/md.js +15 -0
- package/dist/utils/sanitize-query.js +3 -3
- package/dist/utils/user-name.d.ts +2 -0
- package/dist/utils/user-name.js +16 -0
- package/package.json +26 -23
package/dist/app.js
CHANGED
|
@@ -40,6 +40,7 @@ const graphql_1 = __importDefault(require("./controllers/graphql"));
|
|
|
40
40
|
const items_1 = __importDefault(require("./controllers/items"));
|
|
41
41
|
const not_found_1 = __importDefault(require("./controllers/not-found"));
|
|
42
42
|
const panels_1 = __importDefault(require("./controllers/panels"));
|
|
43
|
+
const notifications_1 = __importDefault(require("./controllers/notifications"));
|
|
43
44
|
const permissions_1 = __importDefault(require("./controllers/permissions"));
|
|
44
45
|
const presets_1 = __importDefault(require("./controllers/presets"));
|
|
45
46
|
const relations_1 = __importDefault(require("./controllers/relations"));
|
|
@@ -163,6 +164,7 @@ async function createApp() {
|
|
|
163
164
|
app.use('/files', files_1.default);
|
|
164
165
|
app.use('/folders', folders_1.default);
|
|
165
166
|
app.use('/items', items_1.default);
|
|
167
|
+
app.use('/notifications', notifications_1.default);
|
|
166
168
|
app.use('/panels', panels_1.default);
|
|
167
169
|
app.use('/permissions', permissions_1.default);
|
|
168
170
|
app.use('/presets', presets_1.default);
|
|
@@ -91,21 +91,16 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
async fetchUserDn(identifier) {
|
|
94
|
-
const { userDn, userAttribute } = this.config;
|
|
94
|
+
const { userDn, userAttribute, userScope } = this.config;
|
|
95
95
|
return new Promise((resolve, reject) => {
|
|
96
96
|
// Search for the user in LDAP by attribute
|
|
97
|
-
this.bindClient.search(userDn, {
|
|
98
|
-
attributes: ['cn'],
|
|
99
|
-
filter: `(${userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn'}=${identifier})`,
|
|
100
|
-
scope: 'one',
|
|
101
|
-
}, (err, res) => {
|
|
97
|
+
this.bindClient.search(userDn, { filter: `(${userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn'}=${identifier})`, scope: userScope !== null && userScope !== void 0 ? userScope : 'one' }, (err, res) => {
|
|
102
98
|
if (err) {
|
|
103
99
|
reject(handleError(err));
|
|
104
100
|
return;
|
|
105
101
|
}
|
|
106
102
|
res.on('searchEntry', ({ object }) => {
|
|
107
|
-
|
|
108
|
-
resolve(`cn=${userCn},${userDn}`.toLowerCase());
|
|
103
|
+
resolve(object.dn.toLowerCase());
|
|
109
104
|
});
|
|
110
105
|
res.on('error', (err) => {
|
|
111
106
|
reject(handleError(err));
|
|
@@ -147,7 +142,7 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
147
142
|
});
|
|
148
143
|
}
|
|
149
144
|
async fetchUserGroups(userDn) {
|
|
150
|
-
const { groupDn, groupAttribute } = this.config;
|
|
145
|
+
const { groupDn, groupAttribute, groupScope } = this.config;
|
|
151
146
|
if (!groupDn) {
|
|
152
147
|
return Promise.resolve([]);
|
|
153
148
|
}
|
|
@@ -157,7 +152,7 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
157
152
|
this.bindClient.search(groupDn, {
|
|
158
153
|
attributes: ['cn'],
|
|
159
154
|
filter: `(${groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member'}=${userDn})`,
|
|
160
|
-
scope: 'one',
|
|
155
|
+
scope: groupScope !== null && groupScope !== void 0 ? groupScope : 'one',
|
|
161
156
|
}, (err, res) => {
|
|
162
157
|
if (err) {
|
|
163
158
|
reject(handleError(err));
|
|
@@ -244,12 +239,13 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
|
|
|
244
239
|
reject(handleError(err));
|
|
245
240
|
});
|
|
246
241
|
client.bind(user.external_identifier, password, (err) => {
|
|
247
|
-
client.destroy();
|
|
248
242
|
if (err) {
|
|
249
243
|
reject(handleError(err));
|
|
250
|
-
return;
|
|
251
244
|
}
|
|
252
|
-
|
|
245
|
+
else {
|
|
246
|
+
resolve();
|
|
247
|
+
}
|
|
248
|
+
client.destroy();
|
|
253
249
|
});
|
|
254
250
|
});
|
|
255
251
|
}
|
|
@@ -13,7 +13,7 @@ export declare class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
13
13
|
generateAuthUrl(codeVerifier: string): string;
|
|
14
14
|
private fetchUserId;
|
|
15
15
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
16
|
-
login(user: User
|
|
16
|
+
login(user: User): Promise<SessionData>;
|
|
17
17
|
refresh(user: User, sessionData: SessionData): Promise<SessionData>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOAuth2AuthRouter(providerName: string): Router;
|
|
@@ -123,8 +123,8 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
|
|
|
123
123
|
});
|
|
124
124
|
return (await this.fetchUserId(identifier));
|
|
125
125
|
}
|
|
126
|
-
async login(user
|
|
127
|
-
return this.refresh(user,
|
|
126
|
+
async login(user) {
|
|
127
|
+
return this.refresh(user, null);
|
|
128
128
|
}
|
|
129
129
|
async refresh(user, sessionData) {
|
|
130
130
|
let authData = user.auth_data;
|
|
@@ -13,7 +13,7 @@ export declare class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
13
13
|
generateAuthUrl(codeVerifier: string): Promise<string>;
|
|
14
14
|
private fetchUserId;
|
|
15
15
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
16
|
-
login(user: User
|
|
16
|
+
login(user: User): Promise<SessionData>;
|
|
17
17
|
refresh(user: User, sessionData: SessionData): Promise<SessionData>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOpenIDAuthRouter(providerName: string): Router;
|
|
@@ -31,6 +31,12 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
31
31
|
this.client = new Promise((resolve, reject) => {
|
|
32
32
|
openid_client_1.Issuer.discover(issuerUrl)
|
|
33
33
|
.then((issuer) => {
|
|
34
|
+
const supportedTypes = issuer.metadata.response_types_supported;
|
|
35
|
+
if (!(supportedTypes === null || supportedTypes === void 0 ? void 0 : supportedTypes.includes('code'))) {
|
|
36
|
+
reject(new exceptions_1.InvalidConfigException('OpenID provider does not support required code flow', {
|
|
37
|
+
provider: additionalConfig.provider,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
34
40
|
resolve(new issuer.Client({
|
|
35
41
|
client_id: clientId,
|
|
36
42
|
client_secret: clientSecret,
|
|
@@ -125,8 +131,8 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
|
|
|
125
131
|
});
|
|
126
132
|
return (await this.fetchUserId(identifier));
|
|
127
133
|
}
|
|
128
|
-
async login(user
|
|
129
|
-
return this.refresh(user,
|
|
134
|
+
async login(user) {
|
|
135
|
+
return this.refresh(user, null);
|
|
130
136
|
}
|
|
131
137
|
async refresh(user, sessionData) {
|
|
132
138
|
let authData = user.auth_data;
|
|
@@ -0,0 +1,147 @@
|
|
|
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 express_1 = __importDefault(require("express"));
|
|
7
|
+
const exceptions_1 = require("../exceptions");
|
|
8
|
+
const respond_1 = require("../middleware/respond");
|
|
9
|
+
const use_collection_1 = __importDefault(require("../middleware/use-collection"));
|
|
10
|
+
const validate_batch_1 = require("../middleware/validate-batch");
|
|
11
|
+
const services_1 = require("../services");
|
|
12
|
+
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
13
|
+
const router = express_1.default.Router();
|
|
14
|
+
router.use((0, use_collection_1.default)('directus_notifications'));
|
|
15
|
+
router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
|
|
16
|
+
const service = new services_1.NotificationsService({
|
|
17
|
+
accountability: req.accountability,
|
|
18
|
+
schema: req.schema,
|
|
19
|
+
});
|
|
20
|
+
const savedKeys = [];
|
|
21
|
+
if (Array.isArray(req.body)) {
|
|
22
|
+
const keys = await service.createMany(req.body);
|
|
23
|
+
savedKeys.push(...keys);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const key = await service.createOne(req.body);
|
|
27
|
+
savedKeys.push(key);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
if (Array.isArray(req.body)) {
|
|
31
|
+
const records = await service.readMany(savedKeys, req.sanitizedQuery);
|
|
32
|
+
res.locals.payload = { data: records };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
|
|
36
|
+
res.locals.payload = { data: record };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof exceptions_1.ForbiddenException) {
|
|
41
|
+
return next();
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
return next();
|
|
46
|
+
}), respond_1.respond);
|
|
47
|
+
const readHandler = (0, async_handler_1.default)(async (req, res, next) => {
|
|
48
|
+
const service = new services_1.NotificationsService({
|
|
49
|
+
accountability: req.accountability,
|
|
50
|
+
schema: req.schema,
|
|
51
|
+
});
|
|
52
|
+
const metaService = new services_1.MetaService({
|
|
53
|
+
accountability: req.accountability,
|
|
54
|
+
schema: req.schema,
|
|
55
|
+
});
|
|
56
|
+
let result;
|
|
57
|
+
if (req.singleton) {
|
|
58
|
+
result = await service.readSingleton(req.sanitizedQuery);
|
|
59
|
+
}
|
|
60
|
+
else if (req.body.keys) {
|
|
61
|
+
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
result = await service.readByQuery(req.sanitizedQuery);
|
|
65
|
+
}
|
|
66
|
+
const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery);
|
|
67
|
+
res.locals.payload = { data: result, meta };
|
|
68
|
+
return next();
|
|
69
|
+
});
|
|
70
|
+
router.get('/', (0, validate_batch_1.validateBatch)('read'), readHandler, respond_1.respond);
|
|
71
|
+
router.search('/', (0, validate_batch_1.validateBatch)('read'), readHandler, respond_1.respond);
|
|
72
|
+
router.get('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
|
|
73
|
+
const service = new services_1.NotificationsService({
|
|
74
|
+
accountability: req.accountability,
|
|
75
|
+
schema: req.schema,
|
|
76
|
+
});
|
|
77
|
+
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
|
|
78
|
+
res.locals.payload = { data: record || null };
|
|
79
|
+
return next();
|
|
80
|
+
}), respond_1.respond);
|
|
81
|
+
router.patch('/', (0, validate_batch_1.validateBatch)('update'), (0, async_handler_1.default)(async (req, res, next) => {
|
|
82
|
+
const service = new services_1.NotificationsService({
|
|
83
|
+
accountability: req.accountability,
|
|
84
|
+
schema: req.schema,
|
|
85
|
+
});
|
|
86
|
+
let keys = [];
|
|
87
|
+
if (req.body.keys) {
|
|
88
|
+
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
keys = await service.updateByQuery(req.body.query, req.body.data);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const result = await service.readMany(keys, req.sanitizedQuery);
|
|
95
|
+
res.locals.payload = { data: result };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof exceptions_1.ForbiddenException) {
|
|
99
|
+
return next();
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
return next();
|
|
104
|
+
}), respond_1.respond);
|
|
105
|
+
router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
|
|
106
|
+
const service = new services_1.NotificationsService({
|
|
107
|
+
accountability: req.accountability,
|
|
108
|
+
schema: req.schema,
|
|
109
|
+
});
|
|
110
|
+
const primaryKey = await service.updateOne(req.params.pk, req.body);
|
|
111
|
+
try {
|
|
112
|
+
const record = await service.readOne(primaryKey, req.sanitizedQuery);
|
|
113
|
+
res.locals.payload = { data: record };
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error instanceof exceptions_1.ForbiddenException) {
|
|
117
|
+
return next();
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
return next();
|
|
122
|
+
}), respond_1.respond);
|
|
123
|
+
router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
|
|
124
|
+
const service = new services_1.NotificationsService({
|
|
125
|
+
accountability: req.accountability,
|
|
126
|
+
schema: req.schema,
|
|
127
|
+
});
|
|
128
|
+
if (Array.isArray(req.body)) {
|
|
129
|
+
await service.deleteMany(req.body);
|
|
130
|
+
}
|
|
131
|
+
else if (req.body.keys) {
|
|
132
|
+
await service.deleteMany(req.body.keys);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
await service.deleteByQuery(req.body.query);
|
|
136
|
+
}
|
|
137
|
+
return next();
|
|
138
|
+
}), respond_1.respond);
|
|
139
|
+
router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
|
|
140
|
+
const service = new services_1.NotificationsService({
|
|
141
|
+
accountability: req.accountability,
|
|
142
|
+
schema: req.schema,
|
|
143
|
+
});
|
|
144
|
+
await service.deleteOne(req.params.pk);
|
|
145
|
+
return next();
|
|
146
|
+
}), respond_1.respond);
|
|
147
|
+
exports.default = router;
|
|
@@ -0,0 +1,28 @@
|
|
|
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_notifications', (table) => {
|
|
6
|
+
table.increments();
|
|
7
|
+
table.timestamp('timestamp').notNullable();
|
|
8
|
+
table.string('status').defaultTo('inbox');
|
|
9
|
+
table.uuid('recipient').notNullable().references('id').inTable('directus_users').onDelete('CASCADE');
|
|
10
|
+
table.uuid('sender').notNullable().references('id').inTable('directus_users');
|
|
11
|
+
table.string('subject').notNullable();
|
|
12
|
+
table.text('message');
|
|
13
|
+
table.string('collection', 64);
|
|
14
|
+
table.string('item');
|
|
15
|
+
});
|
|
16
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
17
|
+
table.boolean('email_notifications').defaultTo(true);
|
|
18
|
+
});
|
|
19
|
+
await knex('directus_users').update({ email_notifications: true });
|
|
20
|
+
}
|
|
21
|
+
exports.up = up;
|
|
22
|
+
async function down(knex) {
|
|
23
|
+
await knex.schema.dropTable('directus_notifications');
|
|
24
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
25
|
+
table.dropColumn('email_notifications');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
exports.down = down;
|
|
@@ -72,6 +72,20 @@
|
|
|
72
72
|
- collection: directus_settings
|
|
73
73
|
action: read
|
|
74
74
|
|
|
75
|
+
- collection: directus_notifications
|
|
76
|
+
action: read
|
|
77
|
+
permissions:
|
|
78
|
+
recipient:
|
|
79
|
+
_eq: $CURRENT_USER
|
|
80
|
+
fields: '*'
|
|
81
|
+
|
|
82
|
+
- collection: directus_notifications
|
|
83
|
+
action: update
|
|
84
|
+
permissions:
|
|
85
|
+
recipient:
|
|
86
|
+
_eq: $CURRENT_USER
|
|
87
|
+
fields: 'status'
|
|
88
|
+
|
|
75
89
|
- collection: directus_users
|
|
76
90
|
action: read
|
|
77
91
|
permissions:
|
|
@@ -82,3 +82,9 @@ data:
|
|
|
82
82
|
- many_collection: directus_panels
|
|
83
83
|
many_field: user_created
|
|
84
84
|
one_collection: directus_users
|
|
85
|
+
- many_collection: directus_notifications
|
|
86
|
+
many_field: recipient
|
|
87
|
+
one_collection: directus_users
|
|
88
|
+
- many_collection: directus_notifications
|
|
89
|
+
many_field: sender
|
|
90
|
+
one_collection: directus_users
|
|
@@ -3,112 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const utils_1 = require("@directus/shared/utils");
|
|
7
|
-
const lodash_1 = require("lodash");
|
|
8
|
-
const database_1 = __importDefault(require("../database"));
|
|
9
|
-
const app_access_permissions_1 = require("../database/system-data/app-access-permissions");
|
|
10
6
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
11
|
-
const
|
|
12
|
-
const users_1 = require("../services/users");
|
|
13
|
-
const roles_1 = require("../services/roles");
|
|
14
|
-
const cache_1 = require("../cache");
|
|
15
|
-
const object_hash_1 = __importDefault(require("object-hash"));
|
|
16
|
-
const env_1 = __importDefault(require("../env"));
|
|
7
|
+
const get_permissions_1 = require("../utils/get-permissions");
|
|
17
8
|
const getPermissions = (0, async_handler_1.default)(async (req, res, next) => {
|
|
18
|
-
const database = (0, database_1.default)();
|
|
19
|
-
const { systemCache } = (0, cache_1.getCache)();
|
|
20
|
-
let permissions = [];
|
|
21
9
|
if (!req.accountability) {
|
|
22
|
-
throw new Error('
|
|
10
|
+
throw new Error('getPermissions middleware needs to be called after authenticate');
|
|
23
11
|
}
|
|
24
|
-
|
|
25
|
-
throw new Error('"getPermissions" needs to be used after the "schema" middleware');
|
|
26
|
-
}
|
|
27
|
-
const { user, role, app, admin } = req.accountability;
|
|
28
|
-
const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin })}`;
|
|
29
|
-
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
30
|
-
const cachedPermissions = await systemCache.get(cacheKey);
|
|
31
|
-
if (cachedPermissions) {
|
|
32
|
-
req.accountability.permissions = cachedPermissions;
|
|
33
|
-
return next();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (req.accountability.admin !== true) {
|
|
37
|
-
const permissionsForRole = await database
|
|
38
|
-
.select('*')
|
|
39
|
-
.from('directus_permissions')
|
|
40
|
-
.where({ role: req.accountability.role });
|
|
41
|
-
const requiredPermissionData = {
|
|
42
|
-
$CURRENT_USER: [],
|
|
43
|
-
$CURRENT_ROLE: [],
|
|
44
|
-
};
|
|
45
|
-
permissions = permissionsForRole.map((permissionRaw) => {
|
|
46
|
-
const permission = (0, lodash_1.cloneDeep)(permissionRaw);
|
|
47
|
-
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
48
|
-
permission.permissions = JSON.parse(permission.permissions);
|
|
49
|
-
}
|
|
50
|
-
else if (permission.permissions === null) {
|
|
51
|
-
permission.permissions = {};
|
|
52
|
-
}
|
|
53
|
-
if (permission.validation && typeof permission.validation === 'string') {
|
|
54
|
-
permission.validation = JSON.parse(permission.validation);
|
|
55
|
-
}
|
|
56
|
-
else if (permission.validation === null) {
|
|
57
|
-
permission.validation = {};
|
|
58
|
-
}
|
|
59
|
-
if (permission.presets && typeof permission.presets === 'string') {
|
|
60
|
-
permission.presets = JSON.parse(permission.presets);
|
|
61
|
-
}
|
|
62
|
-
else if (permission.presets === null) {
|
|
63
|
-
permission.presets = {};
|
|
64
|
-
}
|
|
65
|
-
if (permission.fields && typeof permission.fields === 'string') {
|
|
66
|
-
permission.fields = permission.fields.split(',');
|
|
67
|
-
}
|
|
68
|
-
else if (permission.fields === null) {
|
|
69
|
-
permission.fields = [];
|
|
70
|
-
}
|
|
71
|
-
const extractPermissionData = (val) => {
|
|
72
|
-
if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
|
|
73
|
-
requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
|
|
74
|
-
}
|
|
75
|
-
if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
|
|
76
|
-
requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
|
|
77
|
-
}
|
|
78
|
-
return val;
|
|
79
|
-
};
|
|
80
|
-
(0, utils_1.deepMap)(permission.permissions, extractPermissionData);
|
|
81
|
-
(0, utils_1.deepMap)(permission.validation, extractPermissionData);
|
|
82
|
-
(0, utils_1.deepMap)(permission.presets, extractPermissionData);
|
|
83
|
-
return permission;
|
|
84
|
-
});
|
|
85
|
-
if (req.accountability.app === true) {
|
|
86
|
-
permissions = (0, merge_permissions_1.mergePermissions)(permissions, app_access_permissions_1.appAccessMinimalPermissions.map((perm) => ({ ...perm, role: req.accountability.role })));
|
|
87
|
-
}
|
|
88
|
-
const usersService = new users_1.UsersService({ schema: req.schema });
|
|
89
|
-
const rolesService = new roles_1.RolesService({ schema: req.schema });
|
|
90
|
-
const filterContext = {};
|
|
91
|
-
if (req.accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
|
|
92
|
-
filterContext.$CURRENT_USER = await usersService.readOne(req.accountability.user, {
|
|
93
|
-
fields: requiredPermissionData.$CURRENT_USER,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
if (req.accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
|
|
97
|
-
filterContext.$CURRENT_ROLE = await rolesService.readOne(req.accountability.role, {
|
|
98
|
-
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
permissions = permissions.map((permission) => {
|
|
102
|
-
permission.permissions = (0, utils_1.parseFilter)(permission.permissions, req.accountability, filterContext);
|
|
103
|
-
permission.validation = (0, utils_1.parseFilter)(permission.validation, req.accountability, filterContext);
|
|
104
|
-
permission.presets = (0, utils_1.parseFilter)(permission.presets, req.accountability, filterContext);
|
|
105
|
-
return permission;
|
|
106
|
-
});
|
|
107
|
-
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
108
|
-
await systemCache.set(cacheKey, permissions);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
req.accountability.permissions = permissions;
|
|
12
|
+
req.accountability.permissions = await (0, get_permissions_1.getPermissions)(req.accountability, req.schema);
|
|
112
13
|
return next();
|
|
113
14
|
});
|
|
114
15
|
exports.default = getPermissions;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { AbstractServiceOptions } from '../types';
|
|
2
|
-
import { ItemsService } from './index';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
1
|
+
import { AbstractServiceOptions, PrimaryKey, Item } from '../types';
|
|
2
|
+
import { ItemsService, MutationOptions } from './index';
|
|
3
|
+
import { NotificationsService } from './notifications';
|
|
4
|
+
import { UsersService } from './users';
|
|
6
5
|
export declare class ActivityService extends ItemsService {
|
|
6
|
+
notificationsService: NotificationsService;
|
|
7
|
+
usersService: UsersService;
|
|
7
8
|
constructor(options: AbstractServiceOptions);
|
|
9
|
+
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
8
10
|
}
|
|
@@ -1,13 +1,92 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.ActivityService = void 0;
|
|
7
|
+
const types_1 = require("../types");
|
|
4
8
|
const index_1 = require("./index");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
const notifications_1 = require("./notifications");
|
|
10
|
+
const users_1 = require("./users");
|
|
11
|
+
const authorization_1 = require("./authorization");
|
|
12
|
+
const get_permissions_1 = require("../utils/get-permissions");
|
|
13
|
+
const forbidden_1 = require("../exceptions/forbidden");
|
|
14
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
15
|
+
const user_name_1 = require("../utils/user-name");
|
|
16
|
+
const lodash_1 = require("lodash");
|
|
17
|
+
const env_1 = __importDefault(require("../env"));
|
|
8
18
|
class ActivityService extends index_1.ItemsService {
|
|
9
19
|
constructor(options) {
|
|
10
20
|
super('directus_activity', options);
|
|
21
|
+
this.notificationsService = new notifications_1.NotificationsService({ schema: this.schema });
|
|
22
|
+
this.usersService = new users_1.UsersService({ schema: this.schema });
|
|
23
|
+
}
|
|
24
|
+
async createOne(data, opts) {
|
|
25
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
26
|
+
if (data.action === types_1.Action.COMMENT && typeof data.comment === 'string') {
|
|
27
|
+
const usersRegExp = new RegExp(/@[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/gi);
|
|
28
|
+
const mentions = (0, lodash_1.uniq)((_a = data.comment.match(usersRegExp)) !== null && _a !== void 0 ? _a : []);
|
|
29
|
+
const sender = await this.usersService.readOne(this.accountability.user, {
|
|
30
|
+
fields: ['id', 'first_name', 'last_name', 'email'],
|
|
31
|
+
});
|
|
32
|
+
for (const mention of mentions) {
|
|
33
|
+
const userID = mention.substring(1);
|
|
34
|
+
const user = await this.usersService.readOne(userID, {
|
|
35
|
+
fields: ['id', 'first_name', 'last_name', 'email', 'role.id', 'role.admin_access', 'role.app_access'],
|
|
36
|
+
});
|
|
37
|
+
const accountability = {
|
|
38
|
+
user: userID,
|
|
39
|
+
role: (_c = (_b = user.role) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null,
|
|
40
|
+
admin: (_e = (_d = user.role) === null || _d === void 0 ? void 0 : _d.admin_access) !== null && _e !== void 0 ? _e : null,
|
|
41
|
+
app: (_g = (_f = user.role) === null || _f === void 0 ? void 0 : _f.app_access) !== null && _g !== void 0 ? _g : null,
|
|
42
|
+
};
|
|
43
|
+
accountability.permissions = await (0, get_permissions_1.getPermissions)(accountability, this.schema);
|
|
44
|
+
const authorizationService = new authorization_1.AuthorizationService({ schema: this.schema, accountability });
|
|
45
|
+
const usersService = new users_1.UsersService({ schema: this.schema, accountability });
|
|
46
|
+
try {
|
|
47
|
+
await authorizationService.checkAccess('read', data.collection, data.item);
|
|
48
|
+
const templateData = await usersService.readByQuery({
|
|
49
|
+
fields: ['id', 'first_name', 'last_name', 'email'],
|
|
50
|
+
filter: { id: { _in: mentions.map((mention) => mention.substring(1)) } },
|
|
51
|
+
});
|
|
52
|
+
const userPreviews = templateData.reduce((acc, user) => {
|
|
53
|
+
acc[user.id] = `<em>${(0, user_name_1.userName)(user)}</em>`;
|
|
54
|
+
return acc;
|
|
55
|
+
}, {});
|
|
56
|
+
let comment = data.comment;
|
|
57
|
+
for (const mention of mentions) {
|
|
58
|
+
comment = comment.replace(mention, (_h = userPreviews[mention.substring(1)]) !== null && _h !== void 0 ? _h : '@Unknown User');
|
|
59
|
+
}
|
|
60
|
+
comment = `> ${comment}`;
|
|
61
|
+
const message = `
|
|
62
|
+
Hello ${(0, user_name_1.userName)(user)},
|
|
63
|
+
|
|
64
|
+
${(0, user_name_1.userName)(sender)} has mentioned you in a comment:
|
|
65
|
+
|
|
66
|
+
${comment}
|
|
67
|
+
|
|
68
|
+
<a href="${env_1.default.PUBLIC_URL}/admin/content/${data.collection}/${data.item}">Click here to view.</a>
|
|
69
|
+
`;
|
|
70
|
+
await this.notificationsService.createOne({
|
|
71
|
+
recipient: userID,
|
|
72
|
+
sender: sender.id,
|
|
73
|
+
subject: `You were mentioned in ${data.collection}`,
|
|
74
|
+
message,
|
|
75
|
+
collection: data.collection,
|
|
76
|
+
item: data.item,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof forbidden_1.ForbiddenException) {
|
|
81
|
+
logger_1.default.warn(`User ${userID} doesn't have proper permissions to receive notification for this item.`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return super.createOne(data, opts);
|
|
11
90
|
}
|
|
12
91
|
}
|
|
13
92
|
exports.ActivityService = ActivityService;
|
|
@@ -171,11 +171,22 @@ class CollectionsService {
|
|
|
171
171
|
}));
|
|
172
172
|
meta.push(...collections_1.systemCollectionRows);
|
|
173
173
|
if (this.accountability && this.accountability.admin !== true) {
|
|
174
|
-
const
|
|
174
|
+
const collectionsGroups = meta.reduce((meta, item) => ({
|
|
175
|
+
...meta,
|
|
176
|
+
[item.collection]: item.group,
|
|
177
|
+
}), {});
|
|
178
|
+
let collectionsYouHavePermissionToRead = this.accountability
|
|
175
179
|
.permissions.filter((permission) => {
|
|
176
180
|
return permission.action === 'read';
|
|
177
181
|
})
|
|
178
182
|
.map(({ collection }) => collection);
|
|
183
|
+
for (const collection of collectionsYouHavePermissionToRead) {
|
|
184
|
+
const group = collectionsGroups[collection];
|
|
185
|
+
if (group)
|
|
186
|
+
collectionsYouHavePermissionToRead.push(group);
|
|
187
|
+
delete collectionsGroups[collection];
|
|
188
|
+
}
|
|
189
|
+
collectionsYouHavePermissionToRead = [...new Set([...collectionsYouHavePermissionToRead])];
|
|
179
190
|
tablesInDatabase = tablesInDatabase.filter((table) => {
|
|
180
191
|
return collectionsYouHavePermissionToRead.includes(table.name);
|
|
181
192
|
});
|
package/dist/services/graphql.js
CHANGED
|
@@ -28,6 +28,7 @@ const folders_1 = require("./folders");
|
|
|
28
28
|
const items_1 = require("./items");
|
|
29
29
|
const permissions_1 = require("./permissions");
|
|
30
30
|
const presets_1 = require("./presets");
|
|
31
|
+
const notifications_1 = require("./notifications");
|
|
31
32
|
const relations_1 = require("./relations");
|
|
32
33
|
const revisions_1 = require("./revisions");
|
|
33
34
|
const roles_1 = require("./roles");
|
|
@@ -1252,6 +1253,8 @@ class GraphQLService {
|
|
|
1252
1253
|
return new permissions_1.PermissionsService(opts);
|
|
1253
1254
|
case 'directus_presets':
|
|
1254
1255
|
return new presets_1.PresetsService(opts);
|
|
1256
|
+
case 'directus_notifications':
|
|
1257
|
+
return new notifications_1.NotificationsService(opts);
|
|
1255
1258
|
case 'directus_revisions':
|
|
1256
1259
|
return new revisions_1.RevisionsService(opts);
|
|
1257
1260
|
case 'directus_roles':
|