diff --git a/.gitignore b/.gitignore index c51fc7e..3953e33 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ venv/ htmlcov/ .coverage .coverage.* +*.db +*.sqlite +*.sqlite3 # Rust target/ diff --git a/auth.py b/auth.py index cd6c268..fc86fff 100644 --- a/auth.py +++ b/auth.py @@ -1,3 +1,4 @@ +import hmac import os from fastapi import Security, HTTPException, status from fastapi.security.api_key import APIKeyHeader @@ -15,8 +16,8 @@ def verify_api_key( cypher_token: str = Security(cypher_token_header) ) -> str: """ - Verifies that the request provides a valid API Key or Cypher Token. - Keeps file sharing strictly authorized. + Verifies that the request provides a valid API Key or Cypher Token in constant time. + Keeps file sharing strictly authorized against side-channel timing attacks. """ provided_key = api_key or cypher_token if not provided_key: @@ -25,7 +26,7 @@ def verify_api_key( detail="Missing API Key or X-Cypher-Token header." ) - if provided_key != CYPHER_API_KEY: + if not hmac.compare_digest(provided_key, CYPHER_API_KEY): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Invalid authorization token." diff --git a/tests/test_share_system.py b/tests/test_share_system.py index 6dd9a48..4e5fd83 100644 --- a/tests/test_share_system.py +++ b/tests/test_share_system.py @@ -158,6 +158,23 @@ def test_api_create_share_link_unauthenticated(self): response = self.client.post("/api/v1/share/create", json=payload, headers=headers) self.assertEqual(response.status_code, 403) # Invalid key + def test_api_create_share_link_timing_safety(self): + payload = { + "video_id": "vid_api_timing", + "file_path": self.temp_file.name + } + # Verify rejection of keys with varying lengths and invalid characters + invalid_keys = [ + "c", + "cypher_", + "cypher_secure_secret_token_2025", + CYPHER_API_KEY + "_extra", + "X" * len(CYPHER_API_KEY) + ] + for key in invalid_keys: + response = self.client.post("/api/v1/share/create", json=payload, headers={"X-API-Key": key}) + self.assertEqual(response.status_code, 403) + def test_api_create_share_link_authenticated(self): payload = { "video_id": "vid_api_2",