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:
@@ -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;
|
||||
Reference in New Issue
Block a user