API key: salvata in chiaro e senza scadenza #41

Closed
opened 2026-09-21 21:56:03 +00:00 by Adriano · 3 comments
Owner

COSA: users.api_key è in chiaro nel DB e non scade (src/backend/services/auth_service.py, src/backend/models/orm/user.py:35). Da #25 la key è riusata fra postazioni dello stesso utente, quindi vive più a lungo di prima.
CRITERIO DI FATTO: key salvata come hash (SHA-256), confronto sull'hash, TTL configurabile con rinnovo all'uso, migrazione Alembic che invalida le key esistenti. Escluso dichiaratamente dall'analisi di #25.

ORIGINE: residui dichiarati alla chiusura delle issue della revisione del 21/09/2026.

COSA: `users.api_key` è in chiaro nel DB e non scade (src/backend/services/auth_service.py, src/backend/models/orm/user.py:35). Da #25 la key è riusata fra postazioni dello stesso utente, quindi vive più a lungo di prima. CRITERIO DI FATTO: key salvata come hash (SHA-256), confronto sull'hash, TTL configurabile con rinnovo all'uso, migrazione Alembic che invalida le key esistenti. Escluso dichiaratamente dall'analisi di #25. ORIGINE: residui dichiarati alla chiusura delle issue della revisione del 21/09/2026.
Adriano added the sviluppo label 2026-09-21 21:56:03 +00:00
Adriano added the assegnata:jeeg label 2026-10-06 09:23:12 +00:00
Author
Owner

⚡ jeeg — preso in carico 2026-10-06T09:23:12Z
ramo: lavoro/jeeg · capacità: docker, gpu, systemd

⚡ jeeg — **preso in carico** 2026-10-06T09:23:12Z ramo: `lavoro/jeeg` · capacità: docker, gpu, systemd
Author
Owner

