From 091e89b382b167e5d69a945a902ad77ef302f832 Mon Sep 17 00:00:00 2001 From: jingrow Date: Wed, 5 Nov 2025 05:45:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=8E=E7=AB=AF=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=99=BB=E5=87=BA=E8=B7=AF=E7=94=B1=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jingrow/frontend/src/shared/api/auth.ts | 53 +++--- apps/jingrow/jingrow/api/auth_api.py | 150 ++++++++++++++++ apps/jingrow/jingrow/utils/auth.py | 172 +++++++++++++++++++ 3 files changed, 350 insertions(+), 25 deletions(-) create mode 100644 apps/jingrow/jingrow/api/auth_api.py diff --git a/apps/jingrow/frontend/src/shared/api/auth.ts b/apps/jingrow/frontend/src/shared/api/auth.ts index bbc2296..db3b981 100644 --- a/apps/jingrow/frontend/src/shared/api/auth.ts +++ b/apps/jingrow/frontend/src/shared/api/auth.ts @@ -28,37 +28,36 @@ export function getSessionCookie(): string | null { } export const loginApi = async (username: string, password: string): Promise => { - const response = await fetch(`/api/action/login`, { + const response = await fetch(`/jingrow/login`, { method: 'POST', headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', 'Accept': 'application/json' }, credentials: 'include', - body: new URLSearchParams({ - cmd: 'login', - usr: username, - pwd: password + body: JSON.stringify({ + username: username, + password: password }) }) if (!response.ok) { - throw new Error('登录请求失败') + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.detail || errorData.message || '登录请求失败') } const data = await response.json() - if (data.message === 'Logged In') { - const userInfo = await getUserInfoApi() - return { message: data.message, user: userInfo } + if (data.success && data.user) { + return { message: data.message || 'Logged In', user: data.user } } else { - throw new Error(data.message || '登录失败') + throw new Error(data.message || data.detail || '登录失败') } } // 获取用户信息 export const getUserInfoApi = async (): Promise => { - const response = await fetch(`/api/action/jingrow.realtime.get_user_info`, { + const response = await fetch(`/jingrow/user-info`, { method: 'GET', headers: { 'Accept': 'application/json', @@ -68,30 +67,34 @@ export const getUserInfoApi = async (): Promise => { }) if (!response.ok) { - throw new Error('获取用户信息失败') + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.detail || errorData.message || '获取用户信息失败') } const data = await response.json() - const userInfo = data.message || data - return { - id: userInfo.user || userInfo.name || userInfo.username, - username: userInfo.user || userInfo.name || userInfo.username, - email: userInfo.email || '', - avatar: userInfo.user_image || '', - first_name: userInfo.first_name || '', - last_name: userInfo.last_name || '', - user_type: userInfo.user_type || 'System User' + if (data.success && data.user_info) { + return data.user_info + } else { + throw new Error(data.message || data.detail || '获取用户信息失败') } } // 登出 export const logoutApi = async (): Promise => { - await fetch(`/api/action/logout`, { - method: 'GET', - headers: { 'Accept': 'application/json' }, + const response = await fetch(`/jingrow/logout`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, credentials: 'include' }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.detail || errorData.message || '登出失败') + } } // 仅使用会话Cookie的最小鉴权头部(不影响现有API Key逻辑) diff --git a/apps/jingrow/jingrow/api/auth_api.py b/apps/jingrow/jingrow/api/auth_api.py new file mode 100644 index 0000000..bf04860 --- /dev/null +++ b/apps/jingrow/jingrow/api/auth_api.py @@ -0,0 +1,150 @@ +from fastapi import APIRouter, HTTPException, Request +from fastapi.responses import JSONResponse +from pydantic import BaseModel +from typing import Optional +import logging + +from jingrow.utils.auth import login, logout, get_user_info, set_context + +logger = logging.getLogger(__name__) +router = APIRouter() + +class LoginRequest(BaseModel): + username: str + password: str + +class LoginResponse(BaseModel): + success: bool + message: str + user: Optional[dict] = None + session_cookie: Optional[str] = None + +class LogoutResponse(BaseModel): + success: bool + message: str + +class UserInfoResponse(BaseModel): + success: bool + user_info: Optional[dict] = None + message: Optional[str] = None + +@router.post("/jingrow/login", response_model=LoginResponse) +async def login_route(request: Request, login_data: LoginRequest): + """ + 登录路由 + """ + try: + # 调用登录函数 + result = login(login_data.username, login_data.password) + + if result.get("success"): + session_cookie = result.get("session_cookie") + + # 获取用户信息 + user_info_result = get_user_info(session_cookie) + user_info = None + if user_info_result.get("success"): + user_info = user_info_result.get("user_info") + + # 创建响应 + response_data = { + "success": True, + "message": result.get("message", "Logged In"), + "user": user_info + } + + # 创建响应并设置cookie + response = JSONResponse(content=response_data) + if session_cookie: + response.set_cookie( + key="sid", + value=session_cookie, + httponly=True, + samesite="lax", + secure=False, # 开发环境可以设为False,生产环境建议设为True + path="/" + ) + + return response + else: + raise HTTPException( + status_code=401, + detail=result.get("message", "登录失败") + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"登录异常: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"登录异常: {str(e)}") + +@router.post("/jingrow/logout", response_model=LogoutResponse) +async def logout_route(request: Request): + """ + 登出路由 + """ + try: + # 从cookie获取session + session_cookie = request.cookies.get('sid') + + # 设置context以便logout函数可以使用 + if session_cookie: + set_context({"session_cookie": session_cookie}) + + # 调用登出函数 + result = logout(session_cookie) + + if result.get("success"): + # 创建响应并清除cookie + response = JSONResponse(content={ + "success": True, + "message": result.get("message", "登出成功") + }) + # 清除所有相关的cookie(需要设置path="/"才能正确删除) + response.delete_cookie(key="sid", path="/", httponly=True, samesite="lax") + response.delete_cookie(key="user_id", path="/") + response.delete_cookie(key="user_image", path="/") + response.delete_cookie(key="full_name", path="/") + response.delete_cookie(key="system_user", path="/") + return response + else: + raise HTTPException( + status_code=400, + detail=result.get("message", "登出失败") + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"登出异常: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"登出异常: {str(e)}") + +@router.get("/jingrow/user-info", response_model=UserInfoResponse) +async def get_user_info_route(request: Request): + """ + 获取用户信息路由 + """ + try: + # 从cookie获取session + session_cookie = request.cookies.get('sid') + + # 设置context以便get_user_info函数可以使用 + if session_cookie: + set_context({"session_cookie": session_cookie}) + + # 调用获取用户信息函数 + result = get_user_info(session_cookie) + + if result.get("success"): + return { + "success": True, + "user_info": result.get("user_info") + } + else: + raise HTTPException( + status_code=401, + detail=result.get("message", "获取用户信息失败") + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"获取用户信息异常: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"获取用户信息异常: {str(e)}") diff --git a/apps/jingrow/jingrow/utils/auth.py b/apps/jingrow/jingrow/utils/auth.py index 6975621..aad9ee5 100644 --- a/apps/jingrow/jingrow/utils/auth.py +++ b/apps/jingrow/jingrow/utils/auth.py @@ -1,6 +1,7 @@ import threading import sys import os +import requests # 导入配置 sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))) @@ -62,3 +63,174 @@ def get_jingrow_cloud_api_headers(): headers["Authorization"] = f"token {api_key}:{api_secret}" return headers + +def login(username: str, password: str) -> dict: + """ + 登录函数,调用jingrow框架的登录API + + Args: + username: 用户名 + password: 密码 + + Returns: + dict: 登录结果,包含success和message字段,成功时还包含session_cookie + """ + try: + url = f"{Config.jingrow_server_url}/api/action/login" + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json" + } + + data = { + "cmd": "login", + "usr": username, + "pwd": password + } + + # 使用session保持cookie + session = requests.Session() + response = session.post(url, headers=headers, data=data, timeout=30) + + if response.status_code == 200: + result = response.json() + if result.get("message") == "Logged In": + # 获取session cookie + session_cookie = None + if 'Set-Cookie' in response.headers: + cookies = response.headers['Set-Cookie'] + if 'sid=' in cookies: + session_cookie = cookies.split('sid=')[1].split(';')[0] + + # 如果没有从Set-Cookie获取,尝试从session的cookies获取 + if not session_cookie: + session_cookie = session.cookies.get('sid') + + return { + "success": True, + "message": result.get("message", "Logged In"), + "session_cookie": session_cookie + } + else: + return { + "success": False, + "message": result.get("message", "登录失败") + } + else: + error_msg = f"登录请求失败 (HTTP {response.status_code})" + try: + error_data = response.json() + error_msg = error_data.get("message", error_data.get("exc", error_msg)) + except: + error_msg = response.text or error_msg + + return { + "success": False, + "message": error_msg + } + except Exception as e: + return { + "success": False, + "message": f"登录异常: {str(e)}" + } + +def logout(session_cookie: str = None) -> dict: + """ + 登出函数,调用jingrow框架的登出API + + Args: + session_cookie: 会话cookie,如果为None则从context获取 + + Returns: + dict: 登出结果,包含success和message字段 + """ + try: + url = f"{Config.jingrow_server_url}/api/action/logout" + headers = { + "Accept": "application/json" + } + + # 如果没有提供session_cookie,尝试从context获取 + if not session_cookie: + context = getattr(_thread_local, 'context', None) + if context: + session_cookie = context.get('session_cookie') + + if session_cookie: + headers["Cookie"] = f"sid={session_cookie}" + + response = requests.get(url, headers=headers, timeout=30) + + if response.status_code == 200: + return { + "success": True, + "message": "登出成功" + } + else: + return { + "success": False, + "message": f"登出请求失败 (HTTP {response.status_code})" + } + except Exception as e: + return { + "success": False, + "message": f"登出异常: {str(e)}" + } + +def get_user_info(session_cookie: str = None) -> dict: + """ + 获取用户信息函数,调用jingrow框架的get_user_info API + + Args: + session_cookie: 会话cookie,如果为None则从context获取 + + Returns: + dict: 用户信息,包含success和user_info字段 + """ + try: + url = f"{Config.jingrow_server_url}/api/action/jingrow.realtime.get_user_info" + headers = { + "Accept": "application/json", + "Content-Type": "application/json" + } + + # 如果没有提供session_cookie,尝试从context获取 + if not session_cookie: + context = getattr(_thread_local, 'context', None) + if context: + session_cookie = context.get('session_cookie') + + if session_cookie: + headers["Cookie"] = f"sid={session_cookie}" + + response = requests.get(url, headers=headers, timeout=30) + + if response.status_code == 200: + data = response.json() + user_info = data.get('message', data) + + # 格式化用户信息 + formatted_user_info = { + "id": user_info.get("user") or user_info.get("name") or user_info.get("username", ""), + "username": user_info.get("user") or user_info.get("name") or user_info.get("username", ""), + "email": user_info.get("email", ""), + "avatar": user_info.get("user_image", ""), + "first_name": user_info.get("first_name", ""), + "last_name": user_info.get("last_name", ""), + "user_type": user_info.get("user_type", "System User") + } + + return { + "success": True, + "user_info": formatted_user_info + } + else: + return { + "success": False, + "message": f"获取用户信息失败 (HTTP {response.status_code})" + } + except Exception as e: + return { + "success": False, + "message": f"获取用户信息异常: {str(e)}" + }