from __future__ import annotations import json import secrets from http import HTTPStatus from backend.features.accounts.security import token_hash class AccountHttpMixin: def _device_hash(self) -> str: raw = self.device_token() return token_hash(raw) if raw else "" def _ensure_device_token(self) -> str: return self.device_token() or secrets.token_urlsafe(32) def _auth_success_headers(self, session_token: str, device_raw: str) -> list[tuple[str, str]]: return [ ("Set-Cookie", self.session_cookie(session_token)), ("Set-Cookie", self.device_cookie(device_raw)), ] def _send_authenticated_session(self, result: dict, status: HTTPStatus, device_raw: str) -> None: self.send_json( { "ok": True, "authenticated": True, "user": result["user"], "csrf_token": result["csrf_token"], }, status, self._auth_success_headers(result["session_token"], device_raw), ) def auth_register(self) -> None: try: body = self.read_json_body() device_raw = self._ensure_device_token() result = self.application_service.register_account( str(body.get("username") or ""), str(body.get("password") or ""), token_hash(device_raw), ) self._send_authenticated_session(result, HTTPStatus.CREATED, device_raw) except (ValueError, json.JSONDecodeError) as exc: self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST) def auth_login(self) -> None: try: body = self.read_json_body() device_raw = self._ensure_device_token() result = self.application_service.login_account( str(body.get("username") or ""), str(body.get("password") or ""), token_hash(device_raw), ) self._send_authenticated_session(result, HTTPStatus.OK, device_raw) except (ValueError, json.JSONDecodeError) as exc: self.send_json({"error": str(exc)}, HTTPStatus.UNAUTHORIZED) def auth_accounts(self) -> None: current_user_id = None if self.require_auth(send_error=False): current_user_id = int(self.auth_user["id"]) payload = self.application_service.accounts.list_device_accounts( self._device_hash(), current_user_id, ) self.send_json({"ok": True, **payload}) def auth_switch(self) -> None: try: body = self.read_json_body() try: user_id = int(body.get("user_id")) except (TypeError, ValueError): user_id = 0 device_raw = self.device_token() result = self.application_service.accounts.switch_account( token_hash(device_raw) if device_raw else "", user_id, ) self._send_authenticated_session( result, HTTPStatus.OK, device_raw or self._ensure_device_token(), ) except PermissionError as exc: self.send_json({"error": str(exc)}, HTTPStatus.UNAUTHORIZED) except (ValueError, json.JSONDecodeError) as exc: self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST) def auth_forget(self) -> None: try: body = self.read_json_body() try: user_id = int(body.get("user_id")) except (TypeError, ValueError): user_id = 0 self.application_service.accounts.forget_account(self._device_hash(), user_id) except (ValueError, json.JSONDecodeError): pass self.send_json({"ok": True}) def auth_me(self) -> None: service = self.application_service if not self.require_auth(send_error=False): self.send_json( { "ok": True, "authenticated": False, "registration_required": service.database.count_users() == 0, } ) return headers = None if not self.device_token(): device_raw = secrets.token_urlsafe(32) service.accounts.remember_account( token_hash(device_raw), int(self.auth_user["id"]), fresh=True, ) headers = [("Set-Cookie", self.device_cookie(device_raw))] self.send_json( { "ok": True, "authenticated": True, "user": { "id": int(self.auth_user["id"]), "username": str(self.auth_user["username"]), "role": str(self.auth_user.get("role") or "user"), "membership": service.membership(), }, "csrf_token": str(self.auth_user["csrf_token"]), }, headers=headers, ) def auth_logout(self) -> None: raw_token = self.session_token() user_id = int(getattr(self, "auth_user", {}).get("id") or 0) if raw_token: self.application_service.database.delete_session(token_hash(raw_token)) self.application_service.accounts.revoke_current_device_grant( self._device_hash(), user_id, ) self.send_json( {"ok": True}, headers={"Set-Cookie": self.session_cookie("", clear=True)}, ) def save_birth_profile(self) -> None: try: body = self.read_json_body() personal = self.application_service.save_birth_profile(body) self.send_json({"ok": True, "personal": personal}) except (ValueError, json.JSONDecodeError) as exc: self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST) def change_password(self) -> None: try: body = self.read_json_body() current = str(body.get("current_password") or "") new = str(body.get("new_password") or "") confirmation = str(body.get("confirm_password") or "") if new != confirmation: raise ValueError("两次输入的新密码不一致。") self.application_service.change_password(current, new) self.send_json({"ok": True}) except (ValueError, json.JSONDecodeError) as exc: self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST) def save_membership(self) -> None: try: service = self.application_service service.update_membership(self.read_json_body()) self.send_json({"ok": True, "users": service.admin_users()}) except (ValueError, json.JSONDecodeError) as exc: self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)