diff --git a/components/operations.js b/components/operations.js index b2dbba014a..2434b4bebc 100644 --- a/components/operations.js +++ b/components/operations.js @@ -384,6 +384,7 @@ async function deployComponent(req) { allowInstallScripts: req.install_allow_scripts, }; } + if (req.urlPath !== undefined) applicationConfig.urlPath = req.urlPath; await configUtils.addConfig(req.project, applicationConfig); } diff --git a/components/operationsValidation.js b/components/operationsValidation.js index d5d43190f9..0f72ed45f2 100644 --- a/components/operationsValidation.js +++ b/components/operationsValidation.js @@ -242,7 +242,15 @@ function deployComponentValidator(req) { install_timeout: Joi.number().optional(), install_allow_scripts: Joi.boolean().optional(), force: Joi.boolean().optional(), - }); + urlPath: Joi.string() + .min(1) + .custom((value, helpers) => { + if (value.includes('..')) return helpers.error('any.invalid'); + return value; + }) + .optional() + .messages({ 'any.invalid': 'urlPath must not contain ".."' }), + }).with('urlPath', 'package'); return validator.validateBySchema(req, deployProjSchema); } diff --git a/unitTests/server/fastifyRoutes/operationsValidation.test.js b/unitTests/server/fastifyRoutes/operationsValidation.test.js index cd4f376b6b..748e91cade 100644 --- a/unitTests/server/fastifyRoutes/operationsValidation.test.js +++ b/unitTests/server/fastifyRoutes/operationsValidation.test.js @@ -222,4 +222,43 @@ describe('Test operationsValidation module', () => { expect(result.message).to.equal('Project name can only contain alphanumeric, dash and underscores characters'); }); }); + + describe('Test deployComponentValidator function', () => { + it('accepts valid package-based deploy request', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: '@scope/pkg' }); + expect(result).to.be.undefined; + }); + + it('accepts urlPath alongside package', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: '@scope/pkg', urlPath: '/api' }); + expect(result).to.be.undefined; + }); + + it('rejects urlPath without package', () => { + const result = validator.deployComponentValidator({ project: 'my-app', urlPath: '/api' }); + expect(result).to.be.ok; + expect(result.message).to.include('urlPath'); + }); + + it('rejects urlPath containing ..', () => { + const result = validator.deployComponentValidator({ + project: 'my-app', + package: 'pkg', + urlPath: '../etc/passwd', + }); + expect(result).to.be.ok; + expect(result.message).to.include('urlPath'); + }); + + it('rejects empty urlPath', () => { + const result = validator.deployComponentValidator({ project: 'my-app', package: 'pkg', urlPath: '' }); + expect(result).to.be.ok; + }); + + it('rejects missing project', () => { + const result = validator.deployComponentValidator({ package: 'pkg' }); + expect(result).to.be.ok; + expect(result.message).to.include('project'); + }); + }); });