llm-ready-data / app /api /v1 /auth.py
light-infer-chat's picture
feat: add supabase
5d6260a
Raw
History Blame Contribute Delete
8.02 kB
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Request, status
from app.config import get_settings
from app.core.auth.deps import get_current_user, get_db
from app.core.auth.models import User
from app.core.auth.schemas import (
ChangePasswordSchema,
ForgotPasswordSchema,
LoginSchema,
MessageDataResponse,
ProfileResponse,
RegisterSchema,
ResetPasswordSchema,
SchemaResponse,
SessionListResponse,
SessionOut,
TokenRefreshSchema,
TokenResponse,
UpdateProfileSchema,
UserSchemaField,
UserSchemaResponse,
)
from app.services.supabase import SupabaseClient
from app.services.auth_service import AuthService
router = APIRouter(prefix="/auth", tags=["Authentication"])
_settings = get_settings()
@router.post("/register", response_model=ProfileResponse, status_code=status.HTTP_201_CREATED)
async def register(
schema: RegisterSchema,
db: Annotated[SupabaseClient, Depends(get_db)],
):
user = await AuthService.register(db, schema)
profile = await AuthService.user_to_profile(db, user)
return {"success": True, "data": profile.model_dump()}
@router.post("/login", response_model=TokenResponse)
async def login(
schema: LoginSchema,
request: Request,
db: Annotated[SupabaseClient, Depends(get_db)],
):
tokens = await AuthService.login(db, request, schema)
return {"success": True, "data": tokens.model_dump()}
@router.post("/refresh", response_model=TokenResponse)
async def refresh_token(
schema: TokenRefreshSchema,
db: Annotated[SupabaseClient, Depends(get_db)],
):
tokens = await AuthService.refresh(db, schema.refresh_token)
return {"success": True, "data": tokens.model_dump()}
@router.post("/logout", response_model=MessageDataResponse)
async def logout(
schema: TokenRefreshSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.logout(db, current_user, schema.refresh_token)
return {"success": True, "data": {"message": "Logged out successfully"}}
@router.post("/logout-all", response_model=MessageDataResponse)
async def logout_all(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.logout_all(db, current_user)
return {"success": True, "data": {"message": "All sessions revoked"}}
@router.post("/forgot-password", response_model=MessageDataResponse)
async def forgot_password(
schema: ForgotPasswordSchema,
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.forgot_password(db, schema)
return {"success": True, "data": {"message": "If that email exists, a password reset link has been sent."}}
@router.post("/reset-password", response_model=MessageDataResponse)
async def reset_password(
schema: ResetPasswordSchema,
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.reset_password(db, schema)
return {"success": True, "data": {"message": "Password reset successfully"}}
@router.post("/change-password", response_model=MessageDataResponse)
async def change_password(
schema: ChangePasswordSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.change_password(db, current_user, schema)
return {"success": True, "data": {"message": "Password changed successfully"}}
@router.get("/me", response_model=ProfileResponse)
async def get_me(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
profile = await AuthService.user_to_profile(db, current_user)
return {"success": True, "data": profile.model_dump()}
@router.patch("/me", response_model=ProfileResponse)
async def update_me(
schema: UpdateProfileSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
user = await AuthService.update_profile(db, current_user, schema)
profile = await AuthService.user_to_profile(db, user)
return {"success": True, "data": profile.model_dump()}
@router.delete("/me", response_model=MessageDataResponse)
async def delete_me(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.soft_delete(db, current_user)
return {"success": True, "data": {"message": "Account deleted successfully"}}
@router.get("/schema", response_model=SchemaResponse)
async def get_user_schema():
columns = [
UserSchemaField(field="id", type="string (UUID)", required=True, description="Unique user identifier", constraints="Auto-generated"),
UserSchemaField(field="email", type="string", required=True, description="User email address", constraints="Unique, max 255 chars"),
UserSchemaField(field="username", type="string", required=False, description="Unique username", constraints="Unique, 2-50 chars, alphanumeric + underscore"),
UserSchemaField(field="full_name", type="string", required=False, description="Display name", constraints="Max 100 chars"),
UserSchemaField(field="password", type="string", required=True, description="User password (write-only)", constraints="Min 8 chars, uppercase, lowercase, digit"),
UserSchemaField(field="password_hash", type="string", required=True, description="Argon2id password hash (internal)", constraints="Auto-generated"),
UserSchemaField(field="is_active", type="boolean", required=False, description="Whether the user account is active", constraints="Default: true"),
UserSchemaField(field="is_verified", type="boolean", required=False, description="Whether the email is verified", constraints="Default: false"),
UserSchemaField(field="failed_login_attempts", type="integer", required=False, description="Consecutive failed login count", constraints="Default: 0"),
UserSchemaField(field="locked_until", type="datetime (ISO 8601)", required=False, description="Account lock expiry", constraints="Nullable"),
UserSchemaField(field="last_login", type="datetime (ISO 8601)", required=False, description="Last successful login timestamp", constraints="Nullable"),
UserSchemaField(field="password_changed_at", type="datetime (ISO 8601)", required=False, description="Last password change", constraints="Nullable"),
UserSchemaField(field="created_at", type="datetime (ISO 8601)", required=False, description="Account creation timestamp", constraints="Auto-set"),
UserSchemaField(field="updated_at", type="datetime (ISO 8601)", required=False, description="Last update timestamp", constraints="Auto-updated"),
UserSchemaField(field="deleted_at", type="datetime (ISO 8601)", required=False, description="Soft delete timestamp", constraints="Nullable"),
UserSchemaField(field="roles", type="array[string]", required=False, description="Assigned role names", constraints="Via user_roles association table"),
]
return {"success": True, "data": UserSchemaResponse(table_name="users", columns=columns).model_dump()}
@router.get("/sessions", response_model=SessionListResponse)
async def list_sessions(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
sessions = await AuthService.list_sessions(db, current_user)
return {"success": True, "data": [SessionOut.model_validate(s).model_dump() for s in sessions]}
@router.delete("/sessions/{session_id}", response_model=MessageDataResponse)
async def revoke_session(
session_id: str,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[SupabaseClient, Depends(get_db)],
):
await AuthService.revoke_session(db, current_user, session_id)
return {"success": True, "data": {"message": "Session revoked successfully"}}