feat(auth): session cookie signing and verification

This commit is contained in:
Brian Krabach
2026-03-28 21:11:14 -07:00
parent 9adb459d99
commit 22816cd1d5
2 changed files with 78 additions and 0 deletions
+27
View File
@@ -5,6 +5,8 @@ muxplex authentication — password and signing secret file management.
import secrets import secrets
from pathlib import Path from pathlib import Path
from itsdangerous import BadSignature, SignatureExpired, TimestampSigner
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Config directory # Config directory
@@ -66,3 +68,28 @@ def load_or_create_secret() -> str:
path.write_text(secret + "\n") path.write_text(secret + "\n")
path.chmod(0o600) path.chmod(0o600)
return secret return secret
# ---------------------------------------------------------------------------
# Session cookie signing / verification
# ---------------------------------------------------------------------------
def create_session_cookie(secret: str, ttl_seconds: int) -> str:
"""Create a signed, timestamped session cookie value."""
signer = TimestampSigner(secret)
return signer.sign("muxplex-session").decode()
def verify_session_cookie(secret: str, cookie: str, ttl_seconds: int) -> bool:
"""Verify a session cookie's signature and expiry. Returns True/False.
ttl_seconds=0 means session cookie — no server-side expiry check.
"""
signer = TimestampSigner(secret)
try:
max_age = ttl_seconds if ttl_seconds > 0 else None
signer.unsign(cookie, max_age=max_age)
return True
except (BadSignature, SignatureExpired):
return False
+51
View File
@@ -119,3 +119,54 @@ def test_load_or_create_secret_returns_same_value_on_second_call(monkeypatch, tm
first = load_or_create_secret() first = load_or_create_secret()
second = load_or_create_secret() second = load_or_create_secret()
assert first == second assert first == second
# ---------------------------------------------------------------------------
# Session cookie signing / verification
# ---------------------------------------------------------------------------
def test_create_session_cookie_returns_string():
"""create_session_cookie() returns a non-empty string."""
from muxplex.auth import create_session_cookie
cookie = create_session_cookie("test-secret", ttl_seconds=3600)
assert isinstance(cookie, str)
assert len(cookie) > 0
def test_verify_session_cookie_valid_roundtrip():
"""A cookie created by create_session_cookie verifies successfully."""
from muxplex.auth import create_session_cookie, verify_session_cookie
cookie = create_session_cookie("test-secret", ttl_seconds=3600)
assert verify_session_cookie("test-secret", cookie, ttl_seconds=3600) is True
def test_verify_session_cookie_tampered():
"""A tampered cookie fails verification."""
from muxplex.auth import create_session_cookie, verify_session_cookie
cookie = create_session_cookie("test-secret", ttl_seconds=3600)
tampered = cookie + "X"
assert verify_session_cookie("test-secret", tampered, ttl_seconds=3600) is False
def test_verify_session_cookie_wrong_secret():
"""A cookie signed with a different secret fails verification."""
from muxplex.auth import create_session_cookie, verify_session_cookie
cookie = create_session_cookie("secret-A", ttl_seconds=3600)
assert verify_session_cookie("secret-B", cookie, ttl_seconds=3600) is False
def test_verify_session_cookie_expired():
"""A cookie verified with a very short TTL fails (simulates expiry)."""
import time
from muxplex.auth import create_session_cookie, verify_session_cookie
cookie = create_session_cookie("test-secret", ttl_seconds=1)
# itsdangerous uses integer-second timestamps; sleep 2s to guarantee
# age = 2 > max_age = 1, ensuring reliable expiry detection
time.sleep(2)
assert verify_session_cookie("test-secret", cookie, ttl_seconds=1) is False