33import importlib
44import json
55from datetime import datetime , timezone
6- from fastapi import APIRouter , Depends , HTTPException , Header , Request
6+ from fastapi import APIRouter , Depends , HTTPException , Header , Request , Response
77from fastapi .responses import JSONResponse
88from pydantic import BaseModel
99from typing import Optional , List , Dict , Any
4646
4747# Store active admin session tokens (in production, use Redis or database)
4848active_admin_tokens = set ()
49+ ADMIN_SESSION_COOKIE_NAME = "admin_session"
4950SUPPORTED_API_CAPTCHA_METHODS = {"yescaptcha" , "capmonster" , "ezcaptcha" , "capsolver" }
5051
5152
@@ -688,24 +689,40 @@ class TokenRefreshConfigRequest(BaseModel):
688689
689690# ========== Auth Middleware ==========
690691
691- async def verify_admin_token (authorization : str = Header (None )):
692+ async def verify_admin_token (request : Request , authorization : str = Header (None )):
692693 """Verify admin session token (NOT API key)"""
693- if not authorization or not authorization .startswith ("Bearer " ):
694- raise HTTPException (status_code = 401 , detail = "Missing authorization" )
694+ header_token = ""
695+ if authorization and authorization .startswith ("Bearer " ):
696+ header_token = authorization [7 :].strip ()
695697
696- token = authorization [ 7 :]
698+ cookie_token = get_admin_token_from_cookie ( request ) or ""
697699
698- # Check if token is in active session tokens
699- if token not in active_admin_tokens :
700+ if header_token and header_token in active_admin_tokens :
701+ return header_token
702+
703+ if cookie_token and cookie_token in active_admin_tokens :
704+ return cookie_token
705+
706+ if header_token or cookie_token :
700707 raise HTTPException (status_code = 401 , detail = "Invalid or expired admin token" )
701708
702- return token
709+ raise HTTPException (status_code = 401 , detail = "Missing authorization" )
710+
711+
712+ def get_admin_token_from_cookie (request : Request ) -> Optional [str ]:
713+ token = str (request .cookies .get (ADMIN_SESSION_COOKIE_NAME ) or "" ).strip ()
714+ return token or None
715+
716+
717+ def is_admin_session_token_valid (token : Optional [str ]) -> bool :
718+ normalized = str (token or "" ).strip ()
719+ return bool (normalized ) and normalized in active_admin_tokens
703720
704721
705722# ========== Auth Endpoints ==========
706723
707724@router .post ("/api/admin/login" )
708- async def admin_login (request : LoginRequest ):
725+ async def admin_login (request : LoginRequest , response : Response ):
709726 """Admin login - returns session token (NOT API key)"""
710727 admin_config = await db .get_admin_config ()
711728
@@ -718,6 +735,15 @@ async def admin_login(request: LoginRequest):
718735 # Store in active tokens
719736 active_admin_tokens .add (session_token )
720737
738+ response .set_cookie (
739+ key = ADMIN_SESSION_COOKIE_NAME ,
740+ value = session_token ,
741+ httponly = True ,
742+ samesite = "lax" ,
743+ secure = False ,
744+ path = "/" ,
745+ )
746+
721747 return {
722748 "success" : True ,
723749 "token" : session_token , # Session token (NOT API key)
@@ -726,9 +752,10 @@ async def admin_login(request: LoginRequest):
726752
727753
728754@router .post ("/api/admin/logout" )
729- async def admin_logout (token : str = Depends (verify_admin_token )):
755+ async def admin_logout (response : Response , token : str = Depends (verify_admin_token )):
730756 """Admin logout - invalidate session token"""
731757 active_admin_tokens .discard (token )
758+ response .delete_cookie (ADMIN_SESSION_COOKIE_NAME , path = "/" )
732759 return {"success" : True , "message" : "退出登录成功" }
733760
734761
@@ -1419,15 +1446,15 @@ async def get_system_info(token: str = Depends(verify_admin_token)):
14191446# ========== Additional Routes for Frontend Compatibility ==========
14201447
14211448@router .post ("/api/login" )
1422- async def login (request : LoginRequest ):
1449+ async def login (request : LoginRequest , response : Response ):
14231450 """Login endpoint (alias for /api/admin/login)"""
1424- return await admin_login (request )
1451+ return await admin_login (request , response )
14251452
14261453
14271454@router .post ("/api/logout" )
1428- async def logout (token : str = Depends (verify_admin_token )):
1455+ async def logout (response : Response , token : str = Depends (verify_admin_token )):
14291456 """Logout endpoint (alias for /api/admin/logout)"""
1430- return await admin_logout (token )
1457+ return await admin_logout (response , token )
14311458
14321459
14331460@router .get ("/health" )
0 commit comments