From 8f34ef38721c3b05d75faad76b823989b27ff333 Mon Sep 17 00:00:00 2001 From: pollev Date: Sun, 17 Aug 2025 20:58:08 +0200 Subject: [PATCH] fix minor authentication bug --- tildes/tildes/auth.py | 46 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/tildes/tildes/auth.py b/tildes/tildes/auth.py index 6b72f63..b98631c 100644 --- a/tildes/tildes/auth.py +++ b/tildes/tildes/auth.py @@ -33,8 +33,12 @@ class DefaultRootFactory: pass -def get_authenticated_user(request: Request) -> Optional[User]: - """Return the User object for the authed user making the request.""" +def get_unauthenticated_user(request: Request) -> Optional[User]: + """Return the User object for the unauthenticated user making the request. + + This is used to get the user object before authentication has been processed. + It is cached in the request for performance reasons. + """ user_id = request.unauthenticated_userid if not user_id: return None @@ -48,25 +52,47 @@ def get_authenticated_user(request: Request) -> Optional[User]: return query.one_or_none() +def get_authenticated_user(request: Request) -> Optional[User]: + """Return the User object for the authed user making the request.""" + + # Simply calling the property authenticated_userid will trigger + # auth_callback to be called. If that returns not None, We can + # trust the user object we cached earlier as authenticated. + user_id = request.authenticated_userid + if not user_id: + return None + + return request.unauthenticated_user + + def auth_callback(user_id: int, request: Request) -> Optional[Sequence[str]]: """Return authorization principals for a user_id from the session. - This is a callback function needed by SessionAuthenticationPolicy. It should return + This is a callback function needed by our AuthenticationPolicies. It should return None if the user_id does not exist (such as a deleted user). """ - if not request.user: + + # This function needs to verify that the user exists and is not deleted/banned, + # This callback is called every time request.authenticated_userid is used. + # It is also called whenever someone calls has_permission() + # That means it should be fast and not do any expensive queries. + + if not request.unauthenticated_user: return None # if the user is deleted or banned, log them out # (is there a better place to do this?) - if request.user.is_banned or request.user.is_deleted: + if ( + request.unauthenticated_user.is_banned + or request.unauthenticated_user.is_deleted + ): request.session.invalidate() raise HTTPFound("/") - if user_id != request.user.user_id: + if user_id != request.unauthenticated_user.user_id: raise AssertionError("auth_callback called with different user_id") - return request.user.auth_principals + return request.unauthenticated_user.auth_principals def includeme(config: Configurator) -> None: @@ -87,6 +113,12 @@ def includeme(config: Configurator) -> None: # enable CSRF checking globally by default config.set_default_csrf_options(require_csrf=True) + # make the partially authenticated User object available as request.user + # Mainly for caching during authentication, so we can avoid expensive queries + config.add_request_method( + get_unauthenticated_user, "unauthenticated_user", reify=True + ) + # make the logged-in User object available as request.user config.add_request_method(get_authenticated_user, "user", reify=True)