mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 10:49:35 +00:00
refactor(backend): use stacksRouter.param for stackName validation (#799)
* refactor(backend): use stacksRouter.param for stackName validation Registers a router-level param validator on :stackName so the 400 'Invalid stack name' guard runs once per route entry instead of being duplicated in every handler. Removes ~22 inline isValidStackName checks across the stacks router (deploy, down, env, files, services, update-preview, rollback, backup, etc.). Validation now fires before per-handler tier and permission checks, which matches the standard input-validate-first pattern. The body-field validators in POST / and POST /from-git remain inline because they operate on req.body, not the route param. Closes #752 * fix(stacks): remove unused stackName local in upload multer wrapper The multer middleware wrapper for POST /:stackName/files/upload no longer needs a local stackName binding now that param-level validation handles the check. Removes the stale assignment that ESLint flagged and corrects the leftover indentation on the requirePaid line.
This commit is contained in:
@@ -112,6 +112,14 @@ function getRelPath(req: Request): string {
|
||||
|
||||
export const stacksRouter = Router();
|
||||
|
||||
stacksRouter.param('stackName', (req, res, next, stackName) => {
|
||||
if (typeof stackName !== 'string' || !isValidStackName(stackName)) {
|
||||
res.status(400).json({ error: 'Invalid stack name' });
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
stacksRouter.get('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stacks = await FileSystemService.getInstance(req.nodeId).getStacks();
|
||||
@@ -159,10 +167,6 @@ stacksRouter.get('/auto-update-settings', (req: Request, res: Response): void =>
|
||||
stacksRouter.get('/:stackName/auto-update', (req: Request, res: Response): void => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
res.status(400).json({ error: 'Invalid stack name' });
|
||||
return;
|
||||
}
|
||||
const enabled = DatabaseService.getInstance().getStackAutoUpdateEnabled(req.nodeId, stackName);
|
||||
res.json({ enabled });
|
||||
} catch (error) {
|
||||
@@ -176,10 +180,6 @@ stacksRouter.put('/:stackName/auto-update', (req: Request, res: Response): void
|
||||
if (!requireAdmin(req, res)) return;
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
res.status(400).json({ error: 'Invalid stack name' });
|
||||
return;
|
||||
}
|
||||
const { enabled } = req.body as { enabled?: unknown };
|
||||
if (typeof enabled !== 'boolean') {
|
||||
res.status(400).json({ error: '"enabled" must be a boolean' });
|
||||
@@ -204,9 +204,6 @@ stacksRouter.put('/:stackName/auto-update', (req: Request, res: Response): void
|
||||
stacksRouter.get('/:stackName', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const content = await FileSystemService.getInstance(req.nodeId).getStackContent(stackName);
|
||||
res.send(content);
|
||||
} catch (error) {
|
||||
@@ -217,9 +214,6 @@ stacksRouter.get('/:stackName', async (req: Request, res: Response) => {
|
||||
stacksRouter.put('/:stackName', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:edit', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
const { content } = req.body;
|
||||
if (typeof content !== 'string') {
|
||||
@@ -239,9 +233,6 @@ stacksRouter.put('/:stackName', async (req: Request, res: Response) => {
|
||||
stacksRouter.get('/:stackName/envs', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const envPaths = await resolveAllEnvFilePaths(req.nodeId, stackName);
|
||||
res.json({ envFiles: envPaths });
|
||||
} catch (error) {
|
||||
@@ -252,9 +243,6 @@ stacksRouter.get('/:stackName/envs', async (req: Request, res: Response) => {
|
||||
stacksRouter.get('/:stackName/env', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const requestedFile = req.query.file as string | undefined;
|
||||
const envPaths = await resolveAllEnvFilePaths(req.nodeId, stackName);
|
||||
|
||||
@@ -320,9 +308,6 @@ stacksRouter.get('/:stackName/env', async (req: Request, res: Response) => {
|
||||
stacksRouter.put('/:stackName/env', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:edit', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
const { content } = req.body;
|
||||
if (typeof content !== 'string') {
|
||||
@@ -523,9 +508,6 @@ stacksRouter.post('/from-git', async (req: Request, res: Response) => {
|
||||
stacksRouter.delete('/:stackName', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:delete', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const pruneVolumes = req.query.pruneVolumes === 'true';
|
||||
try {
|
||||
try {
|
||||
@@ -571,9 +553,6 @@ stacksRouter.delete('/:stackName', async (req: Request, res: Response) => {
|
||||
stacksRouter.get('/:stackName/containers', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const dockerController = DockerController.getInstance(req.nodeId);
|
||||
const containers = await dockerController.getContainersByStack(stackName);
|
||||
res.json(containers);
|
||||
@@ -585,9 +564,6 @@ stacksRouter.get('/:stackName/containers', async (req: Request, res: Response) =
|
||||
stacksRouter.get('/:stackName/services', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const content = await FileSystemService.getInstance(req.nodeId).getStackContent(stackName);
|
||||
const parsed = YAML.parse(content);
|
||||
const services = parsed?.services ? Object.keys(parsed.services) : [];
|
||||
@@ -601,9 +577,6 @@ stacksRouter.get('/:stackName/services', async (req: Request, res: Response) =>
|
||||
stacksRouter.post('/:stackName/deploy', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:deploy', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
if (!(await runPolicyGate(req, res, stackName, req.nodeId))) return;
|
||||
const debug = isDebugEnabled();
|
||||
@@ -631,9 +604,6 @@ stacksRouter.post('/:stackName/deploy', async (req: Request, res: Response) => {
|
||||
stacksRouter.post('/:stackName/down', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:deploy', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
await ComposeService.getInstance(req.nodeId).runCommand(stackName, 'down', getTerminalWs());
|
||||
invalidateNodeCaches(req.nodeId);
|
||||
@@ -655,10 +625,6 @@ async function bulkContainerOp(
|
||||
): Promise<void> {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:deploy', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
res.status(400).json({ error: 'Invalid stack name' });
|
||||
return;
|
||||
}
|
||||
const titleCase = action.charAt(0).toUpperCase() + action.slice(1);
|
||||
try {
|
||||
const dockerController = DockerController.getInstance(req.nodeId);
|
||||
@@ -702,10 +668,6 @@ async function handleServiceAction(
|
||||
const stackName = req.params.stackName as string;
|
||||
const serviceName = req.params.serviceName as string;
|
||||
if (!requirePermission(req, res, 'stack:deploy', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
res.status(400).json({ error: 'Invalid stack name' });
|
||||
return;
|
||||
}
|
||||
if (!isValidServiceName(serviceName)) {
|
||||
res.status(400).json({ error: 'Invalid service name' });
|
||||
return;
|
||||
@@ -753,9 +715,6 @@ stacksRouter.post('/:stackName/services/:serviceName/start', (req, res) =>
|
||||
|
||||
stacksRouter.get('/:stackName/update-preview', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
const preview = await UpdatePreviewService.getInstance().getPreview(req.nodeId, stackName);
|
||||
res.json(preview);
|
||||
@@ -768,9 +727,6 @@ stacksRouter.get('/:stackName/update-preview', async (req: Request, res: Respons
|
||||
stacksRouter.post('/:stackName/update', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:deploy', 'stack', stackName)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
if (!(await runPolicyGate(req, res, stackName, req.nodeId))) return;
|
||||
const debug = isDebugEnabled();
|
||||
@@ -799,9 +755,6 @@ stacksRouter.post('/:stackName/rollback', async (req: Request, res: Response) =>
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!requirePermission(req, res, 'stack:deploy', 'stack', stackName)) return;
|
||||
if (!requirePaid(req, res)) return;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
try {
|
||||
const fsSvc = FileSystemService.getInstance(req.nodeId);
|
||||
const backupInfo = await fsSvc.getBackupInfo(stackName);
|
||||
@@ -824,9 +777,6 @@ stacksRouter.post('/:stackName/rollback', async (req: Request, res: Response) =>
|
||||
stacksRouter.get('/:stackName/backup', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) {
|
||||
return res.status(400).json({ error: 'Invalid stack name' });
|
||||
}
|
||||
const fsSvc = FileSystemService.getInstance(req.nodeId);
|
||||
const info = await fsSvc.getBackupInfo(stackName);
|
||||
res.json(info);
|
||||
@@ -866,7 +816,6 @@ function sendFsError(
|
||||
|
||||
stacksRouter.get('/:stackName/files', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
const relPath = getRelPath(req);
|
||||
if (relPath !== '' && !isValidRelativeStackPath(relPath)) {
|
||||
return res.status(400).json({ error: 'Invalid path', code: 'INVALID_PATH' });
|
||||
@@ -881,7 +830,6 @@ stacksRouter.get('/:stackName/files', async (req: Request, res: Response) => {
|
||||
|
||||
stacksRouter.get('/:stackName/files/content', async (req: Request, res: Response) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
const relPath = getRelPath(req);
|
||||
if (!relPath) return res.status(400).json({ error: 'path query parameter is required', code: 'INVALID_PATH' });
|
||||
if (!isValidRelativeStackPath(relPath)) {
|
||||
@@ -898,7 +846,6 @@ stacksRouter.get('/:stackName/files/content', async (req: Request, res: Response
|
||||
stacksRouter.get('/:stackName/files/download', async (req: Request, res: Response) => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
const relPath = getRelPath(req);
|
||||
if (relPath !== '' && !isValidRelativeStackPath(relPath)) {
|
||||
return res.status(400).json({ error: 'Invalid path', code: 'INVALID_PATH' });
|
||||
@@ -926,8 +873,6 @@ stacksRouter.get('/:stackName/files/download', async (req: Request, res: Respons
|
||||
stacksRouter.post(
|
||||
'/:stackName/files/upload',
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
if (!requirePaid(req, res)) return;
|
||||
upload.single('file')(req, res, (err) => {
|
||||
if (err && (err as multer.MulterError).code === 'LIMIT_FILE_SIZE') {
|
||||
@@ -964,7 +909,6 @@ stacksRouter.post(
|
||||
stacksRouter.put('/:stackName/files/content', async (req: Request, res: Response) => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
if (!requirePermission(req, res, 'stack:edit', 'stack', stackName)) return;
|
||||
const relPath = getRelPath(req);
|
||||
if (relPath !== '' && !isValidRelativeStackPath(relPath)) {
|
||||
@@ -985,7 +929,6 @@ stacksRouter.put('/:stackName/files/content', async (req: Request, res: Response
|
||||
stacksRouter.delete('/:stackName/files', async (req: Request, res: Response) => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
if (!requirePermission(req, res, 'stack:edit', 'stack', stackName)) return;
|
||||
const relPath = getRelPath(req);
|
||||
if (relPath === '') return res.status(400).json({ error: 'Path is required for delete' });
|
||||
@@ -1004,7 +947,6 @@ stacksRouter.delete('/:stackName/files', async (req: Request, res: Response) =>
|
||||
stacksRouter.post('/:stackName/files/folder', async (req: Request, res: Response) => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
const stackName = req.params.stackName as string;
|
||||
if (!isValidStackName(stackName)) return res.status(400).json({ error: 'Invalid stack name' });
|
||||
if (!requirePermission(req, res, 'stack:edit', 'stack', stackName)) return;
|
||||
const relPath = getRelPath(req);
|
||||
if (relPath === '') return res.status(400).json({ error: 'Path is required to create a folder' });
|
||||
|
||||
Reference in New Issue
Block a user