登录改用访问令牌 + 刷新令牌,不用 cookie

- 访问令牌:JWT(HS256),15 分钟,Authorization: Bearer;WebSocket 放在网址参数里
- 刷新令牌:30 天,每次刷新换新的,数据库只存哈希;退出登录即作废
- 重置密码、删除学生:令牌版本加一并删除刷新令牌,所有设备立即退出
- 签名密钥首次使用时生成,存在数据库 settings 表
- 浏览器:令牌存 localStorage;快过期先刷新,401 时刷新一次再重试;多个标签页同时刷新不冲突

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 18:20:41 +08:00
1 parent a3eb563b70
commit 5bb2940969
11 files changed
+332 -110

No files matched your search

+4 -4
View File
@@ -1,6 +1,6 @@
// 班级和学生名单(老师用)。学生账号由老师上传名单导入,密码由老师发放,学生不能自己改。
import type { Database } from 'bun:sqlite'
import { endSessions } from './auth'
import { revokeTokens } from './auth'
export interface ClassInfo {
id: number
@@ -115,7 +115,7 @@ export function importStudents(db: Database, classId: number, rows: ImportRow[])
}
if (existing) {
update.run(name, classId, password ?? null, existing.id)
if (password) endSessions(db, existing.id)
if (password) revokeTokens(db, existing.id)
result.updated++
} else {
insert.run(username, name, password ?? generatePassword(), classId, Date.now())
@@ -131,11 +131,11 @@ export function resetPassword(db: Database, studentId: number): string | null {
const password = generatePassword()
const changed = db.query(`UPDATE users SET password = ? WHERE id = ? AND role = 'student'`).run(password, studentId).changes
if (!changed) return null
endSessions(db, studentId)
revokeTokens(db, studentId)
return password
}
/** 删除学生,连同他的作品和登录会话 */
/** 删除学生,连同他的作品 */
export function deleteStudent(db: Database, studentId: number) {
db.query(`DELETE FROM users WHERE id = ? AND role = 'student'`).run(studentId)
}