feat: items #9-13 — password change, webmail SSO, DKIM live, file manager security, cache busting

#9  auth.php: add self-service change-password action (current+new+confirm)
    accounts.php: fix admin change-password — accept account_id, fetch username
    for chpasswd (was using int ID), add Auth::require('admin') guard
    user.js: add Change Password page + navItem + submitChangePassword()

#10 EmailManager: store AES-256-CBC enc_password alongside SHA512-CRYPT hash
    webmail.php: rewrite login-url to use webmail_sso_tokens table
    novacpx-sso.php: Roundcube SSO bridge (validate token, decrypt, autosubmit)
    Migration 005: add enc_password column + webmail_sso_tokens table

#11 opendkim: installed, configured (/etc/opendkim.conf, signing.table,
    key.table, trusted.hosts), socket at /var/spool/postfix/opendkim/,
    Postfix milter wired, service enabled+running, key generation verified

#12 files.php: fix safe_path() for non-existent paths (write/mkdir),
    add safe_path_new() helper using parent-dir realpath check,
    fix delete guard (block deleting account root dirs),
    fix rename destination, clamp chmod to 0777

#13 nova.js: api() handles network errors, 429 rate-limit with retry-after,
    non-JSON responses (PHP fatal pages) — graceful error instead of throw
    admin/user/reseller index.php: filemtime-based cache-busting on all assets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 01:19:33 +00:00
parent 62707d62ce
commit 6fdccc6dbd
27 changed files with 1736 additions and 79 deletions
+5 -5
View File
@@ -109,15 +109,15 @@ match ($action) {
Response::success(null, 'Account terminated');
})(),
'change-password' => (function() use ($db, $body) {
$id = (int)($body['id'] ?? 0);
'change-password' => (function() use ($db, $body, $user) {
Auth::getInstance()->require('admin');
$id = (int)($body['account_id'] ?? $body['id'] ?? 0);
$pass = $body['password'] ?? '';
if (strlen($pass) < 8) Response::error("Password must be at least 8 characters");
$acct = $db->fetchOne("SELECT user_id FROM accounts WHERE id = ?", [$id]);
$acct = $db->fetchOne("SELECT a.user_id, a.username FROM accounts a WHERE a.id = ?", [$id]);
if (!$acct) Response::error("Account not found", 404);
$db->execute("UPDATE users SET password = ? WHERE id = ?", [password_hash($pass, PASSWORD_BCRYPT), $acct['user_id']]);
// Also update system user password
shell_exec("echo " . escapeshellarg("{$id}:{$pass}") . " | chpasswd 2>/dev/null");
shell_exec("echo " . escapeshellarg("{$acct['username']}:{$pass}") . " | sudo chpasswd 2>/dev/null");
audit('account.change-password', "account:$id");
Response::success(null, 'Password changed');
})(),