Fix jwt-token
This commit is contained in:
200
server/models/ManagementUser.js
Normal file
200
server/models/ManagementUser.js
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Management User Model
|
||||
* Completely separate from tenant users for security isolation
|
||||
*/
|
||||
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
const ManagementUser = sequelize.define('ManagementUser', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
len: [3, 50]
|
||||
},
|
||||
comment: 'Unique management username'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
isEmail: true
|
||||
},
|
||||
comment: 'Management user email'
|
||||
},
|
||||
password_hash: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
comment: 'Bcrypt hashed password'
|
||||
},
|
||||
first_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
last_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.ENUM('super_admin', 'platform_admin', 'support_admin'),
|
||||
allowNull: false,
|
||||
defaultValue: 'support_admin',
|
||||
comment: 'Management role - separate from tenant roles'
|
||||
},
|
||||
is_active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true,
|
||||
comment: 'Whether the management user is active'
|
||||
},
|
||||
last_login: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: 'Last successful login timestamp'
|
||||
},
|
||||
login_attempts: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: 'Failed login attempt counter'
|
||||
},
|
||||
locked_until: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: 'Account lock expiration time'
|
||||
},
|
||||
two_factor_enabled: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: 'Whether 2FA is enabled'
|
||||
},
|
||||
two_factor_secret: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'TOTP secret for 2FA'
|
||||
},
|
||||
api_access: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true,
|
||||
comment: 'Whether user can access management API'
|
||||
},
|
||||
permissions: {
|
||||
type: DataTypes.JSONB,
|
||||
defaultValue: [],
|
||||
comment: 'Additional granular permissions'
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Username of who created this management user'
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Admin notes about this user'
|
||||
}
|
||||
}, {
|
||||
tableName: 'management_users',
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['username']
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
fields: ['email']
|
||||
},
|
||||
{
|
||||
fields: ['role']
|
||||
},
|
||||
{
|
||||
fields: ['is_active']
|
||||
},
|
||||
{
|
||||
fields: ['last_login']
|
||||
}
|
||||
],
|
||||
hooks: {
|
||||
beforeCreate: (user) => {
|
||||
// Ensure management users have strong password requirements
|
||||
// This would be handled in the route, but we can add validation here
|
||||
},
|
||||
beforeUpdate: (user) => {
|
||||
if (user.changed('password_hash')) {
|
||||
// Log password changes for security audit
|
||||
console.log(`Management password change for user: ${user.username}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Static methods for management user operations
|
||||
ManagementUser.createInitialAdmin = async function(adminData) {
|
||||
const bcrypt = require('bcryptjs');
|
||||
const hashedPassword = await bcrypt.hash(adminData.password, 12); // Higher cost for management users
|
||||
|
||||
return await this.create({
|
||||
username: adminData.username,
|
||||
email: adminData.email,
|
||||
password_hash: hashedPassword,
|
||||
first_name: adminData.first_name,
|
||||
last_name: adminData.last_name,
|
||||
role: 'super_admin',
|
||||
created_by: 'system'
|
||||
});
|
||||
};
|
||||
|
||||
ManagementUser.findByCredentials = async function(username, password) {
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const user = await this.findOne({
|
||||
where: {
|
||||
username: username,
|
||||
is_active: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if account is locked
|
||||
if (user.locked_until && user.locked_until > new Date()) {
|
||||
throw new Error('Account is temporarily locked');
|
||||
}
|
||||
|
||||
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
||||
|
||||
if (!isValidPassword) {
|
||||
// Increment failed attempts
|
||||
await user.increment('login_attempts');
|
||||
|
||||
// Lock account after 5 failed attempts for 30 minutes
|
||||
if (user.login_attempts >= 4) {
|
||||
await user.update({
|
||||
locked_until: new Date(Date.now() + 30 * 60 * 1000) // 30 minutes
|
||||
});
|
||||
throw new Error('Account locked due to multiple failed attempts');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reset login attempts on successful login
|
||||
await user.update({
|
||||
login_attempts: 0,
|
||||
locked_until: null,
|
||||
last_login: new Date()
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
return ManagementUser;
|
||||
};
|
||||
@@ -27,6 +27,7 @@ const User = require('./User')(sequelize);
|
||||
const AlertRule = require('./AlertRule')(sequelize);
|
||||
const AlertLog = require('./AlertLog')(sequelize);
|
||||
const Tenant = require('./Tenant')(sequelize);
|
||||
const ManagementUser = require('./ManagementUser')(sequelize);
|
||||
|
||||
// Define associations
|
||||
Device.hasMany(DroneDetection, { foreignKey: 'device_id', as: 'detections' });
|
||||
@@ -56,5 +57,6 @@ module.exports = {
|
||||
User,
|
||||
AlertRule,
|
||||
AlertLog,
|
||||
Tenant
|
||||
Tenant,
|
||||
ManagementUser
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ const router = express.Router();
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcryptjs'); // Fixed: use bcryptjs instead of bcrypt
|
||||
const { Op } = require('sequelize'); // Add Sequelize operators
|
||||
const { Tenant, User } = require('../models');
|
||||
const { Tenant, User, ManagementUser } = require('../models');
|
||||
|
||||
// Management-specific authentication middleware - NO shared auth with tenants
|
||||
const requireManagementAuth = (req, res, next) => {
|
||||
@@ -56,20 +56,10 @@ 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'
|
||||
}
|
||||
};
|
||||
// Use ManagementUser model instead of hardcoded users
|
||||
const managementUser = await ManagementUser.findByCredentials(username, password);
|
||||
|
||||
const managementUser = MANAGEMENT_USERS[username];
|
||||
if (!managementUser || !await bcrypt.compare(password, managementUser.password)) {
|
||||
if (!managementUser) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid management credentials'
|
||||
@@ -78,8 +68,8 @@ router.post('/auth/login', async (req, res) => {
|
||||
|
||||
const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production';
|
||||
const token = jwt.sign({
|
||||
userId: username,
|
||||
username: username,
|
||||
userId: managementUser.id,
|
||||
username: managementUser.username,
|
||||
role: managementUser.role,
|
||||
isManagement: true
|
||||
}, MANAGEMENT_SECRET, { expiresIn: '8h' });
|
||||
@@ -88,7 +78,11 @@ router.post('/auth/login', async (req, res) => {
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
username,
|
||||
id: managementUser.id,
|
||||
username: managementUser.username,
|
||||
email: managementUser.email,
|
||||
first_name: managementUser.first_name,
|
||||
last_name: managementUser.last_name,
|
||||
role: managementUser.role
|
||||
}
|
||||
});
|
||||
@@ -506,14 +500,14 @@ router.post('/tenants/:tenantId/users', async (req, res) => {
|
||||
// Create user with tenant association
|
||||
const user = await User.create({
|
||||
...userData,
|
||||
password: hashedPassword,
|
||||
password_hash: hashedPassword, // Use correct field name
|
||||
tenant_id: tenantId,
|
||||
created_by: req.managementUser.username
|
||||
});
|
||||
|
||||
// Remove password from response
|
||||
// Remove password_hash from response
|
||||
const userResponse = user.toJSON();
|
||||
delete userResponse.password;
|
||||
delete userResponse.password_hash;
|
||||
|
||||
console.log(`Management: Admin ${req.managementUser.username} created user ${userData.username} in tenant ${tenant.name}`);
|
||||
|
||||
@@ -563,14 +557,15 @@ router.put('/tenants/:tenantId/users/:userId', async (req, res) => {
|
||||
// Hash password if provided
|
||||
if (updates.password) {
|
||||
const bcrypt = require('bcryptjs');
|
||||
updates.password = await bcrypt.hash(updates.password, 10);
|
||||
updates.password_hash = await bcrypt.hash(updates.password, 10);
|
||||
delete updates.password; // Remove plain password
|
||||
}
|
||||
|
||||
await user.update(updates);
|
||||
|
||||
// Remove password from response
|
||||
// Remove password_hash from response
|
||||
const userResponse = user.toJSON();
|
||||
delete userResponse.password;
|
||||
delete userResponse.password_hash;
|
||||
|
||||
console.log(`Management: Admin ${req.managementUser.username} updated user ${user.username} in tenant ${user.tenant.name}`);
|
||||
|
||||
@@ -686,7 +681,7 @@ router.get('/tenants/:tenantId/users', async (req, res) => {
|
||||
|
||||
const users = await User.findAndCountAll({
|
||||
where: whereClause,
|
||||
attributes: { exclude: ['password'] },
|
||||
attributes: { exclude: ['password_hash'] }, // Exclude password_hash, not password
|
||||
limit: Math.min(parseInt(limit), 100),
|
||||
offset: parseInt(offset),
|
||||
order: [['created_at', 'DESC']]
|
||||
|
||||
63
server/scripts/seed-management-users.js
Normal file
63
server/scripts/seed-management-users.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Seed script for creating initial management users
|
||||
* Run this once to set up the platform admin account
|
||||
*/
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { ManagementUser } = require('../models');
|
||||
|
||||
async function createInitialManagementUser() {
|
||||
try {
|
||||
// Check if any management users exist
|
||||
const existingUsers = await ManagementUser.count();
|
||||
|
||||
if (existingUsers > 0) {
|
||||
console.log('✅ Management users already exist. Skipping seed.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🌱 Creating initial management user...');
|
||||
|
||||
// Hash the password
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
|
||||
// Create the admin user
|
||||
const adminUser = await ManagementUser.create({
|
||||
username: 'admin',
|
||||
email: 'admin@platform.local',
|
||||
password_hash: hashedPassword,
|
||||
role: 'super_admin',
|
||||
first_name: 'Platform',
|
||||
last_name: 'Administrator',
|
||||
is_active: true,
|
||||
created_by: 'system'
|
||||
});
|
||||
|
||||
console.log('✅ Initial management user created successfully:');
|
||||
console.log(` Username: ${adminUser.username}`);
|
||||
console.log(` Email: ${adminUser.email}`);
|
||||
console.log(` Role: ${adminUser.role}`);
|
||||
console.log(` Password: admin123 (Please change this immediately!)`);
|
||||
console.log('');
|
||||
console.log('🔐 SECURITY WARNING: Change the default password immediately!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error creating initial management user:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// If called directly, run the seed
|
||||
if (require.main === module) {
|
||||
createInitialManagementUser()
|
||||
.then(() => {
|
||||
console.log('🎉 Management user seed completed successfully');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('💥 Management user seed failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { createInitialManagementUser };
|
||||
@@ -1,11 +1,15 @@
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { User, Device, AlertRule } = require('./models');
|
||||
const { User, Device, AlertRule, ManagementUser } = require('./models');
|
||||
const { createInitialManagementUser } = require('./scripts/seed-management-users');
|
||||
|
||||
async function seedDatabase() {
|
||||
try {
|
||||
console.log('🌱 Seeding database...');
|
||||
|
||||
// Check if admin user exists
|
||||
// First, create management users (platform admins)
|
||||
await createInitialManagementUser();
|
||||
|
||||
// Check if admin user exists (legacy tenant admin)
|
||||
const existingAdmin = await User.findOne({ where: { username: 'admin' } });
|
||||
|
||||
if (!existingAdmin) {
|
||||
|
||||
Reference in New Issue
Block a user