2025-12-13 22:34:11 -06:00
|
|
|
const express = require("express");
|
|
|
|
|
const bcrypt = require("bcrypt");
|
|
|
|
|
const { query } = require("../config/database");
|
|
|
|
|
const { requireAuth, requireRole } = require("../middleware/auth");
|
2026-01-04 17:52:37 -06:00
|
|
|
const { apiLimiter } = require("../config/rateLimiter");
|
2025-12-19 20:44:46 -06:00
|
|
|
const logger = require("../config/logger");
|
|
|
|
|
const {
|
|
|
|
|
validators,
|
|
|
|
|
handleValidationErrors,
|
|
|
|
|
} = require("../middleware/validators");
|
|
|
|
|
const { asyncHandler } = require("../middleware/errorHandler");
|
2025-12-13 22:34:11 -06:00
|
|
|
const router = express.Router();
|
|
|
|
|
|
2026-01-04 17:52:37 -06:00
|
|
|
// Apply rate limiting
|
|
|
|
|
router.use(apiLimiter);
|
|
|
|
|
|
2025-12-13 22:34:11 -06:00
|
|
|
// Require admin role for all routes
|
|
|
|
|
router.use(requireAuth);
|
|
|
|
|
router.use(requireRole("role-admin"));
|
|
|
|
|
|
|
|
|
|
// Get all users with roles
|
|
|
|
|
router.get("/", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await query(`
|
|
|
|
|
SELECT
|
2025-12-14 01:54:40 -06:00
|
|
|
u.id, u.username, u.email, u.name, u.role, u.isactive,
|
|
|
|
|
u.last_login, u.createdat, u.passwordneverexpires
|
2025-12-13 22:34:11 -06:00
|
|
|
FROM adminusers u
|
|
|
|
|
ORDER BY u.createdat DESC
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
users: result.rows,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Get users error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Get all roles
|
|
|
|
|
router.get("/roles", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await query(`
|
|
|
|
|
SELECT id, name, description, permissions
|
|
|
|
|
FROM roles
|
|
|
|
|
ORDER BY name
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
roles: result.rows,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Get roles error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-01 22:24:30 -06:00
|
|
|
// Get single user by ID
|
|
|
|
|
router.get("/:id", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const result = await query(
|
|
|
|
|
`
|
|
|
|
|
SELECT
|
|
|
|
|
u.id, u.username, u.email, u.name, u.role, u.isactive,
|
|
|
|
|
u.last_login, u.createdat, u.passwordneverexpires, u.role_id
|
|
|
|
|
FROM adminusers u
|
|
|
|
|
WHERE u.id = $1
|
|
|
|
|
`,
|
2026-01-18 02:22:05 -06:00
|
|
|
[id],
|
2026-01-01 22:24:30 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "User not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
user: result.rows[0],
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Get user error:", error);
|
|
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-13 22:34:11 -06:00
|
|
|
// Create new user
|
|
|
|
|
router.post("/", async (req, res) => {
|
|
|
|
|
try {
|
2026-01-01 22:24:30 -06:00
|
|
|
const { name, username, email, password, role, passwordneverexpires } =
|
2025-12-13 22:34:11 -06:00
|
|
|
req.body;
|
|
|
|
|
|
|
|
|
|
// Validate required fields
|
2026-01-01 22:24:30 -06:00
|
|
|
if (!username || !email || !password || !role) {
|
2025-12-13 22:34:11 -06:00
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
2026-01-01 22:24:30 -06:00
|
|
|
message: "Name, username, email, password, and role are required",
|
2025-12-13 22:34:11 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user already exists
|
2026-01-01 22:24:30 -06:00
|
|
|
const existing = await query(
|
|
|
|
|
"SELECT id FROM adminusers WHERE email = $1 OR username = $2",
|
2026-01-18 02:22:05 -06:00
|
|
|
[email, username],
|
2026-01-01 22:24:30 -06:00
|
|
|
);
|
2025-12-13 22:34:11 -06:00
|
|
|
|
|
|
|
|
if (existing.rows.length > 0) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
2026-01-01 22:24:30 -06:00
|
|
|
message: "User with this email or username already exists",
|
2025-12-13 22:34:11 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 02:22:05 -06:00
|
|
|
// Hash password with bcrypt (12 rounds minimum for security)
|
|
|
|
|
const BCRYPT_COST = 12;
|
|
|
|
|
|
|
|
|
|
// Validate password requirements
|
|
|
|
|
if (password.length < 8) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must be at least 8 characters long",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[A-Z]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one uppercase letter",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[a-z]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one lowercase letter",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[0-9]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one number",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hashedPassword = await bcrypt.hash(password, BCRYPT_COST);
|
2025-12-13 22:34:11 -06:00
|
|
|
|
|
|
|
|
// Calculate password expiry (90 days from now if not never expires)
|
|
|
|
|
let passwordExpiresAt = null;
|
2026-01-01 22:24:30 -06:00
|
|
|
if (!passwordneverexpires) {
|
2025-12-13 22:34:11 -06:00
|
|
|
const expiryDate = new Date();
|
|
|
|
|
expiryDate.setDate(expiryDate.getDate() + 90);
|
|
|
|
|
passwordExpiresAt = expiryDate.toISOString();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 02:22:05 -06:00
|
|
|
// Resolve role - handle both role ID (e.g., 'role-admin') and role name (e.g., 'Admin')
|
|
|
|
|
let roleId, roleName;
|
|
|
|
|
|
|
|
|
|
// First try to find by ID
|
|
|
|
|
let roleResult = await query("SELECT id, name FROM roles WHERE id = $1", [
|
|
|
|
|
role,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (roleResult.rows.length > 0) {
|
|
|
|
|
// Found by ID
|
|
|
|
|
roleId = roleResult.rows[0].id;
|
|
|
|
|
roleName = roleResult.rows[0].name;
|
|
|
|
|
} else {
|
|
|
|
|
// Try to find by name
|
|
|
|
|
roleResult = await query("SELECT id, name FROM roles WHERE name = $1", [
|
|
|
|
|
role,
|
|
|
|
|
]);
|
|
|
|
|
if (roleResult.rows.length > 0) {
|
|
|
|
|
roleId = roleResult.rows[0].id;
|
|
|
|
|
roleName = roleResult.rows[0].name;
|
|
|
|
|
} else {
|
|
|
|
|
// Default to admin role
|
|
|
|
|
roleId = "role-admin";
|
|
|
|
|
roleName = "Admin";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Insert new user with both role and role_id fields
|
2025-12-13 22:34:11 -06:00
|
|
|
const result = await query(
|
|
|
|
|
`
|
|
|
|
|
INSERT INTO adminusers (
|
2026-01-18 02:22:05 -06:00
|
|
|
id, name, username, email, passwordhash, role, role_id,
|
2026-01-01 22:24:30 -06:00
|
|
|
passwordneverexpires, password_expires_at,
|
|
|
|
|
isactive, created_by, createdat, lastpasswordchange
|
2025-12-13 22:34:11 -06:00
|
|
|
) VALUES (
|
|
|
|
|
'user-' || gen_random_uuid()::text,
|
2026-01-18 02:22:05 -06:00
|
|
|
$1, $2, $3, $4, $5, $6, $7, $8, true, $9, NOW(), NOW()
|
2025-12-13 22:34:11 -06:00
|
|
|
)
|
2026-01-18 02:22:05 -06:00
|
|
|
RETURNING id, name, username, email, role, role_id, isactive, createdat, passwordneverexpires
|
2025-12-13 22:34:11 -06:00
|
|
|
`,
|
|
|
|
|
[
|
2026-01-01 22:24:30 -06:00
|
|
|
name || username,
|
2025-12-13 22:34:11 -06:00
|
|
|
username,
|
|
|
|
|
email,
|
|
|
|
|
hashedPassword,
|
2026-01-18 02:22:05 -06:00
|
|
|
roleName,
|
|
|
|
|
roleId,
|
2026-01-01 22:24:30 -06:00
|
|
|
passwordneverexpires || false,
|
2025-12-13 22:34:11 -06:00
|
|
|
passwordExpiresAt,
|
|
|
|
|
req.session.user.email,
|
2026-01-18 02:22:05 -06:00
|
|
|
],
|
2025-12-13 22:34:11 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "User created successfully",
|
|
|
|
|
user: result.rows[0],
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Create user error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update user
|
|
|
|
|
router.put("/:id", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
2026-01-01 22:24:30 -06:00
|
|
|
const {
|
|
|
|
|
name,
|
|
|
|
|
username,
|
|
|
|
|
email,
|
|
|
|
|
role,
|
|
|
|
|
isactive,
|
|
|
|
|
passwordneverexpires,
|
|
|
|
|
password,
|
|
|
|
|
} = req.body;
|
2025-12-13 22:34:11 -06:00
|
|
|
|
|
|
|
|
// Build update query dynamically
|
|
|
|
|
const updates = [];
|
|
|
|
|
const values = [];
|
|
|
|
|
let paramCount = 1;
|
|
|
|
|
|
2026-01-01 22:24:30 -06:00
|
|
|
if (name !== undefined) {
|
|
|
|
|
updates.push(`name = $${paramCount++}`);
|
|
|
|
|
values.push(name);
|
|
|
|
|
}
|
2025-12-13 22:34:11 -06:00
|
|
|
if (username !== undefined) {
|
|
|
|
|
updates.push(`username = $${paramCount++}`);
|
|
|
|
|
values.push(username);
|
|
|
|
|
}
|
|
|
|
|
if (email !== undefined) {
|
|
|
|
|
updates.push(`email = $${paramCount++}`);
|
|
|
|
|
values.push(email);
|
|
|
|
|
}
|
2026-01-01 22:24:30 -06:00
|
|
|
if (role !== undefined) {
|
2026-01-18 02:22:05 -06:00
|
|
|
// Resolve role - handle both role ID (e.g., 'role-admin') and role name (e.g., 'Admin')
|
|
|
|
|
let roleId, roleName;
|
|
|
|
|
|
|
|
|
|
// First try to find by ID
|
|
|
|
|
let roleResult = await query("SELECT id, name FROM roles WHERE id = $1", [
|
|
|
|
|
role,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (roleResult.rows.length > 0) {
|
|
|
|
|
roleId = roleResult.rows[0].id;
|
|
|
|
|
roleName = roleResult.rows[0].name;
|
|
|
|
|
} else {
|
|
|
|
|
// Try to find by name
|
|
|
|
|
roleResult = await query("SELECT id, name FROM roles WHERE name = $1", [
|
|
|
|
|
role,
|
|
|
|
|
]);
|
|
|
|
|
if (roleResult.rows.length > 0) {
|
|
|
|
|
roleId = roleResult.rows[0].id;
|
|
|
|
|
roleName = roleResult.rows[0].name;
|
|
|
|
|
} else {
|
|
|
|
|
// Default to admin role
|
|
|
|
|
roleId = "role-admin";
|
|
|
|
|
roleName = "Admin";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 22:24:30 -06:00
|
|
|
updates.push(`role = $${paramCount++}`);
|
2026-01-18 02:22:05 -06:00
|
|
|
values.push(roleName);
|
|
|
|
|
updates.push(`role_id = $${paramCount++}`);
|
|
|
|
|
values.push(roleId);
|
2025-12-13 22:34:11 -06:00
|
|
|
}
|
|
|
|
|
if (isactive !== undefined) {
|
|
|
|
|
updates.push(`isactive = $${paramCount++}`);
|
|
|
|
|
values.push(isactive);
|
|
|
|
|
}
|
2026-01-01 22:24:30 -06:00
|
|
|
if (passwordneverexpires !== undefined) {
|
|
|
|
|
updates.push(`passwordneverexpires = $${paramCount++}`);
|
|
|
|
|
values.push(passwordneverexpires);
|
2025-12-13 22:34:11 -06:00
|
|
|
|
|
|
|
|
// If setting to never expire, clear expiry date
|
2026-01-01 22:24:30 -06:00
|
|
|
if (passwordneverexpires) {
|
2025-12-13 22:34:11 -06:00
|
|
|
updates.push(`password_expires_at = NULL`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 22:24:30 -06:00
|
|
|
// Handle password update if provided
|
|
|
|
|
if (password !== undefined && password !== "") {
|
2026-01-18 02:22:05 -06:00
|
|
|
// Validate password requirements
|
|
|
|
|
if (password.length < 8) {
|
2026-01-04 17:52:37 -06:00
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
2026-01-18 02:22:05 -06:00
|
|
|
message: "Password must be at least 8 characters long",
|
2026-01-04 17:52:37 -06:00
|
|
|
});
|
|
|
|
|
}
|
2026-01-18 02:22:05 -06:00
|
|
|
if (!/[A-Z]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one uppercase letter",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[a-z]/.test(password)) {
|
2026-01-01 22:24:30 -06:00
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
2026-01-18 02:22:05 -06:00
|
|
|
message: "Password must contain at least one lowercase letter",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[0-9]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one number",
|
2026-01-01 22:24:30 -06:00
|
|
|
});
|
|
|
|
|
}
|
2026-01-04 17:52:37 -06:00
|
|
|
|
2026-01-18 02:22:05 -06:00
|
|
|
const hashedPassword = await bcrypt.hash(password, 12);
|
2026-01-01 22:24:30 -06:00
|
|
|
updates.push(`passwordhash = $${paramCount++}`);
|
|
|
|
|
values.push(hashedPassword);
|
|
|
|
|
updates.push(`lastpasswordchange = NOW()`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updates.push(`updatedat = NOW()`);
|
2025-12-13 22:34:11 -06:00
|
|
|
values.push(id);
|
|
|
|
|
|
|
|
|
|
const result = await query(
|
|
|
|
|
`
|
|
|
|
|
UPDATE adminusers
|
|
|
|
|
SET ${updates.join(", ")}
|
|
|
|
|
WHERE id = $${paramCount}
|
2026-01-18 02:22:05 -06:00
|
|
|
RETURNING id, name, username, email, role, role_id, isactive, passwordneverexpires
|
2025-12-13 22:34:11 -06:00
|
|
|
`,
|
2026-01-18 02:22:05 -06:00
|
|
|
values,
|
2025-12-13 22:34:11 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "User not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "User updated successfully",
|
|
|
|
|
user: result.rows[0],
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Update user error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-01 22:24:30 -06:00
|
|
|
// Change user password (PUT endpoint for password modal)
|
|
|
|
|
router.put("/:id/password", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { password } = req.body;
|
|
|
|
|
|
2026-01-18 02:22:05 -06:00
|
|
|
// Validate password requirements
|
2026-01-01 22:24:30 -06:00
|
|
|
if (!password || password.length < 8) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must be at least 8 characters long",
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-01-18 02:22:05 -06:00
|
|
|
if (!/[A-Z]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one uppercase letter",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[a-z]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one lowercase letter",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (!/[0-9]/.test(password)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must contain at least one number",
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-01-01 22:24:30 -06:00
|
|
|
|
2026-01-18 02:22:05 -06:00
|
|
|
// Hash new password with bcrypt (12 rounds)
|
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 12);
|
2026-01-01 22:24:30 -06:00
|
|
|
|
|
|
|
|
// Get user's password expiry setting
|
|
|
|
|
const userResult = await query(
|
|
|
|
|
"SELECT passwordneverexpires FROM adminusers WHERE id = $1",
|
2026-01-18 02:22:05 -06:00
|
|
|
[id],
|
2026-01-01 22:24:30 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (userResult.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "User not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate new expiry date (90 days from now if not never expires)
|
|
|
|
|
let passwordExpiresAt = null;
|
|
|
|
|
if (!userResult.rows[0].passwordneverexpires) {
|
|
|
|
|
const expiryDate = new Date();
|
|
|
|
|
expiryDate.setDate(expiryDate.getDate() + 90);
|
|
|
|
|
passwordExpiresAt = expiryDate.toISOString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update password
|
|
|
|
|
await query(
|
|
|
|
|
`
|
|
|
|
|
UPDATE adminusers
|
|
|
|
|
SET passwordhash = $1,
|
|
|
|
|
password_expires_at = $2,
|
|
|
|
|
lastpasswordchange = NOW(),
|
|
|
|
|
updatedat = NOW()
|
|
|
|
|
WHERE id = $3
|
|
|
|
|
`,
|
2026-01-18 02:22:05 -06:00
|
|
|
[hashedPassword, passwordExpiresAt, id],
|
2026-01-01 22:24:30 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Password changed successfully",
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Change password error:", error);
|
|
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-13 22:34:11 -06:00
|
|
|
// Reset user password
|
|
|
|
|
router.post("/:id/reset-password", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { new_password } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!new_password || new_password.length < 6) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Password must be at least 6 characters long",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 22:24:30 -06:00
|
|
|
// Hash new password with bcrypt (10 rounds)
|
2025-12-13 22:34:11 -06:00
|
|
|
const hashedPassword = await bcrypt.hash(new_password, 10);
|
|
|
|
|
|
|
|
|
|
// Get user's password expiry setting
|
|
|
|
|
const userResult = await query(
|
2026-01-18 02:22:05 -06:00
|
|
|
"SELECT passwordneverexpires FROM adminusers WHERE id = $1",
|
|
|
|
|
[id],
|
2025-12-13 22:34:11 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (userResult.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "User not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate new expiry date (90 days from now if not never expires)
|
|
|
|
|
let passwordExpiresAt = null;
|
2026-01-18 02:22:05 -06:00
|
|
|
if (!userResult.rows[0].passwordneverexpires) {
|
2025-12-13 22:34:11 -06:00
|
|
|
const expiryDate = new Date();
|
|
|
|
|
expiryDate.setDate(expiryDate.getDate() + 90);
|
|
|
|
|
passwordExpiresAt = expiryDate.toISOString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update password
|
|
|
|
|
await query(
|
|
|
|
|
`
|
|
|
|
|
UPDATE adminusers
|
|
|
|
|
SET passwordhash = $1,
|
|
|
|
|
password_expires_at = $2,
|
2026-01-18 02:22:05 -06:00
|
|
|
lastpasswordchange = NOW(),
|
|
|
|
|
updatedat = NOW()
|
2025-12-13 22:34:11 -06:00
|
|
|
WHERE id = $3
|
|
|
|
|
`,
|
2026-01-18 02:22:05 -06:00
|
|
|
[hashedPassword, passwordExpiresAt, id],
|
2025-12-13 22:34:11 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Password reset successfully",
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Reset password error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Delete user
|
|
|
|
|
router.delete("/:id", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
// Prevent deleting yourself
|
|
|
|
|
if (id === req.session.user.id) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Cannot delete your own account",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await query(
|
|
|
|
|
"DELETE FROM adminusers WHERE id = $1 RETURNING id",
|
2026-01-18 02:22:05 -06:00
|
|
|
[id],
|
2025-12-13 22:34:11 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "User not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "User deleted successfully",
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Delete user error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Toggle user active status
|
|
|
|
|
router.post("/:id/toggle-status", async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
// Prevent deactivating yourself
|
|
|
|
|
if (id === req.session.user.id) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "Cannot deactivate your own account",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await query(
|
|
|
|
|
`
|
|
|
|
|
UPDATE adminusers
|
|
|
|
|
SET isactive = NOT isactive,
|
|
|
|
|
updated_at = NOW()
|
|
|
|
|
WHERE id = $1
|
|
|
|
|
RETURNING id, isactive
|
|
|
|
|
`,
|
2026-01-18 02:22:05 -06:00
|
|
|
[id],
|
2025-12-13 22:34:11 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "User not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: `User ${
|
|
|
|
|
result.rows[0].isactive ? "activated" : "deactivated"
|
|
|
|
|
} successfully`,
|
|
|
|
|
isactive: result.rows[0].isactive,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.error("Toggle status error:", error);
|
2025-12-13 22:34:11 -06:00
|
|
|
res.status(500).json({ success: false, message: "Server error" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|