Validate project filename (#1046)

This commit is contained in:
Alex Christoffer Rasmussen
2024-06-06 22:37:41 +02:00
committed by GitHub
parent edb48a2fc7
commit c2fa115946
7 changed files with 174 additions and 107 deletions
+8 -21
View File
@@ -16,7 +16,6 @@ import { failEmptyObjects } from '../../utils/routerUtils.js';
import { resolveDbDirectory, resolveProjectsDirectory } from '../../setup/index.js';
import * as projectService from '../../services/project-service/ProjectService.js';
import { ensureJsonExtension } from '../../utils/fileManagement.js';
import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js';
import { appStateService } from '../../services/app-state-service/AppStateService.js';
import { oscIntegration } from '../../services/integration-service/OscIntegration.js';
@@ -54,8 +53,7 @@ export async function patchPartialProjectFile(req: Request, res: Response<Databa
*/
export async function createProjectFile(req: Request, res: Response<{ filename: string } | ErrorResponse>) {
try {
const originalFilename = ensureJsonExtension(req.body.title || 'Untitled');
const filename = generateUniqueFileName(resolveProjectsDirectory, originalFilename);
const filename = generateUniqueFileName(resolveProjectsDirectory, req.body.filename);
const errors = projectService.validateProjectFiles({ newFilename: filename });
if (errors.length) {
@@ -71,7 +69,7 @@ export async function createProjectFile(req: Request, res: Response<{ filename:
backstageInfo: req.body?.backstageInfo ?? '',
};
projectService.createProjectFile(filename, newProjectData);
await projectService.createProjectFile(filename, newProjectData);
res.status(200).send({
filename,
@@ -83,29 +81,18 @@ export async function createProjectFile(req: Request, res: Response<{ filename:
}
/**
* Utility function finds the correct project file to download
*/
function selectProjectFile(fileName?: string) {
const projectsDirectory = resolveDbDirectory;
const fileToDownload = fileName ? ensureJsonExtension(fileName) : projectService.getProjectTitle();
const pathToFile = join(projectsDirectory, fileToDownload);
return { pathToFile, name: fileToDownload };
}
/**
* Allows downloading of a optionally given project files
* If no {filename} is provided, loaded file will be served
* Allows downloading of project files
*/
export async function projectDownload(req: Request, res: Response) {
const { pathToFile, name } = selectProjectFile(req.body?.fileName);
const { filename } = req.body;
const pathToFile = join(resolveDbDirectory, filename);
// Check if the file exists before attempting to download
if (!existsSync(pathToFile)) {
return res.status(404).send({ message: `Project ${name} not found.` });
return res.status(404).send({ message: `Project ${filename} not found.` });
}
res.download(pathToFile, name, (error) => {
res.download(pathToFile, filename, (error) => {
if (error) {
const message = getErrorMessage(error);
res.status(500).send({ message });
@@ -228,7 +215,7 @@ export async function duplicateProjectFile(req: Request, res: Response<MessageRe
*/
export async function renameProjectFile(req: Request, res: Response<MessageResponse | ErrorResponse>) {
try {
const { newFilename } = req.body;
const { filename: newFilename } = req.body;
const { filename } = req.params;
const errors = projectService.validateProjectFiles({ filename, newFilename });
+11 -14
View File
@@ -14,28 +14,25 @@ import {
} from './db.controller.js';
import { uploadProjectFile } from './db.middleware.js';
import {
projectSanitiser,
sanitizeProjectFilename,
validateDownloadProject,
validateLoadProjectFile,
validatePatchProjectFile,
validateProjectDuplicate,
validateProjectRename,
validateNewProject,
validatePatchProject,
validateFilenameBody,
validateFilenameParam,
} from './db.validation.js';
export const router = express.Router();
router.post('/download', validateDownloadProject, projectDownload);
router.post('/download', validateFilenameBody, projectDownload);
router.post('/upload', uploadProjectFile, postProjectFile);
router.patch('/', validatePatchProjectFile, patchPartialProjectFile);
router.post('/new', projectSanitiser, createProjectFile);
router.patch('/', validatePatchProject, patchPartialProjectFile);
router.post('/new', validateFilenameBody, validateNewProject, createProjectFile);
router.get('/all', listProjects);
router.post('/load', validateLoadProjectFile, sanitizeProjectFilename, loadProject);
router.post('/:filename/duplicate', validateProjectDuplicate, sanitizeProjectFilename, duplicateProjectFile);
router.put('/:filename/rename', validateProjectRename, sanitizeProjectFilename, renameProjectFile);
router.delete('/:filename', sanitizeProjectFilename, deleteProjectFile);
router.post('/load', validateFilenameBody, loadProject);
router.post('/:filename/duplicate', validateFilenameParam, validateFilenameBody, duplicateProjectFile);
router.put('/:filename/rename', validateFilenameParam, validateFilenameBody, renameProjectFile);
router.delete('/:filename', validateFilenameParam, deleteProjectFile);
router.get('/info', getInfo);
+28 -60
View File
@@ -1,9 +1,12 @@
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
import { body, param, validationResult } from 'express-validator';
import { ensureJsonExtension } from '../../utils/fileManagement.js';
import sanitize from 'sanitize-filename';
export const projectSanitiser = [
/**
* @description Validates request for a new project.
*/
export const validateNewProject = [
body('title').optional().isString().trim(),
body('description').optional().isString().trim(),
body('publicUrl').optional().isString().trim(),
@@ -19,18 +22,10 @@ export const projectSanitiser = [
},
];
export const sanitizeProjectFilename = (req: Request, _res: Response, next: NextFunction) => {
const { filename, newFilename } = req.body;
const { filename: projectName } = req.params;
req.body.filename = ensureJsonExtension(filename);
req.body.newFilename = ensureJsonExtension(newFilename);
req.params.filename = ensureJsonExtension(projectName);
next();
};
export const validatePatchProjectFile = [
/**
* @description Validates request for pathing data in the project.
*/
export const validatePatchProject = [
body('rundown').isArray().optional({ nullable: false }),
body('project').isObject().optional({ nullable: false }),
body('settings').isObject().optional({ nullable: false }),
@@ -47,31 +42,18 @@ export const validatePatchProjectFile = [
];
/**
* @description Validates the filename for loading a project file.
* @description Validates request with filename in the body.
*/
export const validateLoadProjectFile = [
body('filename').exists().withMessage('Filename is required').isString().withMessage('Filename must be a string'),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
];
/**
* @description Validates the filenames for duplicating a project.
*/
export const validateProjectDuplicate = [
body('newFilename')
export const validateFilenameBody = [
body('filename')
.exists()
.withMessage('New project filename is required')
.isString()
.withMessage('New project filename must be a string')
.isLength({ min: 1, max: 255 })
.withMessage('New project filename must be between 1 and 255 characters'),
.trim()
.customSanitizer((input: string) => sanitize(input))
.withMessage('Failed to sanitize the filename')
.notEmpty()
.withMessage('Filename was empty or contained only invalid characters')
.customSanitizer((input: string) => ensureJsonExtension(input)),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
@@ -84,32 +66,18 @@ export const validateProjectDuplicate = [
];
/**
* @description Validates the filenames for renaming a project.
* @description Validates request with filename in the params.
*/
export const validateProjectRename = [
body('newFilename')
export const validateFilenameParam = [
param('filename')
.exists()
.withMessage('Duplicate project filename is required')
.isString()
.withMessage('Duplicate project filename must be a string')
.isLength({ min: 1, max: 255 })
.withMessage('Duplicate project filename must be between 1 and 255 characters'),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
];
/**
* @description Validates a download request which can include an optional project name.
*/
export const validateDownloadProject = [
body('fileName').isString().optional(),
.trim()
.customSanitizer((input: string) => sanitize(input))
.withMessage('Failed to sanitize the filename')
.notEmpty()
.withMessage('Filename was empty or contained only invalid characters')
.customSanitizer((input: string) => ensureJsonExtension(input)),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);