2026-02-05 15:13:06 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-02-25 10:20:43 +08:00
|
|
|
from typing import Annotated
|
|
|
|
|
|
2026-02-05 15:13:06 +08:00
|
|
|
from fastapi import APIRouter, Depends, Response
|
2026-02-25 10:20:43 +08:00
|
|
|
from fastapi import HTTPException
|
2026-02-05 15:13:06 +08:00
|
|
|
|
2026-02-25 10:20:43 +08:00
|
|
|
from core.auth.models import CurrentUser
|
2026-02-25 13:34:02 +08:00
|
|
|
from v1.auth.rate_limit import enforce_rate_limit
|
2026-02-05 15:13:06 +08:00
|
|
|
from v1.auth.dependencies import get_auth_service
|
2026-02-26 13:41:32 +08:00
|
|
|
from v1.users.dependencies import get_current_user
|
2026-02-24 16:38:30 +08:00
|
|
|
from v1.auth.schemas import (
|
2026-02-27 15:22:42 +08:00
|
|
|
PasswordResetConfirmRequest,
|
|
|
|
|
PasswordResetRequest,
|
2026-02-26 13:41:32 +08:00
|
|
|
SessionCreateRequest,
|
|
|
|
|
SessionDeleteRequest,
|
|
|
|
|
SessionRefreshRequest,
|
|
|
|
|
SessionResponse,
|
|
|
|
|
UserByEmailResponse,
|
|
|
|
|
VerificationCreateRequest,
|
|
|
|
|
VerificationCreateResponse,
|
|
|
|
|
VerificationResendRequest,
|
|
|
|
|
VerificationVerifyRequest,
|
2026-02-05 15:13:06 +08:00
|
|
|
)
|
|
|
|
|
from v1.auth.service import AuthService
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.post(
|
|
|
|
|
"/verifications", response_model=VerificationCreateResponse, status_code=202
|
|
|
|
|
)
|
|
|
|
|
async def create_verification(
|
|
|
|
|
payload: VerificationCreateRequest,
|
2026-02-25 13:34:02 +08:00
|
|
|
service: AuthService = Depends(get_auth_service),
|
2026-02-26 13:41:32 +08:00
|
|
|
) -> VerificationCreateResponse:
|
2026-02-25 13:34:02 +08:00
|
|
|
await enforce_rate_limit(
|
|
|
|
|
scope="signup_start",
|
|
|
|
|
identifier=payload.email,
|
|
|
|
|
limit=5,
|
|
|
|
|
window_seconds=60,
|
|
|
|
|
)
|
2026-02-26 13:41:32 +08:00
|
|
|
return await service.create_verification(payload)
|
2026-02-25 13:34:02 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.post("/verifications/verify", response_model=SessionResponse)
|
|
|
|
|
async def verify_verification(
|
|
|
|
|
payload: VerificationVerifyRequest,
|
2026-02-05 15:13:06 +08:00
|
|
|
service: AuthService = Depends(get_auth_service),
|
2026-02-26 13:41:32 +08:00
|
|
|
) -> SessionResponse:
|
2026-02-25 13:34:02 +08:00
|
|
|
await enforce_rate_limit(
|
|
|
|
|
scope="signup_verify",
|
|
|
|
|
identifier=payload.email,
|
|
|
|
|
limit=10,
|
|
|
|
|
window_seconds=600,
|
|
|
|
|
)
|
2026-02-26 13:41:32 +08:00
|
|
|
return await service.verify_verification(payload)
|
2026-02-25 13:34:02 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.post("/verifications/resend", status_code=204)
|
|
|
|
|
async def resend_verification(
|
|
|
|
|
payload: VerificationResendRequest,
|
2026-02-25 13:34:02 +08:00
|
|
|
service: AuthService = Depends(get_auth_service),
|
2026-02-26 13:41:32 +08:00
|
|
|
) -> Response:
|
2026-02-25 13:34:02 +08:00
|
|
|
await enforce_rate_limit(
|
|
|
|
|
scope="signup_resend",
|
|
|
|
|
identifier=payload.email,
|
2026-02-26 12:07:40 +08:00
|
|
|
limit=5,
|
2026-02-25 13:34:02 +08:00
|
|
|
window_seconds=60,
|
|
|
|
|
)
|
2026-02-26 13:41:32 +08:00
|
|
|
await service.resend_verification(payload)
|
|
|
|
|
return Response(status_code=204)
|
2026-02-05 15:13:06 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.post("/sessions", response_model=SessionResponse)
|
|
|
|
|
async def create_session(
|
|
|
|
|
payload: SessionCreateRequest,
|
2026-02-05 15:13:06 +08:00
|
|
|
service: AuthService = Depends(get_auth_service),
|
2026-02-26 13:41:32 +08:00
|
|
|
) -> SessionResponse:
|
2026-02-25 16:51:12 +08:00
|
|
|
await enforce_rate_limit(
|
|
|
|
|
scope="login",
|
|
|
|
|
identifier=payload.email,
|
|
|
|
|
limit=10,
|
|
|
|
|
window_seconds=60,
|
|
|
|
|
)
|
2026-02-26 13:41:32 +08:00
|
|
|
return await service.create_session(payload)
|
2026-02-05 15:13:06 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.post("/sessions/refresh", response_model=SessionResponse)
|
|
|
|
|
async def refresh_session(
|
|
|
|
|
payload: SessionRefreshRequest,
|
2026-02-05 15:13:06 +08:00
|
|
|
service: AuthService = Depends(get_auth_service),
|
2026-02-26 13:41:32 +08:00
|
|
|
) -> SessionResponse:
|
2026-02-25 16:51:12 +08:00
|
|
|
await enforce_rate_limit(
|
|
|
|
|
scope="refresh",
|
|
|
|
|
identifier=payload.refresh_token,
|
|
|
|
|
limit=10,
|
|
|
|
|
window_seconds=60,
|
|
|
|
|
)
|
2026-02-26 13:41:32 +08:00
|
|
|
return await service.refresh_session(payload)
|
2026-02-05 15:13:06 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.delete("/sessions", status_code=204)
|
|
|
|
|
async def delete_session(
|
|
|
|
|
payload: SessionDeleteRequest,
|
2026-02-05 15:13:06 +08:00
|
|
|
service: AuthService = Depends(get_auth_service),
|
|
|
|
|
) -> Response:
|
2026-02-25 16:51:12 +08:00
|
|
|
await enforce_rate_limit(
|
|
|
|
|
scope="logout",
|
|
|
|
|
identifier=payload.refresh_token,
|
|
|
|
|
limit=10,
|
|
|
|
|
window_seconds=60,
|
|
|
|
|
)
|
2026-02-26 13:41:32 +08:00
|
|
|
await service.delete_session(payload.refresh_token)
|
2026-02-05 15:13:06 +08:00
|
|
|
return Response(status_code=204)
|
2026-02-25 10:20:43 +08:00
|
|
|
|
|
|
|
|
|
2026-02-26 13:41:32 +08:00
|
|
|
@router.get("/users", response_model=UserByEmailResponse)
|
2026-02-25 10:20:43 +08:00
|
|
|
async def get_user_by_email(
|
|
|
|
|
email: str,
|
|
|
|
|
current_user: Annotated[CurrentUser, Depends(get_current_user)],
|
|
|
|
|
service: AuthService = Depends(get_auth_service),
|
2026-02-26 13:41:32 +08:00
|
|
|
) -> UserByEmailResponse:
|
2026-02-25 10:20:43 +08:00
|
|
|
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)
|
2026-02-27 15:22:42 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@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)
|