# Prompt di Ingaggio: User Authentication (T17-T22) ## ๐ŸŽฏ MISSIONE Implementare la fase **User Authentication** (T17-T22) del progetto OpenRouter API Key Monitor seguendo rigorosamente TDD. --- ## ๐Ÿ“‹ CONTEXTO **AGENTE:** @tdd-developer **Repository:** `/home/google/Sources/LucaSacchiNet/openrouter-watcher` **Stato Attuale:** - โœ… Setup completato (T01-T05): 59 test - โœ… Database & Models (T06-T11): 73 test - โœ… Security Services (T12-T16): 70 test - ๐ŸŽฏ **Totale: 202 test passanti, 100% coverage sui moduli implementati** **Servizi Pronti da utilizzare:** - `hash_password()`, `verify_password()` - in `services/password.py` - `create_access_token()`, `decode_access_token()` - in `services/jwt.py` - `EncryptionService` - in `services/encryption.py` - `generate_api_token()`, `verify_api_token()` - in `services/token.py` - `User`, `ApiKey`, `UsageStats`, `ApiToken` models - `get_db()`, `Base` - in `database.py` **Documentazione:** - PRD: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/prd.md` - Architecture: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/architecture.md` - Prompt Dettagliato: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/prompt/prompt-authentication.md` --- ## ๐Ÿ”ง TASK DA IMPLEMENTARE ### T17: Creare Pydantic Schemas per Autenticazione **File:** `src/openrouter_monitor/schemas/auth.py` **Requisiti:** - `UserRegister`: email (EmailStr), password (min 12), password_confirm - Validatore: richiama `validate_password_strength()` - Root validator: password == password_confirm - `UserLogin`: email, password - `UserResponse`: id, email, created_at, is_active (orm_mode=True) - `TokenResponse`: access_token, token_type="bearer", expires_in - `TokenData`: user_id (sub), exp **Test:** `tests/unit/schemas/test_auth_schemas.py` --- ### T18: Implementare Endpoint POST /api/auth/register **File:** `src/openrouter_monitor/routers/auth.py` **Requisiti:** - Endpoint: `POST /api/auth/register` - Riceve: `UserRegister` schema - Logica: 1. Verifica email non esista: `db.query(User).filter(User.email == ...).first()` 2. Se esiste: HTTPException 400 "Email already registered" 3. Hash password: `hash_password(user_data.password)` 4. Crea User: `User(email=..., password_hash=...)` 5. Salva: `db.add()`, `db.commit()`, `db.refresh()` 6. Ritorna: `UserResponse`, status 201 **Test:** Register success, email duplicata (400), password debole (422) --- ### T19: Implementare Endpoint POST /api/auth/login **File:** `src/openrouter_monitor/routers/auth.py` **Requisiti:** - Endpoint: `POST /api/auth/login` - Riceve: `UserLogin` schema - Logica: 1. Trova utente per email 2. Se non trovato o inattivo: HTTPException 401 "Invalid credentials" 3. Verifica password: `verify_password(credentials.password, user.password_hash)` 4. Se fallita: HTTPException 401 5. Genera JWT: `create_access_token(data={"sub": str(user.id)})` 6. Ritorna: `TokenResponse` con access_token **Test:** Login success (200 + token), email inesistente (401), password sbagliata (401), utente inattivo (401) --- ### T20: Implementare Endpoint POST /api/auth/logout **File:** `src/openrouter_monitor/routers/auth.py` **Requisiti:** - Endpoint: `POST /api/auth/logout` - Richiede: `current_user: User = Depends(get_current_user)` - Logica: JWT stateless, logout gestito lato client - Ritorna: `{"message": "Successfully logged out"}` **Test:** Logout con token valido (200), senza token (401) --- ### T21: Implementare Dipendenza get_current_user **File:** `src/openrouter_monitor/dependencies/auth.py` **Requisiti:** ```python from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials security = HTTPBearer() async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db) ) -> User: token = credentials.credentials try: payload = decode_access_token(token) user_id = int(payload.get("sub")) if not user_id: raise HTTPException(401, "Invalid token payload") except JWTError: raise HTTPException(401, "Invalid or expired token") user = db.query(User).filter(User.id == user_id).first() if not user or not user.is_active: raise HTTPException(401, "User not found or inactive") return user ``` **Test:** Token valido ritorna utente, token mancante (401), token scaduto (401), token invalido (401), utente inesistente (401), utente inattivo (401) --- ### T22: Scrivere Test per Auth Endpoints **File:** `tests/unit/routers/test_auth.py` **Requisiti:** - Usare `TestClient` da FastAPI - Fixture: `test_user`, `auth_token`, `authorized_client` - Test coverage >= 90% **Test da implementare:** - Register: success (201), email duplicata (400), password debole (422), email invalida (422) - Login: success (200 + token), email inesistente (401), password sbagliata (401), utente inattivo (401) - Logout: success (200), senza token (401) - get_current_user: protetto con token valido, senza token (401), token scaduto (401) --- ## ๐Ÿ”„ WORKFLOW TDD Per **OGNI** task: 1. **RED**: Scrivi test che fallisce (prima del codice!) 2. **GREEN**: Implementa codice minimo per passare il test 3. **REFACTOR**: Migliora codice, test rimangono verdi --- ## ๐Ÿ“ STRUTTURA FILE DA CREARE ``` src/openrouter_monitor/ โ”œโ”€โ”€ schemas/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ””โ”€โ”€ auth.py # T17 โ”œโ”€โ”€ routers/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ””โ”€โ”€ auth.py # T18, T19, T20 โ””โ”€โ”€ dependencies/ โ”œโ”€โ”€ __init__.py โ””โ”€โ”€ auth.py # T21 tests/unit/ โ”œโ”€โ”€ schemas/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ””โ”€โ”€ test_auth_schemas.py # T17 + T22 โ””โ”€โ”€ routers/ โ”œโ”€โ”€ __init__.py โ””โ”€โ”€ test_auth.py # T18-T21 + T22 ``` --- ## ๐Ÿงช ESEMPI TEST ### Test Schema ```python @pytest.mark.unit def test_user_register_valid_data_passes_validation(): data = UserRegister( email="test@example.com", password="SecurePass123!", password_confirm="SecurePass123!" ) assert data.email == "test@example.com" ``` ### Test Endpoint ```python @pytest.mark.asyncio async def test_register_new_user_returns_201(client, db_session): response = client.post("/api/auth/register", json={ "email": "test@example.com", "password": "SecurePass123!", "password_confirm": "SecurePass123!" }) assert response.status_code == 201 assert response.json()["email"] == "test@example.com" ``` --- ## โœ… CRITERI DI ACCETTAZIONE - [ ] T17: Schemas auth con validazione completa - [ ] T18: POST /api/auth/register (201/400/422) - [ ] T19: POST /api/auth/login (200/401) - [ ] T20: POST /api/auth/logout (200) - [ ] T21: get_current_user dependency funzionante - [ ] T22: Test auth coverage >= 90% - [ ] Tutti i test passano: `pytest tests/unit/routers/test_auth.py -v` - [ ] 6 commit atomici con conventional commits - [ ] progress.md aggiornato --- ## ๐Ÿ“ COMMIT MESSAGES ``` feat(schemas): T17 add Pydantic auth schemas feat(auth): T18 implement user registration endpoint feat(auth): T19 implement user login endpoint feat(auth): T20 implement user logout endpoint feat(deps): T21 implement get_current_user dependency test(auth): T22 add comprehensive auth endpoint tests ``` --- ## ๐Ÿš€ VERIFICA FINALE ```bash cd /home/google/Sources/LucaSacchiNet/openrouter-watcher # Test schemas pytest tests/unit/schemas/test_auth_schemas.py -v # Test routers pytest tests/unit/routers/test_auth.py -v --cov=src/openrouter_monitor/routers # Test completo pytest tests/unit/ -v --cov=src/openrouter_monitor ``` --- ## โš ๏ธ NOTE IMPORTANTI - **Path assoluti**: Usa sempre `/home/google/Sources/LucaSacchiNet/openrouter-watcher/` - **Servizi esistenti**: Riutilizza `hash_password`, `verify_password`, `create_access_token`, `decode_access_token` - **Database**: Usa `get_db()` da `database.py` per dependency injection - **Models**: Importa da `models` package (User, ApiKey, etc.) - **Sicurezza**: Mai loggare password o token in plaintext - **Errori**: Errori generici per credenziali invalide (non leakare info) --- **AGENTE:** @tdd-developer **INIZIA CON:** T17 - Pydantic schemas **QUANDO FINITO:** Conferma completamento e aggiorna progress.md