From cfbb134587195f319c0f3a1292408723c85350ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=96=BB=E5=8B=87=E7=A5=A5?= <237899745@qq.com> Date: Mon, 24 Nov 2025 15:48:32 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E9=AA=8C=E8=AF=81=E5=92=8C=E5=AF=86=E7=A0=81=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=E6=97=B6=E9=97=B4=E6=88=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端修复: - 修改时间戳存储格式:从 ISO 字符串改为数值时间戳(毫秒) - 优化时间戳比较逻辑:兼容新旧格式的时间戳 - 修复 VerificationDB.consumeVerificationToken() 的时间比较 - 修复 PasswordResetTokenDB.use() 的时间比较 - 统一使用 Date.now() 生成时间戳 前端改进: - 新增 verifyMessage 独立显示验证相关提示 - 优化邮箱验证成功后的用户体验(自动切换到登录表单) - 优化密码重置成功后的用户体验(自动切换到登录表单) - 改进提示信息显示方式 技术细节: - SQLite 时间比较:strftime('%s','now')*1000 获取当前毫秒时间戳 - 兼容旧数据:同时支持字符串和数值时间戳比较 这个修复解决了邮箱验证令牌一直提示过期的问题。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- backend/database.js | 22 ++++++++++++++++------ backend/server.js | 12 ++++++------ frontend/app.html | 1 + frontend/app.js | 9 ++++++--- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/backend/database.js b/backend/database.js index 295ff7b..e275f97 100644 --- a/backend/database.js +++ b/backend/database.js @@ -510,17 +510,24 @@ const SettingsDB = { // 邮箱验证管理 const VerificationDB = { - setVerification(userId, token, expiresAt) { + setVerification(userId, token, expiresAtMs) { db.prepare(` UPDATE users SET verification_token = ?, verification_expires_at = ?, is_verified = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ? - `).run(token, expiresAt, userId); + `).run(token, expiresAtMs, userId); }, consumeVerificationToken(token) { const row = db.prepare(` SELECT * FROM users - WHERE verification_token = ? AND verification_expires_at > CURRENT_TIMESTAMP AND is_verified = 0 + WHERE verification_token = ? + AND ( + verification_expires_at IS NULL + OR verification_expires_at = '' + OR verification_expires_at > strftime('%s','now')*1000 -- 数值时间戳(ms) + OR verification_expires_at > CURRENT_TIMESTAMP -- 兼容旧的字符串时间 + ) + AND is_verified = 0 `).get(token); if (!row) return null; @@ -535,16 +542,19 @@ const VerificationDB = { // 密码重置 Token 管理 const PasswordResetTokenDB = { - create(userId, token, expiresAt) { + create(userId, token, expiresAtMs) { db.prepare(` INSERT INTO password_reset_tokens (user_id, token, expires_at, used) VALUES (?, ?, ?, 0) - `).run(userId, token, expiresAt); + `).run(userId, token, expiresAtMs); }, use(token) { const row = db.prepare(` SELECT * FROM password_reset_tokens - WHERE token = ? AND used = 0 AND expires_at > CURRENT_TIMESTAMP + WHERE token = ? AND used = 0 AND ( + expires_at > strftime('%s','now')*1000 -- 数值时间戳 + OR expires_at > CURRENT_TIMESTAMP -- 兼容旧的字符串时间 + ) `).get(token); if (!row) return null; db.prepare(`UPDATE password_reset_tokens SET used = 1 WHERE id = ?`).run(row.id); diff --git a/backend/server.js b/backend/server.js index 7f6b98d..673d673 100644 --- a/backend/server.js +++ b/backend/server.js @@ -815,7 +815,7 @@ app.post('/api/register', } const verifyToken = generateRandomToken(24); - const expiresAt = new Date(Date.now() + 30 * 60 * 1000).toISOString(); // 30分钟 + const expiresAtMs = Date.now() + 30 * 60 * 1000; // 30分钟 // 创建用户(不需要FTP配置),标记未验证 const userId = UserDB.create({ @@ -824,7 +824,7 @@ app.post('/api/register', password, is_verified: 0, verification_token: verifyToken, - verification_expires_at: expiresAt + verification_expires_at: expiresAtMs }); const verifyLink = `${getProtocol(req)}://${req.get('host')}/?verifyToken=${verifyToken}`; @@ -891,8 +891,8 @@ app.post('/api/resend-verification', [ } const verifyToken = generateRandomToken(24); - const expiresAt = new Date(Date.now() + 30 * 60 * 1000).toISOString(); - VerificationDB.setVerification(user.id, verifyToken, expiresAt); + const expiresAtMs = Date.now() + 30 * 60 * 1000; + VerificationDB.setVerification(user.id, verifyToken, expiresAtMs); const verifyLink = `${getProtocol(req)}://${req.get('host')}/?verifyToken=${verifyToken}`; await sendMail( @@ -956,8 +956,8 @@ app.post('/api/password/forgot', [ } const token = generateRandomToken(24); - const expiresAt = new Date(Date.now() + 30 * 60 * 1000).toISOString(); - PasswordResetTokenDB.create(user.id, token, expiresAt); + const expiresAtMs = Date.now() + 30 * 60 * 1000; + PasswordResetTokenDB.create(user.id, token, expiresAtMs); const resetLink = `${getProtocol(req)}://${req.get('host')}/?resetToken=${token}`; await sendMail( diff --git a/frontend/app.html b/frontend/app.html index 7ab7d77..ea534cd 100644 --- a/frontend/app.html +++ b/frontend/app.html @@ -663,6 +663,7 @@
{{ errorMessage }}
{{ successMessage }}
+
{{ verifyMessage }}
diff --git a/frontend/app.js b/frontend/app.js index 2b21b7a..a45f316 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -158,6 +158,7 @@ createApp({ // 提示信息 errorMessage: '', successMessage: '', + verifyMessage: '', // 存储相关 storageType: 'sftp', // 当前使用的存储类型 @@ -453,7 +454,8 @@ handleDragLeave(e) { try { const response = await axios.get(`${this.apiBase}/api/verify-email`, { params: { token } }); if (response.data.success) { - this.showToast('success', '成功', '邮箱验证成功,请登录'); + this.verifyMessage = '邮箱验证成功,请登录'; + this.isLogin = true; // 清理URL const url = new URL(window.location.href); url.searchParams.delete('verifyToken'); @@ -461,7 +463,7 @@ handleDragLeave(e) { } } catch (error) { console.error('邮箱验证失败:', error); - this.showToast('error', '错误', error.response?.data?.message || '验证失败'); + this.verifyMessage = error.response?.data?.message || '验证失败'; } }, @@ -1675,7 +1677,8 @@ handleDragLeave(e) { try { const response = await axios.post(`${this.apiBase}/api/password/reset`, this.resetPasswordForm); if (response.data.success) { - this.showToast('success', '成功', '密码已重置,请登录'); + this.verifyMessage = '密码已重置,请登录'; + this.isLogin = true; this.showResetPasswordModal = false; this.resetPasswordForm = { token: '', new_password: '' }; // 清理URL中的token