mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
Storage & auth security fix
This commit is contained in:
@@ -19,7 +19,6 @@
|
||||
"db:migrate:dev": "ts-node-dev src/database/migrate.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"@aws-sdk/client-s3": "^3.844.0",
|
||||
"@aws-sdk/lib-storage": "^3.844.0",
|
||||
"@azure/msal-node": "^3.6.3",
|
||||
@@ -31,8 +30,10 @@
|
||||
"cross-fetch": "^4.1.0",
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
"dotenv": "^17.2.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"express-validator": "^7.2.1",
|
||||
"google-auth-library": "^10.1.0",
|
||||
"googleapis": "^152.0.0",
|
||||
|
||||
@@ -1,28 +1,46 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { StorageService } from '../../services/StorageService';
|
||||
import * as path from 'path';
|
||||
import { storage as storageConfig } from '../../config/storage';
|
||||
|
||||
export class StorageController {
|
||||
constructor(private storageService: StorageService) { }
|
||||
|
||||
public downloadFile = async (req: Request, res: Response): Promise<void> => {
|
||||
const filePath = req.query.path as string;
|
||||
const unsafePath = req.query.path as string;
|
||||
|
||||
if (!filePath) {
|
||||
if (!unsafePath) {
|
||||
res.status(400).send('File path is required');
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize the path to prevent directory traversal
|
||||
const normalizedPath = path.normalize(unsafePath).replace(/^(\.\.(\/|\\|$))+/, '');
|
||||
|
||||
// Determine the base path from storage configuration
|
||||
const basePath = storageConfig.type === 'local' ? storageConfig.rootPath : '/';
|
||||
|
||||
// Resolve the full path and ensure it's within the storage directory
|
||||
const fullPath = path.join(basePath, normalizedPath);
|
||||
|
||||
if (!fullPath.startsWith(basePath)) {
|
||||
res.status(400).send('Invalid file path');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the sanitized, relative path for storage service operations
|
||||
const safePath = path.relative(basePath, fullPath);
|
||||
|
||||
try {
|
||||
const fileExists = await this.storageService.exists(filePath);
|
||||
const fileExists = await this.storageService.exists(safePath);
|
||||
if (!fileExists) {
|
||||
console.log(filePath);
|
||||
res.status(404).send('File not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileStream = await this.storageService.get(filePath);
|
||||
const fileName = filePath.split('/').pop();
|
||||
res.setHeader('Content-Disposition', `attachment; filename=${fileName}`);
|
||||
const fileStream = await this.storageService.get(safePath);
|
||||
const fileName = path.basename(safePath);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
|
||||
10
packages/backend/src/api/middleware/rateLimiter.ts
Normal file
10
packages/backend/src/api/middleware/rateLimiter.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import rateLimit from 'express-rate-limit';
|
||||
|
||||
// Rate limiter to prevent brute-force attacks on the login endpoint
|
||||
export const loginRateLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 10, // Limit each IP to 10 login requests per windowMs
|
||||
message: 'Too many login attempts from this IP, please try again after 15 minutes',
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Router } from 'express';
|
||||
import { loginRateLimiter } from '../middleware/rateLimiter';
|
||||
import type { AuthController } from '../controllers/auth.controller';
|
||||
|
||||
export const createAuthRouter = (authController: AuthController): Router => {
|
||||
@@ -9,7 +10,7 @@ export const createAuthRouter = (authController: AuthController): Router => {
|
||||
* @description Authenticates a user and returns a JWT.
|
||||
* @access Public
|
||||
*/
|
||||
router.post('/login', authController.login);
|
||||
router.post('/login', loginRateLimiter, authController.login);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user