278 lines
7.9 KiB
JavaScript
278 lines
7.9 KiB
JavaScript
import db from "./db.js"
|
|
import bcrypt from "bcryptjs"
|
|
import { randomBytes } from "crypto"
|
|
|
|
// Create a new user
|
|
export async function createUser({ name, username, password, role = 'user', is_active = true, can_be_assigned = true }) {
|
|
const existingUser = db.prepare("SELECT id FROM users WHERE username = ?").get(username)
|
|
if (existingUser) {
|
|
throw new Error("User with this username already exists")
|
|
}
|
|
|
|
const passwordHash = await bcrypt.hash(password, 12)
|
|
const userId = randomBytes(16).toString('hex')
|
|
|
|
const result = db.prepare(`
|
|
INSERT INTO users (id, name, username, password_hash, role, is_active, can_be_assigned)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`).run(userId, name, username, passwordHash, role, is_active ? 1 : 0, can_be_assigned ? 1 : 0)
|
|
|
|
return db.prepare(`
|
|
SELECT id, name, username, role, created_at, updated_at, last_login,
|
|
is_active, failed_login_attempts, locked_until, initial, can_be_assigned
|
|
FROM users WHERE id = ?
|
|
`).get(userId)
|
|
}
|
|
|
|
// Get user by ID
|
|
export function getUserById(id) {
|
|
return db.prepare(`
|
|
SELECT id, name, username, password_hash, role, created_at, updated_at, last_login,
|
|
is_active, failed_login_attempts, locked_until, initial, can_be_assigned
|
|
FROM users WHERE id = ?
|
|
`).get(id)
|
|
}
|
|
|
|
// Get user by username
|
|
export function getUserByUsername(username) {
|
|
return db.prepare(`
|
|
SELECT id, name, username, role, created_at, last_login, is_active, initial
|
|
FROM users WHERE username = ?
|
|
`).get(username)
|
|
}
|
|
|
|
// Get all users (for admin)
|
|
export function getAllUsers() {
|
|
return db.prepare(`
|
|
SELECT id, name, username, password_hash, role, created_at, updated_at, last_login, is_active,
|
|
failed_login_attempts, locked_until, initial, can_be_assigned
|
|
FROM users
|
|
ORDER BY created_at DESC
|
|
`).all()
|
|
}
|
|
|
|
// Update user role
|
|
export function updateUserRole(userId, role) {
|
|
const validRoles = ['admin', 'team_lead', 'project_manager', 'user', 'read_only']
|
|
if (!validRoles.includes(role)) {
|
|
throw new Error("Invalid role")
|
|
}
|
|
|
|
const result = db.prepare(`
|
|
UPDATE users SET role = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`).run(role, userId)
|
|
|
|
return result.changes > 0
|
|
}
|
|
|
|
// Activate/deactivate user
|
|
export function setUserActive(userId, isActive) {
|
|
const result = db.prepare(`
|
|
UPDATE users SET is_active = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`).run(isActive ? 1 : 0, userId)
|
|
|
|
return result.changes > 0
|
|
}
|
|
|
|
// Change user password
|
|
export async function changeUserPassword(userId, newPassword) {
|
|
const passwordHash = await bcrypt.hash(newPassword, 12)
|
|
|
|
const result = db.prepare(`
|
|
UPDATE users
|
|
SET password_hash = ?, updated_at = CURRENT_TIMESTAMP,
|
|
failed_login_attempts = 0, locked_until = NULL
|
|
WHERE id = ?
|
|
`).run(passwordHash, userId)
|
|
|
|
return result.changes > 0
|
|
}
|
|
|
|
// Clean up expired sessions
|
|
export function cleanupExpiredSessions() {
|
|
const result = db.prepare(`
|
|
DELETE FROM sessions WHERE expires < datetime('now')
|
|
`).run()
|
|
|
|
return result.changes
|
|
}
|
|
|
|
// Get user sessions
|
|
export function getUserSessions(userId) {
|
|
return db.prepare(`
|
|
SELECT id, session_token, expires, created_at
|
|
FROM sessions
|
|
WHERE user_id = ? AND expires > datetime('now')
|
|
ORDER BY created_at DESC
|
|
`).all(userId)
|
|
}
|
|
|
|
// Revoke user session
|
|
export function revokeSession(sessionToken) {
|
|
const result = db.prepare(`
|
|
DELETE FROM sessions WHERE session_token = ?
|
|
`).run(sessionToken)
|
|
|
|
return result.changes > 0
|
|
}
|
|
|
|
// Get audit logs for user
|
|
export function getUserAuditLogs(userId, limit = 50) {
|
|
return db.prepare(`
|
|
SELECT action, resource_type, resource_id, ip_address, timestamp, details
|
|
FROM audit_logs
|
|
WHERE user_id = ?
|
|
ORDER BY timestamp DESC
|
|
LIMIT ?
|
|
`).all(userId, limit)
|
|
}
|
|
|
|
// Update user (comprehensive update function)
|
|
export async function updateUser(userId, updates) {
|
|
const user = getUserById(userId);
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
|
|
// Check if username is being changed and if it already exists
|
|
if (updates.username && updates.username !== user.username) {
|
|
const existingUser = db.prepare("SELECT id FROM users WHERE username = ? AND id != ?").get(updates.username, userId);
|
|
if (existingUser) {
|
|
throw new Error("User with this username already exists");
|
|
}
|
|
}
|
|
|
|
// Prepare update fields
|
|
const updateFields = [];
|
|
const updateValues = [];
|
|
|
|
if (updates.name !== undefined) {
|
|
updateFields.push("name = ?");
|
|
updateValues.push(updates.name);
|
|
}
|
|
|
|
if (updates.username !== undefined) {
|
|
updateFields.push("username = ?");
|
|
updateValues.push(updates.username);
|
|
}
|
|
|
|
if (updates.role !== undefined) {
|
|
const validRoles = ['admin', 'team_lead', 'project_manager', 'user', 'read_only'];
|
|
if (!validRoles.includes(updates.role)) {
|
|
throw new Error("Invalid role");
|
|
}
|
|
updateFields.push("role = ?");
|
|
updateValues.push(updates.role);
|
|
}
|
|
|
|
if (updates.is_active !== undefined) {
|
|
updateFields.push("is_active = ?");
|
|
updateValues.push(updates.is_active ? 1 : 0);
|
|
}
|
|
|
|
if (updates.can_be_assigned !== undefined) {
|
|
updateFields.push("can_be_assigned = ?");
|
|
updateValues.push(updates.can_be_assigned ? 1 : 0);
|
|
}
|
|
|
|
if (updates.initial !== undefined) {
|
|
updateFields.push("initial = ?");
|
|
updateValues.push(updates.initial);
|
|
}
|
|
|
|
if (updates.password !== undefined) {
|
|
const passwordHash = await bcrypt.hash(updates.password, 12);
|
|
updateFields.push("password_hash = ?");
|
|
updateValues.push(passwordHash);
|
|
// Reset failed login attempts when password is changed
|
|
updateFields.push("failed_login_attempts = 0");
|
|
updateFields.push("locked_until = NULL");
|
|
}
|
|
|
|
if (updateFields.length === 0) {
|
|
return getUserById(userId); // Return existing user if no updates
|
|
}
|
|
|
|
updateFields.push("updated_at = CURRENT_TIMESTAMP");
|
|
updateValues.push(userId);
|
|
|
|
const query = `
|
|
UPDATE users
|
|
SET ${updateFields.join(", ")}
|
|
WHERE id = ?
|
|
`;
|
|
|
|
const result = db.prepare(query).run(...updateValues);
|
|
|
|
if (result.changes > 0) {
|
|
return db.prepare(`
|
|
SELECT id, name, username, role, created_at, updated_at, last_login,
|
|
is_active, failed_login_attempts, locked_until, initial, can_be_assigned
|
|
FROM users WHERE id = ?
|
|
`).get(userId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Delete user
|
|
export function deleteUser(userId) {
|
|
// First, delete related data (sessions, audit logs, etc.)
|
|
db.prepare("DELETE FROM sessions WHERE user_id = ?").run(userId);
|
|
db.prepare("DELETE FROM audit_logs WHERE user_id = ?").run(userId);
|
|
|
|
// Then delete the user
|
|
const result = db.prepare("DELETE FROM users WHERE id = ?").run(userId);
|
|
|
|
return result.changes > 0;
|
|
}
|
|
|
|
// Reset user password (admin function)
|
|
export async function resetUserPassword(userId, newPassword) {
|
|
const passwordHash = await bcrypt.hash(newPassword, 12);
|
|
|
|
const result = db.prepare(`
|
|
UPDATE users
|
|
SET password_hash = ?,
|
|
failed_login_attempts = 0,
|
|
locked_until = NULL,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`).run(passwordHash, userId);
|
|
|
|
return result.changes > 0;
|
|
}
|
|
|
|
// Unlock user account
|
|
export function unlockUserAccount(userId) {
|
|
const result = db.prepare(`
|
|
UPDATE users
|
|
SET failed_login_attempts = 0,
|
|
locked_until = NULL,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`).run(userId);
|
|
|
|
return result.changes > 0;
|
|
}
|
|
|
|
// Get user statistics
|
|
export function getUserStats() {
|
|
const stats = db.prepare(`
|
|
SELECT
|
|
COUNT(*) as total_users,
|
|
COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_users,
|
|
COUNT(CASE WHEN is_active = 0 THEN 1 END) as inactive_users,
|
|
COUNT(CASE WHEN role = 'admin' THEN 1 END) as admin_users,
|
|
COUNT(CASE WHEN role = 'project_manager' THEN 1 END) as manager_users,
|
|
COUNT(CASE WHEN role = 'user' THEN 1 END) as regular_users,
|
|
COUNT(CASE WHEN role = 'read_only' THEN 1 END) as readonly_users,
|
|
COUNT(CASE WHEN last_login IS NOT NULL THEN 1 END) as users_with_login
|
|
FROM users
|
|
`).get();
|
|
|
|
return stats;
|
|
}
|