@@ -657,3 +657,120 @@ def test_login_missing_csrf_auto_recovers(
657657 assert "info=session_expired" in location , (
658658 f"Expected info=session_expired in redirect URL, got { location } "
659659 )
660+
661+
662+ # =============================================================================
663+ # Bug #715: CSRF Token Race Condition Tests
664+ # =============================================================================
665+
666+
667+ class TestCSRFTokenRaceCondition :
668+ """Tests for Bug #715: CSRF token race condition with HTMX partial polling."""
669+
670+ def test_login_page_reuses_valid_csrf_token_from_cookie (
671+ self , web_infrastructure : WebTestInfrastructure
672+ ):
673+ """
674+ Bug #715: Login page should reuse valid CSRF token from cookie.
675+
676+ Given I have a valid CSRF token cookie
677+ When I request the login page
678+ Then the CSRF token in the form matches my existing cookie
679+ And no new CSRF cookie is set in the response
680+ """
681+ assert web_infrastructure .client is not None
682+ client = web_infrastructure .client
683+
684+ # First request to get a CSRF token
685+ first_response = client .get ("/login" )
686+ assert first_response .status_code == 200
687+
688+ # Extract CSRF token from form
689+ first_csrf_token = web_infrastructure .extract_csrf_token (first_response .text )
690+ assert first_csrf_token is not None , "First request should have CSRF token"
691+
692+ # Second request - should reuse the existing token
693+ second_response = client .get ("/login" )
694+ assert second_response .status_code == 200
695+
696+ # Extract CSRF token from second form
697+ second_csrf_token = web_infrastructure .extract_csrf_token (second_response .text )
698+ assert second_csrf_token is not None , "Second request should have CSRF token"
699+
700+ # The token in the form should be the SAME as the first request
701+ assert first_csrf_token == second_csrf_token , (
702+ f"Bug #715: Login page should reuse existing CSRF token from cookie. "
703+ f"First token: { first_csrf_token [:20 ]} ..., "
704+ f"Second token: { second_csrf_token [:20 ]} ..."
705+ )
706+
707+ def test_login_page_generates_new_token_when_no_cookie (
708+ self , web_infrastructure : WebTestInfrastructure
709+ ):
710+ """
711+ Bug #715: Login page generates new token when no cookie exists.
712+
713+ Given I have no CSRF cookie
714+ When I request the login page
715+ Then a new CSRF token is generated
716+ And a new CSRF cookie is set
717+ """
718+ assert web_infrastructure .client is not None
719+ client = web_infrastructure .client
720+
721+ # Clear any existing cookies to simulate fresh session
722+ client .cookies .clear ()
723+
724+ # Request login page without any CSRF cookie
725+ response = client .get ("/login" )
726+ assert response .status_code == 200
727+
728+ # Should have CSRF token in form
729+ csrf_token = web_infrastructure .extract_csrf_token (response .text )
730+ assert csrf_token is not None , (
731+ "Login page should generate CSRF token when no cookie exists"
732+ )
733+
734+ # Should set new CSRF cookie
735+ csrf_cookie = response .cookies .get ("_csrf" )
736+ assert csrf_cookie is not None , (
737+ "Login page should set CSRF cookie when no cookie exists"
738+ )
739+
740+ def test_login_page_generates_new_token_when_cookie_expired (
741+ self , web_infrastructure : WebTestInfrastructure
742+ ):
743+ """
744+ Bug #715: Login page generates new token when cookie is expired/invalid.
745+
746+ Given I have an expired or invalid CSRF cookie
747+ When I request the login page
748+ Then a new CSRF token is generated
749+ And a new CSRF cookie is set to replace the invalid one
750+ """
751+ assert web_infrastructure .client is not None
752+ client = web_infrastructure .client
753+
754+ # Set an invalid/expired CSRF cookie
755+ client .cookies .set ("_csrf" , "invalid_expired_csrf_token_12345" )
756+
757+ # Request login page with invalid CSRF cookie
758+ response = client .get ("/login" )
759+ assert response .status_code == 200
760+
761+ # Should have CSRF token in form
762+ csrf_token = web_infrastructure .extract_csrf_token (response .text )
763+ assert csrf_token is not None , (
764+ "Login page should generate CSRF token when cookie is invalid"
765+ )
766+
767+ # The token should NOT be the invalid one we sent
768+ assert csrf_token != "invalid_expired_csrf_token_12345" , (
769+ "Login page should not use invalid cookie value as CSRF token"
770+ )
771+
772+ # Should set new CSRF cookie
773+ csrf_cookie = response .cookies .get ("_csrf" )
774+ assert csrf_cookie is not None , (
775+ "Login page should set new CSRF cookie when old one is invalid"
776+ )
0 commit comments