Initial commit: MinIO WebUI - Complete implementation

- Backend: Express.js API with MinIO CLI integration
- Frontend: React with Material-UI for non-technical users
- Features: Bucket management, user creation, storage monitoring
- Security: JWT auth, IP filtering, encrypted passwords
- Docker support for easy deployment
- Automated weekly storage reports
- Setup and deployment scripts included
This commit is contained in:
2025-07-22 16:29:53 +02:00
commit bb44b143ec
43 changed files with 6631 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
const authService = require('../services/auth.service');
const { logger, logAudit } = require('../utils/logger');
const { AppError } = require('./errorHandler.middleware');
const authMiddleware = async (req, res, next) => {
try {
// Get token from cookie or Authorization header
const token = req.cookies.token ||
req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new AppError('Authentication required', 401);
}
// Verify token
const decoded = authService.verifyToken(token);
// Check if IP matches (optional additional security)
if (decoded.ip && decoded.ip !== req.ip) {
logger.warn(`IP mismatch for token. Token IP: ${decoded.ip}, Request IP: ${req.ip}`);
logAudit('SUSPICIOUS_TOKEN_USE', {
ip: req.ip,
tokenIp: decoded.ip,
resource: req.originalUrl,
status: 'blocked',
});
throw new AppError('Invalid session', 401);
}
// Attach user info to request
req.user = decoded;
next();
} catch (error) {
if (error instanceof AppError) {
return res.status(error.statusCode).json({
error: 'Authentication Failed',
message: error.message,
});
}
logger.error('Auth middleware error:', error);
return res.status(401).json({
error: 'Authentication Failed',
message: 'Invalid or expired token',
});
}
};
// Optional middleware for routes that can work with or without auth
const optionalAuthMiddleware = async (req, res, next) => {
try {
const token = req.cookies.token ||
req.headers.authorization?.replace('Bearer ', '');
if (token) {
const decoded = authService.verifyToken(token);
req.user = decoded;
}
} catch (error) {
// Ignore errors for optional auth
logger.debug('Optional auth failed:', error.message);
}
next();
};
module.exports = {
authMiddleware,
optionalAuthMiddleware,
};
@@ -0,0 +1,67 @@
const { logger } = require('../utils/logger');
const config = require('../config');
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
}
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
logger.error({
error: err.message,
stack: err.stack,
url: req.originalUrl,
method: req.method,
ip: req.ip,
});
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = new AppError(message, 404);
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = new AppError(message, 400);
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message).join(', ');
error = new AppError(message, 400);
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
const message = 'Invalid token';
error = new AppError(message, 401);
}
if (err.name === 'TokenExpiredError') {
const message = 'Token expired';
error = new AppError(message, 401);
}
// Default error response
const statusCode = error.statusCode || 500;
const message = error.message || 'Internal Server Error';
res.status(statusCode).json({
error: true,
message,
...(config.app.env === 'development' && { stack: err.stack }),
});
};
module.exports = errorHandler;
module.exports.AppError = AppError;
@@ -0,0 +1,59 @@
const ipRangeCheck = require('ip-range-check');
const config = require('../config');
const { logger, logAudit } = require('../utils/logger');
const ipFilterMiddleware = (req, res, next) => {
// Skip IP filtering if disabled
if (!config.security.enableIpRestriction) {
return next();
}
// Skip for health check endpoint
if (req.path === '/health') {
return next();
}
// Get client IP
const clientIp = req.ip ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.headers['x-forwarded-for']?.split(',')[0];
// Normalize IPv6 localhost to IPv4
const normalizedIp = clientIp === '::1' ? '127.0.0.1' : clientIp;
try {
// Check if IP is in allowed list
const isAllowed = ipRangeCheck(normalizedIp, config.security.allowedIps);
if (isAllowed) {
return next();
}
// Log unauthorized access attempt
logger.warn(`Unauthorized access attempt from IP: ${normalizedIp}`);
logAudit('UNAUTHORIZED_ACCESS', {
ip: normalizedIp,
resource: req.originalUrl,
status: 'blocked',
details: {
method: req.method,
userAgent: req.headers['user-agent'],
},
});
return res.status(403).json({
error: 'Access Denied',
message: 'Your IP address is not authorized to access this resource.',
});
} catch (error) {
logger.error('Error in IP filter middleware:', error);
// In case of error, fail securely by denying access
return res.status(500).json({
error: 'Internal Server Error',
message: 'Unable to verify access permissions.',
});
}
};
module.exports = ipFilterMiddleware;