fix(auth): rendre effectives l'expiration de session et la desactivation de compte

Deux protections etaient configurees sans avoir d'effet.

Expiration de session
  app.py:73 definit PERMANENT_SESSION_LIFETIME = 3600, mais Flask
  n'applique cette duree qu'aux sessions marquees permanentes. Aucune
  occurrence de session.permanent n'existait dans app/. Le cookie emis
  etait donc un cookie de session navigateur, sans expiration, et le
  serveur ne verifiait aucune anciennete. Ajout de session.permanent
  juste avant login_user, apres la rotation anti-fixation.

Desactivation de compte
  is_active_account n'etait consulte qu'au moment du login (auth.py:141).
  User n'ayant pas surcharge is_active, UserMixin renvoyait True en
  permanence. Desactiver un compte empechait donc la reconnexion mais
  laissait vivre la session en cours.

  La propriete is_active seule ne suffit pas : Flask-Login ne la consulte
  qu'a l'appel de login_user, jamais lors de la restauration d'une session
  depuis le cookie. Le verrou effectif est donc dans load_user, qui renvoie
  desormais None pour un compte desactive. La propriete est ajoutee malgre
  tout pour que login_user soit coherent avec le chargeur.

load_user passe au passage de Query.get() (API heritee, avertie en
SQLAlchemy 2.0) a db.session.get(), et tolere un identifiant non entier
sans lever.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
GGThed
2026-08-07 19:16:37 -04:00
co-authored by Claude Opus 5
parent 56aee7f3f3
commit 2c37c05c8f
3 changed files with 32 additions and 1 deletions
+16 -1
View File
@@ -9,6 +9,21 @@ def load_user(user_id):
Returns the correct polymorphic subclass (Admin, Coach, Player, etc.)
automatically because SQLAlchemy resolves the identity column.
Returns None for deactivated accounts so that disabling a user also
invalidates the sessions they already hold. Flask-Login only consults
is_active when login_user() is called, never when restoring a session
from the cookie, so the check has to happen here.
"""
from app.extensions import db
from app.models.user_model.user import User
return User.query.get(int(user_id))
try:
pk = int(user_id)
except (TypeError, ValueError):
return None
user = db.session.get(User, pk)
if user is None or not user.is_active_account:
return None
return user
+11
View File
@@ -51,6 +51,17 @@ class User(UserMixin, db.Model):
'TeamMember', foreign_keys='TeamMember.player_id',
backref='player_ref', lazy='dynamic')
# --- Flask-Login integration -------------------------------------------
@property
def is_active(self):
"""Whether Flask-Login should accept this account.
UserMixin returns True unconditionally, which meant a deactivated
account kept any session it already held. Binding this to
is_active_account makes deactivation take effect on the next request.
"""
return bool(self.is_active_account)
# --- shared helper methods ---------------------------------------------
def get_games_list(self):
"""Return the user's games as a list."""
+5
View File
@@ -154,6 +154,11 @@ def login():
if _csrf_token:
session['csrf_token'] = _csrf_token
# Mark the session permanent so PERMANENT_SESSION_LIFETIME applies.
# Without this, Flask emits a browser-session cookie with no expiry
# and the configured lifetime is silently ignored.
session.permanent = True
login_user(user)
# Validate redirect URL to prevent open redirect vulnerability