diff --git a/app/models/_loaders.py b/app/models/_loaders.py index 0d71338..12efd35 100644 --- a/app/models/_loaders.py +++ b/app/models/_loaders.py @@ -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)) \ No newline at end of file + + 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 diff --git a/app/models/user_model/user.py b/app/models/user_model/user.py index 9f61f6b..0de7282 100644 --- a/app/models/user_model/user.py +++ b/app/models/user_model/user.py @@ -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.""" diff --git a/app/routes/auth.py b/app/routes/auth.py index 95a5c42..ec9ba6f 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -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