feat: encrypt stored service credentials
This commit is contained in:
20
app/security/credentials.py
Normal file
20
app/security/credentials.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from cryptography.fernet import Fernet, InvalidToken
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialCipher:
|
||||||
|
def __init__(self, key: str) -> None:
|
||||||
|
try:
|
||||||
|
self._fernet = Fernet(key.encode("ascii"))
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError("CREDENTIAL_ENCRYPTION_KEY must be a valid Fernet key") from exc
|
||||||
|
|
||||||
|
def encrypt(self, value: str) -> str:
|
||||||
|
if not value:
|
||||||
|
raise ValueError("credential value must not be empty")
|
||||||
|
return self._fernet.encrypt(value.encode("utf-8")).decode("ascii")
|
||||||
|
|
||||||
|
def decrypt(self, token: str) -> str:
|
||||||
|
try:
|
||||||
|
return self._fernet.decrypt(token.encode("ascii")).decode("utf-8")
|
||||||
|
except InvalidToken as exc:
|
||||||
|
raise ValueError("stored credential cannot be decrypted") from exc
|
||||||
20
tests/security/test_credentials.py
Normal file
20
tests/security/test_credentials.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from cryptography.fernet import Fernet
|
||||||
|
|
||||||
|
from app.security.credentials import CredentialCipher
|
||||||
|
|
||||||
|
|
||||||
|
def test_round_trip_and_ciphertext_does_not_contain_plaintext() -> None:
|
||||||
|
cipher = CredentialCipher(Fernet.generate_key().decode("ascii"))
|
||||||
|
encrypted = cipher.encrypt("secret-password")
|
||||||
|
|
||||||
|
assert "secret-password" not in encrypted
|
||||||
|
assert cipher.decrypt(encrypted) == "secret-password"
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_credentials_are_rejected() -> None:
|
||||||
|
cipher = CredentialCipher(Fernet.generate_key().decode("ascii"))
|
||||||
|
try:
|
||||||
|
cipher.encrypt("")
|
||||||
|
except ValueError:
|
||||||
|
return
|
||||||
|
raise AssertionError("empty secrets must be rejected")
|
||||||
Reference in New Issue
Block a user