77 * - POST /file-manager - Upload file (Admin, Reviewer, Editor only)
88 * - GET /file-manager - List all files (All authenticated users)
99 * - GET /file-manager/:id - Download file (All authenticated users)
10+ * - DELETE /file-manager/:id - Delete file (Admin, Reviewer, Editor only)
1011 *
1112 * Access Control:
1213 * - All routes require JWT authentication
13- * - Upload restricted to Admin, Reviewer, Editor (enforced by authorize middleware)
14+ * - Upload and Delete restricted to Admin, Reviewer, Editor (enforced by authorize middleware)
1415 * - List and Download available to all authenticated users
1516 *
1617 * @module routes/fileManager
1718 */
1819
1920import express , { Request , Response , NextFunction } from "express" ;
20- import { uploadFile , listFiles , downloadFile } from "../controllers/fileManager.ctrl" ;
21+ import { uploadFile , listFiles , downloadFile , removeFile } from "../controllers/fileManager.ctrl" ;
2122import authenticateJWT from "../middleware/auth.middleware" ;
2223import authorize from "../middleware/accessControl.middleware" ;
24+ import { fileOperationsLimiter } from "../middleware/rateLimit.middleware" ;
2325import multer from "multer" ;
2426import { STATUS_CODE } from "../utils/statusCode.utils" ;
2527import * as path from "path" ;
2628import * as fs from "fs" ;
2729import { ALLOWED_MIME_TYPES } from "../utils/validations/fileManagerValidation.utils" ;
30+ import logger from "../utils/logger/fileLogger" ;
2831
2932const router = express . Router ( ) ;
3033
@@ -81,12 +84,34 @@ const upload = multer({
8184 * Catches file size limit errors and file type rejection errors
8285 */
8386const handleMulterError = ( err : any , req : Request , res : Response , next : NextFunction ) => {
84- // Clean up temporary file if it exists
87+ // Clean up temporary file if it exists (async, non-blocking)
8588 if ( req . file ?. path ) {
89+ // Secure containment validation using realpathSync to resolve symlinks
90+ let resolvedPath : string ;
91+ let resolvedTempDir : string ;
92+
8693 try {
87- fs . unlinkSync ( req . file . path ) ;
88- } catch ( cleanupError ) {
89- console . error ( "Failed to clean up temporary file:" , cleanupError ) ;
94+ // Resolve real paths (follows symlinks) to prevent directory traversal via symlinks
95+ resolvedTempDir = fs . realpathSync ( tempDir ) ;
96+ resolvedPath = fs . realpathSync ( req . file . path ) ;
97+
98+ // Only clean up if the file is strictly within the temp directory
99+ if ( resolvedPath . startsWith ( resolvedTempDir + path . sep ) ) {
100+ // Fire-and-forget async cleanup to avoid blocking
101+ fs . promises . unlink ( resolvedPath ) . catch ( ( cleanupError ) => {
102+ // Ignore ENOENT (file already deleted), but log other errors
103+ if ( cleanupError . code !== 'ENOENT' ) {
104+ logger . error ( "Failed to clean up temporary file:" , cleanupError ) ;
105+ }
106+ } ) ;
107+ } else {
108+ // Log security violation attempt
109+ logger . warn ( `Security: Blocked cleanup attempt outside temp directory. Path: ${ resolvedPath } , Allowed: ${ resolvedTempDir } ` ) ;
110+ }
111+ } catch ( e ) {
112+ // Unable to resolve file/directory (file may not exist), skip cleanup and continue
113+ logger . warn ( `Failed to resolve path for cleanup: ${ req . file . path } ` , e ) ;
114+ // Do not return - continue to error handling below
90115 }
91116 }
92117
@@ -121,10 +146,12 @@ const handleMulterError = (err: any, req: Request, res: Response, next: NextFunc
121146 * @returns {403 } Access denied (unauthorized role)
122147 * @returns {413 } File size exceeds maximum allowed size
123148 * @returns {415 } Unsupported file type
149+ * @returns {429 } Too many requests - rate limit exceeded
124150 * @returns {500 } Server error
125151 */
126152router . post (
127153 "/" ,
154+ fileOperationsLimiter ,
128155 authenticateJWT ,
129156 authorize ( [ "Admin" , "Reviewer" , "Editor" ] ) ,
130157 upload . single ( "file" ) ,
@@ -139,9 +166,10 @@ router.post(
139166 * @query page - Page number (optional)
140167 * @query pageSize - Items per page (optional)
141168 * @returns {200 } List of files with metadata and pagination
169+ * @returns {429 } Too many requests - rate limit exceeded
142170 * @returns {500 } Server error
143171 */
144- router . get ( "/" , authenticateJWT , listFiles ) ;
172+ router . get ( "/" , fileOperationsLimiter , authenticateJWT , listFiles ) ;
145173
146174/**
147175 * @route GET /file-manager/:id
@@ -151,8 +179,28 @@ router.get("/", authenticateJWT, listFiles);
151179 * @returns {200 } File content with download headers
152180 * @returns {403 } Access denied (file from different organization)
153181 * @returns {404 } File not found
182+ * @returns {429 } Too many requests - rate limit exceeded
154183 * @returns {500 } Server error
155184 */
156- router . get ( "/:id" , authenticateJWT , downloadFile ) ;
185+ router . get ( "/:id" , fileOperationsLimiter , authenticateJWT , downloadFile ) ;
186+
187+ /**
188+ * @route DELETE /file-manager/:id
189+ * @desc Delete a file by ID
190+ * @access Admin, Reviewer, Editor only
191+ * @param id - File ID
192+ * @returns {200 } File deleted successfully
193+ * @returns {403 } Access denied (file from different organization or unauthorized role)
194+ * @returns {404 } File not found
195+ * @returns {429 } Too many requests - rate limit exceeded
196+ * @returns {500 } Server error
197+ */
198+ router . delete (
199+ "/:id" ,
200+ fileOperationsLimiter ,
201+ authenticateJWT ,
202+ authorize ( [ "Admin" , "Reviewer" , "Editor" ] ) ,
203+ removeFile
204+ ) ;
157205
158206export default router ;
0 commit comments