Add authentication schemas for user registration and login: - UserRegister: email, password (with strength validation), password_confirm - UserLogin: email, password - UserResponse: id, email, created_at, is_active (orm_mode=True) - TokenResponse: access_token, token_type, expires_in - TokenData: user_id, exp Includes field validators for password strength and password confirmation matching. Test coverage: 19 tests for all schemas
286 lines
8.2 KiB
Markdown
286 lines
8.2 KiB
Markdown
# 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
|