diff --git a/.cliprepo.yaml b/.cliprepo.yaml index c27733e..c463c21 100644 --- a/.cliprepo.yaml +++ b/.cliprepo.yaml @@ -6,21 +6,27 @@ prompts: preset: auth: - - "app/auth.py" - - "app/auth/*" + - "auth/*" s3: - "app/models/base.py" - "app/models/user.py" - "app/utils/s3.py" - - "app/auth.py" + - "auth.py" db_model: - "app/models/*" - "app/db.py" - # - "app/auth.py" - "app/main.py" graphql: - "app/graphql/*" - - "app/main.py" \ No newline at end of file + - "app/main.py" + + get_current_user: + - "app/main.py" + - "auth/__init__.py" + - "auth/deps.py" + - "auth/tokens.py" + - "auth/oauth/google.py" + - "app/graphql/*" \ No newline at end of file diff --git a/app/graphql/context.py b/app/graphql/context.py index 41cc19c..c5f1e6f 100644 --- a/app/graphql/context.py +++ b/app/graphql/context.py @@ -1,9 +1,9 @@ from app.models.user import User -from auth.deps import get_current_user +from auth.deps import get_current_user_id from fastapi import Request from strawberry.fastapi import BaseContext from strawberry.dataloader import DataLoader -from typing import List +from typing import List, Optional from collections import defaultdict from app.db import SessionLocal from app.models import WorkspaceMember @@ -31,13 +31,22 @@ class CustomContext(BaseContext): super().__init__(request) self.db = SessionLocal() self.request = request + self._current_user: Optional[User] = None self.members_loader = DataLoader(load_fn=load_members_by_workspace_ids) async def get_current_user(self) -> User | None: - # 复用现有的 auth 逻辑 + """在 Context 级别负责把 user_id 变成真正的数据库 User 对象""" + if self._current_user is not None: + return self._current_user + try: - return await get_current_user(self.request) + # 借用 auth 包的纯工具解出 ID + user_id = get_current_user_id(self.request) + # 在 Context 的数据库连接中查询完整用户 + user = self.db.query(User).filter(User.id == user_id).first() + self._current_user = user + return user except Exception: return None diff --git a/app/main.py b/app/main.py index 0a5ea3e..f2c64bc 100644 --- a/app/main.py +++ b/app/main.py @@ -5,7 +5,7 @@ from strawberry import Schema from strawberry.fastapi import GraphQLRouter from dotenv import load_dotenv -from auth import router as auth_router, get_user_by_id, get_current_user +from auth import router as auth_router from app.db import engine, SessionLocal from app.models import Base, User @@ -46,23 +46,7 @@ graphql_router = GraphQLRouter(schema, context_getter=get_context) app.include_router(auth_router) # 保留现有的 auth 路由供 Google OAuth 回调使用 app.include_router(graphql_router, prefix="/graphql") # 挂载 GraphQL 终点,前端以后只需要请求 /graphql -def override_get_user_by_id(user_id: str = Depends(get_current_user)): - db = SessionLocal() - try: - user = db.query(User).filter(User.id == user_id).first() - if not user: - raise HTTPException(status_code=404, detail="User not found") - return { - "email": user.email, - "name": user.name, - "avatar": get_image_url(user.avatar), - } - finally: - db.close() - -app.dependency_overrides[get_user_by_id] = override_get_user_by_id - -# test code +# 打印路由测试 for route in app.routes: methods = getattr(route, "methods", "N/A") print(f"Path: {route.path} | Name: {route.name} | Methods: {methods}") \ No newline at end of file diff --git a/auth/__init__.py b/auth/__init__.py index 7b72900..616cc0e 100644 --- a/auth/__init__.py +++ b/auth/__init__.py @@ -1,53 +1,18 @@ import os -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter -from .deps import get_current_user -from .tokens import create_jwt_token, create_refresh_token, verify_access_token, decode_token +from .deps import get_current_user_id +from .tokens import create_jwt_token, create_refresh_token, verify_access_token from .oauth import oauth_router -# 创建全局 auth 路由器 +# 创建全局 auth 路由器并挂载第三方 OAuth 的路由器 router = APIRouter(tags=["Authentication"]) - -# 挂载第三方 OAuth 的路由器 router.include_router(oauth_router) -# 定义一个动态获取当前完整用户对象的依赖项 -# 这样 auth 包不需要知道 User 模型长什么样 -# 也不需要知道 SessionLocal 是什么 -# 全部由外部动态提供 -def get_user_by_id(user_id: str = Depends(get_current_user)): - """ - 这是一个占位/契约函数。 - 我们在 main.py 中可以通过 app.dependency_overrides 或者直接在使用时, - 把具体的数据库查询逻辑绑定到这里。 - """ - raise NotImplementedError("Please override or inject the implementation of this function in the external `main.py` file.") - -@router.get("/me") -def me(current_user = Depends(get_user_by_id)): - """ - current_user 是由外部解析并传进来的完整用户对象(或者是字典) - """ - return current_user - -@router.post("/refresh") -def refresh_token(refresh_token: str): - payload = decode_token(refresh_token) - if payload.get("type") != "refresh": - raise HTTPException(status_code=401) - user_id = payload.get("sub") - new_access = create_jwt_token({"sub": user_id}) - return {"access_token": new_access} - -@router.post("/logout") -def logout(): - return {"message": "ok"} - # 显式声明暴露的接口 __all__ = [ "router", - "get_current_user", - "get_user_by_id", + "get_current_user_id", "create_jwt_token", "create_refresh_token", "verify_access_token", diff --git a/auth/deps.py b/auth/deps.py index 7209d2b..4d39afc 100644 --- a/auth/deps.py +++ b/auth/deps.py @@ -9,7 +9,8 @@ load_dotenv() JWT_SECRET = os.getenv("JWT_SECRET") JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256") -def get_current_user(request: Request): +def get_current_user_id(request: Request): + """仅从 Cookie 中提取并解密出用户的 user_id (sub)""" token = request.cookies.get("access_token") if not token: diff --git a/auth/oauth/google.py b/auth/oauth/google.py index 1371a04..de92dbd 100644 --- a/auth/oauth/google.py +++ b/auth/oauth/google.py @@ -9,7 +9,6 @@ import json from urllib.parse import quote from dotenv import load_dotenv -from auth.deps import get_current_user from app.db import SessionLocal from app.models import User from auth.tokens import create_jwt_token, create_refresh_token, decode_token @@ -213,29 +212,4 @@ async def auth_callback(request: Request, code: str, state: str, background_task response.delete_cookie("oauth_state") - return response - -# @router.get("/me") -# def me(user = Depends(get_current_user)): -# return { -# "email": user.email, -# "name": user.name, -# "avatar": get_image_url(user.avatar), -# } - -# @router.post("/refresh") -# def refresh_token(refresh_token: str): -# payload = decode_token(refresh_token) - -# if payload.get("type") != "refresh": -# raise HTTPException(status_code=401) - -# user_id = payload.get("sub") - -# new_access = create_jwt_token({"sub": user_id}) - -# return {"access_token": new_access} - -# @router.post("/logout") -# def logout(): -# return {"message": "ok"} \ No newline at end of file + return response \ No newline at end of file