refactor(auth): remove legacy RESTful user routes and migrate authentication to GraphQL context

- Rename get_current_user to get_current_user_id in auth/deps.py to clarify intent
- Remove legacy /me, /logout routes, and placeholder function from auth/__init__.py
- Remove dependency_overrides from app/main.py
- Update CustomContext to resolve and cache full User objects via db query
This commit is contained in:
2026-05-20 02:52:54 +08:00
parent d54aeaa61a
commit 9dfe000f2d
6 changed files with 34 additions and 95 deletions
+10 -4
View File
@@ -6,21 +6,27 @@ prompts:
preset: preset:
auth: auth:
- "app/auth.py" - "auth/*"
- "app/auth/*"
s3: s3:
- "app/models/base.py" - "app/models/base.py"
- "app/models/user.py" - "app/models/user.py"
- "app/utils/s3.py" - "app/utils/s3.py"
- "app/auth.py" - "auth.py"
db_model: db_model:
- "app/models/*" - "app/models/*"
- "app/db.py" - "app/db.py"
# - "app/auth.py"
- "app/main.py" - "app/main.py"
graphql: graphql:
- "app/graphql/*" - "app/graphql/*"
- "app/main.py" - "app/main.py"
get_current_user:
- "app/main.py"
- "auth/__init__.py"
- "auth/deps.py"
- "auth/tokens.py"
- "auth/oauth/google.py"
- "app/graphql/*"
+13 -4
View File
@@ -1,9 +1,9 @@
from app.models.user import User 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 fastapi import Request
from strawberry.fastapi import BaseContext from strawberry.fastapi import BaseContext
from strawberry.dataloader import DataLoader from strawberry.dataloader import DataLoader
from typing import List from typing import List, Optional
from collections import defaultdict from collections import defaultdict
from app.db import SessionLocal from app.db import SessionLocal
from app.models import WorkspaceMember from app.models import WorkspaceMember
@@ -31,13 +31,22 @@ class CustomContext(BaseContext):
super().__init__(request) super().__init__(request)
self.db = SessionLocal() self.db = SessionLocal()
self.request = request self.request = request
self._current_user: Optional[User] = None
self.members_loader = DataLoader(load_fn=load_members_by_workspace_ids) self.members_loader = DataLoader(load_fn=load_members_by_workspace_ids)
async def get_current_user(self) -> User | None: 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: 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: except Exception:
return None return None
+2 -18
View File
@@ -5,7 +5,7 @@ from strawberry import Schema
from strawberry.fastapi import GraphQLRouter from strawberry.fastapi import GraphQLRouter
from dotenv import load_dotenv 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.db import engine, SessionLocal
from app.models import Base, User 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(auth_router) # 保留现有的 auth 路由供 Google OAuth 回调使用
app.include_router(graphql_router, prefix="/graphql") # 挂载 GraphQL 终点,前端以后只需要请求 /graphql 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: for route in app.routes:
methods = getattr(route, "methods", "N/A") methods = getattr(route, "methods", "N/A")
print(f"Path: {route.path} | Name: {route.name} | Methods: {methods}") print(f"Path: {route.path} | Name: {route.name} | Methods: {methods}")
+5 -40
View File
@@ -1,53 +1,18 @@
import os import os
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter
from .deps import get_current_user from .deps import get_current_user_id
from .tokens import create_jwt_token, create_refresh_token, verify_access_token, decode_token from .tokens import create_jwt_token, create_refresh_token, verify_access_token
from .oauth import oauth_router from .oauth import oauth_router
# 创建全局 auth 路由器 # 创建全局 auth 路由器并挂载第三方 OAuth 的路由器
router = APIRouter(tags=["Authentication"]) router = APIRouter(tags=["Authentication"])
# 挂载第三方 OAuth 的路由器
router.include_router(oauth_router) 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__ = [ __all__ = [
"router", "router",
"get_current_user", "get_current_user_id",
"get_user_by_id",
"create_jwt_token", "create_jwt_token",
"create_refresh_token", "create_refresh_token",
"verify_access_token", "verify_access_token",
+2 -1
View File
@@ -9,7 +9,8 @@ load_dotenv()
JWT_SECRET = os.getenv("JWT_SECRET") JWT_SECRET = os.getenv("JWT_SECRET")
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256") 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") token = request.cookies.get("access_token")
if not token: if not token:
-26
View File
@@ -9,7 +9,6 @@ import json
from urllib.parse import quote from urllib.parse import quote
from dotenv import load_dotenv from dotenv import load_dotenv
from auth.deps import get_current_user
from app.db import SessionLocal from app.db import SessionLocal
from app.models import User from app.models import User
from auth.tokens import create_jwt_token, create_refresh_token, decode_token from auth.tokens import create_jwt_token, create_refresh_token, decode_token
@@ -214,28 +213,3 @@ async def auth_callback(request: Request, code: str, state: str, background_task
response.delete_cookie("oauth_state") response.delete_cookie("oauth_state")
return response 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"}