@directus/api 22.1.1 → 23.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 +1 -1
- package/dist/auth/drivers/ldap.js +14 -3
- package/dist/auth/drivers/oauth2.js +13 -2
- package/dist/auth/drivers/openid.js +13 -2
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +6 -6
- package/dist/cli/commands/init/questions.d.ts +5 -5
- package/dist/cli/commands/schema/apply.d.ts +1 -0
- package/dist/cli/commands/schema/apply.js +20 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/utils/create-env/env-stub.liquid +1 -4
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/database/helpers/index.d.ts +2 -0
- package/dist/database/helpers/index.js +2 -0
- package/dist/database/helpers/nullable-update/dialects/default.d.ts +3 -0
- package/dist/database/helpers/nullable-update/dialects/default.js +3 -0
- package/dist/database/helpers/nullable-update/dialects/oracle.d.ts +12 -0
- package/dist/database/helpers/nullable-update/dialects/oracle.js +16 -0
- package/dist/database/helpers/nullable-update/index.d.ts +7 -0
- package/dist/database/helpers/nullable-update/index.js +7 -0
- package/dist/database/helpers/nullable-update/types.d.ts +7 -0
- package/dist/database/helpers/nullable-update/types.js +12 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +3 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +17 -0
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mssql.js +20 -0
- package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mysql.js +33 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +3 -1
- package/dist/database/helpers/schema/dialects/oracle.js +21 -0
- package/dist/database/helpers/schema/dialects/postgres.d.ts +3 -1
- package/dist/database/helpers/schema/dialects/postgres.js +23 -0
- package/dist/database/helpers/schema/dialects/sqlite.d.ts +1 -0
- package/dist/database/helpers/schema/dialects/sqlite.js +3 -0
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +3 -0
- package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +5 -1
- package/dist/database/helpers/schema/utils/preprocess-bindings.js +23 -17
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +1 -1
- package/dist/database/migrations/20240817A-update-icon-fields-length.d.ts +3 -0
- package/dist/database/migrations/20240817A-update-icon-fields-length.js +55 -0
- package/dist/database/run-ast/lib/get-db-query.js +14 -8
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +0 -3
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -2
- package/dist/extensions/manager.js +2 -2
- package/dist/logger/index.d.ts +8 -3
- package/dist/logger/index.js +79 -28
- package/dist/logger/logs-stream.d.ts +10 -0
- package/dist/logger/logs-stream.js +41 -0
- package/dist/mailer.js +0 -6
- package/dist/middleware/authenticate.d.ts +1 -3
- package/dist/middleware/error-handler.d.ts +0 -1
- package/dist/middleware/respond.js +1 -0
- package/dist/middleware/validate-batch.d.ts +1 -4
- package/dist/permissions/lib/fetch-permissions.d.ts +11 -1
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +2 -2
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +1 -2
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +14 -6
- package/dist/permissions/utils/process-permissions.d.ts +11 -1
- package/dist/permissions/utils/process-permissions.js +6 -4
- package/dist/request/agent-with-ip-validation.d.ts +0 -1
- package/dist/request/is-denied-ip.js +7 -1
- package/dist/server.d.ts +0 -3
- package/dist/server.js +4 -2
- package/dist/services/assets.d.ts +0 -1
- package/dist/services/fields.js +52 -20
- package/dist/services/files/utils/get-metadata.d.ts +0 -1
- package/dist/services/files/utils/parse-image-metadata.d.ts +0 -1
- package/dist/services/files.d.ts +0 -1
- package/dist/services/import-export.d.ts +0 -1
- package/dist/services/mail/index.js +1 -5
- package/dist/services/notifications.d.ts +0 -4
- package/dist/services/notifications.js +8 -6
- package/dist/services/server.js +8 -1
- package/dist/services/specifications.js +7 -7
- package/dist/services/tus/data-store.d.ts +0 -1
- package/dist/services/users.js +6 -3
- package/dist/types/graphql.d.ts +0 -1
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/compress.d.ts +0 -1
- package/dist/utils/delete-from-require-cache.js +1 -1
- package/dist/utils/fetch-user-count/fetch-user-count.d.ts +1 -2
- package/dist/utils/generate-hash.js +2 -2
- package/dist/utils/get-address.d.ts +1 -4
- package/dist/utils/get-address.js +6 -1
- package/dist/utils/get-allowed-log-levels.d.ts +3 -0
- package/dist/utils/get-allowed-log-levels.js +11 -0
- package/dist/utils/get-cache-headers.d.ts +0 -1
- package/dist/utils/get-cache-key.d.ts +0 -1
- package/dist/utils/get-column.d.ts +1 -1
- package/dist/utils/get-graphql-query-and-variables.d.ts +0 -1
- package/dist/utils/get-ip-from-req.d.ts +0 -1
- package/dist/utils/get-schema.js +19 -24
- package/dist/utils/get-snapshot.js +1 -1
- package/dist/utils/parse-filter-key.js +1 -5
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/should-skip-cache.d.ts +0 -1
- package/dist/websocket/authenticate.js +1 -1
- package/dist/websocket/controllers/base.d.ts +6 -15
- package/dist/websocket/controllers/base.js +17 -4
- package/dist/websocket/controllers/graphql.d.ts +0 -3
- package/dist/websocket/controllers/graphql.js +3 -1
- package/dist/websocket/controllers/index.d.ts +4 -3
- package/dist/websocket/controllers/index.js +12 -0
- package/dist/websocket/controllers/logs.d.ts +17 -0
- package/dist/websocket/controllers/logs.js +54 -0
- package/dist/websocket/controllers/rest.d.ts +0 -3
- package/dist/websocket/controllers/rest.js +4 -2
- package/dist/websocket/handlers/index.d.ts +1 -0
- package/dist/websocket/handlers/index.js +21 -3
- package/dist/websocket/handlers/logs.d.ts +31 -0
- package/dist/websocket/handlers/logs.js +121 -0
- package/dist/websocket/messages.d.ts +26 -0
- package/dist/websocket/messages.js +9 -0
- package/dist/websocket/types.d.ts +6 -5
- package/package.json +48 -49
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
/// <reference types="node/http.js" />
|
|
3
|
-
/// <reference types="pino-http" />
|
|
4
1
|
import * as http from 'http';
|
|
5
2
|
export declare let SERVER_ONLINE: boolean;
|
|
6
3
|
export declare function createServer(): Promise<http.Server>;
|
package/dist/server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { useLogger } from './logger/index.js';
|
|
|
14
14
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
15
15
|
import { getIPFromReq } from './utils/get-ip-from-req.js';
|
|
16
16
|
import { getAddress } from './utils/get-address.js';
|
|
17
|
-
import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
17
|
+
import { createLogsController, createSubscriptionController, createWebSocketController, getLogsController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
18
18
|
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
19
19
|
export let SERVER_ONLINE = true;
|
|
20
20
|
const env = useEnv();
|
|
@@ -77,6 +77,7 @@ export async function createServer() {
|
|
|
77
77
|
if (toBoolean(env['WEBSOCKETS_ENABLED']) === true) {
|
|
78
78
|
createSubscriptionController(server);
|
|
79
79
|
createWebSocketController(server);
|
|
80
|
+
createLogsController(server);
|
|
80
81
|
startWebSocketHandlers();
|
|
81
82
|
}
|
|
82
83
|
const terminusOptions = {
|
|
@@ -99,6 +100,7 @@ export async function createServer() {
|
|
|
99
100
|
async function onSignal() {
|
|
100
101
|
getSubscriptionController()?.terminate();
|
|
101
102
|
getWebSocketController()?.terminate();
|
|
103
|
+
getLogsController()?.terminate();
|
|
102
104
|
const database = getDatabase();
|
|
103
105
|
await database.destroy();
|
|
104
106
|
logger.info('Database connections destroyed');
|
|
@@ -126,7 +128,7 @@ export async function startServer() {
|
|
|
126
128
|
else {
|
|
127
129
|
listenOptions = {
|
|
128
130
|
host,
|
|
129
|
-
port: parseInt(port
|
|
131
|
+
port: parseInt(port),
|
|
130
132
|
};
|
|
131
133
|
}
|
|
132
134
|
server
|
package/dist/services/fields.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createInspector } from '@directus/schema';
|
|
|
5
5
|
import { addFieldFlag, toArray } from '@directus/utils';
|
|
6
6
|
import { isEqual, isNil, merge } from 'lodash-es';
|
|
7
7
|
import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js';
|
|
8
|
-
import { ALIAS_TYPES } from '../constants.js';
|
|
8
|
+
import { ALIAS_TYPES, ALLOWED_DB_DEFAULT_FUNCTIONS } from '../constants.js';
|
|
9
9
|
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
10
10
|
import { getHelpers } from '../database/helpers/index.js';
|
|
11
11
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
@@ -384,8 +384,10 @@ export class FieldsService {
|
|
|
384
384
|
}
|
|
385
385
|
if (hookAdjustedField.schema) {
|
|
386
386
|
const existingColumn = await this.columnInfo(collection, hookAdjustedField.field);
|
|
387
|
-
if (
|
|
388
|
-
|
|
387
|
+
if (existingColumn.is_primary_key) {
|
|
388
|
+
if (hookAdjustedField.schema?.is_nullable === true) {
|
|
389
|
+
throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
|
|
390
|
+
}
|
|
389
391
|
}
|
|
390
392
|
// Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
|
|
391
393
|
const columnToCompare = opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
|
|
@@ -662,12 +664,12 @@ export class FieldsService {
|
|
|
662
664
|
else {
|
|
663
665
|
throw new InvalidPayloadError({ reason: `Illegal type passed: "${field.type}"` });
|
|
664
666
|
}
|
|
665
|
-
const
|
|
666
|
-
if (defaultValue) {
|
|
667
|
+
const setDefaultValue = (defaultValue) => {
|
|
667
668
|
const newDefaultValueIsString = typeof defaultValue === 'string';
|
|
668
669
|
const newDefaultIsNowFunction = newDefaultValueIsString && defaultValue.toLowerCase() === 'now()';
|
|
669
670
|
const newDefaultIsCurrentTimestamp = newDefaultValueIsString && defaultValue === 'CURRENT_TIMESTAMP';
|
|
670
671
|
const newDefaultIsSetToCurrentTime = newDefaultIsNowFunction || newDefaultIsCurrentTimestamp;
|
|
672
|
+
const newDefaultIsAFunction = newDefaultValueIsString && ALLOWED_DB_DEFAULT_FUNCTIONS.includes(defaultValue);
|
|
671
673
|
const newDefaultIsTimestampWithPrecision = newDefaultValueIsString && defaultValue.includes('CURRENT_TIMESTAMP(') && defaultValue.includes(')');
|
|
672
674
|
if (newDefaultIsSetToCurrentTime) {
|
|
673
675
|
column.defaultTo(this.knex.fn.now());
|
|
@@ -676,31 +678,61 @@ export class FieldsService {
|
|
|
676
678
|
const precision = defaultValue.match(REGEX_BETWEEN_PARENS)[1];
|
|
677
679
|
column.defaultTo(this.knex.fn.now(Number(precision)));
|
|
678
680
|
}
|
|
681
|
+
else if (newDefaultIsAFunction) {
|
|
682
|
+
column.defaultTo(this.knex.raw(defaultValue));
|
|
683
|
+
}
|
|
679
684
|
else {
|
|
680
685
|
column.defaultTo(defaultValue);
|
|
681
686
|
}
|
|
687
|
+
};
|
|
688
|
+
// for a new item, set the default value and nullable as provided without any further considerations
|
|
689
|
+
if (!existing) {
|
|
690
|
+
if (field.schema?.default_value !== undefined) {
|
|
691
|
+
setDefaultValue(field.schema.default_value);
|
|
692
|
+
}
|
|
693
|
+
if (field.schema?.is_nullable || field.schema?.is_nullable === undefined) {
|
|
694
|
+
column.nullable();
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
column.notNullable();
|
|
698
|
+
}
|
|
682
699
|
}
|
|
683
700
|
else {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
701
|
+
// for an existing item: if nullable option changed, we have to provide the default values as well and actually vice versa
|
|
702
|
+
// see https://knexjs.org/guide/schema-builder.html#alter
|
|
703
|
+
// To overwrite a nullable option with the same value this is not possible for Oracle though, hence the DB helper
|
|
704
|
+
if (field.schema?.default_value !== undefined || field.schema?.is_nullable !== undefined) {
|
|
705
|
+
this.helpers.nullableUpdate.updateNullableValue(column, field, existing);
|
|
706
|
+
let defaultValue = null;
|
|
707
|
+
if (field.schema?.default_value !== undefined) {
|
|
708
|
+
defaultValue = field.schema.default_value;
|
|
709
|
+
}
|
|
710
|
+
else if (existing.default_value !== undefined) {
|
|
711
|
+
defaultValue = existing.default_value;
|
|
712
|
+
}
|
|
713
|
+
setDefaultValue(defaultValue);
|
|
714
|
+
}
|
|
692
715
|
}
|
|
693
716
|
if (field.schema?.is_primary_key) {
|
|
694
717
|
column.primary().notNullable();
|
|
695
718
|
}
|
|
696
|
-
else if (
|
|
697
|
-
|
|
698
|
-
|
|
719
|
+
else if (!existing?.is_primary_key) {
|
|
720
|
+
// primary key will already have unique/index constraints
|
|
721
|
+
if (field.schema?.is_unique === true) {
|
|
722
|
+
if (!existing || existing.is_unique === false) {
|
|
723
|
+
column.unique();
|
|
724
|
+
}
|
|
699
725
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
726
|
+
else if (field.schema?.is_unique === false) {
|
|
727
|
+
if (existing && existing.is_unique === true) {
|
|
728
|
+
table.dropUnique([field.field]);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (field.schema?.is_indexed === true && !existing?.is_indexed) {
|
|
732
|
+
column.index();
|
|
733
|
+
}
|
|
734
|
+
else if (field.schema?.is_indexed === false && existing?.is_indexed) {
|
|
735
|
+
table.dropIndex([field.field]);
|
|
704
736
|
}
|
|
705
737
|
}
|
|
706
738
|
if (existing) {
|
package/dist/services/files.d.ts
CHANGED
|
@@ -36,11 +36,7 @@ export class MailService {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
async send(options) {
|
|
39
|
-
const payload = await emitter.emitFilter(`email.send`, options, {
|
|
40
|
-
database: getDatabase(),
|
|
41
|
-
schema: null,
|
|
42
|
-
accountability: null,
|
|
43
|
-
});
|
|
39
|
+
const payload = await emitter.emitFilter(`email.send`, options, {});
|
|
44
40
|
if (!payload)
|
|
45
41
|
return null;
|
|
46
42
|
const { template, ...emailOptions } = payload;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { Notification, PrimaryKey } from '@directus/types';
|
|
2
2
|
import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
|
|
3
3
|
import { ItemsService } from './items.js';
|
|
4
|
-
import { MailService } from './mail/index.js';
|
|
5
|
-
import { UsersService } from './users.js';
|
|
6
4
|
export declare class NotificationsService extends ItemsService {
|
|
7
|
-
usersService: UsersService;
|
|
8
|
-
mailService: MailService;
|
|
9
5
|
constructor(options: AbstractServiceOptions);
|
|
10
6
|
createOne(data: Partial<Notification>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
11
7
|
sendEmail(data: Partial<Notification>): Promise<void>;
|
|
@@ -10,12 +10,8 @@ import { UsersService } from './users.js';
|
|
|
10
10
|
const env = useEnv();
|
|
11
11
|
const logger = useLogger();
|
|
12
12
|
export class NotificationsService extends ItemsService {
|
|
13
|
-
usersService;
|
|
14
|
-
mailService;
|
|
15
13
|
constructor(options) {
|
|
16
14
|
super('directus_notifications', options);
|
|
17
|
-
this.usersService = new UsersService({ schema: this.schema });
|
|
18
|
-
this.mailService = new MailService({ schema: this.schema, accountability: this.accountability });
|
|
19
15
|
}
|
|
20
16
|
async createOne(data, opts) {
|
|
21
17
|
const response = await super.createOne(data, opts);
|
|
@@ -24,7 +20,8 @@ export class NotificationsService extends ItemsService {
|
|
|
24
20
|
}
|
|
25
21
|
async sendEmail(data) {
|
|
26
22
|
if (data.recipient) {
|
|
27
|
-
const
|
|
23
|
+
const usersService = new UsersService({ schema: this.schema, knex: this.knex });
|
|
24
|
+
const user = await usersService.readOne(data.recipient, {
|
|
28
25
|
fields: ['id', 'email', 'email_notifications', 'role'],
|
|
29
26
|
});
|
|
30
27
|
if (user['email'] && user['email_notifications'] === true) {
|
|
@@ -38,7 +35,12 @@ export class NotificationsService extends ItemsService {
|
|
|
38
35
|
roles,
|
|
39
36
|
ip: null,
|
|
40
37
|
}, this.knex);
|
|
41
|
-
|
|
38
|
+
const mailService = new MailService({
|
|
39
|
+
schema: this.schema,
|
|
40
|
+
knex: this.knex,
|
|
41
|
+
accountability: this.accountability,
|
|
42
|
+
});
|
|
43
|
+
mailService
|
|
42
44
|
.send({
|
|
43
45
|
template: {
|
|
44
46
|
name: 'base',
|
package/dist/services/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { merge } from 'lodash-es';
|
|
|
5
5
|
import { Readable } from 'node:stream';
|
|
6
6
|
import { performance } from 'perf_hooks';
|
|
7
7
|
import { getCache } from '../cache.js';
|
|
8
|
+
import { RESUMABLE_UPLOADS } from '../constants.js';
|
|
8
9
|
import getDatabase, { hasDatabaseConnection } from '../database/index.js';
|
|
9
10
|
import { useLogger } from '../logger/index.js';
|
|
10
11
|
import getMailer from '../mailer.js';
|
|
@@ -12,8 +13,8 @@ import { rateLimiterGlobal } from '../middleware/rate-limiter-global.js';
|
|
|
12
13
|
import { rateLimiter } from '../middleware/rate-limiter-ip.js';
|
|
13
14
|
import { SERVER_ONLINE } from '../server.js';
|
|
14
15
|
import { getStorage } from '../storage/index.js';
|
|
16
|
+
import { getAllowedLogLevels } from '../utils/get-allowed-log-levels.js';
|
|
15
17
|
import { SettingsService } from './settings.js';
|
|
16
|
-
import { RESUMABLE_UPLOADS } from '../constants.js';
|
|
17
18
|
const env = useEnv();
|
|
18
19
|
const logger = useLogger();
|
|
19
20
|
export class ServerService {
|
|
@@ -95,6 +96,12 @@ export class ServerService {
|
|
|
95
96
|
info['websocket'].heartbeat = toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED'])
|
|
96
97
|
? env['WEBSOCKETS_HEARTBEAT_PERIOD']
|
|
97
98
|
: false;
|
|
99
|
+
info['websocket'].logs =
|
|
100
|
+
toBoolean(env['WEBSOCKETS_LOGS_ENABLED']) && this.accountability.admin
|
|
101
|
+
? {
|
|
102
|
+
allowedLogLevels: getAllowedLogLevels(env['WEBSOCKETS_LOGS_LEVEL'] || 'info'),
|
|
103
|
+
}
|
|
104
|
+
: false;
|
|
98
105
|
}
|
|
99
106
|
else {
|
|
100
107
|
info['websocket'] = false;
|
|
@@ -37,20 +37,20 @@ class OASSpecsService {
|
|
|
37
37
|
this.schema = options.schema;
|
|
38
38
|
}
|
|
39
39
|
async generate(host) {
|
|
40
|
-
let
|
|
40
|
+
let schemaForSpec = this.schema;
|
|
41
41
|
let permissions = [];
|
|
42
42
|
if (this.accountability && this.accountability.admin !== true) {
|
|
43
43
|
const allowedFields = await fetchAllowedFieldMap({
|
|
44
44
|
accountability: this.accountability,
|
|
45
45
|
action: 'read',
|
|
46
|
-
}, { schema, knex: this.knex });
|
|
47
|
-
|
|
48
|
-
const policies = await fetchPolicies(this.accountability, { schema, knex: this.knex });
|
|
49
|
-
permissions = await fetchPermissions({
|
|
46
|
+
}, { schema: this.schema, knex: this.knex });
|
|
47
|
+
schemaForSpec = reduceSchema(this.schema, allowedFields);
|
|
48
|
+
const policies = await fetchPolicies(this.accountability, { schema: this.schema, knex: this.knex });
|
|
49
|
+
permissions = await fetchPermissions({ policies, accountability: this.accountability }, { schema: this.schema, knex: this.knex });
|
|
50
50
|
}
|
|
51
|
-
const tags = await this.generateTags(
|
|
51
|
+
const tags = await this.generateTags(schemaForSpec);
|
|
52
52
|
const paths = await this.generatePaths(permissions, tags);
|
|
53
|
-
const components = await this.generateComponents(
|
|
53
|
+
const components = await this.generateComponents(schemaForSpec, tags);
|
|
54
54
|
const isDefaultPublicUrl = env['PUBLIC_URL'] === '/';
|
|
55
55
|
const url = isDefaultPublicUrl && host ? host : env['PUBLIC_URL'];
|
|
56
56
|
const spec = {
|
package/dist/services/users.js
CHANGED
|
@@ -102,7 +102,10 @@ export class UsersService extends ItemsService {
|
|
|
102
102
|
*/
|
|
103
103
|
inviteUrl(email, url) {
|
|
104
104
|
const payload = { email, scope: 'invite' };
|
|
105
|
-
const token = jwt.sign(payload, getSecret(), {
|
|
105
|
+
const token = jwt.sign(payload, getSecret(), {
|
|
106
|
+
expiresIn: env['USER_INVITE_TOKEN_TTL'],
|
|
107
|
+
issuer: 'directus',
|
|
108
|
+
});
|
|
106
109
|
return (url ? new Url(url) : new Url(env['PUBLIC_URL']).addPath('admin', 'accept-invite'))
|
|
107
110
|
.setQuery('token', token)
|
|
108
111
|
.toString();
|
|
@@ -370,7 +373,7 @@ export class UsersService extends ItemsService {
|
|
|
370
373
|
await this.createOne(partialUser);
|
|
371
374
|
}
|
|
372
375
|
// We want to be able to re-send the verification email
|
|
373
|
-
else if (user.status !==
|
|
376
|
+
else if (user.status !== 'unverified') {
|
|
374
377
|
// To avoid giving attackers infos about registered emails we dont fail for violated unique constraints
|
|
375
378
|
await stall(STALL_TIME, timeStart);
|
|
376
379
|
return;
|
|
@@ -412,7 +415,7 @@ export class UsersService extends ItemsService {
|
|
|
412
415
|
if (scope !== 'pending-registration')
|
|
413
416
|
throw new ForbiddenError();
|
|
414
417
|
const user = await this.getUserByEmail(email);
|
|
415
|
-
if (user?.status !==
|
|
418
|
+
if (user?.status !== 'unverified') {
|
|
416
419
|
throw new InvalidPayloadError({ reason: 'Invalid verification code' });
|
|
417
420
|
}
|
|
418
421
|
await this.updateOne(user.id, { status: 'active' });
|
package/dist/types/graphql.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Aggregate, Filter, Permission, Query, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { AliasMap } from './get-column-path.js';
|
|
4
|
-
export declare const generateAlias: (size?: number
|
|
4
|
+
export declare const generateAlias: (size?: number) => string;
|
|
5
5
|
type ApplyQueryOptions = {
|
|
6
6
|
aliasMap?: AliasMap;
|
|
7
7
|
isInnerQuery?: boolean;
|
package/dist/utils/compress.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function deleteFromRequireCache(modulePath) {
|
|
|
7
7
|
const moduleCachePath = require.resolve(modulePath);
|
|
8
8
|
delete require.cache[moduleCachePath];
|
|
9
9
|
}
|
|
10
|
-
catch
|
|
10
|
+
catch {
|
|
11
11
|
logger.trace(`Module cache not found for ${modulePath}, skipped cache delete.`);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type FetchAccessLookupOptions } from './fetch-access-lookup.js';
|
|
2
|
-
export
|
|
3
|
-
}
|
|
2
|
+
export type FetchUserCountOptions = FetchAccessLookupOptions;
|
|
4
3
|
export interface UserCount {
|
|
5
4
|
admin: number;
|
|
6
5
|
app: number;
|
|
@@ -3,7 +3,7 @@ import { getConfigFromEnv } from './get-config-from-env.js';
|
|
|
3
3
|
export function generateHash(stringToHash) {
|
|
4
4
|
const argon2HashConfigOptions = getConfigFromEnv('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/directus/directus/discussions/7670#discussioncomment-1255805
|
|
5
5
|
// associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata
|
|
6
|
-
'associatedData' in argon2HashConfigOptions
|
|
7
|
-
|
|
6
|
+
if ('associatedData' in argon2HashConfigOptions)
|
|
7
|
+
argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']);
|
|
8
8
|
return argon2.hash(stringToHash, argon2HashConfigOptions);
|
|
9
9
|
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
/// <reference types="node/http.js" />
|
|
3
|
-
/// <reference types="pino-http" />
|
|
4
1
|
import * as http from 'http';
|
|
5
|
-
export declare function getAddress(server: http.Server):
|
|
2
|
+
export declare function getAddress(server: http.Server): {};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as http from 'http';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
2
3
|
export function getAddress(server) {
|
|
4
|
+
const env = useEnv();
|
|
3
5
|
const address = server.address();
|
|
4
6
|
if (address === null) {
|
|
5
7
|
// Before the 'listening' event has been emitted or after calling server.close()
|
|
6
|
-
|
|
8
|
+
if (env['UNIX_SOCKET_PATH']) {
|
|
9
|
+
return env['UNIX_SOCKET_PATH'];
|
|
10
|
+
}
|
|
11
|
+
return `${env['HOST']}:${env['PORT']}`;
|
|
7
12
|
}
|
|
8
13
|
if (typeof address === 'string') {
|
|
9
14
|
// unix path
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useLogger } from '../logger/index.js';
|
|
2
|
+
const logger = useLogger();
|
|
3
|
+
export const getAllowedLogLevels = (level) => {
|
|
4
|
+
const levelValue = logger.levels.values[level];
|
|
5
|
+
if (levelValue === undefined) {
|
|
6
|
+
throw new Error(`Invalid "${level}" log level`);
|
|
7
|
+
}
|
|
8
|
+
return Object.fromEntries(Object.entries(logger.levels.values)
|
|
9
|
+
.filter(([_, value]) => value >= levelValue)
|
|
10
|
+
.sort((a, b) => a[1] - b[1]));
|
|
11
|
+
};
|
|
@@ -21,5 +21,5 @@ type GetColumnOptions = OriginalCollectionName | (FunctionColumnOptions & Origin
|
|
|
21
21
|
* @param options Optional parameters
|
|
22
22
|
* @returns Knex raw instance
|
|
23
23
|
*/
|
|
24
|
-
export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
|
|
24
|
+
export declare function getColumn(knex: Knex, table: string, column: string, alias: (string | false) | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
|
|
25
25
|
export {};
|
package/dist/utils/get-schema.js
CHANGED
|
@@ -4,7 +4,7 @@ import { systemCollectionRows } from '@directus/system-data';
|
|
|
4
4
|
import { parseJSON, toArray } from '@directus/utils';
|
|
5
5
|
import { mapValues } from 'lodash-es';
|
|
6
6
|
import { useBus } from '../bus/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getLocalSchemaCache, setLocalSchemaCache } from '../cache.js';
|
|
8
8
|
import { ALIAS_TYPES } from '../constants.js';
|
|
9
9
|
import getDatabase from '../database/index.js';
|
|
10
10
|
import { useLock } from '../lock/index.js';
|
|
@@ -22,7 +22,7 @@ export async function getSchema(options, attempt = 0) {
|
|
|
22
22
|
const schemaInspector = createInspector(database);
|
|
23
23
|
return await getDatabaseSchema(database, schemaInspector);
|
|
24
24
|
}
|
|
25
|
-
const cached = await
|
|
25
|
+
const cached = await getLocalSchemaCache();
|
|
26
26
|
if (cached) {
|
|
27
27
|
return cached;
|
|
28
28
|
}
|
|
@@ -40,39 +40,34 @@ export async function getSchema(options, attempt = 0) {
|
|
|
40
40
|
const currentProcessShouldHandleOperation = processId === 1;
|
|
41
41
|
if (currentProcessShouldHandleOperation === false) {
|
|
42
42
|
logger.trace('Schema cache is prepared in another process, waiting for result.');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
bus.subscribe(messageKey, callback);
|
|
50
|
-
async function callback() {
|
|
51
|
-
try {
|
|
52
|
-
if (timeout)
|
|
53
|
-
clearTimeout(timeout);
|
|
54
|
-
const schema = await getSchema(options, attempt + 1);
|
|
55
|
-
resolve(schema);
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
reject(error);
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
bus.unsubscribe(messageKey, callback);
|
|
43
|
+
const timeout = new Promise((_, reject) => setTimeout(reject, env['CACHE_SCHEMA_SYNC_TIMEOUT']));
|
|
44
|
+
const subscription = new Promise((resolve, reject) => {
|
|
45
|
+
bus.subscribe(messageKey, busListener).catch(reject);
|
|
46
|
+
function busListener(options) {
|
|
47
|
+
if (options.schema === null) {
|
|
48
|
+
return reject();
|
|
62
49
|
}
|
|
50
|
+
cleanup();
|
|
51
|
+
setLocalSchemaCache(options.schema).catch(reject);
|
|
52
|
+
resolve(options.schema);
|
|
53
|
+
}
|
|
54
|
+
function cleanup() {
|
|
55
|
+
bus.unsubscribe(messageKey, busListener).catch(reject);
|
|
63
56
|
}
|
|
64
57
|
});
|
|
58
|
+
return Promise.race([timeout, subscription]).catch(() => getSchema(options, attempt + 1));
|
|
65
59
|
}
|
|
60
|
+
let schema = null;
|
|
66
61
|
try {
|
|
67
62
|
const database = options?.database || getDatabase();
|
|
68
63
|
const schemaInspector = createInspector(database);
|
|
69
|
-
|
|
70
|
-
await
|
|
64
|
+
schema = await getDatabaseSchema(database, schemaInspector);
|
|
65
|
+
await setLocalSchemaCache(schema);
|
|
71
66
|
return schema;
|
|
72
67
|
}
|
|
73
68
|
finally {
|
|
69
|
+
await bus.publish(messageKey, { schema });
|
|
74
70
|
await lock.delete(lockKey);
|
|
75
|
-
bus.publish(messageKey, { ready: true });
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
73
|
async function getDatabaseSchema(database, schemaInspector) {
|
|
@@ -13,10 +13,6 @@ export function parseFilterKey(key) {
|
|
|
13
13
|
const match = key.match(FILTER_KEY_REGEX);
|
|
14
14
|
const fieldNameWithFunction = match?.[3]?.trim();
|
|
15
15
|
const fieldName = fieldNameWithFunction || key.trim();
|
|
16
|
-
|
|
17
|
-
if (fieldNameWithFunction) {
|
|
18
|
-
functionName = match?.[1]?.trim();
|
|
19
|
-
return { fieldName, functionName };
|
|
20
|
-
}
|
|
16
|
+
const functionName = fieldNameWithFunction ? match?.[1]?.trim() : undefined;
|
|
21
17
|
return { fieldName, functionName };
|
|
22
18
|
}
|
|
@@ -16,7 +16,7 @@ export declare function sanitizeCollection(collection: Collection | undefined):
|
|
|
16
16
|
* @returns sanitized field
|
|
17
17
|
*/
|
|
18
18
|
export declare function sanitizeField(field: Field | undefined, sanitizeAllSchema?: boolean): Partial<Field> | undefined;
|
|
19
|
-
export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "numeric_precision" | "
|
|
19
|
+
export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "numeric_precision" | "data_type" | "default_value" | "max_length" | "numeric_scale" | "is_nullable" | "is_unique" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
|
|
20
20
|
/**
|
|
21
21
|
* Pick certain database vendor specific relation properties that should be compared when performing diff
|
|
22
22
|
*
|
|
@@ -28,7 +28,7 @@ export async function authenticateConnection(message) {
|
|
|
28
28
|
const expires_at = getExpiresAtForToken(access_token);
|
|
29
29
|
return { accountability, expires_at, refresh_token };
|
|
30
30
|
}
|
|
31
|
-
catch
|
|
31
|
+
catch {
|
|
32
32
|
throw new WebSocketError('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
|
|
33
33
|
}
|
|
34
34
|
}
|