Files
drone-detector/server/models/ManagementUser.js
2025-09-13 13:13:43 +02:00

201 lines
4.9 KiB
JavaScript

/**
* 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;
};