refactor: 统一认证端点并删除冗余 profile 模块
- 合并 auth 端点: /verifications/verify → /verify, /verifications/resend → /resend - 整合密码重置到 /verify 端点 (type=recovery) - 移除未使用的 /auth/users 端点 - 添加 redirect URL 白名单验证 (site_url + additional_redirect_urls) - 限流改用 Redis + IP 标识,替代内存锁 - 删除 v1/profile 死代码模块 - 更新前端 auth_api 适配新端点 - 添加 supabase site_url 和 additional_redirect_urls 配置
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Response
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
from fastapi import HTTPException
|
||||
|
||||
from core.auth.models import CurrentUser
|
||||
@@ -11,7 +11,6 @@ from v1.auth.dependencies import get_auth_service
|
||||
from v1.users.dependencies import get_current_user
|
||||
from v1.auth.schemas import (
|
||||
PasswordResetConfirmRequest,
|
||||
PasswordResetRequest,
|
||||
SessionCreateRequest,
|
||||
SessionDeleteRequest,
|
||||
SessionRefreshRequest,
|
||||
@@ -44,28 +43,45 @@ async def create_verification(
|
||||
return await service.create_verification(payload)
|
||||
|
||||
|
||||
@router.post("/verifications/verify", response_model=SessionResponse)
|
||||
async def verify_verification(
|
||||
@router.post("/verify", response_model=SessionResponse)
|
||||
async def verify(
|
||||
payload: VerificationVerifyRequest,
|
||||
request: Request,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> SessionResponse:
|
||||
) -> SessionResponse | Response:
|
||||
scope = "signup_verify" if payload.type == "signup" else "password_reset_confirm"
|
||||
limit = 10
|
||||
window_seconds = 600
|
||||
await enforce_rate_limit(
|
||||
scope="signup_verify",
|
||||
identifier=payload.email,
|
||||
limit=10,
|
||||
window_seconds=600,
|
||||
scope=scope,
|
||||
identifier=f"{payload.email.lower()}:{_client_ip(request)}",
|
||||
limit=limit,
|
||||
window_seconds=window_seconds,
|
||||
)
|
||||
return await service.verify_verification(payload)
|
||||
if payload.type == "signup":
|
||||
return await service.verify_verification(payload)
|
||||
if payload.new_password is None:
|
||||
raise HTTPException(status_code=422, detail="Invalid request")
|
||||
await service.confirm_password_reset(
|
||||
PasswordResetConfirmRequest(
|
||||
email=payload.email,
|
||||
token=payload.token,
|
||||
new_password=payload.new_password,
|
||||
)
|
||||
)
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@router.post("/verifications/resend", status_code=204)
|
||||
async def resend_verification(
|
||||
@router.post("/resend", status_code=204)
|
||||
async def resend(
|
||||
payload: VerificationResendRequest,
|
||||
request: Request,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> Response:
|
||||
scope = "signup_resend" if payload.type == "signup" else "password_reset_request"
|
||||
await enforce_rate_limit(
|
||||
scope="signup_resend",
|
||||
identifier=payload.email,
|
||||
scope=scope,
|
||||
identifier=f"{payload.email.lower()}:{_client_ip(request)}",
|
||||
limit=5,
|
||||
window_seconds=60,
|
||||
)
|
||||
@@ -90,11 +106,12 @@ async def create_session(
|
||||
@router.post("/sessions/refresh", response_model=SessionResponse)
|
||||
async def refresh_session(
|
||||
payload: SessionRefreshRequest,
|
||||
request: Request,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> SessionResponse:
|
||||
await enforce_rate_limit(
|
||||
scope="refresh",
|
||||
identifier=payload.refresh_token,
|
||||
identifier=_client_ip(request),
|
||||
limit=10,
|
||||
window_seconds=60,
|
||||
)
|
||||
@@ -104,11 +121,12 @@ async def refresh_session(
|
||||
@router.delete("/sessions", status_code=204)
|
||||
async def delete_session(
|
||||
payload: SessionDeleteRequest,
|
||||
request: Request,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> Response:
|
||||
await enforce_rate_limit(
|
||||
scope="logout",
|
||||
identifier=payload.refresh_token,
|
||||
identifier=_client_ip(request),
|
||||
limit=10,
|
||||
window_seconds=60,
|
||||
)
|
||||
@@ -116,42 +134,14 @@ async def delete_session(
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@router.get("/users", response_model=UserByEmailResponse)
|
||||
async def get_user_by_email(
|
||||
email: str,
|
||||
current_user: Annotated[CurrentUser, Depends(get_current_user)],
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> UserByEmailResponse:
|
||||
if current_user.role != "service_role" and current_user.email != email:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
return await service.get_user_by_email(email)
|
||||
|
||||
|
||||
@router.post("/password-reset", status_code=204)
|
||||
async def request_password_reset(
|
||||
payload: PasswordResetRequest,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> Response:
|
||||
await enforce_rate_limit(
|
||||
scope="password_reset_request",
|
||||
identifier=payload.email,
|
||||
limit=5,
|
||||
window_seconds=60,
|
||||
)
|
||||
await service.request_password_reset(payload)
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@router.post("/password-reset/confirm", status_code=204)
|
||||
async def confirm_password_reset(
|
||||
payload: PasswordResetConfirmRequest,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> Response:
|
||||
await enforce_rate_limit(
|
||||
scope="password_reset_confirm",
|
||||
identifier=payload.email,
|
||||
limit=10,
|
||||
window_seconds=600,
|
||||
)
|
||||
await service.confirm_password_reset(payload)
|
||||
return Response(status_code=204)
|
||||
def _client_ip(request: Request) -> str:
|
||||
forwarded_for = request.headers.get("x-forwarded-for", "")
|
||||
if forwarded_for:
|
||||
first = forwarded_for.split(",")[0].strip()
|
||||
if first:
|
||||
return first
|
||||
real_ip = request.headers.get("x-real-ip", "").strip()
|
||||
if real_ip:
|
||||
return real_ip
|
||||
host = request.client.host if request.client else ""
|
||||
return host or "unknown"
|
||||
|
||||
Reference in New Issue
Block a user