@directus/api 19.3.0 → 20.0.0-rc.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 +4 -4
- package/dist/auth/drivers/ldap.js +4 -4
- package/dist/auth/drivers/local.js +4 -4
- package/dist/auth/drivers/oauth2.js +4 -4
- package/dist/auth/drivers/openid.js +2 -4
- package/dist/cache.js +3 -0
- package/dist/cli/commands/bootstrap/index.js +8 -2
- package/dist/cli/commands/init/index.js +9 -10
- package/dist/cli/utils/defaults.d.ts +4 -11
- package/dist/cli/utils/defaults.js +7 -1
- package/dist/constants.d.ts +1 -1
- package/dist/controllers/access.d.ts +2 -0
- package/dist/controllers/access.js +148 -0
- package/dist/controllers/auth.js +5 -16
- package/dist/controllers/permissions.js +14 -2
- package/dist/controllers/policies.d.ts +2 -0
- package/dist/controllers/policies.js +169 -0
- package/dist/controllers/roles.js +22 -1
- package/dist/controllers/users.js +0 -55
- package/dist/database/errors/dialects/mysql.js +23 -23
- package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
- package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.js +190 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
- package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
- package/dist/database/helpers/fn/types.d.ts +2 -1
- package/dist/database/helpers/fn/types.js +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
- package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
- package/dist/database/helpers/geometry/types.d.ts +1 -1
- package/dist/database/helpers/geometry/types.js +4 -2
- package/dist/database/index.js +2 -1
- package/dist/database/migrations/20240619A-permissions-policies.d.ts +3 -0
- package/dist/database/migrations/20240619A-permissions-policies.js +163 -0
- package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
- package/dist/database/run-ast/lib/get-db-query.js +194 -0
- package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
- package/dist/database/run-ast/lib/parse-current-level.js +41 -0
- package/dist/database/run-ast/run-ast.d.ts +7 -0
- package/dist/database/run-ast/run-ast.js +107 -0
- package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
- package/dist/database/run-ast/types.js +1 -0
- package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
- package/dist/database/run-ast/utils/apply-case-when.js +26 -0
- package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
- package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
- package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
- package/dist/database/run-ast/utils/get-field-alias.js +4 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
- package/dist/flows.js +3 -4
- package/dist/middleware/authenticate.js +2 -7
- package/dist/middleware/cache.js +1 -1
- package/dist/middleware/cors.js +4 -4
- package/dist/middleware/respond.js +1 -1
- package/dist/permissions/cache.d.ts +2 -0
- package/dist/permissions/cache.js +23 -0
- package/dist/permissions/lib/fetch-permissions.d.ts +10 -0
- package/dist/permissions/lib/fetch-permissions.js +55 -0
- package/dist/permissions/lib/fetch-policies.d.ts +7 -0
- package/dist/permissions/lib/fetch-policies.js +28 -0
- package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
- package/dist/permissions/lib/fetch-roles-tree.js +28 -0
- package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
- package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
- package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/types.js +1 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
- package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
- package/dist/permissions/modules/process-ast/process-ast.js +39 -0
- package/dist/permissions/modules/process-ast/types.d.ts +24 -0
- package/dist/permissions/modules/process-ast/types.js +1 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +50 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.js +24 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
- package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
- package/dist/permissions/modules/process-payload/process-payload.js +77 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
- package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
- package/dist/permissions/modules/validate-access/validate-access.js +28 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
- package/dist/permissions/types.d.ts +6 -0
- package/dist/permissions/types.js +1 -0
- package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
- package/dist/permissions/utils/create-default-accountability.js +11 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
- package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
- package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
- package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
- package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
- package/dist/permissions/utils/process-permissions.d.ts +7 -0
- package/dist/permissions/utils/process-permissions.js +9 -0
- package/dist/permissions/utils/with-cache.d.ts +10 -0
- package/dist/permissions/utils/with-cache.js +25 -0
- package/dist/services/access.d.ts +10 -0
- package/dist/services/access.js +43 -0
- package/dist/services/activity.js +22 -10
- package/dist/services/assets.d.ts +2 -3
- package/dist/services/assets.js +10 -5
- package/dist/services/authentication.js +18 -18
- package/dist/services/collections.js +18 -17
- package/dist/services/fields.d.ts +0 -1
- package/dist/services/fields.js +53 -24
- package/dist/services/files.d.ts +0 -4
- package/dist/services/files.js +10 -10
- package/dist/services/flows.d.ts +0 -2
- package/dist/services/flows.js +2 -14
- package/dist/services/graphql/index.d.ts +3 -3
- package/dist/services/graphql/index.js +126 -22
- package/dist/services/graphql/subscription.js +2 -4
- package/dist/services/import-export.js +23 -9
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/items.d.ts +40 -14
- package/dist/services/items.js +182 -79
- package/dist/services/meta.js +60 -23
- package/dist/services/notifications.d.ts +0 -1
- package/dist/services/notifications.js +0 -7
- package/dist/services/operations.d.ts +0 -2
- package/dist/services/operations.js +2 -14
- package/dist/services/payload.d.ts +9 -10
- package/dist/services/payload.js +35 -19
- package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
- package/dist/services/{permissions/index.js → permissions.js} +30 -54
- package/dist/services/policies.d.ts +12 -0
- package/dist/services/policies.js +87 -0
- package/dist/services/relations.d.ts +0 -6
- package/dist/services/relations.js +26 -29
- package/dist/services/roles.d.ts +4 -14
- package/dist/services/roles.js +56 -420
- package/dist/services/shares.d.ts +0 -2
- package/dist/services/shares.js +12 -8
- package/dist/services/specifications.d.ts +2 -2
- package/dist/services/specifications.js +39 -27
- package/dist/services/users.d.ts +2 -20
- package/dist/services/users.js +87 -190
- package/dist/services/utils.js +11 -7
- package/dist/services/versions.d.ts +0 -2
- package/dist/services/versions.js +34 -10
- package/dist/telemetry/lib/get-report.js +6 -3
- package/dist/telemetry/types/report.d.ts +4 -0
- package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
- package/dist/telemetry/utils/check-user-limits.js +19 -0
- package/dist/telemetry/utils/get-filesize-sum.d.ts +5 -0
- package/dist/telemetry/utils/get-filesize-sum.js +7 -0
- package/dist/telemetry/utils/should-check-user-limits.d.ts +4 -0
- package/dist/telemetry/utils/should-check-user-limits.js +13 -0
- package/dist/types/ast.d.ts +43 -1
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-query.d.ts +4 -3
- package/dist/utils/apply-query.js +37 -8
- package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +17 -0
- package/dist/utils/fetch-user-count/fetch-access-lookup.js +22 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
- package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
- package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
- package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
- package/dist/utils/fetch-user-count/fetch-user-count.js +57 -0
- package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
- package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
- package/dist/utils/get-accountability-for-role.js +16 -25
- package/dist/utils/get-accountability-for-token.js +17 -16
- package/dist/utils/get-cache-key.d.ts +1 -1
- package/dist/utils/get-cache-key.js +12 -1
- package/dist/utils/get-column.d.ts +2 -1
- package/dist/utils/get-column.js +1 -0
- package/dist/utils/get-graphql-type.js +1 -0
- package/dist/utils/get-service.d.ts +1 -1
- package/dist/utils/get-service.js +14 -10
- package/dist/utils/reduce-schema.d.ts +4 -6
- package/dist/utils/reduce-schema.js +14 -34
- package/dist/utils/validate-user-count-integrity.d.ts +13 -0
- package/dist/utils/validate-user-count-integrity.js +29 -0
- package/dist/websocket/authenticate.d.ts +0 -2
- package/dist/websocket/authenticate.js +0 -12
- package/dist/websocket/controllers/graphql.js +1 -4
- package/dist/websocket/controllers/hooks.js +4 -0
- package/dist/websocket/controllers/rest.js +0 -2
- package/dist/websocket/handlers/subscribe.js +0 -2
- package/dist/websocket/utils/items.d.ts +1 -1
- package/dist/websocket/utils/items.js +4 -1
- package/package.json +36 -35
- package/dist/database/run-ast.js +0 -450
- package/dist/middleware/check-ip.d.ts +0 -2
- package/dist/middleware/check-ip.js +0 -37
- package/dist/middleware/get-permissions.d.ts +0 -3
- package/dist/middleware/get-permissions.js +0 -10
- package/dist/services/authorization.d.ts +0 -17
- package/dist/services/authorization.js +0 -456
- package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
- package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
- package/dist/telemetry/utils/check-increased-user-limits.js +0 -22
- package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
- package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
- package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
- package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
- package/dist/telemetry/utils/get-user-count.d.ts +0 -8
- package/dist/telemetry/utils/get-user-count.js +0 -33
- package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
- package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
- package/dist/utils/get-ast-from-query.d.ts +0 -13
- package/dist/utils/get-ast-from-query.js +0 -297
- package/dist/utils/get-permissions.d.ts +0 -2
- package/dist/utils/get-permissions.js +0 -150
- package/dist/utils/merge-permissions-for-share.d.ts +0 -4
- package/dist/utils/merge-permissions-for-share.js +0 -109
- package/dist/utils/merge-permissions.d.ts +0 -3
- package/dist/utils/merge-permissions.js +0 -95
package/dist/services/shares.js
CHANGED
|
@@ -3,29 +3,33 @@ import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
|
|
|
3
3
|
import argon2 from 'argon2';
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
5
|
import { useLogger } from '../logger.js';
|
|
6
|
+
import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
|
|
6
7
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
7
8
|
import { getSecret } from '../utils/get-secret.js';
|
|
8
9
|
import { md } from '../utils/md.js';
|
|
9
10
|
import { Url } from '../utils/url.js';
|
|
10
11
|
import { userName } from '../utils/user-name.js';
|
|
11
|
-
import { AuthorizationService } from './authorization.js';
|
|
12
12
|
import { ItemsService } from './items.js';
|
|
13
13
|
import { MailService } from './mail/index.js';
|
|
14
14
|
import { UsersService } from './users.js';
|
|
15
15
|
const env = useEnv();
|
|
16
16
|
const logger = useLogger();
|
|
17
17
|
export class SharesService extends ItemsService {
|
|
18
|
-
authorizationService;
|
|
19
18
|
constructor(options) {
|
|
20
19
|
super('directus_shares', options);
|
|
21
|
-
this.authorizationService = new AuthorizationService({
|
|
22
|
-
accountability: this.accountability,
|
|
23
|
-
knex: this.knex,
|
|
24
|
-
schema: this.schema,
|
|
25
|
-
});
|
|
26
20
|
}
|
|
27
21
|
async createOne(data, opts) {
|
|
28
|
-
|
|
22
|
+
if (this.accountability) {
|
|
23
|
+
await validateAccess({
|
|
24
|
+
accountability: this.accountability,
|
|
25
|
+
action: 'share',
|
|
26
|
+
collection: data['collection'],
|
|
27
|
+
primaryKeys: [data['item']],
|
|
28
|
+
}, {
|
|
29
|
+
schema: this.schema,
|
|
30
|
+
knex: this.knex,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
29
33
|
return super.createOne(data, opts);
|
|
30
34
|
}
|
|
31
35
|
async login(payload, options) {
|
|
@@ -9,7 +9,7 @@ export declare class SpecificationService {
|
|
|
9
9
|
schema: SchemaOverview;
|
|
10
10
|
oas: OASSpecsService;
|
|
11
11
|
graphql: GraphQLSpecsService;
|
|
12
|
-
constructor(
|
|
12
|
+
constructor(options: AbstractServiceOptions);
|
|
13
13
|
}
|
|
14
14
|
interface SpecificationSubService {
|
|
15
15
|
generate: (_?: any) => Promise<any>;
|
|
@@ -18,7 +18,7 @@ declare class OASSpecsService implements SpecificationSubService {
|
|
|
18
18
|
accountability: Accountability | null;
|
|
19
19
|
knex: Knex;
|
|
20
20
|
schema: SchemaOverview;
|
|
21
|
-
constructor(
|
|
21
|
+
constructor(options: AbstractServiceOptions);
|
|
22
22
|
generate(host?: string): Promise<OpenAPIObject>;
|
|
23
23
|
private generateTags;
|
|
24
24
|
private generatePaths;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import formatTitle from '@directus/format-title';
|
|
3
3
|
import { spec } from '@directus/specs';
|
|
4
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
4
5
|
import { version } from 'directus/version';
|
|
5
6
|
import { cloneDeep, mergeWith } from 'lodash-es';
|
|
6
7
|
import { OAS_REQUIRED_SCHEMAS } from '../constants.js';
|
|
7
8
|
import getDatabase from '../database/index.js';
|
|
9
|
+
import { fetchPermissions } from '../permissions/lib/fetch-permissions.js';
|
|
10
|
+
import { fetchPolicies } from '../permissions/lib/fetch-policies.js';
|
|
11
|
+
import { fetchAllowedFieldMap } from '../permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js';
|
|
8
12
|
import { getRelationType } from '../utils/get-relation-type.js';
|
|
9
13
|
import { reduceSchema } from '../utils/reduce-schema.js';
|
|
10
14
|
import { GraphQLService } from './graphql/index.js';
|
|
11
|
-
import { isSystemCollection } from '@directus/system-data';
|
|
12
15
|
const env = useEnv();
|
|
13
16
|
export class SpecificationService {
|
|
14
17
|
accountability;
|
|
@@ -16,29 +19,38 @@ export class SpecificationService {
|
|
|
16
19
|
schema;
|
|
17
20
|
oas;
|
|
18
21
|
graphql;
|
|
19
|
-
constructor(
|
|
20
|
-
this.accountability = accountability || null;
|
|
21
|
-
this.knex = knex || getDatabase();
|
|
22
|
-
this.schema = schema;
|
|
23
|
-
this.oas = new OASSpecsService(
|
|
24
|
-
this.graphql = new GraphQLSpecsService(
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.accountability = options.accountability || null;
|
|
24
|
+
this.knex = options.knex || getDatabase();
|
|
25
|
+
this.schema = options.schema;
|
|
26
|
+
this.oas = new OASSpecsService(options);
|
|
27
|
+
this.graphql = new GraphQLSpecsService(options);
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
class OASSpecsService {
|
|
28
31
|
accountability;
|
|
29
32
|
knex;
|
|
30
33
|
schema;
|
|
31
|
-
constructor(
|
|
32
|
-
this.accountability = accountability || null;
|
|
33
|
-
this.knex = knex || getDatabase();
|
|
34
|
-
this.schema =
|
|
35
|
-
this.accountability?.admin === true ? schema : reduceSchema(schema, accountability?.permissions || null);
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.accountability = options.accountability || null;
|
|
36
|
+
this.knex = options.knex || getDatabase();
|
|
37
|
+
this.schema = options.schema;
|
|
36
38
|
}
|
|
37
39
|
async generate(host) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
let schema = this.schema;
|
|
41
|
+
let permissions = [];
|
|
42
|
+
if (this.accountability && this.accountability.admin !== true) {
|
|
43
|
+
const allowedFields = await fetchAllowedFieldMap({
|
|
44
|
+
accountability: this.accountability,
|
|
45
|
+
action: 'read',
|
|
46
|
+
}, { schema, knex: this.knex });
|
|
47
|
+
schema = reduceSchema(schema, allowedFields);
|
|
48
|
+
const policies = await fetchPolicies(this.accountability, { schema, knex: this.knex });
|
|
49
|
+
permissions = await fetchPermissions({ action: 'read', policies, accountability: this.accountability }, { schema, knex: this.knex });
|
|
50
|
+
}
|
|
51
|
+
const tags = await this.generateTags(schema);
|
|
40
52
|
const paths = await this.generatePaths(permissions, tags);
|
|
41
|
-
const components = await this.generateComponents(tags);
|
|
53
|
+
const components = await this.generateComponents(schema, tags);
|
|
42
54
|
const isDefaultPublicUrl = env['PUBLIC_URL'] === '/';
|
|
43
55
|
const url = isDefaultPublicUrl && host ? host : env['PUBLIC_URL'];
|
|
44
56
|
const spec = {
|
|
@@ -62,9 +74,9 @@ class OASSpecsService {
|
|
|
62
74
|
spec.components = components;
|
|
63
75
|
return spec;
|
|
64
76
|
}
|
|
65
|
-
async generateTags() {
|
|
77
|
+
async generateTags(schema) {
|
|
66
78
|
const systemTags = cloneDeep(spec.tags);
|
|
67
|
-
const collections = Object.values(
|
|
79
|
+
const collections = Object.values(schema.collections);
|
|
68
80
|
const tags = [];
|
|
69
81
|
for (const systemTag of systemTags) {
|
|
70
82
|
// Check if necessary authentication level is given
|
|
@@ -246,7 +258,7 @@ class OASSpecsService {
|
|
|
246
258
|
}
|
|
247
259
|
return paths;
|
|
248
260
|
}
|
|
249
|
-
async generateComponents(tags) {
|
|
261
|
+
async generateComponents(schema, tags) {
|
|
250
262
|
if (!tags)
|
|
251
263
|
return;
|
|
252
264
|
let components = cloneDeep(spec.components);
|
|
@@ -264,7 +276,7 @@ class OASSpecsService {
|
|
|
264
276
|
};
|
|
265
277
|
}
|
|
266
278
|
}
|
|
267
|
-
const collections = Object.values(
|
|
279
|
+
const collections = Object.values(schema.collections);
|
|
268
280
|
for (const collection of collections) {
|
|
269
281
|
const tag = tags.find((tag) => tag['x-collection'] === collection.collection);
|
|
270
282
|
if (!tag)
|
|
@@ -277,7 +289,7 @@ class OASSpecsService {
|
|
|
277
289
|
schemaComponent['x-collection'] = collection.collection;
|
|
278
290
|
for (const field of fieldsInCollection) {
|
|
279
291
|
schemaComponent.properties[field.field] =
|
|
280
|
-
cloneDeep(spec.components.schemas[tag.name].properties[field.field]) || this.generateField(collection.collection, field, tags);
|
|
292
|
+
cloneDeep(spec.components.schemas[tag.name].properties[field.field]) || this.generateField(schema, collection.collection, field, tags);
|
|
281
293
|
}
|
|
282
294
|
components.schemas[tag.name] = schemaComponent;
|
|
283
295
|
}
|
|
@@ -288,7 +300,7 @@ class OASSpecsService {
|
|
|
288
300
|
'x-collection': collection.collection,
|
|
289
301
|
};
|
|
290
302
|
for (const field of fieldsInCollection) {
|
|
291
|
-
schemaComponent.properties[field.field] = this.generateField(collection.collection, field, tags);
|
|
303
|
+
schemaComponent.properties[field.field] = this.generateField(schema, collection.collection, field, tags);
|
|
292
304
|
}
|
|
293
305
|
components.schemas[tag.name] = schemaComponent;
|
|
294
306
|
}
|
|
@@ -311,13 +323,13 @@ class OASSpecsService {
|
|
|
311
323
|
return 'read';
|
|
312
324
|
}
|
|
313
325
|
}
|
|
314
|
-
generateField(collection, field, tags) {
|
|
326
|
+
generateField(schema, collection, field, tags) {
|
|
315
327
|
let propertyObject = {};
|
|
316
328
|
propertyObject.nullable = field.nullable;
|
|
317
329
|
if (field.note) {
|
|
318
330
|
propertyObject.description = field.note;
|
|
319
331
|
}
|
|
320
|
-
const relation =
|
|
332
|
+
const relation = schema.relations.find((relation) => (relation.collection === collection && relation.field === field.field) ||
|
|
321
333
|
(relation.related_collection === collection && relation.meta?.one_field === field.field));
|
|
322
334
|
if (!relation) {
|
|
323
335
|
propertyObject = {
|
|
@@ -335,10 +347,10 @@ class OASSpecsService {
|
|
|
335
347
|
const relatedTag = tags.find((tag) => tag['x-collection'] === relation.related_collection);
|
|
336
348
|
if (!relatedTag ||
|
|
337
349
|
!relation.related_collection ||
|
|
338
|
-
relation.related_collection in
|
|
350
|
+
relation.related_collection in schema.collections === false) {
|
|
339
351
|
return propertyObject;
|
|
340
352
|
}
|
|
341
|
-
const relatedCollection =
|
|
353
|
+
const relatedCollection = schema.collections[relation.related_collection];
|
|
342
354
|
const relatedPrimaryKeyField = relatedCollection.fields[relatedCollection.primary];
|
|
343
355
|
propertyObject.oneOf = [
|
|
344
356
|
{
|
|
@@ -351,10 +363,10 @@ class OASSpecsService {
|
|
|
351
363
|
}
|
|
352
364
|
else if (relationType === 'o2m') {
|
|
353
365
|
const relatedTag = tags.find((tag) => tag['x-collection'] === relation.collection);
|
|
354
|
-
if (!relatedTag || !relation.related_collection || relation.collection in
|
|
366
|
+
if (!relatedTag || !relation.related_collection || relation.collection in schema.collections === false) {
|
|
355
367
|
return propertyObject;
|
|
356
368
|
}
|
|
357
|
-
const relatedCollection =
|
|
369
|
+
const relatedCollection = schema.collections[relation.collection];
|
|
358
370
|
const relatedPrimaryKeyField = relatedCollection.fields[relatedCollection.primary];
|
|
359
371
|
if (!relatedTag || !relatedPrimaryKeyField)
|
|
360
372
|
return propertyObject;
|
package/dist/services/users.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Item, PrimaryKey,
|
|
1
|
+
import type { Item, PrimaryKey, RegisterUserInput } from '@directus/types';
|
|
2
2
|
import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
|
|
3
3
|
import { ItemsService } from './items.js';
|
|
4
4
|
export declare class UsersService extends ItemsService {
|
|
@@ -13,11 +13,6 @@ export declare class UsersService extends ItemsService {
|
|
|
13
13
|
* directus_settings.auth_password_policy
|
|
14
14
|
*/
|
|
15
15
|
private checkPasswordPolicy;
|
|
16
|
-
private checkRemainingAdminExistence;
|
|
17
|
-
/**
|
|
18
|
-
* Make sure there's at least one active admin user when updating user status
|
|
19
|
-
*/
|
|
20
|
-
private checkRemainingActiveAdmin;
|
|
21
16
|
/**
|
|
22
17
|
* Get basic information of user identified by email
|
|
23
18
|
*/
|
|
@@ -38,32 +33,19 @@ export declare class UsersService extends ItemsService {
|
|
|
38
33
|
* Create multiple new users
|
|
39
34
|
*/
|
|
40
35
|
createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
41
|
-
/**
|
|
42
|
-
* Update many users by query
|
|
43
|
-
*/
|
|
44
|
-
updateByQuery(query: Query, data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
45
|
-
/**
|
|
46
|
-
* Update a single user by primary key
|
|
47
|
-
*/
|
|
48
|
-
updateOne(key: PrimaryKey, data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
49
|
-
updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
50
36
|
/**
|
|
51
37
|
* Update many users by primary key
|
|
52
38
|
*/
|
|
53
39
|
updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
54
|
-
/**
|
|
55
|
-
* Delete a single user by primary key
|
|
56
|
-
*/
|
|
57
|
-
deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
58
40
|
/**
|
|
59
41
|
* Delete multiple users by primary key
|
|
60
42
|
*/
|
|
61
43
|
deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
62
|
-
deleteByQuery(query: Query, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
63
44
|
inviteUser(email: string | string[], role: string, url: string | null, subject?: string | null): Promise<void>;
|
|
64
45
|
acceptInvite(token: string, password: string): Promise<void>;
|
|
65
46
|
registerUser(input: RegisterUserInput): Promise<void>;
|
|
66
47
|
verifyRegistration(token: string): Promise<string>;
|
|
67
48
|
requestPasswordReset(email: string, url: string | null, subject?: string | null): Promise<void>;
|
|
68
49
|
resetPassword(token: string, password: string): Promise<void>;
|
|
50
|
+
private clearCaches;
|
|
69
51
|
}
|
package/dist/services/users.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
|
-
import { ForbiddenError, InvalidPayloadError, RecordNotUniqueError
|
|
3
|
-
import { getSimpleHash, toArray,
|
|
2
|
+
import { ForbiddenError, InvalidPayloadError, RecordNotUniqueError } from '@directus/errors';
|
|
3
|
+
import { getSimpleHash, toArray, validatePayload } from '@directus/utils';
|
|
4
4
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
5
5
|
import Joi from 'joi';
|
|
6
6
|
import jwt from 'jsonwebtoken';
|
|
7
|
-
import {
|
|
7
|
+
import { isEmpty } from 'lodash-es';
|
|
8
8
|
import { performance } from 'perf_hooks';
|
|
9
|
+
import { clearSystemCache } from '../cache.js';
|
|
9
10
|
import getDatabase from '../database/index.js';
|
|
10
11
|
import { useLogger } from '../logger.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { getRoleCountsByUsers } from '../telemetry/utils/get-role-counts-by-users.js';
|
|
14
|
-
import {} from '../telemetry/utils/get-user-count.js';
|
|
12
|
+
import { validateRemainingAdminUsers } from '../permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js';
|
|
13
|
+
import { createDefaultAccountability } from '../permissions/utils/create-default-accountability.js';
|
|
15
14
|
import { getSecret } from '../utils/get-secret.js';
|
|
16
15
|
import isUrlAllowed from '../utils/is-url-allowed.js';
|
|
17
16
|
import { verifyJWT } from '../utils/jwt.js';
|
|
18
17
|
import { stall } from '../utils/stall.js';
|
|
19
|
-
import { transaction } from '../utils/transaction.js';
|
|
20
18
|
import { Url } from '../utils/url.js';
|
|
19
|
+
import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
|
|
21
20
|
import { ItemsService } from './items.js';
|
|
22
21
|
import { MailService } from './mail/index.js';
|
|
23
22
|
import { SettingsService } from './settings.js';
|
|
@@ -88,42 +87,11 @@ export class UsersService extends ItemsService {
|
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
|
-
async checkRemainingAdminExistence(excludeKeys) {
|
|
92
|
-
// Make sure there's at least one admin user left after this deletion is done
|
|
93
|
-
const otherAdminUsers = await this.knex
|
|
94
|
-
.count('*', { as: 'count' })
|
|
95
|
-
.from('directus_users')
|
|
96
|
-
.whereNotIn('directus_users.id', excludeKeys)
|
|
97
|
-
.andWhere({ 'directus_roles.admin_access': true })
|
|
98
|
-
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
99
|
-
.first();
|
|
100
|
-
const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
|
|
101
|
-
if (otherAdminUsersCount === 0) {
|
|
102
|
-
throw new UnprocessableContentError({ reason: `You can't remove the last admin user from the role` });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Make sure there's at least one active admin user when updating user status
|
|
107
|
-
*/
|
|
108
|
-
async checkRemainingActiveAdmin(excludeKeys) {
|
|
109
|
-
const otherAdminUsers = await this.knex
|
|
110
|
-
.count('*', { as: 'count' })
|
|
111
|
-
.from('directus_users')
|
|
112
|
-
.whereNotIn('directus_users.id', excludeKeys)
|
|
113
|
-
.andWhere({ 'directus_roles.admin_access': true })
|
|
114
|
-
.andWhere({ 'directus_users.status': 'active' })
|
|
115
|
-
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
116
|
-
.first();
|
|
117
|
-
const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
|
|
118
|
-
if (otherAdminUsersCount === 0) {
|
|
119
|
-
throw new UnprocessableContentError({ reason: `You can't change the active status of the last admin user` });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
90
|
/**
|
|
123
91
|
* Get basic information of user identified by email
|
|
124
92
|
*/
|
|
125
93
|
async getUserByEmail(email) {
|
|
126
|
-
return
|
|
94
|
+
return this.knex
|
|
127
95
|
.select('id', 'role', 'status', 'password', 'email')
|
|
128
96
|
.from('directus_users')
|
|
129
97
|
.whereRaw(`LOWER(??) = ?`, ['email', email.toLowerCase()])
|
|
@@ -158,17 +126,34 @@ export class UsersService extends ItemsService {
|
|
|
158
126
|
/**
|
|
159
127
|
* Create a new user
|
|
160
128
|
*/
|
|
161
|
-
async createOne(data, opts) {
|
|
162
|
-
|
|
163
|
-
|
|
129
|
+
async createOne(data, opts = {}) {
|
|
130
|
+
try {
|
|
131
|
+
if ('email' in data) {
|
|
132
|
+
this.validateEmail(data['email']);
|
|
133
|
+
await this.checkUniqueEmails([data['email']]);
|
|
134
|
+
}
|
|
135
|
+
if ('password' in data) {
|
|
136
|
+
await this.checkPasswordPolicy([data['password']]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
opts.preMutationError = err;
|
|
141
|
+
}
|
|
142
|
+
if (!('status' in data) || data['status'] === 'active') {
|
|
143
|
+
// Creating a user only requires checking user limits if the user is active, no need to care about the role
|
|
144
|
+
opts.userIntegrityCheckFlags =
|
|
145
|
+
(opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
|
|
146
|
+
opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
|
|
147
|
+
}
|
|
148
|
+
return await super.createOne(data, opts);
|
|
164
149
|
}
|
|
165
150
|
/**
|
|
166
151
|
* Create multiple new users
|
|
167
152
|
*/
|
|
168
|
-
async createMany(data, opts) {
|
|
169
|
-
const emails = data
|
|
170
|
-
const passwords = data
|
|
171
|
-
const
|
|
153
|
+
async createMany(data, opts = {}) {
|
|
154
|
+
const emails = data.map((payload) => payload['email']).filter((email) => email);
|
|
155
|
+
const passwords = data.map((payload) => payload['password']).filter((password) => password);
|
|
156
|
+
const someActive = data.some((payload) => !('status' in payload) || payload['status'] === 'active');
|
|
172
157
|
try {
|
|
173
158
|
if (emails.length) {
|
|
174
159
|
this.validateEmail(emails);
|
|
@@ -177,128 +162,30 @@ export class UsersService extends ItemsService {
|
|
|
177
162
|
if (passwords.length) {
|
|
178
163
|
await this.checkPasswordPolicy(passwords);
|
|
179
164
|
}
|
|
180
|
-
if (roles.length) {
|
|
181
|
-
const increasedCounts = {
|
|
182
|
-
admin: 0,
|
|
183
|
-
app: 0,
|
|
184
|
-
api: 0,
|
|
185
|
-
};
|
|
186
|
-
const existingRoles = [];
|
|
187
|
-
for (const role of roles) {
|
|
188
|
-
if (typeof role === 'object') {
|
|
189
|
-
if ('admin_access' in role && role['admin_access'] === true) {
|
|
190
|
-
increasedCounts.admin++;
|
|
191
|
-
}
|
|
192
|
-
else if ('app_access' in role && role['app_access'] === true) {
|
|
193
|
-
increasedCounts.app++;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
increasedCounts.api++;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
existingRoles.push(role);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
const existingRoleCounts = await getRoleCountsByRoles(this.knex, existingRoles);
|
|
204
|
-
mergeWith(increasedCounts, existingRoleCounts, (x, y) => x + y);
|
|
205
|
-
await checkIncreasedUserLimits(this.knex, increasedCounts);
|
|
206
|
-
}
|
|
207
165
|
}
|
|
208
166
|
catch (err) {
|
|
209
|
-
|
|
167
|
+
opts.preMutationError = err;
|
|
210
168
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
async updateOne(key, data, opts) {
|
|
224
|
-
await this.updateMany([key], data, opts);
|
|
225
|
-
return key;
|
|
226
|
-
}
|
|
227
|
-
async updateBatch(data, opts = {}) {
|
|
228
|
-
if (!opts.mutationTracker)
|
|
229
|
-
opts.mutationTracker = this.createMutationTracker();
|
|
230
|
-
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
231
|
-
const keys = [];
|
|
232
|
-
await transaction(this.knex, async (trx) => {
|
|
233
|
-
const service = new UsersService({
|
|
234
|
-
accountability: this.accountability,
|
|
235
|
-
knex: trx,
|
|
236
|
-
schema: this.schema,
|
|
237
|
-
});
|
|
238
|
-
for (const item of data) {
|
|
239
|
-
if (!item[primaryKeyField])
|
|
240
|
-
throw new InvalidPayloadError({ reason: `User in update misses primary key` });
|
|
241
|
-
keys.push(await service.updateOne(item[primaryKeyField], item, opts));
|
|
242
|
-
}
|
|
169
|
+
if (someActive) {
|
|
170
|
+
// Creating users only requires checking user limits if the users are active, no need to care about the role
|
|
171
|
+
opts.userIntegrityCheckFlags =
|
|
172
|
+
(opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
|
|
173
|
+
opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
|
|
174
|
+
}
|
|
175
|
+
// Use generic ItemsService to avoid calling `UserService.createOne` to avoid additional work of validating emails,
|
|
176
|
+
// as this requires one query per email if done in `createOne`
|
|
177
|
+
const itemsService = new ItemsService(this.collection, {
|
|
178
|
+
schema: this.schema,
|
|
179
|
+
accountability: this.accountability,
|
|
180
|
+
knex: this.knex,
|
|
243
181
|
});
|
|
244
|
-
return
|
|
182
|
+
return await itemsService.createMany(data, opts);
|
|
245
183
|
}
|
|
246
184
|
/**
|
|
247
185
|
* Update many users by primary key
|
|
248
186
|
*/
|
|
249
|
-
async updateMany(keys, data, opts) {
|
|
187
|
+
async updateMany(keys, data, opts = {}) {
|
|
250
188
|
try {
|
|
251
|
-
if (data['role']) {
|
|
252
|
-
/*
|
|
253
|
-
* data['role'] has the following cases:
|
|
254
|
-
* - a string with existing role id
|
|
255
|
-
* - an object with existing role id for GraphQL mutations
|
|
256
|
-
* - an object with data for new role
|
|
257
|
-
*/
|
|
258
|
-
const role = data['role']?.id ?? data['role'];
|
|
259
|
-
let newRole;
|
|
260
|
-
if (typeof role === 'string') {
|
|
261
|
-
newRole = await this.knex
|
|
262
|
-
.select('admin_access', 'app_access')
|
|
263
|
-
.from('directus_roles')
|
|
264
|
-
.where('id', role)
|
|
265
|
-
.first();
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
newRole = role;
|
|
269
|
-
}
|
|
270
|
-
if (!newRole?.admin_access) {
|
|
271
|
-
await this.checkRemainingAdminExistence(keys);
|
|
272
|
-
}
|
|
273
|
-
if (newRole) {
|
|
274
|
-
const existingCounts = await getRoleCountsByUsers(this.knex, keys);
|
|
275
|
-
const increasedCounts = {
|
|
276
|
-
admin: 0,
|
|
277
|
-
app: 0,
|
|
278
|
-
api: 0,
|
|
279
|
-
};
|
|
280
|
-
if (toBoolean(newRole.admin_access)) {
|
|
281
|
-
increasedCounts.admin = keys.length - existingCounts.admin;
|
|
282
|
-
}
|
|
283
|
-
else if (toBoolean(newRole.app_access)) {
|
|
284
|
-
increasedCounts.app = keys.length - existingCounts.app;
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
increasedCounts.api = keys.length - existingCounts.api;
|
|
288
|
-
}
|
|
289
|
-
await checkIncreasedUserLimits(this.knex, increasedCounts);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (data['role'] === null) {
|
|
293
|
-
await checkIncreasedUserLimits(this.knex, { admin: 0, app: 0, api: 1 });
|
|
294
|
-
}
|
|
295
|
-
if (data['status'] !== undefined && data['status'] !== 'active') {
|
|
296
|
-
await this.checkRemainingActiveAdmin(keys);
|
|
297
|
-
}
|
|
298
|
-
if (data['status'] === 'active') {
|
|
299
|
-
const increasedCounts = await getRoleCountsByUsers(this.knex, keys, { inactiveUsers: true });
|
|
300
|
-
await checkIncreasedUserLimits(this.knex, increasedCounts);
|
|
301
|
-
}
|
|
302
189
|
if (data['email']) {
|
|
303
190
|
if (keys.length > 1) {
|
|
304
191
|
throw new RecordNotUniqueError({
|
|
@@ -329,26 +216,45 @@ export class UsersService extends ItemsService {
|
|
|
329
216
|
}
|
|
330
217
|
}
|
|
331
218
|
catch (err) {
|
|
332
|
-
|
|
219
|
+
opts.preMutationError = err;
|
|
333
220
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
221
|
+
if ('role' in data) {
|
|
222
|
+
opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
|
|
223
|
+
}
|
|
224
|
+
if ('status' in data) {
|
|
225
|
+
if (data['status'] === 'active') {
|
|
226
|
+
// User are being activated, no need to check if there are enough admins
|
|
227
|
+
opts.userIntegrityCheckFlags =
|
|
228
|
+
(opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (opts.userIntegrityCheckFlags) {
|
|
235
|
+
opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
|
|
236
|
+
}
|
|
237
|
+
const result = await super.updateMany(keys, data, opts);
|
|
238
|
+
// Only clear the caches if the role has been updated
|
|
239
|
+
if ('role' in data) {
|
|
240
|
+
await this.clearCaches(opts);
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
342
243
|
}
|
|
343
244
|
/**
|
|
344
245
|
* Delete multiple users by primary key
|
|
345
246
|
*/
|
|
346
|
-
async deleteMany(keys, opts) {
|
|
347
|
-
|
|
348
|
-
|
|
247
|
+
async deleteMany(keys, opts = {}) {
|
|
248
|
+
if (opts?.onRequireUserIntegrityCheck) {
|
|
249
|
+
opts.onRequireUserIntegrityCheck(opts?.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None);
|
|
349
250
|
}
|
|
350
|
-
|
|
351
|
-
|
|
251
|
+
else {
|
|
252
|
+
try {
|
|
253
|
+
await validateRemainingAdminUsers({ excludeUsers: keys }, { knex: this.knex, schema: this.schema });
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
opts.preMutationError = err;
|
|
257
|
+
}
|
|
352
258
|
}
|
|
353
259
|
// Manual constraint, see https://github.com/directus/directus/pull/19912
|
|
354
260
|
await this.knex('directus_notifications').update({ sender: null }).whereIn('sender', keys);
|
|
@@ -356,21 +262,6 @@ export class UsersService extends ItemsService {
|
|
|
356
262
|
await super.deleteMany(keys, opts);
|
|
357
263
|
return keys;
|
|
358
264
|
}
|
|
359
|
-
async deleteByQuery(query, opts) {
|
|
360
|
-
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
361
|
-
const readQuery = cloneDeep(query);
|
|
362
|
-
readQuery.fields = [primaryKeyField];
|
|
363
|
-
// Not authenticated:
|
|
364
|
-
const itemsService = new ItemsService(this.collection, {
|
|
365
|
-
knex: this.knex,
|
|
366
|
-
schema: this.schema,
|
|
367
|
-
});
|
|
368
|
-
const itemsToDelete = await itemsService.readByQuery(readQuery);
|
|
369
|
-
const keys = itemsToDelete.map((item) => item[primaryKeyField]);
|
|
370
|
-
if (keys.length === 0)
|
|
371
|
-
return [];
|
|
372
|
-
return await this.deleteMany(keys, opts);
|
|
373
|
-
}
|
|
374
265
|
async inviteUser(email, role, url, subject) {
|
|
375
266
|
const opts = {};
|
|
376
267
|
try {
|
|
@@ -586,10 +477,16 @@ export class UsersService extends ItemsService {
|
|
|
586
477
|
knex: this.knex,
|
|
587
478
|
schema: this.schema,
|
|
588
479
|
accountability: {
|
|
589
|
-
...(this.accountability ??
|
|
480
|
+
...(this.accountability ?? createDefaultAccountability()),
|
|
590
481
|
admin: true, // We need to skip permissions checks for the update call below
|
|
591
482
|
},
|
|
592
483
|
});
|
|
593
484
|
await service.updateOne(user.id, { password, status: 'active' }, opts);
|
|
594
485
|
}
|
|
486
|
+
async clearCaches(opts) {
|
|
487
|
+
await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
|
|
488
|
+
if (this.cache && opts?.autoPurgeCache !== false) {
|
|
489
|
+
await this.cache.clear();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
595
492
|
}
|
package/dist/services/utils.js
CHANGED
|
@@ -3,6 +3,8 @@ import { systemCollectionRows } from '@directus/system-data';
|
|
|
3
3
|
import { clearSystemCache, getCache } from '../cache.js';
|
|
4
4
|
import getDatabase from '../database/index.js';
|
|
5
5
|
import emitter from '../emitter.js';
|
|
6
|
+
import { fetchAllowedFields } from '../permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js';
|
|
7
|
+
import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
|
|
6
8
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
7
9
|
export class UtilsService {
|
|
8
10
|
knex;
|
|
@@ -20,14 +22,16 @@ export class UtilsService {
|
|
|
20
22
|
if (!sortField) {
|
|
21
23
|
throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't have a sort field` });
|
|
22
24
|
}
|
|
23
|
-
if (this.accountability
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
if (this.accountability && this.accountability.admin !== true) {
|
|
26
|
+
await validateAccess({
|
|
27
|
+
accountability: this.accountability,
|
|
28
|
+
action: 'update',
|
|
29
|
+
collection,
|
|
30
|
+
}, {
|
|
31
|
+
schema: this.schema,
|
|
32
|
+
knex: this.knex,
|
|
26
33
|
});
|
|
27
|
-
|
|
28
|
-
throw new ForbiddenError();
|
|
29
|
-
}
|
|
30
|
-
const allowedFields = permissions.fields ?? [];
|
|
34
|
+
const allowedFields = await fetchAllowedFields({ collection, action: 'update', accountability: this.accountability }, { schema: this.schema, knex: this.knex });
|
|
31
35
|
if (allowedFields[0] !== '*' && allowedFields.includes(sortField) === false) {
|
|
32
36
|
throw new ForbiddenError();
|
|
33
37
|
}
|