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:
zl-q
2026-03-07 14:55:00 +08:00
parent 1f6cb1a48f
commit ec33bb0cee
25 changed files with 421 additions and 1614 deletions
@@ -7,7 +7,11 @@ import pytest
from fastapi import HTTPException
from v1.auth.gateway import SupabaseAuthGateway
from v1.auth.schemas import PasswordResetConfirmRequest, PasswordResetRequest
from v1.auth.schemas import (
PasswordResetConfirmRequest,
PasswordResetRequest,
VerificationResendRequest,
)
class TestSupabaseAuthGateway:
@@ -56,6 +60,22 @@ class TestSupabaseAuthGateway:
options={"redirect_to": "http://localhost:3000/reset-password"},
)
@pytest.mark.asyncio
async def test_request_password_reset_rejects_redirect_outside_allowlist(
self, gateway: tuple[SupabaseAuthGateway, MagicMock, MagicMock]
) -> None:
sut, _, _ = gateway
request = PasswordResetRequest(
email="test@example.com",
redirect_to="https://evil.example/reset",
)
with pytest.raises(HTTPException) as exc_info:
await sut.request_password_reset(request)
assert exc_info.value.status_code == 422
assert exc_info.value.detail == "Invalid redirect URL"
@pytest.mark.asyncio
async def test_request_password_reset_swallows_auth_error(
self, gateway: tuple[SupabaseAuthGateway, MagicMock, MagicMock]
@@ -165,3 +185,24 @@ class TestSupabaseAuthGateway:
assert exc_info.value.status_code == 401
assert exc_info.value.detail == "Invalid or expired verification code"
@pytest.mark.asyncio
async def test_recovery_resend_calls_reset_password_email(
self, gateway: tuple[SupabaseAuthGateway, MagicMock, MagicMock]
) -> None:
sut, mock_client, _ = gateway
mock_reset_email = MagicMock()
mock_client.auth.reset_password_email = mock_reset_email
await sut.resend_verification(
VerificationResendRequest(
type="recovery",
email="test@example.com",
redirect_to="http://localhost:3000/reset-password",
)
)
mock_reset_email.assert_called_once_with(
"test@example.com",
options={"redirect_to": "http://localhost:3000/reset-password"},
)