@directus/api 27.0.1 → 27.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/drivers/openid.js +3 -1
- package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +2 -2
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +11 -11
- package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
- package/dist/extensions/manager.js +26 -0
- package/dist/operations/condition/index.js +1 -1
- package/dist/services/payload.js +2 -2
- package/dist/services/relations.d.ts +1 -1
- package/dist/services/relations.js +5 -2
- package/dist/utils/get-ip-from-req.d.ts +2 -1
- package/dist/utils/get-ip-from-req.js +29 -2
- package/dist/utils/get-schema.js +1 -1
- package/dist/websocket/controllers/base.d.ts +2 -2
- package/dist/websocket/controllers/base.js +33 -5
- package/dist/websocket/types.d.ts +1 -0
- package/package.json +25 -23
|
@@ -311,6 +311,7 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
311
311
|
res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
|
|
312
312
|
}, respond);
|
|
313
313
|
router.get('/callback', asyncHandler(async (req, res, next) => {
|
|
314
|
+
const env = useEnv();
|
|
314
315
|
const logger = useLogger();
|
|
315
316
|
let tokenData;
|
|
316
317
|
try {
|
|
@@ -318,7 +319,8 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
318
319
|
}
|
|
319
320
|
catch (e) {
|
|
320
321
|
logger.warn(e, `[OpenID] Couldn't verify OpenID cookie`);
|
|
321
|
-
|
|
322
|
+
const url = new Url(env['PUBLIC_URL']).addPath('admin', 'login');
|
|
323
|
+
return res.redirect(`${url.toString()}?reason=${ErrorCode.InvalidCredentials}`);
|
|
322
324
|
}
|
|
323
325
|
const { verifier, redirect, prompt } = tokenData;
|
|
324
326
|
const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Accountability, Query, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
export interface ConvertWildcardsOptions {
|
|
4
|
-
|
|
4
|
+
collection: string;
|
|
5
5
|
fields: string[];
|
|
6
|
-
|
|
6
|
+
alias: Query['alias'];
|
|
7
7
|
accountability: Accountability | null;
|
|
8
8
|
}
|
|
9
9
|
export interface ConvertWildCardsContext {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { getRelation } from '@directus/utils';
|
|
2
2
|
import { cloneDeep } from 'lodash-es';
|
|
3
3
|
import { fetchAllowedFields } from '../../../permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js';
|
|
4
|
+
import { parseFilterKey } from '../../../utils/parse-filter-key.js';
|
|
4
5
|
export async function convertWildcards(options, context) {
|
|
5
6
|
const fields = cloneDeep(options.fields);
|
|
6
|
-
const fieldsInCollection = Object.entries(context.schema.collections[options.
|
|
7
|
+
const fieldsInCollection = Object.entries(context.schema.collections[options.collection].fields).map(([name]) => name);
|
|
7
8
|
let allowedFields = fieldsInCollection;
|
|
8
9
|
if (options.accountability && options.accountability.admin === false) {
|
|
9
10
|
allowedFields = await fetchAllowedFields({
|
|
10
|
-
collection: options.
|
|
11
|
+
collection: options.collection,
|
|
11
12
|
action: 'read',
|
|
12
13
|
accountability: options.accountability,
|
|
13
14
|
}, context);
|
|
@@ -22,7 +23,7 @@ export async function convertWildcards(options, context) {
|
|
|
22
23
|
if (fieldKey.includes('*') === false)
|
|
23
24
|
continue;
|
|
24
25
|
if (fieldKey === '*') {
|
|
25
|
-
const aliases = Object.keys(options.
|
|
26
|
+
const aliases = Object.keys(options.alias ?? {});
|
|
26
27
|
// Set to all fields in collection
|
|
27
28
|
if (allowedFields.includes('*')) {
|
|
28
29
|
fields.splice(index, 1, ...fieldsInCollection, ...aliases);
|
|
@@ -30,8 +31,8 @@ export async function convertWildcards(options, context) {
|
|
|
30
31
|
else {
|
|
31
32
|
// Set to all allowed fields
|
|
32
33
|
const allowedAliases = aliases.filter((fieldKey) => {
|
|
33
|
-
const
|
|
34
|
-
return allowedFields.includes(
|
|
34
|
+
const { fieldName } = parseFilterKey(options.alias[fieldKey]);
|
|
35
|
+
return allowedFields.includes(fieldName);
|
|
35
36
|
});
|
|
36
37
|
fields.splice(index, 1, ...allowedFields, ...allowedAliases);
|
|
37
38
|
}
|
|
@@ -41,16 +42,15 @@ export async function convertWildcards(options, context) {
|
|
|
41
42
|
const parts = fieldKey.split('.');
|
|
42
43
|
const relationalFields = allowedFields.includes('*')
|
|
43
44
|
? context.schema.relations
|
|
44
|
-
.filter((relation) => relation.collection === options.
|
|
45
|
-
relation.related_collection === options.parentCollection)
|
|
45
|
+
.filter((relation) => relation.collection === options.collection || relation.related_collection === options.collection)
|
|
46
46
|
.map((relation) => {
|
|
47
|
-
const isMany = relation.collection === options.
|
|
47
|
+
const isMany = relation.collection === options.collection;
|
|
48
48
|
return isMany ? relation.field : relation.meta?.one_field;
|
|
49
49
|
})
|
|
50
|
-
: allowedFields.filter((fieldKey) => !!getRelation(context.schema.relations, options.
|
|
50
|
+
: allowedFields.filter((fieldKey) => !!getRelation(context.schema.relations, options.collection, fieldKey));
|
|
51
51
|
const nonRelationalFields = allowedFields.filter((fieldKey) => relationalFields.includes(fieldKey) === false);
|
|
52
|
-
const aliasFields = Object.keys(options.
|
|
53
|
-
const name = options.
|
|
52
|
+
const aliasFields = Object.keys(options.alias ?? {}).map((fieldKey) => {
|
|
53
|
+
const name = options.alias[fieldKey];
|
|
54
54
|
if (relationalFields.includes(name)) {
|
|
55
55
|
return `${fieldKey}.${parts.slice(1).join('.')}`;
|
|
56
56
|
}
|
|
@@ -14,8 +14,8 @@ export async function parseFields(options, context) {
|
|
|
14
14
|
return [];
|
|
15
15
|
fields = await convertWildcards({
|
|
16
16
|
fields,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
collection: options.parentCollection,
|
|
18
|
+
alias: options.query.alias,
|
|
19
19
|
accountability: options.accountability,
|
|
20
20
|
}, context);
|
|
21
21
|
if (!fields || !Array.isArray(fields))
|
|
@@ -169,13 +169,25 @@ export class ExtensionManager {
|
|
|
169
169
|
* Installs an external extension from registry
|
|
170
170
|
*/
|
|
171
171
|
async install(versionId) {
|
|
172
|
+
const logger = useLogger();
|
|
172
173
|
await this.installationManager.install(versionId);
|
|
173
174
|
await this.reload({ forceSync: true });
|
|
175
|
+
emitter.emitAction('extensions.installed', {
|
|
176
|
+
extensions: this.extensions,
|
|
177
|
+
versionId,
|
|
178
|
+
});
|
|
179
|
+
logger.info(`Installed extension: ${versionId}`);
|
|
174
180
|
await this.broadcastReloadNotification();
|
|
175
181
|
}
|
|
176
182
|
async uninstall(folder) {
|
|
183
|
+
const logger = useLogger();
|
|
177
184
|
await this.installationManager.uninstall(folder);
|
|
178
185
|
await this.reload({ forceSync: true });
|
|
186
|
+
emitter.emitAction('extensions.uninstalled', {
|
|
187
|
+
extensions: this.extensions,
|
|
188
|
+
folder,
|
|
189
|
+
});
|
|
190
|
+
logger.info(`Uninstalled extension: ${folder}`);
|
|
179
191
|
await this.broadcastReloadNotification();
|
|
180
192
|
}
|
|
181
193
|
async broadcastReloadNotification() {
|
|
@@ -211,6 +223,10 @@ export class ExtensionManager {
|
|
|
211
223
|
this.appExtensionsBundle = await this.generateExtensionBundle();
|
|
212
224
|
}
|
|
213
225
|
this.isLoaded = true;
|
|
226
|
+
emitter.emitAction('extensions.load', {
|
|
227
|
+
extensions: this.extensions,
|
|
228
|
+
});
|
|
229
|
+
logger.info('Extensions loaded');
|
|
214
230
|
}
|
|
215
231
|
/**
|
|
216
232
|
* Unregister all extensions from the current process
|
|
@@ -220,6 +236,11 @@ export class ExtensionManager {
|
|
|
220
236
|
this.localEmitter.offAll();
|
|
221
237
|
this.appExtensionsBundle = null;
|
|
222
238
|
this.isLoaded = false;
|
|
239
|
+
emitter.emitAction('extensions.unload', {
|
|
240
|
+
extensions: this.extensions,
|
|
241
|
+
});
|
|
242
|
+
const logger = useLogger();
|
|
243
|
+
logger.info('Extensions unloaded');
|
|
223
244
|
}
|
|
224
245
|
/**
|
|
225
246
|
* Reload all the extensions. Will unload if extensions have already been loaded
|
|
@@ -247,6 +268,11 @@ export class ExtensionManager {
|
|
|
247
268
|
this.updateWatchedExtensions(added, removed);
|
|
248
269
|
const addedExtensions = added.map((extension) => extension.name);
|
|
249
270
|
const removedExtensions = removed.map((extension) => extension.name);
|
|
271
|
+
emitter.emitAction('extensions.reload', {
|
|
272
|
+
extensions: this.extensions,
|
|
273
|
+
added: addedExtensions,
|
|
274
|
+
removed: removedExtensions,
|
|
275
|
+
});
|
|
250
276
|
if (addedExtensions.length > 0) {
|
|
251
277
|
logger.info(`Added extensions: ${addedExtensions.join(', ')}`);
|
|
252
278
|
}
|
|
@@ -4,7 +4,7 @@ import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '
|
|
|
4
4
|
export default defineOperationApi({
|
|
5
5
|
id: 'condition',
|
|
6
6
|
handler: ({ filter }, { data, accountability }) => {
|
|
7
|
-
const parsedFilter = parseFilter(filter, accountability);
|
|
7
|
+
const parsedFilter = parseFilter(filter, accountability, undefined, true);
|
|
8
8
|
if (!parsedFilter) {
|
|
9
9
|
return null;
|
|
10
10
|
}
|
package/dist/services/payload.js
CHANGED
|
@@ -285,7 +285,7 @@ export class PayloadService {
|
|
|
285
285
|
payload[name] = newValue;
|
|
286
286
|
}
|
|
287
287
|
if (dateColumn.type === 'dateTime') {
|
|
288
|
-
const year = String(value.getFullYear());
|
|
288
|
+
const year = String(value.getFullYear()).padStart(4, '0');
|
|
289
289
|
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
290
290
|
const day = String(value.getDate()).padStart(2, '0');
|
|
291
291
|
const hours = String(value.getHours()).padStart(2, '0');
|
|
@@ -295,7 +295,7 @@ export class PayloadService {
|
|
|
295
295
|
payload[name] = newValue;
|
|
296
296
|
}
|
|
297
297
|
if (dateColumn.type === 'date') {
|
|
298
|
-
const year = String(value.getFullYear());
|
|
298
|
+
const year = String(value.getFullYear()).padStart(4, '0');
|
|
299
299
|
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
300
300
|
const day = String(value.getDate()).padStart(2, '0');
|
|
301
301
|
// Strip off the time / timezone information from a date-only value
|
|
@@ -16,7 +16,7 @@ export declare class RelationsService {
|
|
|
16
16
|
helpers: Helpers;
|
|
17
17
|
constructor(options: AbstractServiceOptions);
|
|
18
18
|
foreignKeys(collection?: string): Promise<ForeignKey[]>;
|
|
19
|
-
readAll(collection?: string, opts?: QueryOptions): Promise<Relation[]>;
|
|
19
|
+
readAll(collection?: string, opts?: QueryOptions, bypassCache?: boolean): Promise<Relation[]>;
|
|
20
20
|
readOne(collection: string, field: string): Promise<Relation>;
|
|
21
21
|
/**
|
|
22
22
|
* Create a new relationship / foreign key constraint
|
|
@@ -58,7 +58,7 @@ export class RelationsService {
|
|
|
58
58
|
}
|
|
59
59
|
return foreignKeys;
|
|
60
60
|
}
|
|
61
|
-
async readAll(collection, opts) {
|
|
61
|
+
async readAll(collection, opts, bypassCache) {
|
|
62
62
|
if (this.accountability) {
|
|
63
63
|
await validateAccess({
|
|
64
64
|
accountability: this.accountability,
|
|
@@ -87,7 +87,10 @@ export class RelationsService {
|
|
|
87
87
|
return true;
|
|
88
88
|
return metaRow.many_collection === collection;
|
|
89
89
|
});
|
|
90
|
-
|
|
90
|
+
let schemaRows = bypassCache ? await this.schemaInspector.foreignKeys() : await this.foreignKeys(collection);
|
|
91
|
+
if (collection && bypassCache) {
|
|
92
|
+
schemaRows = schemaRows.filter((row) => row.table === collection);
|
|
93
|
+
}
|
|
91
94
|
const results = this.stitchRelations(metaRows, schemaRows);
|
|
92
95
|
return await this.filterForbidden(results);
|
|
93
96
|
}
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { isIP } from 'net';
|
|
3
|
+
import proxyAddr from 'proxy-addr';
|
|
3
4
|
import { useLogger } from '../logger/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Generate the trusted ip list
|
|
7
|
+
*
|
|
8
|
+
* Adapted to have feature parity with the express equivalent https://github.com/expressjs/express/blob/9f4dbe3a1332cd883069ba9b73a9eed99234cfc7/lib/utils.js#L192
|
|
9
|
+
*/
|
|
10
|
+
function getTrustValue(trust) {
|
|
11
|
+
if (typeof trust === 'boolean') {
|
|
12
|
+
// Support plain true/false
|
|
13
|
+
return (_addr, _i) => trust;
|
|
14
|
+
}
|
|
15
|
+
else if (typeof trust === 'number') {
|
|
16
|
+
// Support trusting hop count
|
|
17
|
+
return (_addr, i) => i < trust;
|
|
18
|
+
}
|
|
19
|
+
else if (typeof trust === 'string') {
|
|
20
|
+
// Support comma-separated values
|
|
21
|
+
trust = trust.split(',').map((v) => v.trim());
|
|
22
|
+
}
|
|
23
|
+
return proxyAddr.compile(trust || []);
|
|
24
|
+
}
|
|
4
25
|
export function getIPFromReq(req) {
|
|
5
26
|
const env = useEnv();
|
|
6
27
|
const logger = useLogger();
|
|
7
|
-
let ip = req.ip;
|
|
28
|
+
let ip = 'ip' in req ? req.ip : proxyAddr(req, getTrustValue(env['IP_TRUST_PROXY']));
|
|
8
29
|
if (env['IP_CUSTOM_HEADER']) {
|
|
9
|
-
const
|
|
30
|
+
const customIPHeaderName = env['IP_CUSTOM_HEADER'].toLowerCase();
|
|
31
|
+
// All req.headers are auto lower-cased
|
|
32
|
+
let customIPHeaderValue = req.headers[customIPHeaderName];
|
|
33
|
+
// // Done to have feature parity with https://github.com/expressjs/express/blob/9f4dbe3a1332cd883069ba9b73a9eed99234cfc7/lib/request.js#L63
|
|
34
|
+
if (customIPHeaderName === 'referer' || customIPHeaderName === 'referrer') {
|
|
35
|
+
customIPHeaderValue = req.headers['referrer'] || req.headers['referer'];
|
|
36
|
+
}
|
|
10
37
|
if (typeof customIPHeaderValue === 'string' && isIP(customIPHeaderValue) !== 0) {
|
|
11
38
|
ip = customIPHeaderValue;
|
|
12
39
|
}
|
package/dist/utils/get-schema.js
CHANGED
|
@@ -162,6 +162,6 @@ async function getDatabaseSchema(database, schemaInspector) {
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
const relationsService = new RelationsService({ knex: database, schema: result });
|
|
165
|
-
result.relations = await relationsService.readAll();
|
|
165
|
+
result.relations = await relationsService.readAll(undefined, undefined, true);
|
|
166
166
|
return result;
|
|
167
167
|
}
|
|
@@ -22,8 +22,8 @@ export default abstract class SocketController {
|
|
|
22
22
|
protected getRateLimiter(): RateLimiterAbstract | null;
|
|
23
23
|
private catchInvalidMessages;
|
|
24
24
|
protected handleUpgrade(request: IncomingMessage, socket: internal.Duplex, head: Buffer): Promise<void>;
|
|
25
|
-
protected handleTokenUpgrade({ request, socket, head }: UpgradeContext, token: string | null): Promise<void>;
|
|
26
|
-
protected handleHandshakeUpgrade({ request, socket, head }: UpgradeContext): Promise<void>;
|
|
25
|
+
protected handleTokenUpgrade({ request, socket, head, accountabilityOverrides }: UpgradeContext, token: string | null): Promise<void>;
|
|
26
|
+
protected handleHandshakeUpgrade({ request, socket, head, accountabilityOverrides }: UpgradeContext): Promise<void>;
|
|
27
27
|
createClient(ws: WebSocket, { accountability, expires_at }: AuthenticationState): WebSocketClient;
|
|
28
28
|
protected parseMessage(data: string): WebSocketMessage;
|
|
29
29
|
protected handleAuthRequest(client: WebSocketClient, message: WebSocketAuthMessage): Promise<void>;
|
|
@@ -8,15 +8,16 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
8
8
|
import { fromZodError } from 'zod-validation-error';
|
|
9
9
|
import emitter from '../../emitter.js';
|
|
10
10
|
import { useLogger } from '../../logger/index.js';
|
|
11
|
+
import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
|
|
11
12
|
import { createRateLimiter } from '../../rate-limiter.js';
|
|
12
13
|
import { getAccountabilityForToken } from '../../utils/get-accountability-for-token.js';
|
|
14
|
+
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
13
15
|
import { authenticateConnection, authenticationSuccess } from '../authenticate.js';
|
|
14
16
|
import { WebSocketError, handleWebSocketError } from '../errors.js';
|
|
15
17
|
import { AuthMode, WebSocketAuthMessage, WebSocketMessage } from '../messages.js';
|
|
16
18
|
import { getExpiresAtForToken } from '../utils/get-expires-at-for-token.js';
|
|
17
19
|
import { getMessageType } from '../utils/message.js';
|
|
18
20
|
import { waitForAnyMessage, waitForMessageType } from '../utils/wait-for-message.js';
|
|
19
|
-
import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
|
|
20
21
|
const TOKEN_CHECK_INTERVAL = 15 * 60 * 1000; // 15 minutes
|
|
21
22
|
const logger = useLogger();
|
|
22
23
|
export default class SocketController {
|
|
@@ -98,8 +99,17 @@ export default class SocketController {
|
|
|
98
99
|
}
|
|
99
100
|
const env = useEnv();
|
|
100
101
|
const cookies = request.headers.cookie ? cookie.parse(request.headers.cookie) : {};
|
|
101
|
-
const context = { request, socket, head };
|
|
102
102
|
const sessionCookieName = env['SESSION_COOKIE_NAME'];
|
|
103
|
+
const accountabilityOverrides = {
|
|
104
|
+
ip: getIPFromReq(request) ?? null,
|
|
105
|
+
};
|
|
106
|
+
const userAgent = request.headers['user-agent']?.substring(0, 1024);
|
|
107
|
+
if (userAgent)
|
|
108
|
+
accountabilityOverrides.userAgent = userAgent;
|
|
109
|
+
const origin = request.headers['origin'];
|
|
110
|
+
if (origin)
|
|
111
|
+
accountabilityOverrides.origin = origin;
|
|
112
|
+
const context = { request, socket, head, accountabilityOverrides };
|
|
103
113
|
if (this.authentication.mode === 'strict' || query['access_token'] || cookies[sessionCookieName]) {
|
|
104
114
|
let token = null;
|
|
105
115
|
if (typeof query['access_token'] === 'string') {
|
|
@@ -117,11 +127,14 @@ export default class SocketController {
|
|
|
117
127
|
}
|
|
118
128
|
this.server.handleUpgrade(request, socket, head, async (ws) => {
|
|
119
129
|
this.catchInvalidMessages(ws);
|
|
120
|
-
const state = {
|
|
130
|
+
const state = {
|
|
131
|
+
accountability: createDefaultAccountability(accountabilityOverrides),
|
|
132
|
+
expires_at: null,
|
|
133
|
+
};
|
|
121
134
|
this.server.emit('connection', ws, state);
|
|
122
135
|
});
|
|
123
136
|
}
|
|
124
|
-
async handleTokenUpgrade({ request, socket, head }, token) {
|
|
137
|
+
async handleTokenUpgrade({ request, socket, head, accountabilityOverrides }, token) {
|
|
125
138
|
let accountability = null;
|
|
126
139
|
let expires_at = null;
|
|
127
140
|
if (token) {
|
|
@@ -149,13 +162,14 @@ export default class SocketController {
|
|
|
149
162
|
socket.destroy();
|
|
150
163
|
return;
|
|
151
164
|
}
|
|
165
|
+
Object.assign(accountability, accountabilityOverrides);
|
|
152
166
|
this.server.handleUpgrade(request, socket, head, async (ws) => {
|
|
153
167
|
this.catchInvalidMessages(ws);
|
|
154
168
|
const state = { accountability, expires_at };
|
|
155
169
|
this.server.emit('connection', ws, state);
|
|
156
170
|
});
|
|
157
171
|
}
|
|
158
|
-
async handleHandshakeUpgrade({ request, socket, head }) {
|
|
172
|
+
async handleHandshakeUpgrade({ request, socket, head, accountabilityOverrides }) {
|
|
159
173
|
this.server.handleUpgrade(request, socket, head, async (ws) => {
|
|
160
174
|
this.catchInvalidMessages(ws);
|
|
161
175
|
try {
|
|
@@ -163,6 +177,9 @@ export default class SocketController {
|
|
|
163
177
|
if (getMessageType(payload) !== 'auth')
|
|
164
178
|
throw new Error();
|
|
165
179
|
const state = await authenticateConnection(WebSocketAuthMessage.parse(payload));
|
|
180
|
+
if (state.accountability) {
|
|
181
|
+
Object.assign(state.accountability, accountabilityOverrides);
|
|
182
|
+
}
|
|
166
183
|
this.checkUserRequirements(state.accountability);
|
|
167
184
|
ws.send(authenticationSuccess(payload['uid'], state.refresh_token));
|
|
168
185
|
this.server.emit('connection', ws, state);
|
|
@@ -253,6 +270,17 @@ export default class SocketController {
|
|
|
253
270
|
try {
|
|
254
271
|
const { accountability, expires_at, refresh_token } = await authenticateConnection(message);
|
|
255
272
|
this.checkUserRequirements(accountability);
|
|
273
|
+
/**
|
|
274
|
+
* Re-use the existing ip, userAgent and origin accountability properties.
|
|
275
|
+
* They are only sent in the original connection request
|
|
276
|
+
*/
|
|
277
|
+
if (accountability && client.accountability) {
|
|
278
|
+
Object.assign(accountability, {
|
|
279
|
+
ip: client.accountability.ip,
|
|
280
|
+
userAgent: client.accountability.userAgent,
|
|
281
|
+
origin: client.accountability.origin,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
256
284
|
client.accountability = accountability;
|
|
257
285
|
client.expires_at = expires_at;
|
|
258
286
|
this.setTokenExpireTimer(client);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "27.0
|
|
3
|
+
"version": "27.1.0",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -136,10 +136,11 @@
|
|
|
136
136
|
"pino-pretty": "13.0.0",
|
|
137
137
|
"pm2": "5.4.3",
|
|
138
138
|
"prom-client": "15.1.3",
|
|
139
|
+
"proxy-addr": "2.0.7",
|
|
139
140
|
"qs": "6.14.0",
|
|
140
141
|
"rate-limiter-flexible": "5.0.5",
|
|
141
142
|
"rollup": "4.34.9",
|
|
142
|
-
"samlify": "2.
|
|
143
|
+
"samlify": "2.10.0",
|
|
143
144
|
"sanitize-html": "2.14.0",
|
|
144
145
|
"sharp": "0.33.5",
|
|
145
146
|
"snappy": "7.2.2",
|
|
@@ -150,30 +151,30 @@
|
|
|
150
151
|
"ws": "8.18.1",
|
|
151
152
|
"zod": "3.24.2",
|
|
152
153
|
"zod-validation-error": "3.4.0",
|
|
153
|
-
"@directus/app": "13.
|
|
154
|
-
"@directus/
|
|
155
|
-
"@directus/env": "5.0.4",
|
|
154
|
+
"@directus/app": "13.10.0",
|
|
155
|
+
"@directus/env": "5.0.5",
|
|
156
156
|
"@directus/errors": "2.0.1",
|
|
157
|
-
"@directus/extensions
|
|
158
|
-
"@directus/
|
|
159
|
-
"@directus/extensions": "3.0.
|
|
157
|
+
"@directus/extensions": "3.0.6",
|
|
158
|
+
"@directus/constants": "13.0.1",
|
|
159
|
+
"@directus/extensions-registry": "3.0.6",
|
|
160
|
+
"@directus/pressure": "3.0.5",
|
|
160
161
|
"@directus/format-title": "12.0.1",
|
|
161
|
-
"@directus/memory": "3.0.
|
|
162
|
+
"@directus/memory": "3.0.5",
|
|
163
|
+
"@directus/extensions-sdk": "13.1.1",
|
|
162
164
|
"@directus/schema": "13.0.1",
|
|
163
|
-
"@directus/schema-builder": "0.0.
|
|
164
|
-
"@directus/pressure": "3.0.4",
|
|
165
|
-
"@directus/storage": "12.0.0",
|
|
166
|
-
"@directus/storage-driver-azure": "12.0.4",
|
|
167
|
-
"@directus/storage-driver-cloudinary": "12.0.4",
|
|
165
|
+
"@directus/schema-builder": "0.0.2",
|
|
168
166
|
"@directus/specs": "11.1.0",
|
|
169
|
-
"@directus/storage
|
|
167
|
+
"@directus/storage": "12.0.0",
|
|
168
|
+
"@directus/storage-driver-cloudinary": "12.0.5",
|
|
169
|
+
"@directus/storage-driver-gcs": "12.0.5",
|
|
170
170
|
"@directus/storage-driver-local": "12.0.0",
|
|
171
|
-
"@directus/storage-driver-
|
|
172
|
-
"@directus/storage-driver-
|
|
173
|
-
"@directus/
|
|
171
|
+
"@directus/storage-driver-azure": "12.0.5",
|
|
172
|
+
"@directus/storage-driver-s3": "12.0.5",
|
|
173
|
+
"@directus/storage-driver-supabase": "3.0.5",
|
|
174
174
|
"@directus/system-data": "3.1.0",
|
|
175
|
-
"directus": "
|
|
176
|
-
"
|
|
175
|
+
"@directus/utils": "13.0.6",
|
|
176
|
+
"directus": "11.8.0",
|
|
177
|
+
"@directus/validation": "2.0.5"
|
|
177
178
|
},
|
|
178
179
|
"devDependencies": {
|
|
179
180
|
"@directus/tsconfig": "3.0.0",
|
|
@@ -204,6 +205,7 @@
|
|
|
204
205
|
"@types/nodemailer": "6.4.17",
|
|
205
206
|
"@types/object-hash": "3.0.6",
|
|
206
207
|
"@types/papaparse": "5.3.15",
|
|
208
|
+
"@types/proxy-addr": "2.0.3",
|
|
207
209
|
"@types/qs": "6.9.18",
|
|
208
210
|
"@types/sanitize-html": "2.13.0",
|
|
209
211
|
"@types/stream-json": "1.7.8",
|
|
@@ -217,8 +219,8 @@
|
|
|
217
219
|
"typescript": "5.8.2",
|
|
218
220
|
"vitest": "2.1.9",
|
|
219
221
|
"@directus/random": "2.0.1",
|
|
220
|
-
"@directus/
|
|
221
|
-
"@directus/
|
|
222
|
+
"@directus/schema-builder": "0.0.2",
|
|
223
|
+
"@directus/types": "13.1.2"
|
|
222
224
|
},
|
|
223
225
|
"optionalDependencies": {
|
|
224
226
|
"@keyv/redis": "3.0.1",
|
|
@@ -233,7 +235,7 @@
|
|
|
233
235
|
"node": ">=22"
|
|
234
236
|
},
|
|
235
237
|
"scripts": {
|
|
236
|
-
"build": "tsc --project tsconfig.prod.json && copyfiles \"src/**/*.{yaml,liquid}\" -u 1 dist",
|
|
238
|
+
"build": "rimraf ./dist && tsc --project tsconfig.prod.json && copyfiles \"src/**/*.{yaml,liquid}\" -u 1 dist",
|
|
237
239
|
"cli": "NODE_ENV=development SERVE_APP=false tsx src/cli/run.ts",
|
|
238
240
|
"dev": "NODE_ENV=development SERVE_APP=true tsx watch --ignore extensions --clear-screen=false src/start.ts",
|
|
239
241
|
"test": "vitest run",
|