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
This commit is contained in:
Cohee
2026-05-02 17:08:57 +03:00
committed by GitHub
parent c325c6d8e9
commit 3eb3861596
3 changed files with 29 additions and 9 deletions
+5
View File
@@ -7957,6 +7957,11 @@ export async function getSettings(initLoaderHandle = null) {
Object.assign(extension_settings, (settings.extension_settings ?? {})); Object.assign(extension_settings, (settings.extension_settings ?? {}));
$('#third_party_extension_button').addClass('disabled'); $('#third_party_extension_button').addClass('disabled');
$('#extensions_details').addClass('disabled'); $('#extensions_details').addClass('disabled');
$('#extensions_connect').addClass('disabled');
$('#extensions_notify_updates').attr('disabled', 'disabled');
$('#extensions_autoconnect').attr('disabled', 'disabled');
$('#extensions_url').attr('disabled', 'disabled');
$('#extensions_api_key').attr('disabled', 'disabled');
} }
firstRun = !!settings.firstRun; firstRun = !!settings.firstRun;
+22 -8
View File
@@ -65,14 +65,20 @@ async function checkIfRepoIsUpToDate(extensionPath) {
export const router = express.Router(); export const router = express.Router();
// Feature flag guard: don't allow calling any of the endpoints if extensions are disabled /**
router.use((_, response, next) => { * Feature flag guard: don't allow calling any of the endpoints if extensions are disabled
* @type {import('express').RequestHandler}
*/
export const extensionsEnabledFeatureGuard = (_, response, next) => {
const enabled = !!getConfigValue('extensions.enabled', true, 'boolean'); const enabled = !!getConfigValue('extensions.enabled', true, 'boolean');
if (!enabled) { if (!enabled) {
return response.status(400).send('Bad Request: Extensions are disabled.'); response.sendStatus(404);
return;
} }
next(); next();
}); };
router.use(extensionsEnabledFeatureGuard);
/** /**
* HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest, * 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) => {
} }
const extensionPath = path.join(basePath, extensionNameSanitized); const extensionPath = path.join(basePath, extensionNameSanitized);
const folderName = path.basename(extensionPath);
if (fs.existsSync(extensionPath)) { if (fs.existsSync(extensionPath)) {
return response.status(409).send(`Directory already exists at ${extensionPath}`); return response.status(409).send(`Directory already exists at ${extensionPath}`);
@@ -131,10 +138,17 @@ router.post('/install', async (request, response) => {
await git.clone(parsedUrl.href, extensionPath, cloneOptions); await git.clone(parsedUrl.href, extensionPath, cloneOptions);
console.info(`Extension has been cloned to ${extensionPath} from ${parsedUrl.href} at ${branch || '(default)'} branch`); console.info(`Extension has been cloned to ${extensionPath} from ${parsedUrl.href} at ${branch || '(default)'} branch`);
const { version, author, display_name } = await getManifest(extensionPath); try {
const folderName = path.basename(extensionPath); const manifest = await getManifest(extensionPath);
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
return response.send({ version, author, display_name, extensionPath, folderName }); throw new Error('Manifest is not a valid JSON object.');
}
const { version, author, display_name } = manifest;
return response.send({ version, author, display_name, extensionPath, folderName });
} catch (manifestError) {
await fs.promises.rm(extensionPath, { recursive: true, force: true });
throw manifestError;
}
} catch (error) { } catch (error) {
console.error('Importing extension failed', error); console.error('Importing extension failed', error);
return response.status(500).send('Internal Server Error. Check the server logs for more details.'); return response.status(500).send('Internal Server Error. Check the server logs for more details.');
+2 -1
View File
@@ -22,6 +22,7 @@ import { allowKeysExposure, readSecret, writeSecret, SECRETS_FILE } from './endp
import { getContentOfType } from './endpoints/content-manager.js'; import { getContentOfType } from './endpoints/content-manager.js';
import { serverDirectory } from './server-directory.js'; import { serverDirectory } from './server-directory.js';
import { filterValidIpPatterns, getIpFromRequest } from './express-common.js'; import { filterValidIpPatterns, getIpFromRequest } from './express-common.js';
import { extensionsEnabledFeatureGuard } from './endpoints/extensions.js';
export const KEY_PREFIX = 'user:'; export const KEY_PREFIX = 'user:';
const AVATAR_PREFIX = 'avatar:'; const AVATAR_PREFIX = 'avatar:';
@@ -1215,4 +1216,4 @@ router.use('/User%20Avatars/*', createRouteHandler(req => req.user.directories.a
router.use('/assets/*', createRouteHandler(req => req.user.directories.assets)); router.use('/assets/*', createRouteHandler(req => req.user.directories.assets));
router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages)); router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages));
router.use('/user/files/*', createRouteHandler(req => req.user.directories.files)); router.use('/user/files/*', createRouteHandler(req => req.user.directories.files));
router.use('/scripts/extensions/third-party/*', createExtensionsRouteHandler(req => req.user.directories.extensions)); router.use('/scripts/extensions/third-party/*', extensionsEnabledFeatureGuard, createExtensionsRouteHandler(req => req.user.directories.extensions));