🐛 修复邮箱验证和密码重置的时间戳问题

后端修复:
- 修改时间戳存储格式:从 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 <noreply@anthropic.com>
This commit is contained in:
2025-11-24 15:48:32 +08:00
parent 472b98153d
commit cfbb134587
4 changed files with 29 additions and 15 deletions

View File

@@ -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);

View File

@@ -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(