bb44b143ec
- 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
193 lines
4.8 KiB
JavaScript
193 lines
4.8 KiB
JavaScript
const express = require('express');
|
|
const { body, param, validationResult } = require('express-validator');
|
|
const MinIOService = require('../../services/minio.service');
|
|
const { authMiddleware } = require('../../middleware/auth.middleware');
|
|
const { logger, logAudit } = require('../../utils/logger');
|
|
const { AppError } = require('../../middleware/errorHandler.middleware');
|
|
|
|
const router = express.Router();
|
|
const minioService = new MinIOService();
|
|
|
|
// Apply auth middleware to all routes
|
|
router.use(authMiddleware);
|
|
|
|
// Validation rules
|
|
const userValidation = {
|
|
username: body('username')
|
|
.trim()
|
|
.notEmpty().withMessage('Username is required')
|
|
.matches(/^[a-zA-Z0-9_-]+$/).withMessage('Invalid username format')
|
|
.isLength({ min: 3, max: 32 }).withMessage('Username must be 3-32 characters'),
|
|
|
|
password: body('password')
|
|
.notEmpty().withMessage('Password is required')
|
|
.isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
|
|
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).withMessage('Password must contain uppercase, lowercase, and number'),
|
|
|
|
usernameParam: param('username')
|
|
.trim()
|
|
.notEmpty().withMessage('Username is required')
|
|
.matches(/^[a-zA-Z0-9_-]+$/).withMessage('Invalid username format'),
|
|
};
|
|
|
|
// Handle validation errors
|
|
const handleValidationErrors = (req, res, next) => {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
error: 'Validation Error',
|
|
errors: errors.array(),
|
|
});
|
|
}
|
|
next();
|
|
};
|
|
|
|
// GET /api/users
|
|
router.get('/', async (req, res, next) => {
|
|
try {
|
|
const users = await minioService.listUsers();
|
|
|
|
res.json({
|
|
users,
|
|
count: users.length,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// POST /api/users
|
|
router.post('/',
|
|
[userValidation.username, userValidation.password],
|
|
handleValidationErrors,
|
|
async (req, res, next) => {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
// Create user without bucket (admin can assign policies later)
|
|
await minioService.executeCommand(
|
|
`mc admin user add ${minioService.alias} ${username} ${password}`
|
|
);
|
|
|
|
logAudit('USER_CREATE', {
|
|
userId: req.user.role,
|
|
ip: req.ip,
|
|
resource: username,
|
|
status: 'success',
|
|
});
|
|
|
|
res.status(201).json({
|
|
message: 'User created successfully',
|
|
username,
|
|
});
|
|
} catch (error) {
|
|
logAudit('USER_CREATE', {
|
|
userId: req.user.role,
|
|
ip: req.ip,
|
|
resource: req.body.username,
|
|
status: 'failed',
|
|
details: { error: error.message },
|
|
});
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// DELETE /api/users/:username
|
|
router.delete('/:username',
|
|
userValidation.usernameParam,
|
|
handleValidationErrors,
|
|
async (req, res, next) => {
|
|
try {
|
|
const { username } = req.params;
|
|
|
|
const result = await minioService.removeUser(username);
|
|
|
|
logAudit('USER_DELETE', {
|
|
userId: req.user.role,
|
|
ip: req.ip,
|
|
resource: username,
|
|
status: 'success',
|
|
});
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logAudit('USER_DELETE', {
|
|
userId: req.user.role,
|
|
ip: req.ip,
|
|
resource: req.params.username,
|
|
status: 'failed',
|
|
details: { error: error.message },
|
|
});
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// PUT /api/users/:username/status
|
|
router.put('/:username/status',
|
|
[
|
|
userValidation.usernameParam,
|
|
body('status').isIn(['enabled', 'disabled']).withMessage('Status must be enabled or disabled'),
|
|
],
|
|
handleValidationErrors,
|
|
async (req, res, next) => {
|
|
try {
|
|
const { username } = req.params;
|
|
const { status } = req.body;
|
|
|
|
let result;
|
|
if (status === 'enabled') {
|
|
result = await minioService.enableUser(username);
|
|
} else {
|
|
result = await minioService.disableUser(username);
|
|
}
|
|
|
|
logAudit('USER_STATUS_CHANGE', {
|
|
userId: req.user.role,
|
|
ip: req.ip,
|
|
resource: username,
|
|
status: 'success',
|
|
details: { newStatus: status },
|
|
});
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logAudit('USER_STATUS_CHANGE', {
|
|
userId: req.user.role,
|
|
ip: req.ip,
|
|
resource: req.params.username,
|
|
status: 'failed',
|
|
details: {
|
|
error: error.message,
|
|
requestedStatus: req.body.status,
|
|
},
|
|
});
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// GET /api/users/:username
|
|
router.get('/:username',
|
|
userValidation.usernameParam,
|
|
handleValidationErrors,
|
|
async (req, res, next) => {
|
|
try {
|
|
const { username } = req.params;
|
|
|
|
// Get user info from list
|
|
const users = await minioService.listUsers();
|
|
const user = users.find(u => u.accessKey === username);
|
|
|
|
if (!user) {
|
|
throw new AppError('User not found', 404);
|
|
}
|
|
|
|
res.json({
|
|
username: user.accessKey,
|
|
status: user.status,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
module.exports = router; |