@directus/api 31.0.0 → 32.0.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/auth.d.ts +2 -1
- package/dist/auth/auth.js +7 -2
- package/dist/auth/drivers/ldap.d.ts +0 -2
- package/dist/auth/drivers/ldap.js +9 -7
- package/dist/auth/drivers/oauth2.d.ts +0 -2
- package/dist/auth/drivers/oauth2.js +11 -8
- package/dist/auth/drivers/openid.d.ts +0 -2
- package/dist/auth/drivers/openid.js +11 -8
- package/dist/auth/drivers/saml.d.ts +0 -2
- package/dist/auth/drivers/saml.js +5 -5
- package/dist/auth.js +1 -2
- package/dist/cli/commands/bootstrap/index.js +12 -33
- package/dist/cli/commands/init/index.js +1 -1
- package/dist/cli/commands/schema/apply.d.ts +4 -0
- package/dist/cli/commands/schema/apply.js +26 -3
- package/dist/controllers/collections.js +7 -2
- package/dist/controllers/fields.js +31 -8
- package/dist/controllers/server.js +26 -1
- package/dist/controllers/settings.js +9 -2
- package/dist/controllers/users.js +2 -2
- package/dist/database/helpers/fn/types.js +3 -3
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +13 -0
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mssql.js +23 -0
- package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mysql.js +25 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/oracle.js +13 -0
- package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/postgres.js +13 -0
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +6 -0
- package/dist/database/migrations/20251012A-add-field-searchable.d.ts +3 -0
- package/dist/database/migrations/20251012A-add-field-searchable.js +10 -0
- package/dist/database/migrations/20251014A-add-project-owner.d.ts +3 -0
- package/dist/database/migrations/20251014A-add-project-owner.js +37 -0
- package/dist/database/migrations/20251028A-add-retention-indexes.d.ts +3 -0
- package/dist/database/migrations/20251028A-add-retention-indexes.js +42 -0
- package/dist/database/run-ast/lib/apply-query/add-join.js +2 -2
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
- package/dist/database/run-ast/lib/apply-query/index.d.ts +0 -1
- package/dist/database/run-ast/lib/apply-query/index.js +4 -6
- package/dist/database/run-ast/lib/apply-query/search.js +2 -0
- package/dist/database/run-ast/lib/get-db-query.js +7 -6
- package/dist/database/run-ast/utils/generate-alias.d.ts +6 -0
- package/dist/database/run-ast/utils/generate-alias.js +57 -0
- package/dist/flows.js +1 -0
- package/dist/mcp/schema.d.ts +14 -14
- package/dist/mcp/schema.js +6 -6
- package/dist/mcp/server.d.ts +9 -3
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/tools/collections.d.ts +1 -1
- package/dist/mcp/tools/fields.d.ts +1 -1
- package/dist/mcp/tools/files.d.ts +25 -25
- package/dist/mcp/tools/flows.d.ts +36 -36
- package/dist/mcp/tools/folders.d.ts +18 -18
- package/dist/mcp/tools/items.d.ts +18 -18
- package/dist/mcp/tools/operations.d.ts +19 -19
- package/dist/mcp/tools/prompts/items.md +1 -1
- package/dist/metrics/lib/create-metrics.js +16 -25
- package/dist/middleware/collection-exists.js +2 -2
- package/dist/operations/mail/index.js +3 -1
- package/dist/operations/mail/rate-limiter.d.ts +1 -0
- package/dist/operations/mail/rate-limiter.js +29 -0
- package/dist/permissions/modules/process-payload/process-payload.js +3 -10
- package/dist/permissions/modules/validate-access/validate-access.js +2 -3
- package/dist/schedules/metrics.js +6 -2
- package/dist/schedules/project.d.ts +4 -0
- package/dist/schedules/project.js +27 -0
- package/dist/services/collections.d.ts +3 -3
- package/dist/services/collections.js +16 -1
- package/dist/services/fields.d.ts +21 -5
- package/dist/services/fields.js +105 -28
- package/dist/services/graphql/resolvers/query.js +1 -1
- package/dist/services/graphql/resolvers/system-admin.js +49 -5
- package/dist/services/graphql/schema/parse-query.js +8 -8
- package/dist/services/graphql/utils/aggregate-query.d.ts +1 -1
- package/dist/services/graphql/utils/aggregate-query.js +5 -1
- package/dist/services/graphql/utils/filter-replace-m2a.js +2 -1
- package/dist/services/import-export.d.ts +9 -1
- package/dist/services/import-export.js +287 -101
- package/dist/services/items.d.ts +1 -1
- package/dist/services/items.js +36 -20
- package/dist/services/mail/index.js +2 -0
- package/dist/services/mail/rate-limiter.d.ts +1 -0
- package/dist/services/mail/rate-limiter.js +29 -0
- package/dist/services/meta.js +28 -24
- package/dist/services/schema.js +4 -1
- package/dist/services/server.d.ts +1 -0
- package/dist/services/server.js +14 -18
- package/dist/services/settings.d.ts +2 -1
- package/dist/services/settings.js +15 -0
- package/dist/services/tus/server.js +14 -9
- package/dist/telemetry/lib/get-report.js +4 -4
- package/dist/telemetry/lib/send-report.d.ts +6 -1
- package/dist/telemetry/lib/send-report.js +3 -1
- package/dist/telemetry/types/report.d.ts +17 -1
- package/dist/telemetry/utils/get-settings.d.ts +9 -0
- package/dist/telemetry/utils/get-settings.js +14 -0
- package/dist/test-utils/README.md +760 -0
- package/dist/test-utils/cache.d.ts +51 -0
- package/dist/test-utils/cache.js +59 -0
- package/dist/test-utils/database.d.ts +48 -0
- package/dist/test-utils/database.js +52 -0
- package/dist/test-utils/emitter.d.ts +35 -0
- package/dist/test-utils/emitter.js +38 -0
- package/dist/test-utils/fields-service.d.ts +28 -0
- package/dist/test-utils/fields-service.js +36 -0
- package/dist/test-utils/items-service.d.ts +23 -0
- package/dist/test-utils/items-service.js +37 -0
- package/dist/test-utils/knex.d.ts +164 -0
- package/dist/test-utils/knex.js +268 -0
- package/dist/test-utils/schema.d.ts +26 -0
- package/dist/test-utils/schema.js +35 -0
- package/dist/types/auth.d.ts +0 -2
- package/dist/utils/apply-diff.js +15 -0
- package/dist/utils/create-admin.d.ts +11 -0
- package/dist/utils/create-admin.js +50 -0
- package/dist/utils/get-schema.js +5 -3
- package/dist/utils/get-snapshot-diff.js +49 -5
- package/dist/utils/get-snapshot.js +13 -7
- package/dist/utils/sanitize-schema.d.ts +11 -4
- package/dist/utils/sanitize-schema.js +9 -6
- package/dist/utils/schedule.js +15 -19
- package/dist/utils/validate-diff.js +31 -0
- package/dist/utils/validate-snapshot.js +7 -0
- package/dist/websocket/controllers/hooks.js +12 -20
- package/dist/websocket/messages.d.ts +3 -3
- package/package.json +63 -65
- package/dist/cli/utils/defaults.d.ts +0 -4
- package/dist/cli/utils/defaults.js +0 -17
- package/dist/telemetry/utils/get-project-id.d.ts +0 -2
- package/dist/telemetry/utils/get-project-id.js +0 -4
package/dist/app.js
CHANGED
|
@@ -64,6 +64,7 @@ import metricsSchedule from './schedules/metrics.js';
|
|
|
64
64
|
import retentionSchedule from './schedules/retention.js';
|
|
65
65
|
import telemetrySchedule from './schedules/telemetry.js';
|
|
66
66
|
import tusSchedule from './schedules/tus.js';
|
|
67
|
+
import projectSchedule from './schedules/project.js';
|
|
67
68
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
68
69
|
import { Url } from './utils/url.js';
|
|
69
70
|
import { validateStorage } from './utils/validate-storage.js';
|
|
@@ -262,6 +263,7 @@ export default async function createApp() {
|
|
|
262
263
|
await telemetrySchedule();
|
|
263
264
|
await tusSchedule();
|
|
264
265
|
await metricsSchedule();
|
|
266
|
+
await projectSchedule();
|
|
265
267
|
await emitter.emitInit('app.after', { app });
|
|
266
268
|
return app;
|
|
267
269
|
}
|
package/dist/auth/auth.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
|
+
import { UsersService } from '../services/users.js';
|
|
3
4
|
import type { AuthDriverOptions, User } from '../types/index.js';
|
|
4
5
|
export declare abstract class AuthDriver {
|
|
5
6
|
knex: Knex;
|
|
6
|
-
schema: SchemaOverview;
|
|
7
7
|
constructor(options: AuthDriverOptions, _config: Record<string, any>);
|
|
8
|
+
protected getUsersService(schema: SchemaOverview): UsersService;
|
|
8
9
|
/**
|
|
9
10
|
* Get user id for a given provider payload
|
|
10
11
|
*
|
package/dist/auth/auth.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { UsersService } from '../services/users.js';
|
|
1
2
|
export class AuthDriver {
|
|
2
3
|
knex;
|
|
3
|
-
schema;
|
|
4
4
|
constructor(options, _config) {
|
|
5
5
|
this.knex = options.knex;
|
|
6
|
-
|
|
6
|
+
}
|
|
7
|
+
getUsersService(schema) {
|
|
8
|
+
return new UsersService({
|
|
9
|
+
knex: this.knex,
|
|
10
|
+
schema,
|
|
11
|
+
});
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
9
14
|
* Check with the (external) provider if the user is allowed entry to Directus
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import type { Client } from 'ldapjs';
|
|
3
|
-
import { UsersService } from '../../services/users.js';
|
|
4
3
|
import type { AuthDriverOptions, User } from '../../types/index.js';
|
|
5
4
|
import { AuthDriver } from '../auth.js';
|
|
6
5
|
export declare class LDAPAuthDriver extends AuthDriver {
|
|
7
6
|
bindClient: Client;
|
|
8
|
-
usersService: UsersService;
|
|
9
7
|
config: Record<string, any>;
|
|
10
8
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
11
9
|
private validateBindClient;
|
|
@@ -10,17 +10,16 @@ import { useLogger } from '../../logger/index.js';
|
|
|
10
10
|
import { respond } from '../../middleware/respond.js';
|
|
11
11
|
import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
|
|
12
12
|
import { AuthenticationService } from '../../services/authentication.js';
|
|
13
|
-
import { UsersService } from '../../services/users.js';
|
|
14
13
|
import asyncHandler from '../../utils/async-handler.js';
|
|
15
14
|
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
16
15
|
import { AuthDriver } from '../auth.js';
|
|
16
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
17
17
|
// 0x2: ACCOUNTDISABLE
|
|
18
18
|
// 0x10: LOCKOUT
|
|
19
19
|
// 0x800000: PASSWORD_EXPIRED
|
|
20
20
|
const INVALID_ACCOUNT_FLAGS = 0x800012;
|
|
21
21
|
export class LDAPAuthDriver extends AuthDriver {
|
|
22
22
|
bindClient;
|
|
23
|
-
usersService;
|
|
24
23
|
config;
|
|
25
24
|
constructor(options, config) {
|
|
26
25
|
super(options, config);
|
|
@@ -39,7 +38,6 @@ export class LDAPAuthDriver extends AuthDriver {
|
|
|
39
38
|
this.bindClient.on('error', (err) => {
|
|
40
39
|
logger.warn(err);
|
|
41
40
|
});
|
|
42
|
-
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
43
41
|
this.config = config;
|
|
44
42
|
}
|
|
45
43
|
async validateBindClient() {
|
|
@@ -216,9 +214,11 @@ export class LDAPAuthDriver extends AuthDriver {
|
|
|
216
214
|
email: userInfo.email,
|
|
217
215
|
};
|
|
218
216
|
}
|
|
219
|
-
const
|
|
217
|
+
const schema = await getSchema();
|
|
218
|
+
const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, { identifier: userInfo.dn, provider: this.config['provider'], providerPayload: { userInfo, userRole } }, { database: getDatabase(), schema, accountability: null });
|
|
220
219
|
// Update user to update properties that might have changed
|
|
221
|
-
|
|
220
|
+
const usersService = this.getUsersService(schema);
|
|
221
|
+
await usersService.updateOne(userId, updatedUserPayload);
|
|
222
222
|
return userId;
|
|
223
223
|
}
|
|
224
224
|
if (!userInfo) {
|
|
@@ -232,11 +232,13 @@ export class LDAPAuthDriver extends AuthDriver {
|
|
|
232
232
|
external_identifier: userInfo.dn,
|
|
233
233
|
role: userRole?.id ?? defaultRoleId,
|
|
234
234
|
};
|
|
235
|
+
const schema = await getSchema();
|
|
235
236
|
// Run hook so the end user has the chance to augment the
|
|
236
237
|
// user that is about to be created
|
|
237
|
-
const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, { identifier: userInfo.dn, provider: this.config['provider'], providerPayload: { userInfo, userRole } }, { database: getDatabase(), schema
|
|
238
|
+
const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, { identifier: userInfo.dn, provider: this.config['provider'], providerPayload: { userInfo, userRole } }, { database: getDatabase(), schema, accountability: null });
|
|
238
239
|
try {
|
|
239
|
-
|
|
240
|
+
const usersService = this.getUsersService(schema);
|
|
241
|
+
await usersService.createOne(updatedUserPayload);
|
|
240
242
|
}
|
|
241
243
|
catch (e) {
|
|
242
244
|
if (isDirectusError(e, ErrorCode.RecordNotUnique)) {
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import type { Client } from 'openid-client';
|
|
3
|
-
import { UsersService } from '../../services/users.js';
|
|
4
3
|
import type { AuthDriverOptions, User } from '../../types/index.js';
|
|
5
4
|
import type { RoleMap } from '../../types/rolemap.js';
|
|
6
5
|
import { LocalAuthDriver } from './local.js';
|
|
7
6
|
export declare class OAuth2AuthDriver extends LocalAuthDriver {
|
|
8
7
|
client: Client;
|
|
9
8
|
redirectUrl: string;
|
|
10
|
-
usersService: UsersService;
|
|
11
9
|
config: Record<string, any>;
|
|
12
10
|
roleMap: RoleMap;
|
|
13
11
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
@@ -13,7 +13,6 @@ import { useLogger } from '../../logger/index.js';
|
|
|
13
13
|
import { respond } from '../../middleware/respond.js';
|
|
14
14
|
import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
|
|
15
15
|
import { AuthenticationService } from '../../services/authentication.js';
|
|
16
|
-
import { UsersService } from '../../services/users.js';
|
|
17
16
|
import asyncHandler from '../../utils/async-handler.js';
|
|
18
17
|
import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
19
18
|
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
@@ -22,10 +21,10 @@ import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js
|
|
|
22
21
|
import { verifyJWT } from '../../utils/jwt.js';
|
|
23
22
|
import { Url } from '../../utils/url.js';
|
|
24
23
|
import { LocalAuthDriver } from './local.js';
|
|
24
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
25
25
|
export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
26
26
|
client;
|
|
27
27
|
redirectUrl;
|
|
28
|
-
usersService;
|
|
29
28
|
config;
|
|
30
29
|
roleMap;
|
|
31
30
|
constructor(options, config) {
|
|
@@ -39,7 +38,6 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
39
38
|
}
|
|
40
39
|
const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback');
|
|
41
40
|
this.redirectUrl = redirectUrl.toString();
|
|
42
|
-
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
43
41
|
this.config = additionalConfig;
|
|
44
42
|
this.roleMap = {};
|
|
45
43
|
const roleMapping = this.config['roleMapping'];
|
|
@@ -177,14 +175,16 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
177
175
|
email: userPayload.email,
|
|
178
176
|
};
|
|
179
177
|
}
|
|
178
|
+
const schema = await getSchema();
|
|
180
179
|
const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, {
|
|
181
180
|
identifier,
|
|
182
181
|
provider: this.config['provider'],
|
|
183
182
|
providerPayload: { accessToken: tokenSet.access_token, idToken: tokenSet.id_token, userInfo },
|
|
184
|
-
}, { database: getDatabase(), schema
|
|
183
|
+
}, { database: getDatabase(), schema, accountability: null });
|
|
185
184
|
// Update user to update refresh_token and other properties that might have changed
|
|
186
185
|
if (Object.values(updatedUserPayload).some((value) => value !== undefined)) {
|
|
187
|
-
|
|
186
|
+
const usersService = this.getUsersService(schema);
|
|
187
|
+
await usersService.updateOne(userId, updatedUserPayload);
|
|
188
188
|
}
|
|
189
189
|
return userId;
|
|
190
190
|
}
|
|
@@ -193,15 +193,17 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
193
193
|
logger.warn(`[OAuth2] User doesn't exist, and public registration not allowed for provider "${provider}"`);
|
|
194
194
|
throw new InvalidCredentialsError();
|
|
195
195
|
}
|
|
196
|
+
const schema = await getSchema();
|
|
196
197
|
// Run hook so the end user has the chance to augment the
|
|
197
198
|
// user that is about to be created
|
|
198
199
|
const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, {
|
|
199
200
|
identifier,
|
|
200
201
|
provider: this.config['provider'],
|
|
201
202
|
providerPayload: { accessToken: tokenSet.access_token, idToken: tokenSet.id_token, userInfo },
|
|
202
|
-
}, { database: getDatabase(), schema
|
|
203
|
+
}, { database: getDatabase(), schema, accountability: null });
|
|
203
204
|
try {
|
|
204
|
-
|
|
205
|
+
const usersService = this.getUsersService(schema);
|
|
206
|
+
await usersService.createOne(updatedUserPayload);
|
|
205
207
|
}
|
|
206
208
|
catch (e) {
|
|
207
209
|
if (isDirectusError(e, ErrorCode.RecordNotUnique)) {
|
|
@@ -231,7 +233,8 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
231
233
|
const tokenSet = await this.client.refresh(authData['refreshToken']);
|
|
232
234
|
// Update user refreshToken if provided
|
|
233
235
|
if (tokenSet.refresh_token) {
|
|
234
|
-
|
|
236
|
+
const usersService = this.getUsersService(await getSchema());
|
|
237
|
+
await usersService.updateOne(user.id, {
|
|
235
238
|
auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
|
236
239
|
});
|
|
237
240
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import type { Client } from 'openid-client';
|
|
3
|
-
import { UsersService } from '../../services/users.js';
|
|
4
3
|
import type { AuthDriverOptions, User } from '../../types/index.js';
|
|
5
4
|
import type { RoleMap } from '../../types/rolemap.js';
|
|
6
5
|
import { LocalAuthDriver } from './local.js';
|
|
7
6
|
export declare class OpenIDAuthDriver extends LocalAuthDriver {
|
|
8
7
|
client: null | Client;
|
|
9
8
|
redirectUrl: string;
|
|
10
|
-
usersService: UsersService;
|
|
11
9
|
config: Record<string, any>;
|
|
12
10
|
roleMap: RoleMap;
|
|
13
11
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
@@ -13,7 +13,6 @@ import { useLogger } from '../../logger/index.js';
|
|
|
13
13
|
import { respond } from '../../middleware/respond.js';
|
|
14
14
|
import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
|
|
15
15
|
import { AuthenticationService } from '../../services/authentication.js';
|
|
16
|
-
import { UsersService } from '../../services/users.js';
|
|
17
16
|
import asyncHandler from '../../utils/async-handler.js';
|
|
18
17
|
import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
19
18
|
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
@@ -22,10 +21,10 @@ import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js
|
|
|
22
21
|
import { verifyJWT } from '../../utils/jwt.js';
|
|
23
22
|
import { Url } from '../../utils/url.js';
|
|
24
23
|
import { LocalAuthDriver } from './local.js';
|
|
24
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
25
25
|
export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
26
26
|
client;
|
|
27
27
|
redirectUrl;
|
|
28
|
-
usersService;
|
|
29
28
|
config;
|
|
30
29
|
roleMap;
|
|
31
30
|
constructor(options, config) {
|
|
@@ -40,7 +39,6 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
40
39
|
}
|
|
41
40
|
const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', provider, 'callback');
|
|
42
41
|
this.redirectUrl = redirectUrl.toString();
|
|
43
|
-
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
44
42
|
this.config = config;
|
|
45
43
|
this.roleMap = {};
|
|
46
44
|
const roleMapping = this.config['roleMapping'];
|
|
@@ -227,14 +225,16 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
227
225
|
email: userPayload.email,
|
|
228
226
|
};
|
|
229
227
|
}
|
|
228
|
+
const schema = await getSchema();
|
|
230
229
|
const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, {
|
|
231
230
|
identifier,
|
|
232
231
|
provider: this.config['provider'],
|
|
233
232
|
providerPayload: { accessToken: tokenSet.access_token, idToken: tokenSet.id_token, userInfo },
|
|
234
|
-
}, { database: getDatabase(), schema
|
|
233
|
+
}, { database: getDatabase(), schema, accountability: null });
|
|
235
234
|
// Update user to update refresh_token and other properties that might have changed
|
|
236
235
|
if (Object.values(updatedUserPayload).some((value) => value !== undefined)) {
|
|
237
|
-
|
|
236
|
+
const usersService = this.getUsersService(schema);
|
|
237
|
+
await usersService.updateOne(userId, updatedUserPayload);
|
|
238
238
|
}
|
|
239
239
|
return userId;
|
|
240
240
|
}
|
|
@@ -244,15 +244,17 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
244
244
|
logger.warn(`[OpenID] User doesn't exist, and public registration not allowed for provider "${provider}"`);
|
|
245
245
|
throw new InvalidCredentialsError();
|
|
246
246
|
}
|
|
247
|
+
const schema = await getSchema();
|
|
247
248
|
// Run hook so the end user has the chance to augment the
|
|
248
249
|
// user that is about to be created
|
|
249
250
|
const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, {
|
|
250
251
|
identifier,
|
|
251
252
|
provider: this.config['provider'],
|
|
252
253
|
providerPayload: { accessToken: tokenSet.access_token, idToken: tokenSet.id_token, userInfo },
|
|
253
|
-
}, { database: getDatabase(), schema
|
|
254
|
+
}, { database: getDatabase(), schema, accountability: null });
|
|
254
255
|
try {
|
|
255
|
-
|
|
256
|
+
const usersService = this.getUsersService(schema);
|
|
257
|
+
await usersService.createOne(updatedUserPayload);
|
|
256
258
|
}
|
|
257
259
|
catch (e) {
|
|
258
260
|
if (isDirectusError(e, ErrorCode.RecordNotUnique)) {
|
|
@@ -283,7 +285,8 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
283
285
|
const tokenSet = await client.refresh(authData['refreshToken']);
|
|
284
286
|
// Update user refreshToken if provided
|
|
285
287
|
if (tokenSet.refresh_token) {
|
|
286
|
-
|
|
288
|
+
const usersService = this.getUsersService(await getSchema());
|
|
289
|
+
await usersService.updateOne(user.id, {
|
|
287
290
|
auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
|
288
291
|
});
|
|
289
292
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as samlify from 'samlify';
|
|
2
|
-
import { UsersService } from '../../services/users.js';
|
|
3
2
|
import type { AuthDriverOptions, User } from '../../types/index.js';
|
|
4
3
|
import { LocalAuthDriver } from './local.js';
|
|
5
4
|
export declare class SAMLAuthDriver extends LocalAuthDriver {
|
|
6
5
|
sp: samlify.ServiceProviderInstance;
|
|
7
6
|
idp: samlify.IdentityProviderInstance;
|
|
8
|
-
usersService: UsersService;
|
|
9
7
|
config: Record<string, any>;
|
|
10
8
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
11
9
|
fetchUserID(identifier: string): Promise<any>;
|
|
@@ -10,22 +10,20 @@ import emitter from '../../emitter.js';
|
|
|
10
10
|
import { useLogger } from '../../logger/index.js';
|
|
11
11
|
import { respond } from '../../middleware/respond.js';
|
|
12
12
|
import { AuthenticationService } from '../../services/authentication.js';
|
|
13
|
-
import { UsersService } from '../../services/users.js';
|
|
14
13
|
import asyncHandler from '../../utils/async-handler.js';
|
|
15
14
|
import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
16
15
|
import { LocalAuthDriver } from './local.js';
|
|
17
16
|
import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js';
|
|
17
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
18
18
|
// Register the samlify schema validator
|
|
19
19
|
samlify.setSchemaValidator(validator);
|
|
20
20
|
export class SAMLAuthDriver extends LocalAuthDriver {
|
|
21
21
|
sp;
|
|
22
22
|
idp;
|
|
23
|
-
usersService;
|
|
24
23
|
config;
|
|
25
24
|
constructor(options, config) {
|
|
26
25
|
super(options, config);
|
|
27
26
|
this.config = config;
|
|
28
|
-
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
29
27
|
this.sp = samlify.ServiceProvider(getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_SP`));
|
|
30
28
|
this.idp = samlify.IdentityProvider(getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_IDP`));
|
|
31
29
|
}
|
|
@@ -63,11 +61,13 @@ export class SAMLAuthDriver extends LocalAuthDriver {
|
|
|
63
61
|
external_identifier: identifier.toLowerCase(),
|
|
64
62
|
role: this.config['defaultRoleId'],
|
|
65
63
|
};
|
|
64
|
+
const schema = await getSchema();
|
|
66
65
|
// Run hook so the end user has the chance to augment the
|
|
67
66
|
// user that is about to be created
|
|
68
|
-
const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, { identifier: identifier.toLowerCase(), provider: this.config['provider'], providerPayload: { ...payload } }, { database: getDatabase(), schema
|
|
67
|
+
const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, { identifier: identifier.toLowerCase(), provider: this.config['provider'], providerPayload: { ...payload } }, { database: getDatabase(), schema, accountability: null });
|
|
69
68
|
try {
|
|
70
|
-
|
|
69
|
+
const usersService = this.getUsersService(schema);
|
|
70
|
+
return await usersService.createOne(updatedUserPayload);
|
|
71
71
|
}
|
|
72
72
|
catch (error) {
|
|
73
73
|
if (isDirectusError(error, ErrorCode.RecordNotUnique)) {
|
package/dist/auth.js
CHANGED
|
@@ -6,7 +6,6 @@ import { DEFAULT_AUTH_PROVIDER } from './constants.js';
|
|
|
6
6
|
import getDatabase from './database/index.js';
|
|
7
7
|
import { useLogger } from './logger/index.js';
|
|
8
8
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
9
|
-
import { getSchema } from './utils/get-schema.js';
|
|
10
9
|
const providers = new Map();
|
|
11
10
|
export function getAuthProvider(provider) {
|
|
12
11
|
const logger = useLogger();
|
|
@@ -19,7 +18,7 @@ export function getAuthProvider(provider) {
|
|
|
19
18
|
export async function registerAuthProviders() {
|
|
20
19
|
const env = useEnv();
|
|
21
20
|
const logger = useLogger();
|
|
22
|
-
const options = { knex: getDatabase()
|
|
21
|
+
const options = { knex: getDatabase() };
|
|
23
22
|
const providerNames = toArray(env['AUTH_PROVIDERS']);
|
|
24
23
|
// Register default provider if not disabled
|
|
25
24
|
if (!env['AUTH_DISABLE_DEFAULT']) {
|
|
@@ -3,13 +3,10 @@ import getDatabase, { hasDatabaseConnection, isInstalled, validateDatabaseConnec
|
|
|
3
3
|
import runMigrations from '../../../database/migrations/run.js';
|
|
4
4
|
import installDatabase from '../../../database/seeds/run.js';
|
|
5
5
|
import { useLogger } from '../../../logger/index.js';
|
|
6
|
-
import { AccessService } from '../../../services/access.js';
|
|
7
|
-
import { PoliciesService } from '../../../services/policies.js';
|
|
8
|
-
import { RolesService } from '../../../services/roles.js';
|
|
9
6
|
import { SettingsService } from '../../../services/settings.js';
|
|
10
|
-
import { UsersService } from '../../../services/users.js';
|
|
11
7
|
import { getSchema } from '../../../utils/get-schema.js';
|
|
12
|
-
import {
|
|
8
|
+
import { createAdmin } from '../../../utils/create-admin.js';
|
|
9
|
+
import { email } from 'zod';
|
|
13
10
|
export default async function bootstrap({ skipAdminInit }) {
|
|
14
11
|
const logger = useLogger();
|
|
15
12
|
logger.info('Initializing bootstrap...');
|
|
@@ -23,15 +20,23 @@ export default async function bootstrap({ skipAdminInit }) {
|
|
|
23
20
|
await runMigrations(database, 'latest');
|
|
24
21
|
const schema = await getSchema();
|
|
25
22
|
if (skipAdminInit == null) {
|
|
26
|
-
await
|
|
23
|
+
await createAdmin(schema);
|
|
27
24
|
}
|
|
28
25
|
else {
|
|
29
26
|
logger.info('Skipping creation of default Admin user and role...');
|
|
30
27
|
}
|
|
28
|
+
const settingsService = new SettingsService({ schema });
|
|
31
29
|
if (env['PROJECT_NAME'] && typeof env['PROJECT_NAME'] === 'string' && env['PROJECT_NAME'].length > 0) {
|
|
32
|
-
const settingsService = new SettingsService({ schema });
|
|
33
30
|
await settingsService.upsertSingleton({ project_name: env['PROJECT_NAME'] });
|
|
34
31
|
}
|
|
32
|
+
if (email().safeParse(env['PROJECT_OWNER']).success) {
|
|
33
|
+
await settingsService.setOwner({
|
|
34
|
+
project_owner: env['PROJECT_OWNER'],
|
|
35
|
+
org_name: null,
|
|
36
|
+
project_usage: null,
|
|
37
|
+
product_updates: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
else {
|
|
37
42
|
logger.info('Database already initialized, skipping install');
|
|
@@ -55,29 +60,3 @@ async function waitForDatabase(database) {
|
|
|
55
60
|
await validateDatabaseConnection(database);
|
|
56
61
|
return database;
|
|
57
62
|
}
|
|
58
|
-
async function createDefaultAdmin(schema) {
|
|
59
|
-
const logger = useLogger();
|
|
60
|
-
const env = useEnv();
|
|
61
|
-
const { nanoid } = await import('nanoid');
|
|
62
|
-
logger.info('Setting up first admin role...');
|
|
63
|
-
const accessService = new AccessService({ schema });
|
|
64
|
-
const policiesService = new PoliciesService({ schema });
|
|
65
|
-
const rolesService = new RolesService({ schema });
|
|
66
|
-
const role = await rolesService.createOne(defaultAdminRole);
|
|
67
|
-
const policy = await policiesService.createOne(defaultAdminPolicy);
|
|
68
|
-
await accessService.createOne({ policy, role });
|
|
69
|
-
logger.info('Adding first admin user...');
|
|
70
|
-
const usersService = new UsersService({ schema });
|
|
71
|
-
let adminEmail = env['ADMIN_EMAIL'];
|
|
72
|
-
if (!adminEmail) {
|
|
73
|
-
logger.info('No admin email provided. Defaulting to "admin@example.com"');
|
|
74
|
-
adminEmail = 'admin@example.com';
|
|
75
|
-
}
|
|
76
|
-
let adminPassword = env['ADMIN_PASSWORD'];
|
|
77
|
-
if (!adminPassword) {
|
|
78
|
-
adminPassword = nanoid(12);
|
|
79
|
-
logger.info(`No admin password provided. Defaulting to "${adminPassword}"`);
|
|
80
|
-
}
|
|
81
|
-
const token = env['ADMIN_TOKEN'] ?? null;
|
|
82
|
-
await usersService.createOne({ ...defaultAdminUser, email: adminEmail, password: adminPassword, token, role });
|
|
83
|
-
}
|
|
@@ -9,7 +9,7 @@ import runSeed from '../../../database/seeds/run.js';
|
|
|
9
9
|
import { generateHash } from '../../../utils/generate-hash.js';
|
|
10
10
|
import createDBConnection from '../../utils/create-db-connection.js';
|
|
11
11
|
import createEnv from '../../utils/create-env/index.js';
|
|
12
|
-
import { defaultAdminPolicy, defaultAdminRole, defaultAdminUser } from '
|
|
12
|
+
import { defaultAdminPolicy, defaultAdminRole, defaultAdminUser } from '../../../utils/create-admin.js';
|
|
13
13
|
import { drivers, getDriverForClient } from '../../utils/drivers.js';
|
|
14
14
|
import { databaseQuestions } from './questions.js';
|
|
15
15
|
export default async function init() {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import type { SnapshotDiff } from '@directus/types';
|
|
2
|
+
export declare function filterSnapshotDiff(snapshot: SnapshotDiff, filters: string[]): SnapshotDiff;
|
|
1
3
|
export declare function apply(snapshotPath: string, options?: {
|
|
2
4
|
yes: boolean;
|
|
3
5
|
dryRun: boolean;
|
|
4
6
|
ignoreRules: string;
|
|
5
7
|
}): Promise<void>;
|
|
8
|
+
export declare function formatPath(path: any[]): string;
|
|
9
|
+
export declare function formatRelatedCollection(relatedCollection: string | null): string;
|
|
@@ -11,7 +11,7 @@ import { isNestedMetaUpdate } from '../../../utils/apply-diff.js';
|
|
|
11
11
|
import { applySnapshot } from '../../../utils/apply-snapshot.js';
|
|
12
12
|
import { getSnapshotDiff } from '../../../utils/get-snapshot-diff.js';
|
|
13
13
|
import { getSnapshot } from '../../../utils/get-snapshot.js';
|
|
14
|
-
function filterSnapshotDiff(snapshot, filters) {
|
|
14
|
+
export function filterSnapshotDiff(snapshot, filters) {
|
|
15
15
|
const filterSet = new Set(filters);
|
|
16
16
|
function shouldKeep(item) {
|
|
17
17
|
if (filterSet.has(item.collection))
|
|
@@ -23,6 +23,7 @@ function filterSnapshotDiff(snapshot, filters) {
|
|
|
23
23
|
const filteredDiff = {
|
|
24
24
|
collections: snapshot.collections.filter((item) => shouldKeep(item)),
|
|
25
25
|
fields: snapshot.fields.filter((item) => shouldKeep(item)),
|
|
26
|
+
systemFields: snapshot.systemFields.filter((item) => shouldKeep(item)),
|
|
26
27
|
relations: snapshot.relations.filter((item) => shouldKeep(item)),
|
|
27
28
|
};
|
|
28
29
|
return filteredDiff;
|
|
@@ -53,6 +54,7 @@ export async function apply(snapshotPath, options) {
|
|
|
53
54
|
}
|
|
54
55
|
if (snapshotDiff.collections.length === 0 &&
|
|
55
56
|
snapshotDiff.fields.length === 0 &&
|
|
57
|
+
snapshotDiff.systemFields.length === 0 &&
|
|
56
58
|
snapshotDiff.relations.length === 0) {
|
|
57
59
|
logger.info('No changes to apply.');
|
|
58
60
|
database.destroy();
|
|
@@ -116,6 +118,27 @@ export async function apply(snapshotPath, options) {
|
|
|
116
118
|
}
|
|
117
119
|
sections.push(lines.join('\n'));
|
|
118
120
|
}
|
|
121
|
+
if (snapshotDiff.systemFields.length > 0) {
|
|
122
|
+
const lines = [chalk.underline.bold('System Fields:')];
|
|
123
|
+
for (const { collection, field, diff } of snapshotDiff.systemFields) {
|
|
124
|
+
if (diff[0]?.kind === DiffKind.EDIT) {
|
|
125
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}.${field}`);
|
|
126
|
+
for (const change of diff) {
|
|
127
|
+
const path = formatPath(change.path);
|
|
128
|
+
if (change.kind === DiffKind.EDIT) {
|
|
129
|
+
lines.push(` - Set ${path} to ${change.rhs}`);
|
|
130
|
+
}
|
|
131
|
+
else if (change.kind === DiffKind.DELETE) {
|
|
132
|
+
lines.push(` - Remove ${path}`);
|
|
133
|
+
}
|
|
134
|
+
else if (change.kind === DiffKind.NEW) {
|
|
135
|
+
lines.push(` - Add ${path} and set it to ${change.rhs}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
sections.push(lines.join('\n'));
|
|
141
|
+
}
|
|
119
142
|
if (snapshotDiff.relations.length > 0) {
|
|
120
143
|
const lines = [chalk.underline.bold('Relations:')];
|
|
121
144
|
for (const { collection, field, related_collection, diff } of snapshotDiff.relations) {
|
|
@@ -169,13 +192,13 @@ export async function apply(snapshotPath, options) {
|
|
|
169
192
|
process.exit(1);
|
|
170
193
|
}
|
|
171
194
|
}
|
|
172
|
-
function formatPath(path) {
|
|
195
|
+
export function formatPath(path) {
|
|
173
196
|
if (path.length === 1) {
|
|
174
197
|
return path.toString();
|
|
175
198
|
}
|
|
176
199
|
return path.slice(1).join('.');
|
|
177
200
|
}
|
|
178
|
-
function formatRelatedCollection(relatedCollection) {
|
|
201
|
+
export function formatRelatedCollection(relatedCollection) {
|
|
179
202
|
// Related collection doesn't exist for a2o relationship types
|
|
180
203
|
if (relatedCollection) {
|
|
181
204
|
return ` → ${relatedCollection}`;
|
|
@@ -11,13 +11,18 @@ router.post('/', asyncHandler(async (req, res, next) => {
|
|
|
11
11
|
accountability: req.accountability,
|
|
12
12
|
schema: req.schema,
|
|
13
13
|
});
|
|
14
|
+
const attemptConcurrentIndex = 'concurrentIndexCreation' in req.query && req.query['concurrentIndexCreation'] !== 'false';
|
|
14
15
|
if (Array.isArray(req.body)) {
|
|
15
|
-
const collectionKey = await collectionsService.createMany(req.body
|
|
16
|
+
const collectionKey = await collectionsService.createMany(req.body, {
|
|
17
|
+
attemptConcurrentIndex,
|
|
18
|
+
});
|
|
16
19
|
const records = await collectionsService.readMany(collectionKey);
|
|
17
20
|
res.locals['payload'] = { data: records || null };
|
|
18
21
|
}
|
|
19
22
|
else {
|
|
20
|
-
const collectionKey = await collectionsService.createOne(req.body
|
|
23
|
+
const collectionKey = await collectionsService.createOne(req.body, {
|
|
24
|
+
attemptConcurrentIndex,
|
|
25
|
+
});
|
|
21
26
|
const record = await collectionsService.readOne(collectionKey);
|
|
22
27
|
res.locals['payload'] = { data: record || null };
|
|
23
28
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TYPES } from '@directus/constants';
|
|
2
|
-
import { isDirectusError } from '@directus/errors';
|
|
2
|
+
import { ForbiddenError, isDirectusError } from '@directus/errors';
|
|
3
3
|
import { Router } from 'express';
|
|
4
4
|
import Joi from 'joi';
|
|
5
5
|
import { ALIAS_TYPES } from '../constants.js';
|
|
@@ -7,8 +7,9 @@ import { ErrorCode, InvalidPayloadError } from '@directus/errors';
|
|
|
7
7
|
import validateCollection from '../middleware/collection-exists.js';
|
|
8
8
|
import { respond } from '../middleware/respond.js';
|
|
9
9
|
import useCollection from '../middleware/use-collection.js';
|
|
10
|
-
import { FieldsService } from '../services/fields.js';
|
|
10
|
+
import { FieldsService, systemFieldUpdateSchema } from '../services/fields.js';
|
|
11
11
|
import asyncHandler from '../utils/async-handler.js';
|
|
12
|
+
import { isSystemField } from '@directus/system-data';
|
|
12
13
|
const router = Router();
|
|
13
14
|
router.use(useCollection('directus_fields'));
|
|
14
15
|
router.get('/', asyncHandler(async (req, res, next) => {
|
|
@@ -64,7 +65,9 @@ router.post('/:collection', validateCollection, asyncHandler(async (req, res, ne
|
|
|
64
65
|
throw new InvalidPayloadError({ reason: error.message });
|
|
65
66
|
}
|
|
66
67
|
const field = req.body;
|
|
67
|
-
await service.createField(req.params['collection'], field
|
|
68
|
+
await service.createField(req.params['collection'], field, undefined, {
|
|
69
|
+
attemptConcurrentIndex: 'concurrentIndexCreation' in req.query && req.query['concurrentIndexCreation'] !== 'false',
|
|
70
|
+
});
|
|
68
71
|
try {
|
|
69
72
|
const createdField = await service.readOne(req.params['collection'], field.field);
|
|
70
73
|
res.locals['payload'] = { data: createdField || null };
|
|
@@ -85,7 +88,16 @@ router.patch('/:collection', validateCollection, asyncHandler(async (req, res, n
|
|
|
85
88
|
if (Array.isArray(req.body) === false) {
|
|
86
89
|
throw new InvalidPayloadError({ reason: 'Submitted body has to be an array' });
|
|
87
90
|
}
|
|
88
|
-
|
|
91
|
+
for (const fieldData of req.body) {
|
|
92
|
+
if (isSystemField(req.params['collection'], fieldData['field'])) {
|
|
93
|
+
const { error } = systemFieldUpdateSchema.safeParse(fieldData);
|
|
94
|
+
if (error)
|
|
95
|
+
throw error.issues.map((details) => new InvalidPayloadError({ reason: details.message }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await service.updateFields(req.params['collection'], req.body, {
|
|
99
|
+
attemptConcurrentIndex: 'concurrentIndexCreation' in req.query && req.query['concurrentIndexCreation'] !== 'false',
|
|
100
|
+
});
|
|
89
101
|
try {
|
|
90
102
|
const results = [];
|
|
91
103
|
for (const field of req.body) {
|
|
@@ -120,14 +132,22 @@ router.patch('/:collection/:field', validateCollection, asyncHandler(async (req,
|
|
|
120
132
|
accountability: req.accountability,
|
|
121
133
|
schema: req.schema,
|
|
122
134
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
if (isSystemField(req.params['collection'], req.params['field'])) {
|
|
136
|
+
const { error } = systemFieldUpdateSchema.safeParse(req.body);
|
|
137
|
+
if (error)
|
|
138
|
+
throw error.issues.map((details) => new InvalidPayloadError({ reason: details.message }));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const { error } = updateSchema.validate(req.body);
|
|
142
|
+
if (error)
|
|
143
|
+
throw new InvalidPayloadError({ reason: error.message });
|
|
126
144
|
}
|
|
127
145
|
const fieldData = req.body;
|
|
128
146
|
if (!fieldData.field)
|
|
129
147
|
fieldData.field = req.params['field'];
|
|
130
|
-
await service.updateField(req.params['collection'], fieldData
|
|
148
|
+
await service.updateField(req.params['collection'], fieldData, {
|
|
149
|
+
attemptConcurrentIndex: 'concurrentIndexCreation' in req.query && req.query['concurrentIndexCreation'] !== 'false',
|
|
150
|
+
});
|
|
131
151
|
try {
|
|
132
152
|
const updatedField = await service.readOne(req.params['collection'], req.params['field']);
|
|
133
153
|
res.locals['payload'] = { data: updatedField || null };
|
|
@@ -145,6 +165,9 @@ router.delete('/:collection/:field', validateCollection, asyncHandler(async (req
|
|
|
145
165
|
accountability: req.accountability,
|
|
146
166
|
schema: req.schema,
|
|
147
167
|
});
|
|
168
|
+
if (isSystemField(req.params['collection'], req.params['field'])) {
|
|
169
|
+
throw new ForbiddenError();
|
|
170
|
+
}
|
|
148
171
|
await service.deleteField(req.params['collection'], req.params['field']);
|
|
149
172
|
return next();
|
|
150
173
|
}), respond);
|