From 8b1492142fc7cef1cb755bb463efb2ee44eb81ec Mon Sep 17 00:00:00 2001 From: Krystie Date: Sun, 29 Mar 2026 20:13:25 -0700 Subject: [PATCH] refactor(routes): Phase 3.4 - standardize browse.js --- dashcaddy-api/routes/browse.js | 28 +++++++++++++++++++--------- dashcaddy-api/src/app.js | 7 ++++++- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/dashcaddy-api/routes/browse.js b/dashcaddy-api/routes/browse.js index 02262a5..ff7caa7 100644 --- a/dashcaddy-api/routes/browse.js +++ b/dashcaddy-api/routes/browse.js @@ -4,8 +4,18 @@ const fsp = require('fs').promises; const path = require('path'); const { exists, isAccessible } = require('../fs-helpers'); const { paginate, parsePaginationParams } = require('../pagination'); +const { ValidationError } = require('../errors'); -module.exports = function(ctx) { +/** + * Browse route factory + * @param {Object} deps - Explicit dependencies + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Function} deps.validateSecurePath - Path traversal validator + * @param {Object} deps.auditLogger - Audit logger + * @param {Object} deps.docker - Docker client + * @returns {express.Router} + */ +module.exports = function({ asyncHandler, validateSecurePath, auditLogger, docker }) { const router = express.Router(); // Parse browse roots from environment @@ -20,7 +30,7 @@ module.exports = function(ctx) { }); // Get available browse roots - router.get('/browse/roots', ctx.asyncHandler(async (req, res) => { + router.get('/browse/roots', asyncHandler(async (req, res) => { const allRoots = BROWSE_ROOTS.map(r => ({ name: r.hostPath, path: r.hostPath, @@ -38,7 +48,7 @@ module.exports = function(ctx) { }, 'browse-roots')); // Browse directory contents - router.get('/browse/directories', ctx.asyncHandler(async (req, res) => { + router.get('/browse/directories', asyncHandler(async (req, res) => { const requestedPath = req.query.path || ''; if (!requestedPath) { @@ -62,7 +72,7 @@ module.exports = function(ctx) { ); if (!matchingRoot) { - return ctx.errorResponse(res, 400, 'Path not in browseable roots', { + throw new ValidationError('Path not in browseable roots', { availableRoots: BROWSE_ROOTS.map(r => r.hostPath) }); } @@ -73,10 +83,10 @@ module.exports = function(ctx) { const allowedRoots = BROWSE_ROOTS.map(r => r.containerPath); let resolvedPath; try { - resolvedPath = await ctx.validateSecurePath(containerFullPath, allowedRoots, ctx.auditLogger); + resolvedPath = await validateSecurePath(containerFullPath, allowedRoots, auditLogger); } catch (error) { if (error.constructor.name === 'ValidationError') { - ctx.auditLogger.logSecurityEvent('path_traversal_attempt', { + auditLogger.logSecurityEvent('path_traversal_attempt', { requestedPath, containerFullPath, allowedRoots, error: error.message, ip: req.ip, @@ -124,7 +134,7 @@ module.exports = function(ctx) { }, 'browse-dir')); // Detect media mounts from existing media server containers - router.get('/media/detected-mounts', ctx.asyncHandler(async (req, res) => { + router.get('/media/detected-mounts', asyncHandler(async (req, res) => { const mediaServerPatterns = [ 'plex', 'jellyfin', 'emby', 'kodi', 'navidrome', 'airsonic', 'subsonic', 'funkwhale', 'beets', 'lidarr', 'sonarr', 'radarr', @@ -136,7 +146,7 @@ module.exports = function(ctx) { '/tmp', '/var', '/etc', '/opt', '/root', '/home', '/.', '/caddyfile' ]; - const containers = await ctx.docker.client.listContainers({ all: false }); + const containers = await docker.client.listContainers({ all: false }); const detectedMounts = []; const seenPaths = new Set(); @@ -145,7 +155,7 @@ module.exports = function(ctx) { const isMediaServer = mediaServerPatterns.some(p => imageName.includes(p)); if (!isMediaServer) continue; - const container = ctx.docker.client.getContainer(containerInfo.Id); + const container = docker.client.getContainer(containerInfo.Id); const details = await container.inspect(); const binds = details.HostConfig?.Binds || []; diff --git a/dashcaddy-api/src/app.js b/dashcaddy-api/src/app.js index 5ca9d0e..0ad2473 100644 --- a/dashcaddy-api/src/app.js +++ b/dashcaddy-api/src/app.js @@ -371,7 +371,12 @@ async function createApp() { asyncHandler: ctx.asyncHandler })); apiRouter.use('/ca', caRoutes(ctx)); - apiRouter.use(browseRoutes(ctx)); + apiRouter.use(browseRoutes({ + asyncHandler: ctx.asyncHandler, + validateSecurePath: ctx.validateSecurePath, + auditLogger: ctx.auditLogger, + docker: ctx.docker + })); apiRouter.use(errorLogsRoutes({ ERROR_LOG_FILE: ctx.ERROR_LOG_FILE, auditLogger: ctx.auditLogger,