import hashlib import secrets from datetime import datetime, timedelta, timezone from .connection import connect TOKEN_TTL_MINUTES = 60 def _hash_token(token): return hashlib.sha256(token.encode("utf-8")).hexdigest() def create_password_reset_token(user_id): """Un seul jeton valide à la fois par utilisateur : en créer un nouveau invalide silencieusement tout jeu envoyé plus tôt (par exemple si l'utilisateur redemande un email parce que le premier s'est perdu), pour ne jamais avoir deux liens valides en même temps.""" token = secrets.token_urlsafe(32) expires_at = (datetime.now(timezone.utc) + timedelta(minutes=TOKEN_TTL_MINUTES)).isoformat() conn = connect() conn.execute("DELETE FROM _password_reset_tokens WHERE user_id = ?", (user_id,)) conn.execute( "INSERT INTO _password_reset_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)", (user_id, _hash_token(token), expires_at), ) conn.commit() conn.close() return token def get_user_id_for_valid_token(token): if not token: return None conn = connect() row = conn.execute( "SELECT user_id, expires_at, used_at FROM _password_reset_tokens WHERE token_hash = ?", (_hash_token(token),), ).fetchone() conn.close() if not row or row["used_at"]: return None if datetime.fromisoformat(row["expires_at"]) < datetime.now(timezone.utc): return None return row["user_id"] def consume_password_reset_token(token): conn = connect() conn.execute( "UPDATE _password_reset_tokens SET used_at = CURRENT_TIMESTAMP WHERE token_hash = ?", (_hash_token(token),), ) conn.commit() conn.close()