llm-ready-data / app /api /v1 /auth.py
light-infer-chat's picture
ok
08919be
Raw
History Blame Contribute Delete
9.33 kB
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import get_settings
from app.core.auth.deps import get_current_user, get_db, get_temp_db_warning, require_application_id
from app.core.auth.models import RefreshSession, User
from app.core.auth.schemas import (
ChangePasswordSchema,
ForgotPasswordSchema,
LoginSchema,
MessageDataResponse,
ProfileResponse,
RegisterSchema,
ResetPasswordSchema,
SchemaResponse,
SessionListResponse,
SessionOut,
TokenRefreshSchema,
TokenResponse,
UpdateProfileSchema,
UserSchemaField,
UserSchemaResponse,
DataResponse,
)
from app.services.auth_service import AuthService
router = APIRouter(prefix="/auth", tags=["Authentication"])
_settings = get_settings()
def _build(data, warning):
result = {"success": True, "data": data}
if _settings.application_id:
result["application_id"] = _settings.application_id
if warning:
result["warning"] = warning
return result
@router.post("/register", response_model=ProfileResponse, status_code=status.HTTP_201_CREATED)
async def register(
schema: RegisterSchema,
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
user = await AuthService.register(db, schema)
warning = get_temp_db_warning()
return _build(AuthService.user_to_profile(user).model_dump(), warning)
@router.post("/login", response_model=TokenResponse)
async def login(
schema: LoginSchema,
request: Request,
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
tokens = await AuthService.login(db, request, schema)
warning = get_temp_db_warning()
return _build(tokens.model_dump(), warning)
@router.post("/refresh", response_model=TokenResponse)
async def refresh_token(
schema: TokenRefreshSchema,
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
tokens = await AuthService.refresh(db, schema.refresh_token)
warning = get_temp_db_warning()
return _build(tokens.model_dump(), warning)
@router.post("/logout", response_model=MessageDataResponse)
async def logout(
schema: TokenRefreshSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.logout(db, current_user, schema.refresh_token)
warning = get_temp_db_warning()
return _build({"message": "Logged out successfully"}, warning)
@router.post("/logout-all", response_model=MessageDataResponse)
async def logout_all(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.logout_all(db, current_user)
warning = get_temp_db_warning()
return _build({"message": "All sessions revoked"}, warning)
@router.post("/forgot-password", response_model=MessageDataResponse)
async def forgot_password(
schema: ForgotPasswordSchema,
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.forgot_password(db, schema)
warning = get_temp_db_warning()
return _build({"message": "If that email exists, a password reset link has been sent."}, warning)
@router.post("/reset-password", response_model=MessageDataResponse)
async def reset_password(
schema: ResetPasswordSchema,
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.reset_password(db, schema)
warning = get_temp_db_warning()
return _build({"message": "Password reset successfully"}, warning)
@router.post("/change-password", response_model=MessageDataResponse)
async def change_password(
schema: ChangePasswordSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.change_password(db, current_user, schema)
warning = get_temp_db_warning()
return _build({"message": "Password changed successfully"}, warning)
@router.get("/me", response_model=ProfileResponse)
async def get_me(
current_user: Annotated[User, Depends(get_current_user)],
_: Annotated[bool, Depends(require_application_id)],
):
warning = get_temp_db_warning()
return _build(AuthService.user_to_profile(current_user).model_dump(), warning)
@router.patch("/me", response_model=ProfileResponse)
async def update_me(
schema: UpdateProfileSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
user = await AuthService.update_profile(db, current_user, schema)
warning = get_temp_db_warning()
return _build(AuthService.user_to_profile(user).model_dump(), warning)
@router.delete("/me", response_model=MessageDataResponse)
async def delete_me(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.soft_delete(db, current_user)
warning = get_temp_db_warning()
return _build({"message": "Account deleted successfully"}, warning)
@router.get("/schema", response_model=SchemaResponse)
async def get_user_schema(
_: Annotated[bool, Depends(require_application_id)],
):
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"),
]
warning = get_temp_db_warning()
return _build(UserSchemaResponse(table_name="users", columns=columns).model_dump(), warning)
@router.get("/sessions", response_model=SessionListResponse)
async def list_sessions(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
sessions = await AuthService.list_sessions(db, current_user)
warning = get_temp_db_warning()
return _build([SessionOut.model_validate(s).model_dump() for s in sessions], warning)
@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[AsyncSession, Depends(get_db)],
_: Annotated[bool, Depends(require_application_id)],
):
await AuthService.revoke_session(db, current_user, session_id)
warning = get_temp_db_warning()
return _build({"message": "Session revoked successfully"}, warning)