feat: Implement user management functionality with CRUD operations; add user edit page, API routes for user actions, and enhance authentication middleware

This commit is contained in:
2025-06-25 13:37:10 +02:00
parent 1524e1e9bb
commit 988a4eb71b
6 changed files with 1120 additions and 10 deletions

View File

@@ -3,7 +3,7 @@ import bcrypt from "bcryptjs"
import { randomBytes } from "crypto"
// Create a new user
export async function createUser({ name, email, password, role = 'user' }) {
export async function createUser({ name, email, password, role = 'user', is_active = true }) {
const existingUser = db.prepare("SELECT id FROM users WHERE email = ?").get(email)
if (existingUser) {
throw new Error("User with this email already exists")
@@ -13,17 +13,22 @@ export async function createUser({ name, email, password, role = 'user' }) {
const userId = randomBytes(16).toString('hex')
const result = db.prepare(`
INSERT INTO users (id, name, email, password_hash, role)
VALUES (?, ?, ?, ?, ?)
`).run(userId, name, email, passwordHash, role)
INSERT INTO users (id, name, email, password_hash, role, is_active)
VALUES (?, ?, ?, ?, ?, ?)
`).run(userId, name, email, passwordHash, role, is_active ? 1 : 0)
return { id: userId, name, email, role }
return db.prepare(`
SELECT id, name, email, role, created_at, updated_at, last_login,
is_active, failed_login_attempts, locked_until
FROM users WHERE id = ?
`).get(userId)
}
// Get user by ID
export function getUserById(id) {
return db.prepare(`
SELECT id, name, email, role, created_at, last_login, is_active
SELECT id, name, email, password_hash, role, created_at, updated_at, last_login,
is_active, failed_login_attempts, locked_until
FROM users WHERE id = ?
`).get(id)
}
@@ -39,7 +44,7 @@ export function getUserByEmail(email) {
// Get all users (for admin)
export function getAllUsers() {
return db.prepare(`
SELECT id, name, email, role, created_at, last_login, is_active,
SELECT id, name, email, password_hash, role, created_at, updated_at, last_login, is_active,
failed_login_attempts, locked_until
FROM users
ORDER BY created_at DESC
@@ -123,3 +128,140 @@ export function getUserAuditLogs(userId, limit = 50) {
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 email is being changed and if it already exists
if (updates.email && updates.email !== user.email) {
const existingUser = db.prepare("SELECT id FROM users WHERE email = ? AND id != ?").get(updates.email, userId);
if (existingUser) {
throw new Error("User with this email already exists");
}
}
// Prepare update fields
const updateFields = [];
const updateValues = [];
if (updates.name !== undefined) {
updateFields.push("name = ?");
updateValues.push(updates.name);
}
if (updates.email !== undefined) {
updateFields.push("email = ?");
updateValues.push(updates.email);
}
if (updates.role !== undefined) {
const validRoles = ['admin', '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.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, email, role, created_at, updated_at, last_login,
is_active, failed_login_attempts, locked_until
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;
}