修复后台删不掉内容系统发布的文章,登录状态重启后不再失效
- 删除文章:先删引用它的发布记录和实体关联再删文章(之前顺序反了,数据库外键拒绝删除),并放在一个事务里,失败时不会只删掉一半 - 管理员登录状态存到数据库(admin_sessions,只存令牌哈希),服务重启或部署后不用重新登录;登录过期时后台回到登录页并提示,而不是操作一直失败 - 内容系统发布的文章可修改知识中心列表里的摘要 - 文章缺少扩展记录时,修改分类和发布时间也能保存 - 提示文字改为“约 1 分钟内在网站生效”(前台页面缓存 60 秒) - 新增不连数据库的单元测试 tests/adminFixes.unit.test.js(其他测试会写 .env 里的库) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,9 @@ const crypto = require('crypto');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { getDb } = require('../config/database');
|
||||
|
||||
const sessions = new Map();
|
||||
const SESSION_MS = 8 * 60 * 60 * 1000;
|
||||
// Only a hash of the token is stored, so the table never holds a usable login.
|
||||
const tokenHash = (token) => crypto.createHash('sha256').update(String(token)).digest('hex');
|
||||
|
||||
const hashPassword = (password, salt = crypto.randomBytes(16).toString('hex')) => {
|
||||
const hash = crypto.scryptSync(password, salt, 64).toString('hex');
|
||||
@@ -23,17 +25,18 @@ class AdminAuthService {
|
||||
throw new Error('用户名或密码错误');
|
||||
}
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
sessions.set(token, { userId: user.user_id, username: user.username, expiresAt: Date.now() + 8 * 60 * 60 * 1000 });
|
||||
return { token, username: user.username, expiresIn: 8 * 60 * 60 };
|
||||
const db = getDb();
|
||||
db.prepare('DELETE FROM admin_sessions WHERE expires_at < ?').run(Date.now());
|
||||
db.prepare('INSERT INTO admin_sessions (token_hash, user_id, username, expires_at) VALUES (?, ?, ?, ?)')
|
||||
.run(tokenHash(token), user.user_id, user.username, Date.now() + SESSION_MS);
|
||||
return { token, username: user.username, expiresIn: SESSION_MS / 1000 };
|
||||
}
|
||||
|
||||
static verify(token) {
|
||||
const session = sessions.get(token);
|
||||
if (!session || session.expiresAt < Date.now()) {
|
||||
if (token) sessions.delete(token);
|
||||
return null;
|
||||
}
|
||||
return session;
|
||||
if (!token) return null;
|
||||
const row = getDb().prepare('SELECT user_id, username, expires_at FROM admin_sessions WHERE token_hash = ?').get(tokenHash(token));
|
||||
if (!row || Number(row.expires_at) < Date.now()) return null;
|
||||
return { userId: row.user_id, username: row.username, expiresAt: Number(row.expires_at) };
|
||||
}
|
||||
|
||||
static changePassword(username, currentPassword, newPassword) {
|
||||
@@ -43,14 +46,12 @@ class AdminAuthService {
|
||||
if (!user || !verifyPassword(currentPassword, user.password_hash)) throw new Error('当前密码错误');
|
||||
db.prepare('UPDATE admin_users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE username = ?')
|
||||
.run(hashPassword(newPassword), username);
|
||||
for (const [token, session] of sessions.entries()) {
|
||||
if (session.username === username) sessions.delete(token);
|
||||
}
|
||||
db.prepare('DELETE FROM admin_sessions WHERE username = ?').run(username);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
static logout(token) {
|
||||
sessions.delete(token);
|
||||
if (token) getDb().prepare('DELETE FROM admin_sessions WHERE token_hash = ?').run(tokenHash(token));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user