Release v2.4.2

This commit is contained in:
2026-08-03 12:45:27 +01:00
parent 2b9cabdab2
commit e95928f31c
141 changed files with 7694 additions and 5078 deletions
+61 -14
View File
@@ -123,7 +123,7 @@
const connection = await pool.getConnection();
try {
const name = String(req.body.name || '').trim();
const username = String(req.body.username || '').trim();
const username = String(req.body.username || user.username || '').trim();
const password = String(req.body.password || '');
const confirmPassword = String(req.body.confirm_password || '');
const selectedRoleIds = readArrayField(req.body, ['role_ids[]', 'role_ids']);
@@ -171,7 +171,7 @@
);
await rbacData.syncUserRoles(connection, result.insertId, roleCheck.roleIds);
await connection.commit();
res.redirect('/users?message=' + encodeURIComponent('User created.'));
res.redirect('/users/' + result.insertId + '/edit?message=' + encodeURIComponent('User created.'));
} catch (error) {
try {
await connection.rollback();
@@ -213,46 +213,93 @@
});
app.post('/users/:id/username', requirePermission('users.update'), async function (req, res, next) {
const connection = await pool.getConnection();
try {
const userId = Number(req.params.id);
const name = String(req.body.name || '').trim();
const selectedRoleIds = readArrayField(req.body, ['role_ids[]', 'role_ids']);
const password = String(req.body.password || '');
const confirmPassword = String(req.body.confirm_password || '');
const shouldUpdatePassword = Boolean(password || confirmPassword);
if (!Number.isInteger(userId) || userId <= 0) {
return res.status(400).send('Invalid user.');
}
if (!name) {
return res.redirect('/users/' + userId + '/edit?message=' + encodeURIComponent('Name is required.'));
}
if (Number(req.currentUser.id) === userId) {
return res.redirect('/account?message=' + encodeURIComponent('Use My Account to update your own username or password.'));
return res.redirect('/account?message=' + encodeURIComponent('Use My Account to update your own login details.'));
}
const [userRows] = await pool.query('SELECT id, username FROM a_users WHERE id = ? LIMIT 1', [userId]);
if (!userRows.length) {
const user = await rbacData.fetchUserWithRoles(pool, userId);
if (!user) {
return res.status(404).send('User not found.');
}
const username = String(req.body.username || userRows[0].username || '').trim();
if (!username) {
return res.redirect('/users/' + userId + '/edit?message=' + encodeURIComponent('Username is required.'));
const username = String(req.body.username || user.username || '').trim();
const [countRows] = await pool.query('SELECT COUNT(*) AS user_count FROM a_users');
const canDelete = !countRows.length || Number(countRows[0].user_count) > 1;
const roleCheck = await validateRoleIds(selectedRoleIds);
async function renderValidationError(message) {
const roles = await fetchRoleOptions();
return res.status(400).send(pages.renderUsersEditPage(Object.assign({}, user, {
name: name || user.name,
username: username || user.username,
roleIds: selectedRoleIds,
inUse: !canDelete
}), message, req.currentUser, mapRolesForForm(roles, selectedRoleIds)));
}
if (!name) {
return renderValidationError('Name is required.');
}
if (!username) {
return renderValidationError('Username is required.');
}
if (shouldUpdatePassword && (!password || password.length < 8)) {
return renderValidationError('Password must be at least 8 characters.');
}
if (shouldUpdatePassword && password !== confirmPassword) {
return renderValidationError('Passwords do not match.');
}
if (!roleCheck.ok) {
return renderValidationError(roleCheck.message);
}
if (await common.fetchDuplicateName(pool, 'a_users', name, userId)) {
return res.redirect('/users/' + userId + '/edit?message=' + encodeURIComponent('That name already exists.'));
return renderValidationError('That name already exists.');
}
const [existingRows] = await pool.query('SELECT id FROM a_users WHERE username = ? AND id <> ? LIMIT 1', [username, userId]);
if (existingRows.length) {
return res.redirect('/users/' + userId + '/edit?message=' + encodeURIComponent('That username already exists.'));
return renderValidationError('That username already exists.');
}
const [result] = await pool.query('UPDATE a_users SET name = ?, username = ?, modified_by = ? WHERE id = ?', [name, username, getAuditUserId(req), userId]);
await connection.beginTransaction();
const [result] = await connection.query('UPDATE a_users SET name = ?, username = ?, modified_by = ? WHERE id = ?', [name, username, getAuditUserId(req), userId]);
if (!result.affectedRows) {
await connection.rollback();
return res.status(404).send('User not found.');
}
await rbacData.syncUserRoles(connection, userId, roleCheck.roleIds);
if (shouldUpdatePassword) {
const passwordRecord = hashPassword(password);
await connection.query(
'UPDATE a_users SET password_hash = ?, password_salt = ?, password_iterations = ?, modified_by = ? WHERE id = ?',
[passwordRecord.hash, passwordRecord.salt, passwordRecord.iterations, getAuditUserId(req), userId]
);
await connection.query('DELETE FROM a_sessions WHERE user_id = ?', [userId]);
}
await connection.commit();
res.redirect('/users?message=' + encodeURIComponent('User updated.'));
} catch (error) {
try {
await connection.rollback();
} catch (_rollbackError) {
// Ignore rollback failures and surface the original error.
}
next(error);
} finally {
connection.release();
}
});