mirror of
https://github.com/dqzboy/Docker-Proxy.git
synced 2026-01-12 16:25:42 +08:00
feat: Add user password reset and username modification features
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -161,3 +161,4 @@ cython_debug/
|
||||
node_modules
|
||||
.DS_Store
|
||||
hubcmdui/package-lock.json
|
||||
hubcmdui/data/app.db
|
||||
|
||||
@@ -73,7 +73,12 @@ app.use('/api', (req, res, next) => {
|
||||
'/api/config',
|
||||
'/api/monitoring-config',
|
||||
'/api/documentation',
|
||||
'/api/documentation/file'
|
||||
'/api/documentation/file',
|
||||
'/api/captcha',
|
||||
'/api/auth/captcha',
|
||||
'/api/auth/request-reset-token',
|
||||
'/api/auth/reset-password',
|
||||
'/api/auth/validate-reset-token'
|
||||
];
|
||||
|
||||
// 如果是公共API或用户已登录,则继续
|
||||
|
||||
@@ -82,6 +82,31 @@ router.post('/change-password', requireLogin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 修改用户名
|
||||
router.post('/change-username', requireLogin, async (req, res) => {
|
||||
const { newUsername, password } = req.body;
|
||||
|
||||
// 用户名格式校验
|
||||
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
|
||||
if (!usernameRegex.test(newUsername)) {
|
||||
return res.status(400).json({ error: '用户名格式不正确(3-20位,只能包含字母、数字和下划线)' });
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUsername = req.session.user.username;
|
||||
const result = await userServiceDB.changeUsername(currentUsername, newUsername, password);
|
||||
|
||||
// 更新session中的用户名
|
||||
req.session.user.username = newUsername;
|
||||
|
||||
logger.info(`用户 ${currentUsername} 已修改用户名为 ${newUsername}`);
|
||||
res.json({ success: true, newUsername });
|
||||
} catch (error) {
|
||||
logger.error('修改用户名失败:', error);
|
||||
res.status(400).json({ error: error.message || '修改用户名失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取用户信息
|
||||
router.get('/user-info', requireLogin, async (req, res) => {
|
||||
try {
|
||||
@@ -138,6 +163,79 @@ router.get('/check-session', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 请求密码重置令牌(需要用户名验证)
|
||||
router.post('/request-reset-token', async (req, res) => {
|
||||
const { username, captcha } = req.body;
|
||||
|
||||
// 验证码检查
|
||||
if (req.session.captcha !== parseInt(captcha)) {
|
||||
logger.warn(`重置密码验证码验证失败: ${username}`);
|
||||
return res.status(401).json({ error: '验证码错误' });
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证用户是否存在
|
||||
const user = await userServiceDB.getUserByUsername(username);
|
||||
if (!user) {
|
||||
logger.warn(`密码重置请求失败,用户不存在: ${username}`);
|
||||
return res.status(404).json({ error: '用户不存在' });
|
||||
}
|
||||
|
||||
// 生成重置令牌
|
||||
const token = userServiceDB.generateResetToken(username);
|
||||
|
||||
logger.info(`用户 ${username} 请求了密码重置令牌`);
|
||||
|
||||
// 返回令牌(在生产环境中,这应该通过邮件发送)
|
||||
res.json({
|
||||
success: true,
|
||||
token,
|
||||
message: '重置令牌已生成,有效期10分钟',
|
||||
expiresIn: '10分钟'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('生成重置令牌失败:', error);
|
||||
res.status(500).json({ error: '生成重置令牌失败', details: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 使用令牌重置密码
|
||||
router.post('/reset-password', async (req, res) => {
|
||||
const { token, newPassword, confirmPassword } = req.body;
|
||||
|
||||
// 验证密码是否匹配
|
||||
if (newPassword !== confirmPassword) {
|
||||
return res.status(400).json({ error: '两次输入的密码不一致' });
|
||||
}
|
||||
|
||||
// 密码复杂度校验
|
||||
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[.,\-_+=()[\]{}|\\;:'"<>?/@$!%*#?&])[A-Za-z\d.,\-_+=()[\]{}|\\;:'"<>?/@$!%*#?&]{8,16}$/;
|
||||
if (!passwordRegex.test(newPassword)) {
|
||||
return res.status(400).json({ error: '密码需要8-16位,包含至少一个字母、一个数字和一个特殊字符' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await userServiceDB.resetPasswordWithToken(token, newPassword);
|
||||
logger.info(`用户 ${result.username} 通过重置令牌成功修改了密码`);
|
||||
res.json({ success: true, message: '密码重置成功,请使用新密码登录' });
|
||||
} catch (error) {
|
||||
logger.error('重置密码失败:', error);
|
||||
res.status(400).json({ error: error.message || '重置密码失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 验证重置令牌是否有效
|
||||
router.post('/validate-reset-token', (req, res) => {
|
||||
const { token } = req.body;
|
||||
|
||||
const username = userServiceDB.validateResetToken(token);
|
||||
if (username) {
|
||||
res.json({ valid: true, username });
|
||||
} else {
|
||||
res.status(400).json({ valid: false, error: '令牌无效或已过期' });
|
||||
}
|
||||
});
|
||||
|
||||
logger.success('✓ 认证路由已加载');
|
||||
|
||||
// 导出路由
|
||||
|
||||
@@ -155,6 +155,57 @@ class UserServiceDB {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户名
|
||||
*/
|
||||
async changeUsername(currentUsername, newUsername, password) {
|
||||
try {
|
||||
const user = await this.getUserByUsername(currentUsername);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
throw new Error('密码不正确');
|
||||
}
|
||||
|
||||
// 验证新用户名格式
|
||||
if (!this.isUsernameValid(newUsername)) {
|
||||
throw new Error('用户名格式不正确(3-20位,只能包含字母、数字和下划线)');
|
||||
}
|
||||
|
||||
// 检查新用户名是否已存在
|
||||
const existingUser = await this.getUserByUsername(newUsername);
|
||||
if (existingUser) {
|
||||
throw new Error('该用户名已被使用');
|
||||
}
|
||||
|
||||
// 更新用户名
|
||||
await database.run(
|
||||
'UPDATE users SET username = ?, updated_at = ? WHERE username = ?',
|
||||
[newUsername, new Date().toISOString(), currentUsername]
|
||||
);
|
||||
|
||||
logger.info(`用户 ${currentUsername} 已成功修改用户名为 ${newUsername}`);
|
||||
return { success: true, newUsername };
|
||||
} catch (error) {
|
||||
logger.error('修改用户名失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户名格式
|
||||
*/
|
||||
isUsernameValid(username) {
|
||||
// 3-20位,只能包含字母、数字和下划线
|
||||
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
|
||||
return usernameRegex.test(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码复杂度
|
||||
*/
|
||||
@@ -185,6 +236,132 @@ class UserServiceDB {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成密码重置令牌
|
||||
* 返回一个临时令牌,存储在内存中,有效期10分钟
|
||||
*/
|
||||
generateResetToken(username) {
|
||||
const crypto = require('crypto');
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
const expiry = Date.now() + 10 * 60 * 1000; // 10分钟有效期
|
||||
|
||||
// 存储在内存中(简单实现)
|
||||
if (!this.resetTokens) {
|
||||
this.resetTokens = {};
|
||||
}
|
||||
|
||||
this.resetTokens[token] = {
|
||||
username,
|
||||
expiry
|
||||
};
|
||||
|
||||
// 清理过期令牌
|
||||
this.cleanExpiredTokens();
|
||||
|
||||
logger.info(`为用户 ${username} 生成了密码重置令牌`);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的重置令牌
|
||||
*/
|
||||
cleanExpiredTokens() {
|
||||
if (!this.resetTokens) return;
|
||||
|
||||
const now = Date.now();
|
||||
for (const token in this.resetTokens) {
|
||||
if (this.resetTokens[token].expiry < now) {
|
||||
delete this.resetTokens[token];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证重置令牌
|
||||
*/
|
||||
validateResetToken(token) {
|
||||
if (!this.resetTokens || !this.resetTokens[token]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenData = this.resetTokens[token];
|
||||
if (tokenData.expiry < Date.now()) {
|
||||
delete this.resetTokens[token];
|
||||
return null;
|
||||
}
|
||||
|
||||
return tokenData.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用令牌重置密码
|
||||
*/
|
||||
async resetPasswordWithToken(token, newPassword) {
|
||||
try {
|
||||
const username = this.validateResetToken(token);
|
||||
if (!username) {
|
||||
throw new Error('重置令牌无效或已过期');
|
||||
}
|
||||
|
||||
// 验证新密码复杂度
|
||||
if (!this.isPasswordComplex(newPassword)) {
|
||||
throw new Error('新密码不符合复杂度要求(需要8-16位,包含字母、数字和特殊字符)');
|
||||
}
|
||||
|
||||
const user = await this.getUserByUsername(username);
|
||||
if (!user) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
const hashedNewPassword = await bcrypt.hash(newPassword, 10);
|
||||
await database.run(
|
||||
'UPDATE users SET password = ?, updated_at = ? WHERE username = ?',
|
||||
[hashedNewPassword, new Date().toISOString(), username]
|
||||
);
|
||||
|
||||
// 删除使用过的令牌
|
||||
delete this.resetTokens[token];
|
||||
|
||||
logger.info(`用户 ${username} 密码已通过重置令牌成功修改`);
|
||||
return { success: true, username };
|
||||
} catch (error) {
|
||||
logger.error('重置密码失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接重置用户密码(管理员功能,无需旧密码)
|
||||
* 用于忘记密码时,通过验证用户名来重置
|
||||
*/
|
||||
async forceResetPassword(username, newPassword) {
|
||||
try {
|
||||
const user = await this.getUserByUsername(username);
|
||||
if (!user) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
// 验证新密码复杂度
|
||||
if (!this.isPasswordComplex(newPassword)) {
|
||||
throw new Error('新密码不符合复杂度要求(需要8-16位,包含字母、数字和特殊字符)');
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
const hashedNewPassword = await bcrypt.hash(newPassword, 10);
|
||||
await database.run(
|
||||
'UPDATE users SET password = ?, updated_at = ? WHERE username = ?',
|
||||
[hashedNewPassword, new Date().toISOString(), username]
|
||||
);
|
||||
|
||||
logger.info(`用户 ${username} 密码已被强制重置`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error('强制重置密码失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new UserServiceDB();
|
||||
|
||||
@@ -2963,6 +2963,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改用户名卡片 -->
|
||||
<div class="user-center-card">
|
||||
<div class="user-center-section">
|
||||
<h2 class="user-center-section-title">修改用户名</h2>
|
||||
<form id="changeUsernameForm">
|
||||
<label for="ucNewUsername">新用户名</label>
|
||||
<span class="password-hint">用户名需要3-20位,只能包含字母、数字和下划线</span>
|
||||
<input type="text" id="ucNewUsername" name="newUsername" placeholder="请输入新用户名">
|
||||
<label for="ucUsernamePassword">当前密码</label>
|
||||
<span class="password-hint">修改用户名需要验证当前密码</span>
|
||||
<input type="password" id="ucUsernamePassword" name="password" placeholder="请输入当前密码">
|
||||
<div style="display: flex; align-items: center; margin-top: 10px;">
|
||||
<button type="submit" class="btn btn-primary">修改用户名</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 密码修改卡片 -->
|
||||
<div class="user-center-card">
|
||||
<div class="user-center-section">
|
||||
@@ -2992,9 +3010,10 @@
|
||||
<div class="login-modal" id="loginModal" style="display: none;">
|
||||
<div class="login-content">
|
||||
<div class="login-header">
|
||||
<h2>管理员登录</h2>
|
||||
<h2 id="loginTitle">管理员登录</h2>
|
||||
</div>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<form id="loginForm" class="login-form">
|
||||
<input type="text" id="username" name="username" placeholder="用户名" required>
|
||||
<input type="password" id="password" name="password" placeholder="密码" required>
|
||||
@@ -3004,6 +3023,44 @@
|
||||
<span id="captchaText" onclick="auth.refreshCaptcha()">点击刷新验证码</span>
|
||||
</div>
|
||||
<button type="submit" id="loginButton">登录</button>
|
||||
<div class="forgot-password-link" style="text-align: center; margin-top: 15px;">
|
||||
<a href="javascript:void(0)" onclick="auth.showForgotPassword()" style="color: #3d7cf4; text-decoration: none; font-size: 14px;">忘记密码?</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 忘记密码表单 - 步骤1: 验证用户名 -->
|
||||
<form id="forgotPasswordForm" class="login-form" style="display: none;">
|
||||
<div class="form-description" style="text-align: center; margin-bottom: 15px; color: #666; font-size: 14px;">
|
||||
请输入您的用户名来获取密码重置令牌
|
||||
</div>
|
||||
<input type="text" id="resetUsername" name="resetUsername" placeholder="用户名" required>
|
||||
<div class="captcha-container">
|
||||
<input type="text" id="resetCaptcha" name="resetCaptcha" placeholder="验证码" required>
|
||||
<span id="resetCaptchaText" onclick="auth.refreshCaptcha()">点击刷新验证码</span>
|
||||
</div>
|
||||
<button type="submit" id="getTokenButton">获取重置令牌</button>
|
||||
<div style="text-align: center; margin-top: 15px;">
|
||||
<a href="javascript:void(0)" onclick="auth.showLoginForm()" style="color: #3d7cf4; text-decoration: none; font-size: 14px;">← 返回登录</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 重置密码表单 - 步骤2: 设置新密码 -->
|
||||
<form id="resetPasswordForm" class="login-form" style="display: none;">
|
||||
<div class="form-description" style="text-align: center; margin-bottom: 15px; color: #666; font-size: 14px;">
|
||||
请设置您的新密码
|
||||
</div>
|
||||
<div id="resetTokenDisplay" style="background: #f0f8ff; padding: 10px; border-radius: 5px; margin-bottom: 15px; word-break: break-all; font-size: 12px; display: none;">
|
||||
<strong>重置令牌:</strong><span id="tokenValue"></span>
|
||||
</div>
|
||||
<input type="password" id="resetNewPassword" name="resetNewPassword" placeholder="新密码(8-16位,含字母、数字和特殊字符)" required>
|
||||
<input type="password" id="resetConfirmPassword" name="resetConfirmPassword" placeholder="确认新密码" required>
|
||||
<div class="password-requirements" style="font-size: 12px; color: #888; margin-bottom: 15px;">
|
||||
密码要求:8-16位,包含字母、数字和特殊字符
|
||||
</div>
|
||||
<button type="submit" id="resetPasswordButton">重置密码</button>
|
||||
<div style="text-align: center; margin-top: 15px;">
|
||||
<a href="javascript:void(0)" onclick="auth.showLoginForm()" style="color: #3d7cf4; text-decoration: none; font-size: 14px;">← 返回登录</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// 用户认证相关功能
|
||||
|
||||
// 存储重置令牌
|
||||
let currentResetToken = null;
|
||||
|
||||
// 登录函数
|
||||
async function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
@@ -82,10 +85,28 @@ async function refreshCaptcha() {
|
||||
throw new Error(`验证码获取失败: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
document.getElementById('captchaText').textContent = data.captcha;
|
||||
|
||||
// 更新登录表单验证码
|
||||
const captchaText = document.getElementById('captchaText');
|
||||
if (captchaText) {
|
||||
captchaText.textContent = data.captcha;
|
||||
}
|
||||
|
||||
// 更新忘记密码表单验证码
|
||||
const resetCaptchaText = document.getElementById('resetCaptchaText');
|
||||
if (resetCaptchaText) {
|
||||
resetCaptchaText.textContent = data.captcha;
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('刷新验证码失败:', error);
|
||||
document.getElementById('captchaText').textContent = '验证码加载失败,点击重试';
|
||||
const captchaText = document.getElementById('captchaText');
|
||||
if (captchaText) {
|
||||
captchaText.textContent = '验证码加载失败,点击重试';
|
||||
}
|
||||
const resetCaptchaText = document.getElementById('resetCaptchaText');
|
||||
if (resetCaptchaText) {
|
||||
resetCaptchaText.textContent = '验证码加载失败,点击重试';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,20 +125,171 @@ function showLoginModal() {
|
||||
}
|
||||
|
||||
document.getElementById('loginModal').style.display = 'flex';
|
||||
showLoginForm(); // 确保显示登录表单
|
||||
refreshCaptcha();
|
||||
}
|
||||
|
||||
// 显示登录表单
|
||||
function showLoginForm() {
|
||||
document.getElementById('loginTitle').textContent = '管理员登录';
|
||||
document.getElementById('loginForm').style.display = 'block';
|
||||
document.getElementById('forgotPasswordForm').style.display = 'none';
|
||||
document.getElementById('resetPasswordForm').style.display = 'none';
|
||||
currentResetToken = null;
|
||||
refreshCaptcha();
|
||||
}
|
||||
|
||||
// 显示忘记密码表单
|
||||
function showForgotPassword() {
|
||||
document.getElementById('loginTitle').textContent = '忘记密码';
|
||||
document.getElementById('loginForm').style.display = 'none';
|
||||
document.getElementById('forgotPasswordForm').style.display = 'block';
|
||||
document.getElementById('resetPasswordForm').style.display = 'none';
|
||||
refreshCaptcha();
|
||||
}
|
||||
|
||||
// 显示重置密码表单
|
||||
function showResetPasswordForm(token) {
|
||||
document.getElementById('loginTitle').textContent = '重置密码';
|
||||
document.getElementById('loginForm').style.display = 'none';
|
||||
document.getElementById('forgotPasswordForm').style.display = 'none';
|
||||
document.getElementById('resetPasswordForm').style.display = 'block';
|
||||
|
||||
if (token) {
|
||||
currentResetToken = token;
|
||||
document.getElementById('tokenValue').textContent = token;
|
||||
document.getElementById('resetTokenDisplay').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 请求重置令牌
|
||||
async function requestResetToken() {
|
||||
const username = document.getElementById('resetUsername').value;
|
||||
const captcha = document.getElementById('resetCaptcha').value;
|
||||
|
||||
if (!username) {
|
||||
core.showAlert('请输入用户名', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!captcha) {
|
||||
core.showAlert('请输入验证码', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
core.showLoading();
|
||||
const response = await fetch('/api/auth/request-reset-token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, captcha })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
core.showAlert('重置令牌已生成,有效期10分钟', 'success');
|
||||
showResetPasswordForm(data.token);
|
||||
} else {
|
||||
core.showAlert(data.error || '获取重置令牌失败', 'error');
|
||||
refreshCaptcha();
|
||||
}
|
||||
} catch (error) {
|
||||
core.showAlert('获取重置令牌失败: ' + error.message, 'error');
|
||||
refreshCaptcha();
|
||||
} finally {
|
||||
core.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
async function resetPassword() {
|
||||
const newPassword = document.getElementById('resetNewPassword').value;
|
||||
const confirmPassword = document.getElementById('resetConfirmPassword').value;
|
||||
|
||||
if (!newPassword || !confirmPassword) {
|
||||
core.showAlert('请填写所有密码字段', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
core.showAlert('两次输入的密码不一致', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 密码复杂度验证
|
||||
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[.,\-_+=()[\]{}|\\;:'"<>?/@$!%*#?&])[A-Za-z\d.,\-_+=()[\]{}|\\;:'"<>?/@$!%*#?&]{8,16}$/;
|
||||
if (!passwordRegex.test(newPassword)) {
|
||||
core.showAlert('密码需要8-16位,包含至少一个字母、一个数字和一个特殊字符', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentResetToken) {
|
||||
core.showAlert('重置令牌无效,请重新获取', 'error');
|
||||
showForgotPassword();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
core.showLoading();
|
||||
const response = await fetch('/api/auth/reset-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
token: currentResetToken,
|
||||
newPassword,
|
||||
confirmPassword
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
core.showAlert('密码重置成功!请使用新密码登录', 'success');
|
||||
currentResetToken = null;
|
||||
showLoginForm();
|
||||
} else {
|
||||
core.showAlert(data.error || '重置密码失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
core.showAlert('重置密码失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
core.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// 导出模块
|
||||
const auth = {
|
||||
init: function() {
|
||||
// console.log('初始化认证模块...');
|
||||
// 在这里可以添加认证模块初始化的相关代码
|
||||
// 初始化忘记密码表单事件
|
||||
const forgotPasswordForm = document.getElementById('forgotPasswordForm');
|
||||
if (forgotPasswordForm) {
|
||||
forgotPasswordForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
requestResetToken();
|
||||
});
|
||||
}
|
||||
|
||||
const resetPasswordForm = document.getElementById('resetPasswordForm');
|
||||
if (resetPasswordForm) {
|
||||
resetPasswordForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
resetPassword();
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(); // 返回一个已解决的 Promise,保持与其他模块一致
|
||||
},
|
||||
login,
|
||||
logout,
|
||||
refreshCaptcha,
|
||||
showLoginModal
|
||||
showLoginModal,
|
||||
showLoginForm,
|
||||
showForgotPassword,
|
||||
showResetPasswordForm,
|
||||
requestResetToken,
|
||||
resetPassword
|
||||
};
|
||||
|
||||
// 全局公开认证模块
|
||||
|
||||
@@ -163,6 +163,100 @@ function isPasswordComplex(password) {
|
||||
return passwordRegex.test(password);
|
||||
}
|
||||
|
||||
// 验证用户名格式
|
||||
function isUsernameValid(username) {
|
||||
// 3-20位,只能包含字母、数字和下划线
|
||||
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
|
||||
return usernameRegex.test(username);
|
||||
}
|
||||
|
||||
// 修改用户名
|
||||
async function changeUsername(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const form = document.getElementById('changeUsernameForm');
|
||||
const newUsername = form.querySelector('#ucNewUsername').value;
|
||||
const password = form.querySelector('#ucUsernamePassword').value;
|
||||
|
||||
// 验证表单
|
||||
if (!newUsername || !password) {
|
||||
return core.showAlert('所有字段都不能为空', 'error');
|
||||
}
|
||||
|
||||
// 用户名格式检查
|
||||
if (!isUsernameValid(newUsername)) {
|
||||
return core.showAlert('用户名格式不正确(3-20位,只能包含字母、数字和下划线)', 'error');
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
const originalButtonText = submitButton.innerHTML;
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 提交中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/change-username', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
newUsername,
|
||||
password
|
||||
})
|
||||
});
|
||||
|
||||
// 无论成功与否,去除加载状态
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = originalButtonText;
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || '修改用户名失败');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// 清空表单
|
||||
form.reset();
|
||||
|
||||
// 设置倒计时并显示
|
||||
let countDown = 5;
|
||||
|
||||
Swal.fire({
|
||||
title: '用户名修改成功',
|
||||
html: `您的用户名已成功修改为 <b>${data.newUsername}</b>,系统将在 <b>${countDown}</b> 秒后自动退出,请使用新用户名重新登录。`,
|
||||
icon: 'success',
|
||||
timer: countDown * 1000,
|
||||
timerProgressBar: true,
|
||||
didOpen: () => {
|
||||
const content = Swal.getHtmlContainer();
|
||||
const timerInterval = setInterval(() => {
|
||||
countDown--;
|
||||
if (content) {
|
||||
const b = content.querySelectorAll('b')[1]; // 获取第二个b标签(倒计时)
|
||||
if (b) {
|
||||
b.textContent = countDown > 0 ? countDown : 0;
|
||||
}
|
||||
}
|
||||
if (countDown <= 0) clearInterval(timerInterval);
|
||||
}, 1000);
|
||||
},
|
||||
allowOutsideClick: false,
|
||||
showConfirmButton: true,
|
||||
confirmButtonText: '确定'
|
||||
}).then((result) => {
|
||||
if (result.dismiss === Swal.DismissReason.timer || result.isConfirmed) {
|
||||
auth.logout();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
core.showAlert('修改用户名失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查密码强度
|
||||
function checkUcPasswordStrength() {
|
||||
const password = document.getElementById('ucNewPassword').value;
|
||||
@@ -312,11 +406,20 @@ function loadUserStats() {
|
||||
const userCenter = {
|
||||
init: function() {
|
||||
// console.log('初始化用户中心模块...');
|
||||
// 可以在这里调用初始化逻辑,也可以延迟到需要时调用
|
||||
// 初始化修改用户名表单事件
|
||||
const changeUsernameForm = document.getElementById('changeUsernameForm');
|
||||
if (changeUsernameForm) {
|
||||
changeUsernameForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
changeUsername();
|
||||
});
|
||||
}
|
||||
return Promise.resolve(); // 返回一个已解决的 Promise,保持与其他模块一致
|
||||
},
|
||||
getUserInfo,
|
||||
changePassword,
|
||||
changeUsername,
|
||||
isUsernameValid,
|
||||
checkUcPasswordStrength,
|
||||
initUserCenter,
|
||||
loadUserStats,
|
||||
|
||||
Reference in New Issue
Block a user