@@ -706,6 +706,46 @@ def test_password_reset_auto_logs_in_and_shows_flash(
706706 assert any ("flash_message=" in c for c in cookie_headers )
707707
708708
709+ def test_password_reset_revokes_existing_sessions (
710+ unauth_client : TestClient , session : Session , test_account : Account
711+ ):
712+ """Password reset revokes all existing refresh tokens before auto-login."""
713+ attacker_session = RefreshToken (
714+ account_id = test_account .id ,
715+ expires_at = datetime .now (UTC ) + timedelta (days = 30 ),
716+ )
717+ session .add (attacker_session )
718+ session .commit ()
719+ session .refresh (attacker_session )
720+
721+ reset_token = PasswordResetToken (account_id = test_account .id )
722+ session .add (reset_token )
723+ session .commit ()
724+ session .refresh (reset_token )
725+
726+ response = unauth_client .post (
727+ app .url_path_for ("reset_password" ),
728+ data = {
729+ "email" : test_account .email ,
730+ "token" : reset_token .token ,
731+ "password" : "NewPass123!@#" ,
732+ "confirm_password" : "NewPass123!@#" ,
733+ },
734+ )
735+ assert response .status_code == 303
736+
737+ session .refresh (attacker_session )
738+ assert attacker_session .revoked is True
739+
740+ active_tokens = session .exec (
741+ select (RefreshToken ).where (
742+ RefreshToken .account_id == test_account .id ,
743+ RefreshToken .revoked == False , # noqa: E712
744+ )
745+ ).all ()
746+ assert len (active_tokens ) == 1
747+
748+
709749def test_password_reset_after_recovery_auto_logs_in (
710750 unauth_client : TestClient , session : Session
711751):
0 commit comments