Fix jwt-token
This commit is contained in:
@@ -35,26 +35,27 @@ export const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
const login = async (username, password) => {
|
const login = async (username, password) => {
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/users/login', {
|
// Use dedicated management auth endpoint
|
||||||
|
const response = await api.post('/management/auth/login', {
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
})
|
})
|
||||||
|
|
||||||
const { token, user: userData } = response.data.data
|
const { token, user: userData } = response.data
|
||||||
|
|
||||||
// Check if user is admin
|
// Verify management user
|
||||||
if (userData.role !== 'admin') {
|
if (!userData.role || !['super_admin', 'platform_admin'].includes(userData.role)) {
|
||||||
throw new Error('Access denied. Admin privileges required.')
|
throw new Error('Access denied. Management privileges required.')
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem('management_token', token)
|
localStorage.setItem('management_token', token)
|
||||||
localStorage.setItem('management_user', JSON.stringify(userData))
|
localStorage.setItem('management_user', JSON.stringify(userData))
|
||||||
setUser(userData)
|
setUser(userData)
|
||||||
|
|
||||||
toast.success('Login successful')
|
toast.success(`Welcome, ${userData.username}! Management access granted.`)
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error.response?.data?.message || error.message || 'Login failed'
|
const message = error.response?.data?.message || error.message || 'Management login failed'
|
||||||
toast.error(message)
|
toast.error(message)
|
||||||
return { success: false, message }
|
return { success: false, message }
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,9 @@ export const AuthProvider = ({ children }) => {
|
|||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
isAuthenticated: !!user,
|
isAuthenticated: !!user,
|
||||||
isAdmin: user?.role === 'admin'
|
isAdmin: user?.role === 'admin' || user?.role === 'super_admin' || user?.role === 'platform_admin',
|
||||||
|
isSuperAdmin: user?.role === 'super_admin',
|
||||||
|
isPlatformAdmin: user?.role === 'platform_admin'
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||||
|
|||||||
95
scripts/fix-management-nginx.sh
Normal file
95
scripts/fix-management-nginx.sh
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Update nginx configuration to properly route management portal API calls
|
||||||
|
|
||||||
|
DOMAIN="${DOMAIN:-dev.uggla.uamils.com}"
|
||||||
|
NGINX_CONFIG="/etc/nginx/sites-enabled/dev.uggla.uamils.com"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if management server block exists
|
||||||
|
if ! grep -q "server_name management.$DOMAIN" "$NGINX_CONFIG" 2>/dev/null; then
|
||||||
|
log "Adding management subdomain configuration to nginx..."
|
||||||
|
|
||||||
|
# Backup current config
|
||||||
|
cp "$NGINX_CONFIG" "${NGINX_CONFIG}.backup"
|
||||||
|
|
||||||
|
# Add management server block before the wildcard block
|
||||||
|
# Find the line with wildcard subdomain and insert before it
|
||||||
|
sed -i '/# Wildcard subdomains/i\
|
||||||
|
# Management Portal (specific subdomain - MUST come before wildcard)\
|
||||||
|
server {\
|
||||||
|
listen 443 ssl http2;\
|
||||||
|
server_name management.'$DOMAIN';\
|
||||||
|
\
|
||||||
|
ssl_certificate /etc/letsencrypt/live/'$DOMAIN'/fullchain.pem;\
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/'$DOMAIN'/privkey.pem;\
|
||||||
|
\
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;\
|
||||||
|
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;\
|
||||||
|
ssl_prefer_server_ciphers off;\
|
||||||
|
ssl_session_cache shared:SSL:10m;\
|
||||||
|
ssl_session_timeout 10m;\
|
||||||
|
ssl_stapling on;\
|
||||||
|
ssl_stapling_verify on;\
|
||||||
|
\
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;\
|
||||||
|
add_header X-Frame-Options DENY always;\
|
||||||
|
add_header X-Content-Type-Options nosniff always;\
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;\
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;\
|
||||||
|
\
|
||||||
|
# Management frontend - port 3003\
|
||||||
|
location / {\
|
||||||
|
proxy_pass http://127.0.0.1:3003;\
|
||||||
|
proxy_http_version 1.1;\
|
||||||
|
proxy_set_header Upgrade $http_upgrade;\
|
||||||
|
proxy_set_header Connection '\''upgrade'\'';\
|
||||||
|
proxy_set_header Host $host;\
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;\
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;\
|
||||||
|
proxy_cache_bypass $http_upgrade;\
|
||||||
|
proxy_connect_timeout 30s;\
|
||||||
|
proxy_send_timeout 30s;\
|
||||||
|
proxy_read_timeout 30s;\
|
||||||
|
}\
|
||||||
|
\
|
||||||
|
# Management API - port 3002 with management header\
|
||||||
|
location /api/ {\
|
||||||
|
proxy_pass http://127.0.0.1:3002;\
|
||||||
|
proxy_http_version 1.1;\
|
||||||
|
proxy_set_header Upgrade $http_upgrade;\
|
||||||
|
proxy_set_header Connection '\''upgrade'\'';\
|
||||||
|
proxy_set_header Host $host;\
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;\
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;\
|
||||||
|
proxy_set_header X-Management-Portal "true";\
|
||||||
|
proxy_cache_bypass $http_upgrade;\
|
||||||
|
proxy_connect_timeout 30s;\
|
||||||
|
proxy_send_timeout 30s;\
|
||||||
|
proxy_read_timeout 30s;\
|
||||||
|
}\
|
||||||
|
}\
|
||||||
|
\
|
||||||
|
' "$NGINX_CONFIG"
|
||||||
|
|
||||||
|
log "Management subdomain configuration added"
|
||||||
|
else
|
||||||
|
log "Management subdomain configuration already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test and reload nginx
|
||||||
|
if nginx -t; then
|
||||||
|
log "✅ Nginx configuration is valid"
|
||||||
|
systemctl reload nginx
|
||||||
|
log "✅ Nginx reloaded"
|
||||||
|
log "🌐 Management portal API should now work at: https://management.$DOMAIN/api/"
|
||||||
|
else
|
||||||
|
log "❌ Nginx configuration error - restoring backup"
|
||||||
|
cp "${NGINX_CONFIG}.backup" "$NGINX_CONFIG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -12,6 +12,9 @@ DB_PASSWORD=password
|
|||||||
# JWT Secret
|
# JWT Secret
|
||||||
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
||||||
|
|
||||||
|
# Management Portal JWT Secret (completely separate from tenant auth)
|
||||||
|
MANAGEMENT_JWT_SECRET=management-super-secret-key-change-in-production
|
||||||
|
|
||||||
# Twilio Configuration (for SMS alerts)
|
# Twilio Configuration (for SMS alerts)
|
||||||
TWILIO_ACCOUNT_SID=your-twilio-account-sid
|
TWILIO_ACCOUNT_SID=your-twilio-account-sid
|
||||||
TWILIO_AUTH_TOKEN=your-twilio-auth-token
|
TWILIO_AUTH_TOKEN=your-twilio-auth-token
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Import route modules
|
// Import route modules
|
||||||
|
const managementRoutes = require('./management');
|
||||||
const deviceRoutes = require('./device');
|
const deviceRoutes = require('./device');
|
||||||
const userRoutes = require('./user');
|
const userRoutes = require('./user');
|
||||||
const alertRoutes = require('./alert');
|
const alertRoutes = require('./alert');
|
||||||
@@ -13,6 +14,9 @@ const detectorsRoutes = require('./detectors');
|
|||||||
const detectionsRoutes = require('./detections');
|
const detectionsRoutes = require('./detections');
|
||||||
const droneTypesRoutes = require('./droneTypes');
|
const droneTypesRoutes = require('./droneTypes');
|
||||||
|
|
||||||
|
// Management portal routes (before API versioning)
|
||||||
|
router.use('/management', managementRoutes);
|
||||||
|
|
||||||
// API versioning
|
// API versioning
|
||||||
router.use('/v1/devices', deviceRoutes);
|
router.use('/v1/devices', deviceRoutes);
|
||||||
router.use('/v1/users', userRoutes);
|
router.use('/v1/users', userRoutes);
|
||||||
|
|||||||
469
server/routes/management.js
Normal file
469
server/routes/management.js
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
/**
|
||||||
|
* Management Portal API Routes
|
||||||
|
* Completely separate authentication system for security isolation
|
||||||
|
*/
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const { Tenant, User } = require('../models');
|
||||||
|
|
||||||
|
// Management-specific authentication middleware - NO shared auth with tenants
|
||||||
|
const requireManagementAuth = (req, res, next) => {
|
||||||
|
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Management authentication required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use separate JWT secret for management
|
||||||
|
const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production';
|
||||||
|
const decoded = jwt.verify(token, MANAGEMENT_SECRET);
|
||||||
|
|
||||||
|
// Verify this is a management token with proper role
|
||||||
|
if (!decoded.isManagement || !['super_admin', 'platform_admin'].includes(decoded.role)) {
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Insufficient management privileges'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
req.managementUser = {
|
||||||
|
id: decoded.userId,
|
||||||
|
username: decoded.username,
|
||||||
|
role: decoded.role,
|
||||||
|
isManagement: true
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid management token',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Management login endpoint - separate from tenant auth
|
||||||
|
router.post('/auth/login', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
// Hardcoded management users for now (should be in separate DB table)
|
||||||
|
const MANAGEMENT_USERS = {
|
||||||
|
'admin': {
|
||||||
|
password: await bcrypt.hash('admin123', 10), // Change this!
|
||||||
|
role: 'super_admin'
|
||||||
|
},
|
||||||
|
'platform_admin': {
|
||||||
|
password: await bcrypt.hash('platform123', 10), // Change this!
|
||||||
|
role: 'platform_admin'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const managementUser = MANAGEMENT_USERS[username];
|
||||||
|
if (!managementUser || !await bcrypt.compare(password, managementUser.password)) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid management credentials'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production';
|
||||||
|
const token = jwt.sign({
|
||||||
|
userId: username,
|
||||||
|
username: username,
|
||||||
|
role: managementUser.role,
|
||||||
|
isManagement: true
|
||||||
|
}, MANAGEMENT_SECRET, { expiresIn: '8h' });
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
token,
|
||||||
|
user: {
|
||||||
|
username,
|
||||||
|
role: managementUser.role
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Management authentication error',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply management authentication to all other routes
|
||||||
|
router.use(requireManagementAuth);
|
||||||
|
|
||||||
|
// Security audit logging for all management operations
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
const auditLog = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
user: req.managementUser.username,
|
||||||
|
role: req.managementUser.role,
|
||||||
|
method: req.method,
|
||||||
|
path: req.path,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent']
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[MANAGEMENT AUDIT]', JSON.stringify(auditLog));
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/management/system-info - Platform system information
|
||||||
|
*/
|
||||||
|
router.get('/system-info', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantCount = await Tenant.count();
|
||||||
|
const userCount = await User.count();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
platform: {
|
||||||
|
name: 'UAMILS Platform',
|
||||||
|
version: '1.0.0',
|
||||||
|
environment: process.env.NODE_ENV || 'development'
|
||||||
|
},
|
||||||
|
statistics: {
|
||||||
|
tenants: tenantCount,
|
||||||
|
total_users: userCount,
|
||||||
|
uptime: process.uptime()
|
||||||
|
},
|
||||||
|
security: {
|
||||||
|
management_access_level: req.managementUser.role,
|
||||||
|
last_backup: process.env.LAST_BACKUP_DATE || 'Not configured'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error fetching system info:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to fetch system information',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/management/tenants - List all tenants with admin details
|
||||||
|
*/
|
||||||
|
router.get('/tenants', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { limit = 50, offset = 0, search, auth_provider } = req.query;
|
||||||
|
|
||||||
|
const whereClause = {};
|
||||||
|
if (search) {
|
||||||
|
whereClause[Op.or] = [
|
||||||
|
{ name: { [Op.iLike]: `%${search}%` } },
|
||||||
|
{ slug: { [Op.iLike]: `%${search}%` } },
|
||||||
|
{ domain: { [Op.iLike]: `%${search}%` } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (auth_provider) {
|
||||||
|
whereClause.auth_provider = auth_provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenants = await Tenant.findAndCountAll({
|
||||||
|
where: whereClause,
|
||||||
|
include: [{
|
||||||
|
model: User,
|
||||||
|
as: 'users',
|
||||||
|
attributes: ['id', 'username', 'email', 'role', 'last_login', 'created_at'],
|
||||||
|
limit: 10
|
||||||
|
}],
|
||||||
|
limit: Math.min(parseInt(limit), 100),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
order: [['created_at', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: tenants.rows,
|
||||||
|
pagination: {
|
||||||
|
total: tenants.count,
|
||||||
|
limit: parseInt(limit),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
pages: Math.ceil(tenants.count / parseInt(limit))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error fetching tenants:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to fetch tenants'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/management/tenants - Create new tenant
|
||||||
|
*/
|
||||||
|
router.post('/tenants', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantData = req.body;
|
||||||
|
|
||||||
|
// Enhanced validation for management portal
|
||||||
|
if (!tenantData.name || !tenantData.slug) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Name and slug are required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for unique slug
|
||||||
|
const existingTenant = await Tenant.findOne({ where: { slug: tenantData.slug } });
|
||||||
|
if (existingTenant) {
|
||||||
|
return res.status(409).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Tenant slug already exists'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log management action
|
||||||
|
console.log(`Management: Admin ${req.user.username} creating tenant: ${tenantData.name}`);
|
||||||
|
|
||||||
|
const tenant = await Tenant.create(tenantData);
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
success: true,
|
||||||
|
data: tenant,
|
||||||
|
message: 'Tenant created successfully'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error creating tenant:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to create tenant'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/management/tenants/:id - Get tenant details
|
||||||
|
*/
|
||||||
|
router.get('/tenants/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenant = await Tenant.findByPk(req.params.id, {
|
||||||
|
include: [{
|
||||||
|
model: User,
|
||||||
|
as: 'users',
|
||||||
|
attributes: ['id', 'username', 'email', 'role', 'last_login', 'created_at']
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Tenant not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: tenant
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error fetching tenant:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to fetch tenant'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /api/management/tenants/:id - Update tenant
|
||||||
|
*/
|
||||||
|
router.put('/tenants/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenant = await Tenant.findByPk(req.params.id);
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Tenant not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log management action
|
||||||
|
console.log(`Management: Admin ${req.user.username} updating tenant: ${tenant.name}`);
|
||||||
|
|
||||||
|
await tenant.update(req.body);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: tenant,
|
||||||
|
message: 'Tenant updated successfully'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error updating tenant:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to update tenant'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /api/management/tenants/:id - Delete tenant
|
||||||
|
*/
|
||||||
|
router.delete('/tenants/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenant = await Tenant.findByPk(req.params.id);
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Tenant not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent deletion of default tenant
|
||||||
|
if (tenant.slug === 'default') {
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Cannot delete default tenant'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log management action
|
||||||
|
console.log(`Management: Admin ${req.user.username} deleting tenant: ${tenant.name}`);
|
||||||
|
|
||||||
|
await tenant.destroy();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Tenant deleted successfully'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error deleting tenant:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to delete tenant'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/management/users - List all users across tenants
|
||||||
|
*/
|
||||||
|
router.get('/users', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { limit = 50, offset = 0, search, role, tenant_id } = req.query;
|
||||||
|
|
||||||
|
const whereClause = {};
|
||||||
|
if (search) {
|
||||||
|
whereClause[Op.or] = [
|
||||||
|
{ username: { [Op.iLike]: `%${search}%` } },
|
||||||
|
{ email: { [Op.iLike]: `%${search}%` } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (role) {
|
||||||
|
whereClause.role = role;
|
||||||
|
}
|
||||||
|
if (tenant_id) {
|
||||||
|
whereClause.tenant_id = tenant_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await User.findAndCountAll({
|
||||||
|
where: whereClause,
|
||||||
|
include: [{
|
||||||
|
model: Tenant,
|
||||||
|
as: 'tenant',
|
||||||
|
attributes: ['id', 'name', 'slug']
|
||||||
|
}],
|
||||||
|
attributes: { exclude: ['password'] }, // Never expose passwords
|
||||||
|
limit: Math.min(parseInt(limit), 100),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
order: [['created_at', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: users.rows,
|
||||||
|
pagination: {
|
||||||
|
total: users.count,
|
||||||
|
limit: parseInt(limit),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
pages: Math.ceil(users.count / parseInt(limit))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error fetching users:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to fetch users'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/management/system/info - System information
|
||||||
|
*/
|
||||||
|
router.get('/system/info', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Gather system statistics
|
||||||
|
const tenantCount = await Tenant.count();
|
||||||
|
const userCount = await User.count();
|
||||||
|
const activeTenantsCount = await Tenant.count({ where: { is_active: true } });
|
||||||
|
|
||||||
|
const systemInfo = {
|
||||||
|
version: process.env.APP_VERSION || '1.0.0',
|
||||||
|
environment: process.env.NODE_ENV || 'production',
|
||||||
|
uptime: process.uptime(),
|
||||||
|
database: {
|
||||||
|
status: 'connected',
|
||||||
|
version: 'PostgreSQL 15',
|
||||||
|
connections: 5, // You can get this from pg pool
|
||||||
|
maxConnections: 100
|
||||||
|
},
|
||||||
|
statistics: {
|
||||||
|
tenants: tenantCount,
|
||||||
|
users: userCount,
|
||||||
|
activeTenants: activeTenantsCount
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + 'MB',
|
||||||
|
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + 'MB',
|
||||||
|
percentage: Math.round((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100)
|
||||||
|
},
|
||||||
|
lastBackup: new Date(Date.now() - 24*60*60*1000).toISOString(), // Mock
|
||||||
|
ssl: {
|
||||||
|
status: 'valid',
|
||||||
|
expiresAt: new Date(Date.now() + 90*24*60*60*1000).toISOString() // Mock 90 days
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: systemInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Management: Error fetching system info:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to fetch system information'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user