@directus/api 17.1.0 → 18.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/app.js +8 -2
- package/dist/auth/drivers/ldap.js +14 -16
- package/dist/auth/drivers/local.js +16 -10
- package/dist/auth/drivers/oauth2.js +16 -11
- package/dist/auth/drivers/openid.js +16 -11
- package/dist/auth/drivers/saml.js +27 -12
- package/dist/cli/commands/init/index.js +3 -3
- package/dist/cli/commands/security/key.js +2 -2
- package/dist/cli/utils/create-env/env-stub.liquid +19 -4
- package/dist/cli/utils/create-env/index.js +2 -2
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +11 -4
- package/dist/controllers/auth.js +54 -19
- package/dist/controllers/extensions.js +102 -5
- package/dist/controllers/items.js +3 -2
- package/dist/controllers/permissions.js +1 -1
- package/dist/controllers/shares.js +19 -4
- package/dist/database/migrations/20220429A-add-flows.js +3 -3
- package/dist/database/migrations/20230526A-migrate-translation-strings.js +2 -2
- package/dist/database/migrations/20240204A-marketplace.d.ts +3 -0
- package/dist/database/migrations/20240204A-marketplace.js +88 -0
- package/dist/database/migrations/run.js +3 -2
- package/dist/extensions/lib/get-extensions-settings.d.ts +6 -2
- package/dist/extensions/lib/get-extensions-settings.js +70 -22
- package/dist/extensions/lib/get-extensions.d.ts +5 -1
- package/dist/extensions/lib/get-extensions.js +7 -31
- package/dist/extensions/lib/installation/index.d.ts +2 -0
- package/dist/extensions/lib/installation/index.js +9 -0
- package/dist/extensions/lib/installation/manager.d.ts +5 -0
- package/dist/extensions/lib/installation/manager.js +90 -0
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
- package/dist/extensions/lib/sync-extensions.js +11 -10
- package/dist/extensions/manager.d.ts +27 -25
- package/dist/extensions/manager.js +214 -183
- package/dist/middleware/authenticate.d.ts +1 -0
- package/dist/middleware/error-handler.js +22 -18
- package/dist/middleware/extract-token.d.ts +6 -5
- package/dist/middleware/extract-token.js +27 -11
- package/dist/middleware/merge-content-versions.d.ts +2 -0
- package/dist/middleware/merge-content-versions.js +26 -0
- package/dist/middleware/respond.js +0 -12
- package/dist/middleware/validate-batch.d.ts +1 -0
- package/dist/request/agent-with-ip-validation.d.ts +1 -1
- package/dist/request/agent-with-ip-validation.js +5 -1
- package/dist/services/activity.js +3 -3
- package/dist/services/assets.js +2 -3
- package/dist/services/authentication.d.ts +7 -2
- package/dist/services/authentication.js +21 -13
- package/dist/services/extensions.d.ts +4 -8
- package/dist/services/extensions.js +110 -93
- package/dist/services/fields.js +28 -22
- package/dist/services/graphql/index.js +98 -42
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/mail/index.d.ts +1 -1
- package/dist/services/mail/index.js +6 -5
- package/dist/services/payload.js +2 -2
- package/dist/services/{permissions.d.ts → permissions/index.d.ts} +3 -4
- package/dist/services/{permissions.js → permissions/index.js} +6 -23
- package/dist/services/permissions/lib/with-app-minimal-permissions.d.ts +2 -0
- package/dist/services/permissions/lib/with-app-minimal-permissions.js +13 -0
- package/dist/services/relations.d.ts +2 -3
- package/dist/services/relations.js +2 -2
- package/dist/services/roles.js +1 -1
- package/dist/services/server.js +3 -0
- package/dist/services/shares.d.ts +3 -1
- package/dist/services/shares.js +9 -5
- package/dist/storage/index.js +5 -4
- package/dist/types/auth.d.ts +6 -4
- package/dist/types/graphql.d.ts +1 -0
- package/dist/utils/apply-query.js +3 -3
- package/dist/utils/filter-items.d.ts +2 -2
- package/dist/utils/filter-items.js +1 -3
- package/dist/utils/get-cache-headers.d.ts +1 -0
- package/dist/utils/get-cache-key.d.ts +1 -0
- package/dist/utils/get-graphql-query-and-variables.d.ts +1 -0
- package/dist/utils/get-ip-from-req.d.ts +1 -0
- package/dist/utils/get-milliseconds.d.ts +1 -1
- package/dist/utils/get-milliseconds.js +4 -1
- package/dist/utils/is-login-redirect-allowed.d.ts +4 -0
- package/dist/utils/is-login-redirect-allowed.js +34 -0
- package/dist/utils/is-url-allowed.d.ts +1 -1
- package/dist/utils/is-url-allowed.js +5 -5
- package/dist/utils/is-valid-uuid.d.ts +3 -0
- package/dist/utils/is-valid-uuid.js +21 -0
- package/dist/utils/jwt.d.ts +1 -1
- package/dist/utils/jwt.js +3 -3
- package/dist/utils/merge-version-data.d.ts +3 -0
- package/dist/utils/merge-version-data.js +134 -0
- package/dist/utils/sanitize-query.js +2 -0
- package/dist/utils/should-skip-cache.d.ts +1 -0
- package/dist/utils/validate-keys.js +2 -2
- package/dist/utils/validate-query.js +1 -0
- package/dist/websocket/controllers/base.js +2 -2
- package/package.json +45 -46
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ServiceUnavailableError } from '@directus/errors';
|
|
3
|
+
import { EXTENSION_PKG_KEY, ExtensionManifest } from '@directus/extensions';
|
|
4
|
+
import { download } from '@directus/extensions-registry';
|
|
5
|
+
import DriverLocal from '@directus/storage-driver-local';
|
|
6
|
+
import { move, remove } from 'fs-extra';
|
|
7
|
+
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
8
|
+
import { Readable } from 'node:stream';
|
|
9
|
+
import Queue from 'p-queue';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import tar from 'tar';
|
|
12
|
+
import { useLogger } from '../../../logger.js';
|
|
13
|
+
import { getStorage } from '../../../storage/index.js';
|
|
14
|
+
import { getExtensionsPath } from '../get-extensions-path.js';
|
|
15
|
+
const env = useEnv();
|
|
16
|
+
export class InstallationManager {
|
|
17
|
+
extensionPath = getExtensionsPath();
|
|
18
|
+
async install(versionId) {
|
|
19
|
+
const logger = useLogger();
|
|
20
|
+
const tempDir = join(env['TEMP_PATH'], 'marketplace', versionId);
|
|
21
|
+
const tmpStorage = new DriverLocal({ root: tempDir });
|
|
22
|
+
try {
|
|
23
|
+
await mkdir(tempDir, { recursive: true });
|
|
24
|
+
const options = {};
|
|
25
|
+
if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
|
|
26
|
+
options.registry = env['MARKETPLACE_REGISTRY'];
|
|
27
|
+
}
|
|
28
|
+
const tarReadableStream = await download(versionId, env['MARKETPLACE_TRUST'] === 'sandbox', options);
|
|
29
|
+
if (!tarReadableStream) {
|
|
30
|
+
throw new Error(`No readable stream returned from download`);
|
|
31
|
+
}
|
|
32
|
+
const tarStream = Readable.fromWeb(tarReadableStream);
|
|
33
|
+
const tarPath = join(tempDir, `bin.tar.tgz`);
|
|
34
|
+
await tmpStorage.write('bin.tar.tgz', tarStream);
|
|
35
|
+
/**
|
|
36
|
+
* NPM modules that are packed are always tarballed in a folder called "package"
|
|
37
|
+
*/
|
|
38
|
+
const extractedPath = 'package';
|
|
39
|
+
await tar.extract({
|
|
40
|
+
file: tarPath,
|
|
41
|
+
cwd: tempDir,
|
|
42
|
+
});
|
|
43
|
+
const packageFile = JSON.parse(await readFile(join(tempDir, extractedPath, 'package.json'), { encoding: 'utf-8' }));
|
|
44
|
+
const extensionManifest = await ExtensionManifest.parseAsync(packageFile);
|
|
45
|
+
if (!extensionManifest[EXTENSION_PKG_KEY]?.type) {
|
|
46
|
+
throw new Error(`Extension type not found in package.json`);
|
|
47
|
+
}
|
|
48
|
+
if (env['EXTENSIONS_LOCATION']) {
|
|
49
|
+
// Upload the extension into the configured extensions location
|
|
50
|
+
const storage = await getStorage();
|
|
51
|
+
const remoteDisk = storage.location(env['EXTENSIONS_LOCATION']);
|
|
52
|
+
const queue = new Queue({ concurrency: 1000 });
|
|
53
|
+
for await (const filepath of tmpStorage.list(extractedPath)) {
|
|
54
|
+
const readStream = await tmpStorage.read(filepath);
|
|
55
|
+
const remotePath = join(env['EXTENSIONS_PATH'], '.registry', versionId, filepath.substring(extractedPath.length));
|
|
56
|
+
queue.add(() => remoteDisk.write(remotePath, readStream));
|
|
57
|
+
}
|
|
58
|
+
await queue.onIdle();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// No custom location, so save to regular local extensions folder
|
|
62
|
+
const dest = join(this.extensionPath, '.registry', versionId);
|
|
63
|
+
await move(join(tempDir, extractedPath), dest, { overwrite: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
logger.warn(err);
|
|
68
|
+
throw new ServiceUnavailableError({ service: 'marketplace', reason: 'Could not download and extract the extension' }, { cause: err });
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await rm(tempDir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async uninstall(folder) {
|
|
75
|
+
if (env['EXTENSIONS_LOCATION']) {
|
|
76
|
+
const storage = await getStorage();
|
|
77
|
+
const remoteDisk = storage.location(env['EXTENSIONS_LOCATION']);
|
|
78
|
+
const queue = new Queue({ concurrency: 1000 });
|
|
79
|
+
const prefix = join(env['EXTENSIONS_PATH'], '.registry', folder);
|
|
80
|
+
for await (const filepath of remoteDisk.list(prefix)) {
|
|
81
|
+
queue.add(() => remoteDisk.delete(filepath));
|
|
82
|
+
}
|
|
83
|
+
await queue.onIdle();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const path = join(this.extensionPath, '.registry', folder);
|
|
87
|
+
await remove(path);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -16,7 +16,7 @@ export declare function generateApiExtensionsSandboxEntrypoint(type: ApiExtensio
|
|
|
16
16
|
unregisterFunction: () => Promise<void>;
|
|
17
17
|
} | {
|
|
18
18
|
code: string;
|
|
19
|
-
hostFunctions: ((path: import("isolated-vm").Reference<string>, method: import("isolated-vm").Reference<"GET" | "POST" | "DELETE" | "
|
|
19
|
+
hostFunctions: ((path: import("isolated-vm").Reference<string>, method: import("isolated-vm").Reference<"GET" | "POST" | "DELETE" | "PUT" | "PATCH">, cb: import("isolated-vm").Reference<(req: {
|
|
20
20
|
url: string;
|
|
21
21
|
headers: import("http").IncomingHttpHeaders;
|
|
22
22
|
body: string;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
|
-
import {
|
|
3
|
-
import { ensureExtensionDirs } from '@directus/extensions/node';
|
|
2
|
+
import { exists } from 'fs-extra';
|
|
4
3
|
import mid from 'node-machine-id';
|
|
5
4
|
import { createWriteStream } from 'node:fs';
|
|
6
|
-
import { mkdir } from 'node:fs/promises';
|
|
5
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
7
6
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
8
7
|
import { pipeline } from 'node:stream/promises';
|
|
9
8
|
import Queue from 'p-queue';
|
|
@@ -16,10 +15,7 @@ export const syncExtensions = async () => {
|
|
|
16
15
|
const env = useEnv();
|
|
17
16
|
const logger = useLogger();
|
|
18
17
|
const extensionsPath = getExtensionsPath();
|
|
19
|
-
|
|
20
|
-
// Safe to run with multiple instances since dirs are created with `recursive: true`
|
|
21
|
-
return ensureExtensionDirs(extensionsPath, NESTED_EXTENSION_TYPES);
|
|
22
|
-
}
|
|
18
|
+
const storageExtensionsPath = env['EXTENSIONS_PATH'];
|
|
23
19
|
const messenger = useBus();
|
|
24
20
|
const isPrimaryProcess = String(process.env['NODE_APP_INSTANCE']) === '0' || process.env['NODE_APP_INSTANCE'] === undefined;
|
|
25
21
|
const id = await mid.machineId();
|
|
@@ -36,6 +32,12 @@ export const syncExtensions = async () => {
|
|
|
36
32
|
messenger.subscribe(message, () => resolve());
|
|
37
33
|
});
|
|
38
34
|
}
|
|
35
|
+
if (await exists(extensionsPath)) {
|
|
36
|
+
// In case the FS still contains the cached extensions from a previous invocation. We have to
|
|
37
|
+
// clear them out to ensure the remote extensions folder remains the source of truth for all
|
|
38
|
+
// extensions that are loaded.
|
|
39
|
+
await rm(extensionsPath, { recursive: true, force: true });
|
|
40
|
+
}
|
|
39
41
|
// Ensure that the local extensions cache path exists
|
|
40
42
|
await mkdir(extensionsPath, { recursive: true });
|
|
41
43
|
await setSyncStatus(SyncStatus.SYNCING);
|
|
@@ -44,18 +46,17 @@ export const syncExtensions = async () => {
|
|
|
44
46
|
const disk = storage.location(env['EXTENSIONS_LOCATION']);
|
|
45
47
|
// Make sure we don't overload the file handles
|
|
46
48
|
const queue = new Queue({ concurrency: 1000 });
|
|
47
|
-
for await (const filepath of disk.list(
|
|
49
|
+
for await (const filepath of disk.list(storageExtensionsPath)) {
|
|
48
50
|
const readStream = await disk.read(filepath);
|
|
49
51
|
// We want files to be stored in the root of `$TEMP_PATH/extensions`, so gotta remove the
|
|
50
52
|
// extensions path on disk from the start of the file path
|
|
51
|
-
const destPath = join(extensionsPath, relative(resolve(sep,
|
|
53
|
+
const destPath = join(extensionsPath, relative(resolve(sep, storageExtensionsPath), resolve(sep, filepath)));
|
|
52
54
|
// Ensure that the directory path exists
|
|
53
55
|
await mkdir(dirname(destPath), { recursive: true });
|
|
54
56
|
const writeStream = createWriteStream(destPath);
|
|
55
57
|
queue.add(() => pipeline(readStream, writeStream));
|
|
56
58
|
}
|
|
57
59
|
await queue.onIdle();
|
|
58
|
-
await ensureExtensionDirs(extensionsPath, NESTED_EXTENSION_TYPES);
|
|
59
60
|
await setSyncStatus(SyncStatus.DONE);
|
|
60
61
|
messenger.publish(message, { ready: true });
|
|
61
62
|
};
|
|
@@ -7,10 +7,9 @@ export declare class ExtensionManager {
|
|
|
7
7
|
* Whether or not the extensions have been read from disk and registered into the system
|
|
8
8
|
*/
|
|
9
9
|
private isLoaded;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
private extensions;
|
|
10
|
+
private localExtensions;
|
|
11
|
+
private registryExtensions;
|
|
12
|
+
private moduleExtensions;
|
|
14
13
|
/**
|
|
15
14
|
* Settings for the extensions that are loaded within the current process
|
|
16
15
|
*/
|
|
@@ -57,6 +56,18 @@ export declare class ExtensionManager {
|
|
|
57
56
|
* Optional file system watcher to auto-reload extensions when the local file system changes
|
|
58
57
|
*/
|
|
59
58
|
private watcher;
|
|
59
|
+
/**
|
|
60
|
+
* installation manager responsible for installing extensions from registries
|
|
61
|
+
*/
|
|
62
|
+
private installationManager;
|
|
63
|
+
private messenger;
|
|
64
|
+
/**
|
|
65
|
+
* channel to publish on registering extension from external registry
|
|
66
|
+
*/
|
|
67
|
+
private reloadChannel;
|
|
68
|
+
private processId;
|
|
69
|
+
get extensions(): Extension[];
|
|
70
|
+
getExtension(source: string, folder: string): Extension | undefined;
|
|
60
71
|
/**
|
|
61
72
|
* Load and register all extensions
|
|
62
73
|
*
|
|
@@ -65,6 +76,11 @@ export declare class ExtensionManager {
|
|
|
65
76
|
* @param {boolean} options.watch - Whether or not to watch the local extensions folder for changes
|
|
66
77
|
*/
|
|
67
78
|
initialize(options?: Partial<ExtensionManagerOptions>): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Installs an external extension from registry
|
|
81
|
+
*/
|
|
82
|
+
install(versionId: string): Promise<void>;
|
|
83
|
+
uninstall(folder: string): Promise<void>;
|
|
68
84
|
/**
|
|
69
85
|
* Load all extensions from disk and register them in their respective places
|
|
70
86
|
*/
|
|
@@ -76,7 +92,7 @@ export declare class ExtensionManager {
|
|
|
76
92
|
/**
|
|
77
93
|
* Reload all the extensions. Will unload if extensions have already been loaded
|
|
78
94
|
*/
|
|
79
|
-
reload():
|
|
95
|
+
reload(): Promise<unknown>;
|
|
80
96
|
/**
|
|
81
97
|
* Return the previously generated app extensions bundle
|
|
82
98
|
*/
|
|
@@ -96,10 +112,6 @@ export declare class ExtensionManager {
|
|
|
96
112
|
head: string;
|
|
97
113
|
body: string;
|
|
98
114
|
};
|
|
99
|
-
/**
|
|
100
|
-
* Allow reading the installed extensions
|
|
101
|
-
*/
|
|
102
|
-
getExtensions(): Extension[];
|
|
103
115
|
/**
|
|
104
116
|
* Start the chokidar watcher for extensions on the local filesystem
|
|
105
117
|
*/
|
|
@@ -119,26 +131,16 @@ export declare class ExtensionManager {
|
|
|
119
131
|
*/
|
|
120
132
|
private generateExtensionBundle;
|
|
121
133
|
private registerSandboxedApiExtension;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
private
|
|
127
|
-
/**
|
|
128
|
-
* Import the endpoint module code for all endpoint extensions, and register them individually through
|
|
129
|
-
* registerEndpoint
|
|
130
|
-
*/
|
|
131
|
-
private registerEndpoints;
|
|
134
|
+
private registerApiExtensions;
|
|
135
|
+
private registerHookExtension;
|
|
136
|
+
private registerEndpointExtension;
|
|
137
|
+
private registerOperationExtension;
|
|
138
|
+
private registerBundleExtension;
|
|
132
139
|
/**
|
|
133
140
|
* Import the operation module code for all operation extensions, and register them individually through
|
|
134
141
|
* registerOperation
|
|
135
142
|
*/
|
|
136
|
-
private
|
|
137
|
-
/**
|
|
138
|
-
* Import the module code for all hook, endpoint, and operation extensions registered within a
|
|
139
|
-
* bundle, and register them with their respective registration function
|
|
140
|
-
*/
|
|
141
|
-
private registerBundles;
|
|
143
|
+
private registerInternalOperations;
|
|
142
144
|
/**
|
|
143
145
|
* Register a single hook
|
|
144
146
|
*/
|