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