fix: close refresh token session binding bypass
Add require_session parameter to validate_token to enforce session_id matching for refresh operations. Attacker with stolen refresh token can no longer bypass session binding by omitting session_id from request. Also adds backend guard against deleting builtin admin user (was only blocked at Flask blueprint layer), and removes unused _ALL_RW variable.
This commit is contained in:
@@ -827,6 +827,49 @@ class TestTokenValidationEdgeCases:
|
||||
payload = validate_token(token, "refresh", session_id="wrongsession")
|
||||
assert payload is None
|
||||
|
||||
def test_require_session_accepts_matching_session(self) -> None:
|
||||
"""require_session=True validates when matching session_id provided."""
|
||||
create_user("reqsess", "password123")
|
||||
token = generate_refresh_token("reqsess", session_id="mytoken-session")
|
||||
payload = validate_token(
|
||||
token,
|
||||
"refresh",
|
||||
session_id="mytoken-session",
|
||||
require_session=True,
|
||||
)
|
||||
assert payload is not None
|
||||
|
||||
def test_require_session_rejects_when_session_id_omitted(self) -> None:
|
||||
"""require_session=True rejects when token has session_id but request passes None."""
|
||||
create_user("reqsess2", "password123")
|
||||
token = generate_refresh_token("reqsess2", session_id="mytoken-session")
|
||||
payload = validate_token(
|
||||
token,
|
||||
"refresh",
|
||||
session_id=None,
|
||||
require_session=True,
|
||||
)
|
||||
assert payload is None
|
||||
|
||||
def test_require_session_rejects_wrong_session(self) -> None:
|
||||
"""require_session=True rejects when session_id doesn't match."""
|
||||
create_user("reqsess3", "password123")
|
||||
token = generate_refresh_token("reqsess3", session_id="mytoken-session")
|
||||
payload = validate_token(
|
||||
token,
|
||||
"refresh",
|
||||
session_id="wrong-session",
|
||||
require_session=True,
|
||||
)
|
||||
assert payload is None
|
||||
|
||||
def test_require_session_with_no_session_in_token(self) -> None:
|
||||
"""require_session=True with a token that has no session_id claim works normally."""
|
||||
create_user("reqsess4", "password123")
|
||||
token = generate_refresh_token("reqsess4")
|
||||
payload = validate_token(token, "refresh", require_session=True)
|
||||
assert payload is not None
|
||||
|
||||
def test_refresh_token_generate_tokens_includes_session_id(self) -> None:
|
||||
"""generate_tokens produces refresh token with session_id."""
|
||||
create_user("tokuser", "password123")
|
||||
@@ -966,6 +1009,11 @@ class TestMultiUserAdmin:
|
||||
creds = list_credentials("wgscduser")
|
||||
assert len(creds) == 0
|
||||
|
||||
def test_delete_builtin_admin_rejected(self) -> None:
|
||||
"""Deleting the builtin admin user is rejected."""
|
||||
with pytest.raises(ValueError, match="Cannot delete builtin admin"):
|
||||
delete_user("admin")
|
||||
|
||||
def test_self_deletion_prevention(self) -> None:
|
||||
"""Self-deletion is prevented at the Flask blueprint layer.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user