webupdate

This commit is contained in:
Local Server
2026-01-18 02:22:05 -06:00
parent 6fc159051a
commit 2a2a3d99e5
135 changed files with 54897 additions and 9825 deletions

View File

@@ -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) {