07e23b957d
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
"""Authentication router - login, logout, profile."""
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.backend.database import get_db
|
|
from src.backend.api.middleware.api_key import get_current_user
|
|
from src.backend.models.orm.user import User
|
|
from src.backend.models.api.user import (
|
|
LoginRequest,
|
|
LoginResponse,
|
|
PinLoginRequest,
|
|
PinUserItem,
|
|
UserProfileUpdate,
|
|
UserResponse,
|
|
)
|
|
from src.backend.services.auth_service import (
|
|
authenticate_pin,
|
|
authenticate_user,
|
|
login_user,
|
|
logout_user,
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
|
|
|
|
@router.post("/login", response_model=LoginResponse)
|
|
async def login(data: LoginRequest, db: AsyncSession = Depends(get_db)):
|
|
"""Login with username and password, receive API key."""
|
|
user = await authenticate_user(db, data.username, data.password)
|
|
if user is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid username or password",
|
|
)
|
|
api_key = await login_user(db, user)
|
|
return LoginResponse(
|
|
user=UserResponse.model_validate(user),
|
|
api_key=api_key,
|
|
)
|
|
|
|
|
|
@router.get("/pin-users", response_model=list[PinUserItem])
|
|
async def pin_users(db: AsyncSession = Depends(get_db)):
|
|
"""Utenti che possono entrare con PIN (#59): attivi, non admin, con PIN.
|
|
|
|
Pubblico per costruzione (serve prima del login): espone solo id e nome.
|
|
"""
|
|
result = await db.execute(
|
|
select(User).where(
|
|
User.active == True, # noqa: E712
|
|
User.is_admin == False, # noqa: E712
|
|
User.pin_hash.is_not(None),
|
|
)
|
|
)
|
|
items = [
|
|
PinUserItem(id=u.id, display_name=u.display_name or u.username)
|
|
for u in result.scalars().all()
|
|
]
|
|
items.sort(key=lambda i: i.display_name.casefold())
|
|
return items
|
|
|
|
|
|
@router.post("/login-pin", response_model=LoginResponse)
|
|
async def login_pin(data: PinLoginRequest, db: AsyncSession = Depends(get_db)):
|
|
"""Login con user_id e PIN a 4 cifre (#59); stessa risposta del login."""
|
|
user = await authenticate_pin(db, data.user_id, data.pin)
|
|
api_key = await login_user(db, user)
|
|
return LoginResponse(user=UserResponse.model_validate(user), api_key=api_key)
|
|
|
|
|
|
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def logout(
|
|
request: Request,
|
|
all_sessions: bool = False,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Logout. Deletes this session's API key only; ?all_sessions=true
|
|
deletes every key of the user."""
|
|
await logout_user(
|
|
db, user, key=request.headers.get("X-API-Key"), all_sessions=all_sessions
|
|
)
|
|
|
|
|
|
@router.get("/me", response_model=UserResponse)
|
|
async def get_profile(user: User = Depends(get_current_user)):
|
|
"""Get current user profile."""
|
|
return UserResponse.model_validate(user)
|
|
|
|
|
|
@router.put("/me", response_model=UserResponse)
|
|
async def update_profile(
|
|
data: UserProfileUpdate,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Update own profile (display_name, language, theme)."""
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(user, field, value)
|
|
await db.flush()
|
|
await db.refresh(user)
|
|
return UserResponse.model_validate(user)
|