diff --git a/src/routes/web.js b/src/routes/web.js index 0a0420c6..97b8bc6c 100644 --- a/src/routes/web.js +++ b/src/routes/web.js @@ -146,6 +146,112 @@ router.post('/auth/logout', async (req, res) => { } }); +// 🔑 修改账户信息 +router.post('/auth/change-password', async (req, res) => { + try { + const token = req.headers['authorization']?.replace('Bearer ', '') || req.cookies?.adminToken; + + if (!token) { + return res.status(401).json({ + error: 'No token provided', + message: 'Authentication required' + }); + } + + const { newUsername, currentPassword, newPassword } = req.body; + + if (!currentPassword || !newPassword) { + return res.status(400).json({ + error: 'Missing required fields', + message: 'Current password and new password are required' + }); + } + + // 验证新密码长度 + if (newPassword.length < 8) { + return res.status(400).json({ + error: 'Password too short', + message: 'New password must be at least 8 characters long' + }); + } + + // 获取当前会话 + const sessionData = await redis.getSession(token); + if (!sessionData) { + return res.status(401).json({ + error: 'Invalid token', + message: 'Session expired or invalid' + }); + } + + // 获取当前管理员信息 + const adminData = await redis.getSession('admin_credentials'); + if (!adminData) { + return res.status(500).json({ + error: 'Admin data not found', + message: 'Administrator credentials not found' + }); + } + + // 验证当前密码 + const isValidPassword = await bcrypt.compare(currentPassword, adminData.passwordHash); + if (!isValidPassword) { + logger.security(`🔒 Invalid current password attempt for user: ${sessionData.username}`); + return res.status(401).json({ + error: 'Invalid current password', + message: 'Current password is incorrect' + }); + } + + // 准备更新的数据 + const saltRounds = 10; + const newPasswordHash = await bcrypt.hash(newPassword, saltRounds); + + const updatedAdminData = { + ...adminData, + passwordHash: newPasswordHash, + updatedAt: new Date().toISOString() + }; + + // 如果提供了新用户名,则更新用户名 + if (newUsername && newUsername.trim() && newUsername !== adminData.username) { + updatedAdminData.username = newUsername.trim(); + } + + // 更新Redis中的管理员凭据 + await redis.setSession('admin_credentials', updatedAdminData); + + // 更新data/init.json文件 + const initFilePath = path.join(__dirname, '../../data/init.json'); + if (fs.existsSync(initFilePath)) { + const initData = JSON.parse(fs.readFileSync(initFilePath, 'utf8')); + initData.adminUsername = updatedAdminData.username; + initData.adminPassword = newPassword; // 保存明文密码到init.json + initData.updatedAt = new Date().toISOString(); + + fs.writeFileSync(initFilePath, JSON.stringify(initData, null, 2)); + } + + // 清除当前会话(强制用户重新登录) + await redis.deleteSession(token); + + logger.success(`🔐 Admin password changed successfully for user: ${updatedAdminData.username}`); + + res.json({ + success: true, + message: 'Password changed successfully. Please login again.', + newUsername: updatedAdminData.username + }); + + } catch (error) { + logger.error('❌ Change password error:', error); + res.status(500).json({ + error: 'Change password failed', + message: 'Internal server error' + }); + } +}); + // 🔄 刷新token router.post('/auth/refresh', async (req, res) => { try { diff --git a/web/admin/app.js b/web/admin/app.js index 4e52bc48..b358c66e 100644 --- a/web/admin/app.js +++ b/web/admin/app.js @@ -186,6 +186,20 @@ const app = createApp({ callbackUrl: '' }, + // 用户菜单和账户修改相关 + userMenuOpen: false, + currentUser: { + username: '' + }, + showChangePasswordModal: false, + changePasswordLoading: false, + changePasswordForm: { + newUsername: '', + currentPassword: '', + newPassword: '', + confirmPassword: '' + }, + } }, @@ -202,6 +216,15 @@ const app = createApp({ // 初始化防抖函数 this.setTrendPeriod = this.debounce(this._setTrendPeriod, 300); + // 添加全局点击事件监听器,用于关闭用户菜单 + document.addEventListener('click', (event) => { + // 检查点击是否在用户菜单外部 + const userMenuButton = event.target.closest('.relative'); + if (!userMenuButton || !userMenuButton.querySelector('button[\@click*="userMenuOpen"]')) { + this.userMenuOpen = false; + } + }); + if (this.authToken) { this.isLoggedIn = true; @@ -784,6 +807,10 @@ const app = createApp({ this.authToken = data.token; localStorage.setItem('authToken', this.authToken); this.isLoggedIn = true; + + // 记录当前用户名 + this.currentUser.username = this.loginForm.username; + this.loadDashboard(); } else { this.loginError = data.message; @@ -796,6 +823,75 @@ const app = createApp({ } }, + // 用户菜单相关方法 + openChangePasswordModal() { + this.userMenuOpen = false; + this.showChangePasswordModal = true; + }, + + closeChangePasswordModal() { + this.showChangePasswordModal = false; + this.changePasswordForm = { + newUsername: '', + currentPassword: '', + newPassword: '', + confirmPassword: '' + }; + }, + + async changePassword() { + // 验证表单 + if (this.changePasswordForm.newPassword !== this.changePasswordForm.confirmPassword) { + this.showToast('新密码和确认密码不一致', 'error'); + return; + } + + if (this.changePasswordForm.newPassword.length < 8) { + this.showToast('新密码长度至少8位', 'error'); + return; + } + + this.changePasswordLoading = true; + try { + const response = await fetch('/web/auth/change-password', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + this.authToken + }, + body: JSON.stringify({ + newUsername: this.changePasswordForm.newUsername || undefined, + currentPassword: this.changePasswordForm.currentPassword, + newPassword: this.changePasswordForm.newPassword + }) + }); + + const result = await response.json(); + + if (result.success) { + this.showToast('账户信息修改成功,即将退出登录', 'success'); + this.closeChangePasswordModal(); + + // 将新的用户名更新到本地状态 + if (this.changePasswordForm.newUsername) { + this.currentUser.username = this.changePasswordForm.newUsername; + } + + // 延迟2秒后自动退出登录 + setTimeout(() => { + this.logout(); + }, 2000); + } else { + this.showToast(result.message || '修改失败', 'error'); + } + } catch (error) { + console.error('Change password error:', error); + this.showToast('网络错误,请稍后再试', 'error'); + } finally { + this.changePasswordLoading = false; + } + }, + async logout() { if (this.authToken) { try { diff --git a/web/admin/index.html b/web/admin/index.html index bd08e9bc..66d655d3 100644 --- a/web/admin/index.html +++ b/web/admin/index.html @@ -83,12 +83,42 @@
管理后台
- + +