"""账号登录、会话和密码管理。""" 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