refactor(routes): Phase 3.4 - standardize browse.js
This commit is contained in:
@@ -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 || [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user