Files
mgiustini 58a233236c feat: Fase 1 – Fondamenta complete (backend FastAPI + auth + permessi)
- docker-compose.yml: PostgreSQL 16, Redis 7, MinIO, Nginx
- backend FastAPI: struttura monorepo, config pydantic-settings
- modelli SQLAlchemy: tutti i modelli (tenants, users, mailboxes, messages, archival, permissions, labels, audit_log)
- migrazione Alembic 0001: schema completo in pure SQL
- auth API: login JWT, refresh token rotation, logout, 2FA TOTP (setup/verify/disable)
- CRUD utenti: lista, crea, modifica, reset password, soft delete
- permessi granulari (Fase 1-A): mailbox_permissions, assegna/revoca/lista
- CRUD tenant: gestione super-admin
- sicurezza: AES-256-GCM cifratura credenziali IMAP/SMTP, bcrypt password
- RLS PostgreSQL: isolamento multi-tenant per request
- seed sviluppo: tenant demo + admin + operator
- test unit: security (bcrypt, JWT, AES), auth_service
- test integration: auth endpoints, users endpoints
- CI GitHub Actions: lint (ruff), test (pytest), build Docker, security scan
- infra: nginx.conf, redis.conf
- Makefile con comandi make dev/test/migrate/seed

Definition of Done:
 Login, refresh token e TOTP funzionanti
 make dev porta in piedi tutto lo stack locale
 CI configurata
2026-03-18 16:42:01 +01:00

148 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Router utenti CRUD per admin del tenant.
Endpoint:
GET /api/v1/users → lista utenti (admin)
POST /api/v1/users → crea utente (admin)
GET /api/v1/users/{id} → dettaglio utente (admin)
PATCH /api/v1/users/{id} → modifica utente (admin)
DELETE /api/v1/users/{id} → disabilita utente (admin)
POST /api/v1/users/{id}/reset-password → reset password (admin)
"""
import uuid
from fastapi import APIRouter, Query
from app.dependencies import AdminUser, DB
from app.schemas.user import (
UserCreateRequest,
UserListResponse,
UserPasswordResetRequest,
UserResponse,
UserUpdateRequest,
)
from app.services.user_service import UserService
router = APIRouter(prefix="/users", tags=["Utenti"])
@router.get(
"",
response_model=UserListResponse,
summary="Lista utenti del tenant",
)
async def list_users(
current_user: AdminUser,
db: DB,
page: int = Query(default=1, ge=1),
page_size: int = Query(default=25, ge=1, le=100),
) -> UserListResponse:
service = UserService(db)
users, total = await service.list_users(
tenant_id=current_user.tenant_id,
page=page,
page_size=page_size,
)
import math
return UserListResponse(
items=[UserResponse.model_validate(u) for u in users],
total=total,
page=page,
page_size=page_size,
pages=math.ceil(total / page_size) if page_size else 0,
)
@router.post(
"",
response_model=UserResponse,
status_code=201,
summary="Crea nuovo utente nel tenant",
)
async def create_user(
body: UserCreateRequest,
current_user: AdminUser,
db: DB,
) -> UserResponse:
service = UserService(db)
user = await service.create_user(
tenant_id=current_user.tenant_id,
data=body,
created_by=current_user,
)
return UserResponse.model_validate(user)
@router.get(
"/{user_id}",
response_model=UserResponse,
summary="Dettaglio utente",
)
async def get_user(
user_id: uuid.UUID,
current_user: AdminUser,
db: DB,
) -> UserResponse:
service = UserService(db)
user = await service.get_user(user_id, current_user.tenant_id)
return UserResponse.model_validate(user)
@router.patch(
"/{user_id}",
response_model=UserResponse,
summary="Modifica utente",
)
async def update_user(
user_id: uuid.UUID,
body: UserUpdateRequest,
current_user: AdminUser,
db: DB,
) -> UserResponse:
service = UserService(db)
user = await service.update_user(
user_id=user_id,
tenant_id=current_user.tenant_id,
data=body,
updated_by=current_user,
)
return UserResponse.model_validate(user)
@router.delete(
"/{user_id}",
status_code=204,
summary="Disabilita utente (soft delete)",
)
async def delete_user(
user_id: uuid.UUID,
current_user: AdminUser,
db: DB,
) -> None:
service = UserService(db)
await service.delete_user(
user_id=user_id,
tenant_id=current_user.tenant_id,
deleted_by=current_user,
)
@router.post(
"/{user_id}/reset-password",
status_code=204,
summary="Reset password utente (admin)",
)
async def reset_password(
user_id: uuid.UUID,
body: UserPasswordResetRequest,
current_user: AdminUser,
db: DB,
) -> None:
service = UserService(db)
await service.reset_password(
user_id=user_id,
tenant_id=current_user.tenant_id,
new_password=body.new_password,
)