security: JWT aud/iss validation, token blacklist, encryption key crash-early
C-2 (CRITICAL): JWT decode_token now validates aud, iss, iat, jti claims. Tokens without these claims are rejected. Audience 'ss-tools-api' and issuer 'ss-tools' prevent cross-service token reuse. H-2 (HIGH): Server-side JWT revocation via token_blacklist table. - New TokenBlacklist model (SHA-256 hash only, never raw token) - blacklist_token() on logout — revokes current session - is_token_blacklisted() check in get_current_user - Expired entries auto-pruned via _prune_blacklist() H-3 (HIGH): Encryption key auto-generation removed — crash-early instead. Previously, ensure_encryption_key() would auto-generate a Fernet key on first run and write it to .env. This made encrypted data unrecoverable when the key changed between deployments. Now raises RuntimeError with a clear command to generate the key explicitly. Also: update orthogonal security test for crash-early behavior.
This commit is contained in:
@@ -234,31 +234,27 @@ class TestEncryptionKeyLifecycle:
|
||||
assert result == expected
|
||||
# #endregion test_ensure_encryption_key_from_env
|
||||
|
||||
# #region test_ensure_encryption_key_creates_file [C:2] [TYPE Function]
|
||||
# @BRIEF When neither env nor .env has a key, generate one and persist to .env.
|
||||
def test_ensure_encryption_key_creates_file(self):
|
||||
# #region test_ensure_encryption_key_missing_raises [C:2] [TYPE Function]
|
||||
# @BRIEF When neither env nor .env has a key, crash-early with RuntimeError.
|
||||
# @TEST_EDGE: missing_encryption_key -> RuntimeError with clear message
|
||||
def test_ensure_encryption_key_missing_raises(self):
|
||||
from src.core.encryption_key import ensure_encryption_key
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as f:
|
||||
f.write("# empty .env\n")
|
||||
env_path = Path(f.name)
|
||||
|
||||
try:
|
||||
# Remove any existing ENCRYPTION_KEY from env
|
||||
old = os.environ.pop("ENCRYPTION_KEY", None)
|
||||
try:
|
||||
result = ensure_encryption_key(env_path)
|
||||
assert len(result) > 0
|
||||
|
||||
# Key should be in the file
|
||||
content = env_path.read_text()
|
||||
assert "ENCRYPTION_KEY=" in content
|
||||
assert result in content
|
||||
with pytest.raises(RuntimeError, match="ENCRYPTION_KEY"):
|
||||
ensure_encryption_key(env_path)
|
||||
finally:
|
||||
if old is not None:
|
||||
os.environ["ENCRYPTION_KEY"] = old
|
||||
finally:
|
||||
env_path.unlink(missing_ok=True)
|
||||
# #endregion test_ensure_encryption_key_creates_file
|
||||
# #endregion test_ensure_encryption_key_missing_raises
|
||||
|
||||
# #region test_ensure_encryption_key_loads_from_file [C:2] [TYPE Function]
|
||||
# @BRIEF When env is empty but .env file has the key, load it.
|
||||
|
||||
Reference in New Issue
Block a user