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