From 7b3d58146f52c752c5d8cde3bd4e9ed0cd6daed0 Mon Sep 17 00:00:00 2001 From: PieterCK Date: Wed, 15 Jul 2026 16:37:13 +0700 Subject: [PATCH] oidc: Handle expired redis state data gracefully in `auth_complete`. If a user takes longer than REDIS_EXPIRATION_SECONDS to return from the IdP, the relayed_params data stored in redis at the start of the authentication attempt has expired, and the old `assert relayed_params is not None` crashed with a 500. If the state data is expired, we now log at the info level and return None, which redirects the user back to the login page to retry. Co-Authored-By: Claude Fable 5 --- zerver/tests/test_auth_backends.py | 25 +++++++++++++++++++++++++ zproject/backends.py | 10 +++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 0e731d0368..aa4af80fa0 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -5375,6 +5375,31 @@ class GenericOpenIdConnectTest(SocialAuthBaseWithSyncAttrTest): ], ) + def test_social_auth_state_data_expired_in_redis(self) -> None: + account_data_dict = self.get_account_data_dict(email=self.email, name=self.name) + + # Simulate the state data having expired from redis, like when + # a user takes longer than REDIS_EXPIRATION_SECONDS to return + # from the IdP. + with ( + mock.patch.object( + GenericOpenIdConnectBackend, "get_data_from_redis", return_value=None + ), + self.assertLogs(self.logger_string, level="INFO") as m, + ): + result = self.social_auth_test(account_data_dict, subdomain="zulip") + self.assertEqual(result.status_code, 302) + self.assertEqual("/login/", result["Location"]) + self.assertEqual( + m.output, + [ + self.logger_output( + "State data expired in redis: authentication took too long to complete.", + "info", + ) + ], + ) + @override def test_social_auth_complete(self) -> None: def mock_process_error(backend: BaseOAuth2, data: Mapping[str, object]) -> None: diff --git a/zproject/backends.py b/zproject/backends.py index 87b45cfff5..86c5cfc056 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -4291,7 +4291,15 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth): assert state relayed_params = self.get_data_from_redis(state) - assert relayed_params is not None + if relayed_params is None: + # The relayed_params live in redis with a TTL of + # REDIS_EXPIRATION_SECONDS, so this means the user took + # too long to complete the authentication attempt. + self.logger.info( + "State data expired in redis: authentication took too long to complete." + ) + return None + for param in self.standard_relay_params: relayed_value = relayed_params.get(param) session_value = self.strategy.session_get(param)