webupdate
This commit is contained in:
@@ -70,7 +70,7 @@ router.get("/:id", async (req, res) => {
|
||||
FROM adminusers u
|
||||
WHERE u.id = $1
|
||||
`,
|
||||
[id]
|
||||
[id],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -107,7 +107,7 @@ router.post("/", async (req, res) => {
|
||||
// Check if user already exists
|
||||
const existing = await query(
|
||||
"SELECT id FROM adminusers WHERE email = $1 OR username = $2",
|
||||
[email, username]
|
||||
[email, username],
|
||||
);
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
@@ -117,8 +117,36 @@ router.post("/", async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Hash password with bcrypt (10 rounds)
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
// 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);
|
||||
|
||||
// Calculate password expiry (90 days from now if not never expires)
|
||||
let passwordExpiresAt = null;
|
||||
@@ -128,29 +156,57 @@ router.post("/", async (req, res) => {
|
||||
passwordExpiresAt = expiryDate.toISOString();
|
||||
}
|
||||
|
||||
// Insert new user with both role and name fields
|
||||
// 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
|
||||
const result = await query(
|
||||
`
|
||||
INSERT INTO adminusers (
|
||||
id, name, username, email, passwordhash, role,
|
||||
id, name, username, email, passwordhash, role, role_id,
|
||||
passwordneverexpires, password_expires_at,
|
||||
isactive, created_by, createdat, lastpasswordchange
|
||||
) VALUES (
|
||||
'user-' || gen_random_uuid()::text,
|
||||
$1, $2, $3, $4, $5, $6, $7, true, $8, NOW(), NOW()
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, true, $9, NOW(), NOW()
|
||||
)
|
||||
RETURNING id, name, username, email, role, isactive, createdat, passwordneverexpires
|
||||
RETURNING id, name, username, email, role, role_id, isactive, createdat, passwordneverexpires
|
||||
`,
|
||||
[
|
||||
name || username,
|
||||
username,
|
||||
email,
|
||||
hashedPassword,
|
||||
role,
|
||||
roleName,
|
||||
roleId,
|
||||
passwordneverexpires || false,
|
||||
passwordExpiresAt,
|
||||
req.session.user.email,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
res.json({
|
||||
@@ -196,8 +252,36 @@ router.put("/:id", async (req, res) => {
|
||||
values.push(email);
|
||||
}
|
||||
if (role !== undefined) {
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
|
||||
updates.push(`role = $${paramCount++}`);
|
||||
values.push(role);
|
||||
values.push(roleName);
|
||||
updates.push(`role_id = $${paramCount++}`);
|
||||
values.push(roleId);
|
||||
}
|
||||
if (isactive !== undefined) {
|
||||
updates.push(`isactive = $${paramCount++}`);
|
||||
@@ -215,29 +299,33 @@ router.put("/:id", async (req, res) => {
|
||||
|
||||
// Handle password update if provided
|
||||
if (password !== undefined && password !== "") {
|
||||
// Validate password strength
|
||||
if (password.length < 12) {
|
||||
// Validate password requirements
|
||||
if (password.length < 8) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Password must be at least 12 characters long",
|
||||
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",
|
||||
});
|
||||
}
|
||||
|
||||
// Check password complexity
|
||||
const hasUpperCase = /[A-Z]/.test(password);
|
||||
const hasLowerCase = /[a-z]/.test(password);
|
||||
const hasNumber = /\d/.test(password);
|
||||
const hasSpecialChar = /[@$!%*?&#]/.test(password);
|
||||
|
||||
if (!hasUpperCase || !hasLowerCase || !hasNumber || !hasSpecialChar) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message:
|
||||
"Password must contain uppercase, lowercase, number, and special character",
|
||||
});
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
updates.push(`passwordhash = $${paramCount++}`);
|
||||
values.push(hashedPassword);
|
||||
updates.push(`lastpasswordchange = NOW()`);
|
||||
@@ -251,9 +339,9 @@ router.put("/:id", async (req, res) => {
|
||||
UPDATE adminusers
|
||||
SET ${updates.join(", ")}
|
||||
WHERE id = $${paramCount}
|
||||
RETURNING id, name, username, email, role, isactive, passwordneverexpires
|
||||
RETURNING id, name, username, email, role, role_id, isactive, passwordneverexpires
|
||||
`,
|
||||
values
|
||||
values,
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -280,20 +368,39 @@ router.put("/:id/password", async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { password } = req.body;
|
||||
|
||||
// Validate password requirements
|
||||
if (!password || 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",
|
||||
});
|
||||
}
|
||||
|
||||
// Hash new password with bcrypt (10 rounds)
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
// Hash new password with bcrypt (12 rounds)
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
// Get user's password expiry setting
|
||||
const userResult = await query(
|
||||
"SELECT passwordneverexpires FROM adminusers WHERE id = $1",
|
||||
[id]
|
||||
[id],
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
@@ -321,7 +428,7 @@ router.put("/:id/password", async (req, res) => {
|
||||
updatedat = NOW()
|
||||
WHERE id = $3
|
||||
`,
|
||||
[hashedPassword, passwordExpiresAt, id]
|
||||
[hashedPassword, passwordExpiresAt, id],
|
||||
);
|
||||
|
||||
res.json({
|
||||
@@ -352,8 +459,8 @@ router.post("/:id/reset-password", async (req, res) => {
|
||||
|
||||
// Get user's password expiry setting
|
||||
const userResult = await query(
|
||||
"SELECT password_never_expires FROM adminusers WHERE id = $1",
|
||||
[id]
|
||||
"SELECT passwordneverexpires FROM adminusers WHERE id = $1",
|
||||
[id],
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
@@ -365,7 +472,7 @@ router.post("/:id/reset-password", async (req, res) => {
|
||||
|
||||
// Calculate new expiry date (90 days from now if not never expires)
|
||||
let passwordExpiresAt = null;
|
||||
if (!userResult.rows[0].password_never_expires) {
|
||||
if (!userResult.rows[0].passwordneverexpires) {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 90);
|
||||
passwordExpiresAt = expiryDate.toISOString();
|
||||
@@ -377,11 +484,11 @@ router.post("/:id/reset-password", async (req, res) => {
|
||||
UPDATE adminusers
|
||||
SET passwordhash = $1,
|
||||
password_expires_at = $2,
|
||||
last_password_change = NOW(),
|
||||
updated_at = NOW()
|
||||
lastpasswordchange = NOW(),
|
||||
updatedat = NOW()
|
||||
WHERE id = $3
|
||||
`,
|
||||
[hashedPassword, passwordExpiresAt, id]
|
||||
[hashedPassword, passwordExpiresAt, id],
|
||||
);
|
||||
|
||||
res.json({
|
||||
@@ -409,7 +516,7 @@ router.delete("/:id", async (req, res) => {
|
||||
|
||||
const result = await query(
|
||||
"DELETE FROM adminusers WHERE id = $1 RETURNING id",
|
||||
[id]
|
||||
[id],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
@@ -450,7 +557,7 @@ router.post("/:id/toggle-status", async (req, res) => {
|
||||
WHERE id = $1
|
||||
RETURNING id, isactive
|
||||
`,
|
||||
[id]
|
||||
[id],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user