TASK-022: Hash session tokens in database for security
- Store session tokens as one-way SHA256 hashes instead of plaintext - Hash tokens on write (create_session) and on read (get_session, delete_session) - Add migration to drop plaintext sessions table and recreate with token_hash column - Update Session model: token field still contains raw token for signing - Add test to verify tokens are hashed in database, not plaintext - Update Architekture.md to document session token hashing - Update Backend-Development.md with implementation pattern and best practices Prevents direct session token hijacking if database file is exposed to attacker. If plaintext DB was readable, sessions are invalidated by the migration anyway. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -80,7 +80,7 @@ async def test_init_db_records_schema_version(tmp_path: Path) -> None:
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
assert row is not None
|
||||
assert row[0] == 1
|
||||
assert row[0] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -97,4 +97,4 @@ async def test_init_db_migrates_legacy_database_without_schema_version(tmp_path:
|
||||
) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
assert row is not None
|
||||
assert row[0] == 1
|
||||
assert row[0] == 2
|
||||
|
||||
@@ -116,3 +116,28 @@ class TestSessionRepo:
|
||||
assert deleted == 1
|
||||
assert await session_repo.get_session(db, "expired") is None
|
||||
assert await session_repo.get_session(db, "valid") is not None
|
||||
|
||||
async def test_tokens_are_hashed_in_database(
|
||||
self, db: aiosqlite.Connection
|
||||
) -> None:
|
||||
"""Verify that tokens are stored as hashes, not plaintext."""
|
||||
import hashlib
|
||||
|
||||
token = "plaintext_token_12345"
|
||||
await session_repo.create_session(
|
||||
db,
|
||||
token=token,
|
||||
created_at="2025-01-01T00:00:00+00:00",
|
||||
expires_at="2025-01-01T01:00:00+00:00",
|
||||
)
|
||||
|
||||
# Query the database directly to verify the token is hashed.
|
||||
async with db.execute("SELECT token_hash FROM sessions WHERE token_hash = ?", (hashlib.sha256(token.encode()).hexdigest(),)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
assert row is not None
|
||||
assert row[0] == hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
# The plaintext token should not be in the database.
|
||||
async with db.execute("SELECT * FROM sessions WHERE token_hash = ?", (token,)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
assert row is None
|
||||
|
||||
Reference in New Issue
Block a user