diff --git a/public/index.html b/public/index.html
index 9098046..85d31f8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -302,6 +302,7 @@
+
@@ -374,6 +375,29 @@
+
+
+
diff --git a/public/js/app.js b/public/js/app.js
index 253ceb0..b8d7fc3 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -70,9 +70,14 @@
detailMemTotal: document.getElementById('detailMemTotal'),
detailUptime: document.getElementById('detailUptime'),
detailContainer: document.getElementById('detailContainer'),
- sourceFilter: document.getElementById('sourceFilter'),
pageSizeSelect: document.getElementById('pageSizeSelect'),
- paginationControls: document.getElementById('paginationControls')
+ paginationControls: document.getElementById('paginationControls'),
+ // Auth security
+ oldPasswordInput: document.getElementById('oldPassword'),
+ newPasswordInput: document.getElementById('newPassword'),
+ confirmNewPasswordInput: document.getElementById('confirmNewPassword'),
+ btnChangePassword: document.getElementById('btnChangePassword'),
+ changePasswordMessage: document.getElementById('changePasswordMessage')
};
// ---- State ----
@@ -124,6 +129,11 @@
// Site settings
dom.btnSaveSiteSettings.addEventListener('click', saveSiteSettings);
+
+ // Auth password change
+ if (dom.btnChangePassword) {
+ dom.btnChangePassword.addEventListener('click', saveChangePassword);
+ }
// Keyboard shortcut
document.addEventListener('keydown', (e) => {
@@ -721,6 +731,12 @@
dom.settingsModal.classList.remove('active');
hideMessage();
hideSiteMessage();
+ hideChangePasswordMessage();
+
+ // Reset password fields
+ if (dom.oldPasswordInput) dom.oldPasswordInput.value = '';
+ if (dom.newPasswordInput) dom.newPasswordInput.value = '';
+ if (dom.confirmNewPasswordInput) dom.confirmNewPasswordInput.value = '';
}
// ---- Tab Switching ----
@@ -849,6 +865,76 @@
dom.siteSettingsMessage.className = 'form-message';
}
+ async function saveChangePassword() {
+ if (!user) {
+ showChangePasswordMessage('请先登录后操作', 'error');
+ openLoginModal();
+ return;
+ }
+
+ const oldPassword = dom.oldPasswordInput.value;
+ const newPassword = dom.newPasswordInput.value;
+ const confirmNewPassword = dom.confirmNewPasswordInput.value;
+
+ if (!oldPassword || !newPassword || !confirmNewPassword) {
+ showChangePasswordMessage('请填写所有密码字段', 'error');
+ return;
+ }
+
+ if (newPassword !== confirmNewPassword) {
+ showChangePasswordMessage('两次输入的新密码不一致', 'error');
+ return;
+ }
+
+ if (newPassword.length < 6) {
+ showChangePasswordMessage('新密码长度至少为 6 位', 'error');
+ return;
+ }
+
+ dom.btnChangePassword.disabled = true;
+ dom.btnChangePassword.textContent = '提交中...';
+
+ try {
+ const response = await fetch('/api/auth/change-password', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ oldPassword,
+ newPassword
+ })
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ showChangePasswordMessage('密码修改成功', 'success');
+ dom.oldPasswordInput.value = '';
+ dom.newPasswordInput.value = '';
+ dom.confirmNewPasswordInput.value = '';
+ } else {
+ showChangePasswordMessage(data.error || '修改失败', 'error');
+ if (response.status === 401 && data.error === 'Auth required') openLoginModal();
+ }
+ } catch (err) {
+ showChangePasswordMessage(`请求失败: ${err.message}`, 'error');
+ } finally {
+ dom.btnChangePassword.disabled = false;
+ dom.btnChangePassword.textContent = '提交修改';
+ }
+ }
+
+ function showChangePasswordMessage(text, type) {
+ dom.changePasswordMessage.textContent = text;
+ dom.changePasswordMessage.className = `form-message ${type}`;
+ setTimeout(hideChangePasswordMessage, 5000);
+ }
+
+ function hideChangePasswordMessage() {
+ dom.changePasswordMessage.className = 'form-message';
+ }
+
function updateSourceFilterOptions(sources) {
if (!dom.sourceFilter) return;
const current = dom.sourceFilter.value;
diff --git a/server/index.js b/server/index.js
index de47ff6..6adf177 100644
--- a/server/index.js
+++ b/server/index.js
@@ -80,6 +80,34 @@ app.post('/api/auth/logout', (req, res) => {
res.json({ success: true });
});
+app.post('/api/auth/change-password', requireAuth, async (req, res) => {
+ const { oldPassword, newPassword } = req.body;
+ if (!oldPassword || !newPassword) {
+ return res.status(400).json({ error: '需要输入旧密码和新密码' });
+ }
+
+ try {
+ const [rows] = await db.query('SELECT * FROM users WHERE id = ?', [req.user.id]);
+ if (rows.length === 0) return res.status(404).json({ error: '用户不存在' });
+
+ const user = rows[0];
+ const oldHash = crypto.pbkdf2Sync(oldPassword, user.salt, 1000, 64, 'sha512').toString('hex');
+
+ if (oldHash !== user.password) {
+ return res.status(401).json({ error: '旧密码输入错误' });
+ }
+
+ const newSalt = crypto.randomBytes(16).toString('hex');
+ const newHash = crypto.pbkdf2Sync(newPassword, newSalt, 1000, 64, 'sha512').toString('hex');
+
+ await db.query('UPDATE users SET password = ?, salt = ? WHERE id = ?', [newHash, newSalt, user.id]);
+ res.json({ success: true, message: '密码修改成功' });
+ } catch (err) {
+ console.error('Password update error:', err);
+ res.status(500).json({ error: '服务器错误,修改失败' });
+ }
+});
+
app.get('/api/auth/status', (req, res) => {
const sessionId = getCookie(req, 'session_id');
if (sessionId && sessions.has(sessionId)) {