directus 9.2.2 → 9.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +16 -4
- 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 +2 -3
- package/dist/auth/drivers/local.d.ts +2 -2
- package/dist/auth/drivers/local.js +7 -13
- package/dist/auth/drivers/oauth2.d.ts +3 -3
- package/dist/auth/drivers/oauth2.js +4 -4
- package/dist/auth/drivers/openid.d.ts +3 -3
- package/dist/auth/drivers/openid.js +4 -4
- package/dist/cache.js +1 -3
- package/dist/cli/commands/bootstrap/index.js +3 -2
- package/dist/cli/commands/init/index.js +3 -7
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/utils/defaults.d.ts +11 -0
- package/dist/cli/utils/defaults.js +14 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +16 -2
- package/dist/controllers/activity.js +2 -1
- package/dist/controllers/auth.js +5 -4
- 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/index.js +3 -0
- package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
- package/dist/database/migrations/20211211A-add-shares.js +38 -0
- package/dist/database/migrations/20211230A-add-project-descriptor.d.ts +3 -0
- package/dist/database/migrations/20211230A-add-project-descriptor.js +15 -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/_defaults.yaml +2 -0
- package/dist/database/system-data/fields/sessions.yaml +1 -1
- package/dist/database/system-data/fields/settings.yaml +20 -1
- package/dist/database/system-data/fields/shares.yaml +77 -0
- package/dist/database/system-data/fields/users.yaml +1 -1
- package/dist/database/system-data/relations/relations.yaml +15 -0
- package/dist/emitter.d.ts +3 -2
- package/dist/emitter.js +13 -6
- package/dist/env.js +3 -1
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +10 -4
- package/dist/middleware/authenticate.js +7 -16
- package/dist/middleware/check-ip.js +9 -6
- package/dist/middleware/rate-limiter.js +2 -1
- 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/files.js +14 -8
- package/dist/services/graphql.d.ts +1 -1
- package/dist/services/graphql.js +47 -6
- 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/server.js +1 -0
- 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/extensions.d.ts +2 -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/apply-snapshot.js +69 -14
- package/dist/utils/get-ast-from-query.js +3 -3
- package/dist/utils/get-default-value.js +3 -1
- package/dist/utils/get-ip-from-req.d.ts +2 -0
- package/dist/utils/get-ip-from-req.js +24 -0
- 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/example.env +1 -1
- package/package.json +15 -14
package/dist/emitter.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { ActionHandler, FilterHandler, HookContext, InitHandler } from './types';
|
|
2
|
-
declare class Emitter {
|
|
2
|
+
export declare class Emitter {
|
|
3
3
|
private filterEmitter;
|
|
4
4
|
private actionEmitter;
|
|
5
5
|
private initEmitter;
|
|
6
6
|
constructor();
|
|
7
|
-
eventsToEmit(event: string, meta: Record<string, any>): string[];
|
|
8
7
|
emitFilter<T>(event: string, payload: T, meta: Record<string, any>, context: HookContext): Promise<T>;
|
|
9
8
|
emitAction(event: string, meta: Record<string, any>, context: HookContext): void;
|
|
10
9
|
emitInit(event: string, meta: Record<string, any>): Promise<void>;
|
|
@@ -14,6 +13,8 @@ declare class Emitter {
|
|
|
14
13
|
offFilter(event: string, handler: FilterHandler): void;
|
|
15
14
|
offAction(event: string, handler: ActionHandler): void;
|
|
16
15
|
offInit(event: string, handler: InitHandler): void;
|
|
16
|
+
offAll(): void;
|
|
17
|
+
private eventsToEmit;
|
|
17
18
|
}
|
|
18
19
|
declare const emitter: Emitter;
|
|
19
20
|
export default emitter;
|
package/dist/emitter.js
CHANGED
|
@@ -3,6 +3,7 @@ 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
|
+
exports.Emitter = void 0;
|
|
6
7
|
const eventemitter2_1 = require("eventemitter2");
|
|
7
8
|
const logger_1 = __importDefault(require("./logger"));
|
|
8
9
|
class Emitter {
|
|
@@ -18,12 +19,6 @@ class Emitter {
|
|
|
18
19
|
this.actionEmitter = new eventemitter2_1.EventEmitter2(emitterOptions);
|
|
19
20
|
this.initEmitter = new eventemitter2_1.EventEmitter2(emitterOptions);
|
|
20
21
|
}
|
|
21
|
-
eventsToEmit(event, meta) {
|
|
22
|
-
if (event.startsWith('items')) {
|
|
23
|
-
return [event, `${meta.collection}.${event}`];
|
|
24
|
-
}
|
|
25
|
-
return [event];
|
|
26
|
-
}
|
|
27
22
|
async emitFilter(event, payload, meta, context) {
|
|
28
23
|
const events = this.eventsToEmit(event, meta);
|
|
29
24
|
const listeners = events.flatMap((event) => this.filterEmitter.listeners(event));
|
|
@@ -72,6 +67,18 @@ class Emitter {
|
|
|
72
67
|
offInit(event, handler) {
|
|
73
68
|
this.initEmitter.off(event, handler);
|
|
74
69
|
}
|
|
70
|
+
offAll() {
|
|
71
|
+
this.filterEmitter.removeAllListeners();
|
|
72
|
+
this.actionEmitter.removeAllListeners();
|
|
73
|
+
this.initEmitter.removeAllListeners();
|
|
74
|
+
}
|
|
75
|
+
eventsToEmit(event, meta) {
|
|
76
|
+
if (event.startsWith('items')) {
|
|
77
|
+
return [event, `${meta.collection}.${event}`];
|
|
78
|
+
}
|
|
79
|
+
return [event];
|
|
80
|
+
}
|
|
75
81
|
}
|
|
82
|
+
exports.Emitter = Emitter;
|
|
76
83
|
const emitter = new Emitter();
|
|
77
84
|
exports.default = emitter;
|
package/dist/env.js
CHANGED
|
@@ -20,7 +20,7 @@ const defaults = {
|
|
|
20
20
|
PORT: 8055,
|
|
21
21
|
PUBLIC_URL: '/',
|
|
22
22
|
MAX_PAYLOAD_SIZE: '100kb',
|
|
23
|
-
DB_EXCLUDE_TABLES: 'spatial_ref_sys',
|
|
23
|
+
DB_EXCLUDE_TABLES: 'spatial_ref_sys,sysdiagrams',
|
|
24
24
|
STORAGE_LOCATIONS: 'local',
|
|
25
25
|
STORAGE_LOCAL_DRIVER: 'local',
|
|
26
26
|
STORAGE_LOCAL_ROOT: './uploads',
|
|
@@ -61,6 +61,8 @@ const defaults = {
|
|
|
61
61
|
ASSETS_TRANSFORM_MAX_CONCURRENT: 1,
|
|
62
62
|
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION: 6000,
|
|
63
63
|
ASSETS_TRANSFORM_MAX_OPERATIONS: 5,
|
|
64
|
+
IP_TRUST_PROXY: true,
|
|
65
|
+
IP_CUSTOM_HEADER: false,
|
|
64
66
|
SERVE_APP: true,
|
|
65
67
|
};
|
|
66
68
|
// Allows us to force certain environment variable into a type, instead of relying
|
package/dist/extensions.d.ts
CHANGED
package/dist/extensions.js
CHANGED
|
@@ -28,7 +28,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
28
28
|
const node_1 = require("@directus/shared/utils/node");
|
|
29
29
|
const constants_1 = require("@directus/shared/constants");
|
|
30
30
|
const database_1 = __importDefault(require("./database"));
|
|
31
|
-
const emitter_1 =
|
|
31
|
+
const emitter_1 = __importStar(require("./emitter"));
|
|
32
32
|
const env_1 = __importDefault(require("./env"));
|
|
33
33
|
const exceptions = __importStar(require("./exceptions"));
|
|
34
34
|
const sharedExceptions = __importStar(require("@directus/shared/exceptions"));
|
|
@@ -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) {
|
|
@@ -61,6 +62,7 @@ class ExtensionManager {
|
|
|
61
62
|
this.apiHooks = [];
|
|
62
63
|
this.apiEndpoints = [];
|
|
63
64
|
this.isScheduleHookEnabled = true;
|
|
65
|
+
this.apiEmitter = new emitter_1.Emitter();
|
|
64
66
|
this.endpointRouter = (0, express_1.Router)();
|
|
65
67
|
}
|
|
66
68
|
async initialize({ schedule } = { schedule: true }) {
|
|
@@ -92,6 +94,7 @@ class ExtensionManager {
|
|
|
92
94
|
logger_1.default.info('Reloading extensions');
|
|
93
95
|
this.unregisterHooks();
|
|
94
96
|
this.unregisterEndpoints();
|
|
97
|
+
this.apiEmitter.offAll();
|
|
95
98
|
if (env_1.default.SERVE_APP) {
|
|
96
99
|
this.appExtensions = {};
|
|
97
100
|
}
|
|
@@ -139,12 +142,13 @@ class ExtensionManager {
|
|
|
139
142
|
return bundles;
|
|
140
143
|
}
|
|
141
144
|
async getSharedDepsMapping(deps) {
|
|
142
|
-
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'));
|
|
143
146
|
const depsMapping = {};
|
|
144
147
|
for (const dep of deps) {
|
|
145
|
-
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));
|
|
146
150
|
if (depName) {
|
|
147
|
-
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);
|
|
148
152
|
depsMapping[dep] = depUrl.toString({ rootRelative: true });
|
|
149
153
|
}
|
|
150
154
|
else {
|
|
@@ -237,6 +241,7 @@ class ExtensionManager {
|
|
|
237
241
|
exceptions: { ...exceptions, ...sharedExceptions },
|
|
238
242
|
env: env_1.default,
|
|
239
243
|
database: (0, database_1.default)(),
|
|
244
|
+
emitter: this.apiEmitter,
|
|
240
245
|
logger: logger_1.default,
|
|
241
246
|
getSchema: get_schema_1.getSchema,
|
|
242
247
|
});
|
|
@@ -254,6 +259,7 @@ class ExtensionManager {
|
|
|
254
259
|
exceptions: { ...exceptions, ...sharedExceptions },
|
|
255
260
|
env: env_1.default,
|
|
256
261
|
database: (0, database_1.default)(),
|
|
262
|
+
emitter: this.apiEmitter,
|
|
257
263
|
logger: logger_1.default,
|
|
258
264
|
getSchema: get_schema_1.getSchema,
|
|
259
265
|
});
|
|
@@ -27,6 +27,7 @@ const database_1 = __importDefault(require("../database"));
|
|
|
27
27
|
const env_1 = __importDefault(require("../env"));
|
|
28
28
|
const exceptions_1 = require("../exceptions");
|
|
29
29
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
30
|
+
const get_ip_from_req_1 = require("../utils/get-ip-from-req");
|
|
30
31
|
const is_directus_jwt_1 = __importDefault(require("../utils/is-directus-jwt"));
|
|
31
32
|
/**
|
|
32
33
|
* Verify the passed JWT and assign the user ID and role to `req`
|
|
@@ -37,7 +38,7 @@ const authenticate = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
37
38
|
role: null,
|
|
38
39
|
admin: false,
|
|
39
40
|
app: false,
|
|
40
|
-
ip:
|
|
41
|
+
ip: (0, get_ip_from_req_1.getIPFromReq)(req),
|
|
41
42
|
userAgent: req.get('user-agent'),
|
|
42
43
|
};
|
|
43
44
|
const database = (0, database_1.default)();
|
|
@@ -58,22 +59,12 @@ const authenticate = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
58
59
|
throw err;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
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
|
-
}
|
|
62
|
+
req.accountability.share = payload.share;
|
|
63
|
+
req.accountability.share_scope = payload.share_scope;
|
|
73
64
|
req.accountability.user = payload.id;
|
|
74
|
-
req.accountability.role =
|
|
75
|
-
req.accountability.admin =
|
|
76
|
-
req.accountability.app =
|
|
65
|
+
req.accountability.role = payload.role;
|
|
66
|
+
req.accountability.admin = payload.admin_access === true || payload.admin_access == 1;
|
|
67
|
+
req.accountability.app = payload.app_access === true || payload.app_access == 1;
|
|
77
68
|
}
|
|
78
69
|
else {
|
|
79
70
|
// 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();
|
|
@@ -9,6 +9,7 @@ const env_1 = __importDefault(require("../env"));
|
|
|
9
9
|
const exceptions_1 = require("../exceptions");
|
|
10
10
|
const rate_limiter_1 = require("../rate-limiter");
|
|
11
11
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
12
|
+
const get_ip_from_req_1 = require("../utils/get-ip-from-req");
|
|
12
13
|
const validate_env_1 = require("../utils/validate-env");
|
|
13
14
|
let checkRateLimit = (req, res, next) => next();
|
|
14
15
|
if (env_1.default.RATE_LIMITER_ENABLED === true) {
|
|
@@ -16,7 +17,7 @@ if (env_1.default.RATE_LIMITER_ENABLED === true) {
|
|
|
16
17
|
exports.rateLimiter = (0, rate_limiter_1.createRateLimiter)();
|
|
17
18
|
checkRateLimit = (0, async_handler_1.default)(async (req, res, next) => {
|
|
18
19
|
try {
|
|
19
|
-
await exports.rateLimiter.consume(req
|
|
20
|
+
await exports.rateLimiter.consume((0, get_ip_from_req_1.getIPFromReq)(req), 1);
|
|
20
21
|
}
|
|
21
22
|
catch (rateLimiterRes) {
|
|
22
23
|
if (rateLimiterRes instanceof Error)
|
|
@@ -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', '>=', new Date())
|
|
210
|
+
.andWhere((subQuery) => {
|
|
211
|
+
subQuery.whereNull('d.date_end').orWhere('d.date_end', '>=', new Date());
|
|
212
|
+
})
|
|
213
|
+
.andWhere((subQuery) => {
|
|
214
|
+
subQuery.whereNull('d.date_start').orWhere('d.date_start', '<=', new Date());
|
|
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]);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import SchemaInspector from '@directus/schema';
|
|
2
2
|
import { Knex } from 'knex';
|
|
3
|
-
import { MutationOptions } from '../services/items';
|
|
4
3
|
import Keyv from 'keyv';
|
|
5
|
-
import { AbstractServiceOptions, Collection, CollectionMeta, SchemaOverview } from '../types';
|
|
4
|
+
import { AbstractServiceOptions, Collection, CollectionMeta, SchemaOverview, MutationOptions } from '../types';
|
|
6
5
|
import { Accountability, RawField } from '@directus/shared/types';
|
|
7
6
|
import { Table } from 'knex-schema-inspector/dist/types/table';
|
|
8
7
|
export declare type RawCollection = {
|
|
@@ -359,11 +359,11 @@ class CollectionsService {
|
|
|
359
359
|
await fieldsService.deleteField(relation.collection, relation.field);
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
-
const
|
|
362
|
+
const a2oRelationsThatIncludeThisCollection = this.schema.relations.filter((relation) => {
|
|
363
363
|
var _a, _b;
|
|
364
364
|
return (_b = (_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_allowed_collections) === null || _b === void 0 ? void 0 : _b.includes(collectionKey);
|
|
365
365
|
});
|
|
366
|
-
for (const relation of
|
|
366
|
+
for (const relation of a2oRelationsThatIncludeThisCollection) {
|
|
367
367
|
const newAllowedCollections = relation
|
|
368
368
|
.meta.one_allowed_collections.filter((collection) => collectionKey !== collection)
|
|
369
369
|
.join(',');
|
package/dist/services/files.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { AbstractServiceOptions, File, PrimaryKey } from '../types';
|
|
3
|
-
import { ItemsService
|
|
2
|
+
import { AbstractServiceOptions, File, PrimaryKey, MutationOptions } from '../types';
|
|
3
|
+
import { ItemsService } from './items';
|
|
4
4
|
export declare class FilesService extends ItemsService {
|
|
5
5
|
constructor(options: AbstractServiceOptions);
|
|
6
6
|
/**
|
package/dist/services/files.js
CHANGED
|
@@ -64,14 +64,20 @@ class FilesService extends items_1.ItemsService {
|
|
|
64
64
|
payload.filesize = size;
|
|
65
65
|
if (['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff'].includes(payload.type)) {
|
|
66
66
|
const buffer = await storage_1.default.disk(data.storage).getBuffer(payload.filename_disk);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
try {
|
|
68
|
+
const meta = await (0, sharp_1.default)(buffer.content, {}).metadata();
|
|
69
|
+
if (meta.orientation && meta.orientation >= 5) {
|
|
70
|
+
payload.height = meta.width;
|
|
71
|
+
payload.width = meta.height;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
payload.width = meta.width;
|
|
75
|
+
payload.height = meta.height;
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
catch (err) {
|
|
79
|
+
logger_1.default.warn(`Couldn't extract sharp metadata from file`);
|
|
80
|
+
logger_1.default.warn(err);
|
|
75
81
|
}
|
|
76
82
|
payload.metadata = {};
|
|
77
83
|
try {
|
|
@@ -95,7 +101,7 @@ class FilesService extends items_1.ItemsService {
|
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
catch (err) {
|
|
98
|
-
logger_1.default.warn(`Couldn't extract metadata from file`);
|
|
104
|
+
logger_1.default.warn(`Couldn't extract EXIF metadata from file`);
|
|
99
105
|
logger_1.default.warn(err);
|
|
100
106
|
}
|
|
101
107
|
}
|
|
@@ -70,7 +70,7 @@ export declare class GraphQLService {
|
|
|
70
70
|
* Effectively merges the selections with the fragments used in those selections
|
|
71
71
|
*/
|
|
72
72
|
replaceFragmentsInSelections(selections: readonly SelectionNode[] | undefined, fragments: Record<string, FragmentDefinitionNode>): readonly SelectionNode[] | null;
|
|
73
|
-
injectSystemResolvers(schemaComposer: SchemaComposer<GraphQLParams['contextValue']>, { CreateCollectionTypes, ReadCollectionTypes, DeleteCollectionTypes, }: {
|
|
73
|
+
injectSystemResolvers(schemaComposer: SchemaComposer<GraphQLParams['contextValue']>, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }: {
|
|
74
74
|
CreateCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
|
75
75
|
ReadCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
|
76
76
|
UpdateCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|