feat: auth module with bcrypt password verification and session tokens

This commit is contained in:
Ken
2026-05-26 18:21:18 +00:00
parent 9b941d173d
commit 7acad01047
2 changed files with 109 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
"""Auth module — bcrypt password verification and session token management."""
import os
import secrets
import bcrypt
# Module-level defaults (read from env at import time for AUTH_USER/AUTH_PASS_HASH)
AUTH_USER: str = os.environ.get("AUTH_USER", "admin@localhost")
AUTH_PASS_HASH: str = os.environ.get("AUTH_PASS_HASH", "")
# In-memory session store: token -> email
_sessions: dict[str, str] = {}
def verify_password(password: str) -> bool:
"""Return True if password matches the bcrypt hash in AUTH_PASS_HASH env var.
Reads AUTH_PASS_HASH from env at call time so runtime env changes are respected.
Returns False if no hash is configured, if password is empty, or on any error.
"""
if not password:
return False
hash_str = os.environ.get("AUTH_PASS_HASH", "")
if not hash_str:
return False
try:
return bcrypt.checkpw(password.encode(), hash_str.encode())
except (ValueError, TypeError):
return False
def create_session_token(email: str) -> str:
"""Generate a session token, store it mapped to email, and return the token."""
token = secrets.token_hex(32)
_sessions[token] = email
return token
def validate_session_token(token: str) -> str | None:
"""Return the email for a valid token, or None if token is empty/unknown."""
if not token:
return None
return _sessions.get(token)
def invalidate_session_token(token: str) -> None:
"""Remove a session token from the store."""
_sessions.pop(token, None)
+58
View File
@@ -0,0 +1,58 @@
"""Tests for auth module — bcrypt password verification and session tokens."""
import sys
import os
# Add backend to sys.path so we can import auth
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
import bcrypt
import pytest
from auth import verify_password, create_session_token, validate_session_token
TEST_PASSWORD = "test_password_123"
TEST_EMAIL = "test@example.com"
@pytest.fixture(autouse=True)
def set_auth_env(monkeypatch):
"""Set up AUTH_PASS_HASH env var with a bcrypt-hashed test password."""
hashed = bcrypt.hashpw(TEST_PASSWORD.encode(), bcrypt.gensalt()).decode()
monkeypatch.setenv("AUTH_PASS_HASH", hashed)
monkeypatch.setenv("AUTH_USER", TEST_EMAIL)
def test_verify_password_correct():
"""Correct password returns True."""
assert verify_password(TEST_PASSWORD) is True
def test_verify_password_wrong():
"""Wrong password returns False."""
assert verify_password("wrong_password_xyz") is False
def test_verify_password_empty():
"""Empty password returns False."""
assert verify_password("") is False
def test_create_session_token():
"""create_session_token returns a non-None string of length > 20."""
token = create_session_token(TEST_EMAIL)
assert token is not None
assert isinstance(token, str)
assert len(token) > 20
def test_validate_session_token_valid():
"""Valid token returns the associated email."""
token = create_session_token(TEST_EMAIL)
assert validate_session_token(token) == TEST_EMAIL
def test_validate_session_token_invalid_and_empty():
"""Invalid/bogus token returns None; empty token returns None."""
assert validate_session_token("bogus_token_that_does_not_exist") is None
assert validate_session_token("") is None