ESITO: richiesta
RIF: gitea:Adriano/TieMeasureFlow#41
COSA: analisi (passo 1). API key salvate come hash SHA-256, una per sessione, con TTL che si rinnova all'uso.
NODO: login_user (src/backend/services/auth_service.py:78) oggi RIUSA la key esistente perché lo stesso account lavora da più postazioni (#25). Con l'hash la key in chiaro non si può più restituire, quindi: UNA KEY PER LOGIN, in una tabella nuova. Ogni postazione ha la sua key, e il requisito di #25 resta soddisfatto (nessun login butta fuori un'altra postazione).
FILE:

  • src/backend/models/orm/api_key.py (nuovo): ApiKey(id, user_id FK users ON DELETE CASCADE, key_hash String(64) unique index, created_at, expires_at, last_used_at). Registrarlo dove sono registrati gli altri ORM (models/orm/__init__.py).
  • src/backend/models/orm/user.py:35: togliere la colonna api_key.
  • src/backend/config.py: api_key_ttl_hours: int = 24 (env API_KEY_TTL_HOURS).
  • src/backend/services/auth_service.py: hash_api_key(key) -> sha256 hex; issue_api_key(db, user) crea la riga e restituisce il chiaro; login_user usa issue_api_key (niente riuso); logout_user(db, user, key, all_sessions): di default cancella SOLO la key della sessione, con all_sessions=True tutte quelle dell'utente; revoke_all_keys(db, user_id); regenerate_api_key = revoke_all + issue.
  • src/backend/api/middleware/api_key.py get_current_user: cerca per hash, con expires_at > adesso e user attivo; scaduta = 401 «Invalid or expired API key». Rinnovo all'uso: expires_at = adesso + ttl e last_used_at = adesso, ma SOLO se l'ultimo aggiornamento risale a più di 5 minuti fa (niente scrittura a ogni richiesta).
  • src/backend/api/routers/auth.py: logout passa la key dell'header.
  • src/backend/api/routers/users.py:149 e :171 (cambio password, disattivazione): revoke_all_keys al posto di user.api_key = None.
  • src/backend/api/routers/setup.py:324-342 (create-admin) e :407 (utenti di seed): niente api_key nel costruttore User; create-admin restituisce una key emessa con issue_api_key.
  • src/backend/migrations/versions/015_api_key_hash.py: down_revision 014_vision_fk_ondelete; crea api_keys, toglie users.api_key con indice e vincolo (uq_users_api_key, ix_users_api_key; batch mode per SQLite). Le key esistenti si INVALIDANO di proposito, non si migrano: tutti rifanno il login una volta. downgrade: ricrea la colonna vuota e droppa la tabella.
    TEMPO: le date nuove sono UTC naive: datetime.now(timezone.utc).replace(tzinfo=None). La #42 porterà tutto il DB a UTC, quindi niente datetime.now() locale qui.
    TEST: login due volte con lo stesso utente dà due key diverse, e tutte e due valgono; nel DB c'è l'hash, mai il chiaro; key scaduta = 401; l'uso rinnova expires_at; logout toglie solo la sua key, all_sessions tutte; cambio password e disattivazione invalidano tutte le key; regenerate invalida le vecchie; la migrazione 015 su SQLite fa upgrade e downgrade. I test esistenti che mettono api_key=... su User vanno adattati (conftest e helper).
    NON TOCCARE: frontend Flask (riceve la key dal login come oggi, nessun cambio di contratto); vision_worker; la #42 (gli altri datetime).
    FATTO SE: uv run pytest verde, nessun riferimento a User.api_key (grep ".api_key\b" in src/backend senza risultati, a parte lo schema di risposta del login).
ESITO: richiesta RIF: gitea:Adriano/TieMeasureFlow#41 COSA: analisi (passo 1). API key salvate come hash SHA-256, una per sessione, con TTL che si rinnova all'uso. NODO: login_user (src/backend/services/auth_service.py:78) oggi RIUSA la key esistente perché lo stesso account lavora da più postazioni (#25). Con l'hash la key in chiaro non si può più restituire, quindi: UNA KEY PER LOGIN, in una tabella nuova. Ogni postazione ha la sua key, e il requisito di #25 resta soddisfatto (nessun login butta fuori un'altra postazione). FILE: - src/backend/models/orm/api_key.py (nuovo): ApiKey(id, user_id FK users ON DELETE CASCADE, key_hash String(64) unique index, created_at, expires_at, last_used_at). Registrarlo dove sono registrati gli altri ORM (models/orm/__init__.py). - src/backend/models/orm/user.py:35: togliere la colonna api_key. - src/backend/config.py: api_key_ttl_hours: int = 24 (env API_KEY_TTL_HOURS). - src/backend/services/auth_service.py: hash_api_key(key) -> sha256 hex; issue_api_key(db, user) crea la riga e restituisce il chiaro; login_user usa issue_api_key (niente riuso); logout_user(db, user, key, all_sessions): di default cancella SOLO la key della sessione, con all_sessions=True tutte quelle dell'utente; revoke_all_keys(db, user_id); regenerate_api_key = revoke_all + issue. - src/backend/api/middleware/api_key.py get_current_user: cerca per hash, con expires_at > adesso e user attivo; scaduta = 401 «Invalid or expired API key». Rinnovo all'uso: expires_at = adesso + ttl e last_used_at = adesso, ma SOLO se l'ultimo aggiornamento risale a più di 5 minuti fa (niente scrittura a ogni richiesta). - src/backend/api/routers/auth.py: logout passa la key dell'header. - src/backend/api/routers/users.py:149 e :171 (cambio password, disattivazione): revoke_all_keys al posto di user.api_key = None. - src/backend/api/routers/setup.py:324-342 (create-admin) e :407 (utenti di seed): niente api_key nel costruttore User; create-admin restituisce una key emessa con issue_api_key. - src/backend/migrations/versions/015_api_key_hash.py: down_revision 014_vision_fk_ondelete; crea api_keys, toglie users.api_key con indice e vincolo (uq_users_api_key, ix_users_api_key; batch mode per SQLite). Le key esistenti si INVALIDANO di proposito, non si migrano: tutti rifanno il login una volta. downgrade: ricrea la colonna vuota e droppa la tabella. TEMPO: le date nuove sono UTC naive: datetime.now(timezone.utc).replace(tzinfo=None). La #42 porterà tutto il DB a UTC, quindi niente datetime.now() locale qui. TEST: login due volte con lo stesso utente dà due key diverse, e tutte e due valgono; nel DB c'è l'hash, mai il chiaro; key scaduta = 401; l'uso rinnova expires_at; logout toglie solo la sua key, all_sessions tutte; cambio password e disattivazione invalidano tutte le key; regenerate invalida le vecchie; la migrazione 015 su SQLite fa upgrade e downgrade. I test esistenti che mettono api_key=... su User vanno adattati (conftest e helper). NON TOCCARE: frontend Flask (riceve la key dal login come oggi, nessun cambio di contratto); vision_worker; la #42 (gli altri datetime). FATTO SE: uv run pytest verde, nessun riferimento a User.api_key (grep "\.api_key\b" in src/backend senza risultati, a parte lo schema di risposta del login).
Author
Owner

Soluzione

ESITO: fatto
COMMIT: 35f4692, 0d004c4 su V3.0.0 (ramo lavoro/jeeg-tmf41)
COSA: le API key si salvano come hash SHA-256, una per login, con TTL che si rinnova all'uso.
FILE:

  • src/backend/models/orm/api_key.py (nuovo) + migrazione 015_api_key_hash: tabella api_keys (user_id, key_hash, created_at, expires_at, last_used_at); colonna users.api_key tolta.
  • auth_service: issue_api_key, hash_api_key, revoke_all_keys; login_user emette una key nuova (il requisito di #25 resta: nessun login butta fuori un'altra postazione); logout toglie solo la key della sessione, all_sessions tutte; al login si cancellano le key scadute dell'utente.
  • middleware api_key: ricerca per hash, scadenza → 401 «Invalid or expired API key» (il frontend lo trasforma già in redirect al login, app.py:196); rinnovo di expires_at al massimo ogni 5 minuti.
  • config: api_key_ttl_hours = 24 (env API_KEY_TTL_HOURS).
  • users.py: cambio password e disattivazione revocano tutte le key; setup.py: niente key nel costruttore User.
    AL DEPLOY: la migrazione invalida tutte le key esistenti, quindi tutti rifanno il login una volta.
    PROVA: uv run pytest → 647 passed, 4 skipped (con #43 e #40). Migrazione 015 provata su SQLite (upgrade e downgrade), non su MySQL.
**Soluzione** ESITO: fatto COMMIT: 35f4692, 0d004c4 su V3.0.0 (ramo lavoro/jeeg-tmf41) COSA: le API key si salvano come hash SHA-256, una per login, con TTL che si rinnova all'uso. FILE: - src/backend/models/orm/api_key.py (nuovo) + migrazione 015_api_key_hash: tabella api_keys (user_id, key_hash, created_at, expires_at, last_used_at); colonna users.api_key tolta. - auth_service: issue_api_key, hash_api_key, revoke_all_keys; login_user emette una key nuova (il requisito di #25 resta: nessun login butta fuori un'altra postazione); logout toglie solo la key della sessione, all_sessions tutte; al login si cancellano le key scadute dell'utente. - middleware api_key: ricerca per hash, scadenza → 401 «Invalid or expired API key» (il frontend lo trasforma già in redirect al login, app.py:196); rinnovo di expires_at al massimo ogni 5 minuti. - config: api_key_ttl_hours = 24 (env API_KEY_TTL_HOURS). - users.py: cambio password e disattivazione revocano tutte le key; setup.py: niente key nel costruttore User. AL DEPLOY: la migrazione invalida tutte le key esistenti, quindi tutti rifanno il login una volta. PROVA: uv run pytest → 647 passed, 4 skipped (con #43 e #40). Migrazione 015 provata su SQLite (upgrade e downgrade), non su MySQL.
Adriano removed the assegnata:jeeg label 2026-10-06 09:37:09 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/TieMeasureFlow#41