Files
selfgroup/group_system/storage/accounts.py

177 lines
7.8 KiB
Python

"""账号登录、会话和密码管理。"""
from __future__ import annotations
import hashlib
import secrets
import time
from typing import Any
from group_system.config import SESSION_DAYS, TEACHER_USER
from group_system.security import hash_password, verify_password
class AccountStoreMixin:
def create_session(self, role: str, username: str, student_id: int | None = None) -> str:
token = secrets.token_urlsafe(32)
token_hash = hashlib.sha256(token.encode()).hexdigest()
now = int(time.time())
with self.lock, self.connection:
self.connection.execute(
"INSERT INTO sessions(token_hash,role,username,student_id,created_at,expires_at) VALUES(?,?,?,?,?,?)",
(token_hash, role, username, student_id, now, now + SESSION_DAYS * 86400),
)
self.connection.execute(
"DELETE FROM sessions WHERE expires_at < ?", (now,)
)
return token
def get_session(self, token: str | None) -> dict[str, Any] | None:
if not token:
return None
token_hash = hashlib.sha256(token.encode()).hexdigest()
now = int(time.time())
with self.lock:
row = self.connection.execute(
"""SELECT s.token_hash,s.role,s.username,s.student_id,s.expires_at,
st.class_name,st.student_no,st.name,st.must_change_password
FROM sessions s LEFT JOIN students st ON st.id=s.student_id
WHERE s.token_hash=? AND s.expires_at>?""",
(token_hash, now),
).fetchone()
if row is None:
return None
return dict(row)
def destroy_session(self, token: str | None) -> None:
if not token:
return
token_hash = hashlib.sha256(token.encode()).hexdigest()
with self.lock, self.connection:
self.connection.execute("DELETE FROM sessions WHERE token_hash=?", (token_hash,))
def login_student(self, student_no: str, password: str) -> tuple[dict[str, Any] | None, str]:
student_no = student_no.strip()
active = self.active_class()
with self.lock:
student = self.connection.execute(
"SELECT * FROM students WHERE student_no=?", (student_no,)
).fetchone()
if student is None or not verify_password(password, student["password_hash"]):
return None, "学号或密码不正确"
if student["class_name"] != active:
return None, f"当前仅开放 {active} 班登录,{student['class_name']} 班暂不能登录"
token = self.create_session("student", student["student_no"], student["id"])
return {
"token": token,
"student": {
"id": student["id"],
"name": student["name"],
"student_no": student["student_no"],
"class_name": student["class_name"],
"must_change_password": bool(student["must_change_password"]),
},
}, "登录成功"
def login_teacher(self, username: str, password: str) -> tuple[dict[str, Any] | None, str]:
with self.lock:
expected_user = self.get_setting("teacher_username") or TEACHER_USER
expected_password = self.get_setting("teacher_password")
if username.strip() != expected_user or not verify_password(password, expected_password):
return None, "教师账号或密码不正确"
token = self.create_session("teacher", expected_user, None)
return {"token": token, "teacher": {"username": expected_user}}, "登录成功"
def change_teacher_password(
self, session: dict[str, Any], old_password: str, new_password: str
) -> tuple[bool, str]:
if session.get("role") != "teacher":
return False, "教师会话无效"
old_password = old_password or ""
new_password = new_password or ""
if len(new_password) < 8:
return False, "新密码至少 8 位"
if new_password == old_password:
return False, "新密码不能与原密码相同"
with self.lock:
expected_user = self.get_setting("teacher_username") or TEACHER_USER
if session.get("username") != expected_user:
return False, "教师账号已变更,请重新登录"
stored_password = self.get_setting("teacher_password")
if not verify_password(old_password, stored_password):
return False, "原密码不正确"
self.set_setting("teacher_password", hash_password(new_password))
return True, "教师密码修改成功"
def change_student_password(
self, session: dict[str, Any], old_password: str, new_password: str
) -> tuple[bool, str]:
student_id = session.get("student_id")
if not student_id:
return False, "学生会话无效"
old_password = old_password or ""
new_password = new_password or ""
if len(new_password) < 8:
return False, "新密码至少 8 位"
if new_password == old_password:
return False, "新密码不能与原密码相同"
with self.lock:
student = self.connection.execute(
"SELECT * FROM students WHERE id=?", (student_id,)
).fetchone()
if student is None or not verify_password(old_password, student["password_hash"]):
return False, "原密码不正确"
if new_password == student["student_no"]:
return False, "新密码不能设置为学号"
with self.connection:
self.connection.execute(
"UPDATE students SET password_hash=?, must_change_password=0 WHERE id=?",
(hash_password(new_password), student_id),
)
token_hash = hashlib.sha256(session["_token"].encode()).hexdigest()
self.connection.execute(
"DELETE FROM sessions WHERE student_id=? AND token_hash<>?",
(student_id, token_hash),
)
return True, "密码修改成功"
def teacher_set_student_password(
self, student_id: int, new_password: str
) -> tuple[bool, str]:
"""教师后台重置指定学生的登录密码。"""
new_password = new_password or ""
if len(new_password) < 8:
return False, "新密码至少 8 位"
with self.lock, self.connection:
student = self.connection.execute(
"SELECT student_no FROM students WHERE id=?", (student_id,)
).fetchone()
if student is None:
return False, "学生不存在"
if new_password == student["student_no"]:
return False, "新密码不能设置为学号"
self.connection.execute(
"UPDATE students SET password_hash=?, must_change_password=0 WHERE id=?",
(hash_password(new_password), student_id),
)
self.connection.execute(
"DELETE FROM sessions WHERE student_id=?", (student_id,)
)
return True, f"已修改 {student['student_no']} 的密码"
def get_student(self, student_id: int) -> dict[str, Any] | None:
with self.lock:
row = self.connection.execute("SELECT * FROM students WHERE id=?", (student_id,)).fetchone()
return dict(row) if row else None
def student_membership(self, student_id: int) -> dict[str, Any] | None:
with self.lock:
row = self.connection.execute(
"""SELECT m.group_id,g.name,g.class_name,g.leader_student_id
FROM group_members m JOIN groups g ON g.id=m.group_id
WHERE m.student_id=?""",
(student_id,),
).fetchone()
return dict(row) if row else None