Extension clone improvements (part 2) (#5571) * fix: remove the cloned directory if it contains no manifest * fix: apply feature flag guard to user extension data hosting * fix: disable inactive controls when feature flag is off * fix: change response status to 404

3eb38615960c971b540c2c5e41337eb020bc2c47

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +28 -8Showing whitespace changes
public/script.js+5 -0
@@ -7957,6 +7957,11 @@ export async function getSettings(initLoaderHandle = null) {
79577957 Object.assign(extension_settings, (settings.extension_settings ?? {}));
79587958 $('#third_party_extension_button').addClass('disabled');
79597959 $('#extensions_details').addClass('disabled');
7960+ $('#extensions_connect').addClass('disabled');
7961+ $('#extensions_notify_updates').attr('disabled', 'disabled');
7962+ $('#extensions_autoconnect').attr('disabled', 'disabled');
7963+ $('#extensions_url').attr('disabled', 'disabled');
7964+ $('#extensions_api_key').attr('disabled', 'disabled');
79607965 }
79617966
79627967 firstRun = !!settings.firstRun;
src/endpoints/extensions.js+21 -7
@@ -65,14 +65,20 @@ async function checkIfRepoIsUpToDate(extensionPath) {
6565
6666export const router = express.Router();
6767
68-// Feature flag guard: don't allow calling any of the endpoints if extensions are disabled
68+/**
69-router.use((_, response, next) => {
69+ * Feature flag guard: don't allow calling any of the endpoints if extensions are disabled
70+ * @type {import('express').RequestHandler}
71+ */
72+export const extensionsEnabledFeatureGuard = (_, response, next) => {
7073 const enabled = !!getConfigValue('extensions.enabled', true, 'boolean');
7174 if (!enabled) {
72- return response.status(400).send('Bad Request: Extensions are disabled.');
75+ response.sendStatus(404);
76+ return;
7377 }
7478 next();
7579});
80+
81+router.use(extensionsEnabledFeatureGuard);
7682
7783/**
7884 * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest,
@@ -119,6 +125,7 @@ router.post('/install', async (request, response) => {
119125 }
120126
121127 const extensionPath = path.join(basePath, extensionNameSanitized);
128+ const folderName = path.basename(extensionPath);
122129
123130 if (fs.existsSync(extensionPath)) {
124131 return response.status(409).send(`Directory already exists at ${extensionPath}`);
@@ -131,10 +138,17 @@ router.post('/install', async (request, response) => {
131138 await git.clone(parsedUrl.href, extensionPath, cloneOptions);
132139 console.info(`Extension has been cloned to ${extensionPath} from ${parsedUrl.href} at ${branch || '(default)'} branch`);
133140
134- const { version, author, display_name } = await getManifest(extensionPath);
141+ try {
135142 const folderNamemanifest = path.basenameawait getManifest(extensionPath);
136-
143+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
144+ throw new Error('Manifest is not a valid JSON object.');
145+ }
146+ const { version, author, display_name } = manifest;
137147 return response.send({ version, author, display_name, extensionPath, folderName });
148+ } catch (manifestError) {
149+ await fs.promises.rm(extensionPath, { recursive: true, force: true });
150+ throw manifestError;
151+ }
138152 } catch (error) {
139153 console.error('Importing extension failed', error);
140154 return response.status(500).send('Internal Server Error. Check the server logs for more details.');
src/users.js+2 -1
@@ -22,6 +22,7 @@ import { allowKeysExposure, readSecret, writeSecret, SECRETS_FILE } from './endp
2222import { getContentOfType } from './endpoints/content-manager.js';
2323import { serverDirectory } from './server-directory.js';
2424import { filterValidIpPatterns, getIpFromRequest } from './express-common.js';
25+import { extensionsEnabledFeatureGuard } from './endpoints/extensions.js';
2526
2627export const KEY_PREFIX = 'user:';
2728const AVATAR_PREFIX = 'avatar:';
@@ -1215,4 +1216,4 @@ router.use('/User%20Avatars/*', createRouteHandler(req => req.user.directories.a
12151216router.use('/assets/*', createRouteHandler(req => req.user.directories.assets));
12161217router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages));
12171218router.use('/user/files/*', createRouteHandler(req => req.user.directories.files));
12181219router.use('/scripts/extensions/third-party/*', extensionsEnabledFeatureGuard, createExtensionsRouteHandler(req => req.user.directories.extensions));