diff --git a/backend/letters/migrations/0005_letter_encrypted_dek.py b/backend/letters/migrations/0005_letter_encrypted_dek.py new file mode 100644 index 0000000..76d0ced --- /dev/null +++ b/backend/letters/migrations/0005_letter_encrypted_dek.py @@ -0,0 +1,17 @@ +# Generated by Django 6.0.4 on 2026-04-11 12:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("letters", "0004_letter_burned_at_letter_opened_at_letter_sealed_at"), + ] + + operations = [ + migrations.AddField( + model_name="letter", + name="encrypted_dek", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/backend/letters/models.py b/backend/letters/models.py index 6f4a137..9b3d98e 100644 --- a/backend/letters/models.py +++ b/backend/letters/models.py @@ -28,6 +28,7 @@ class Letter(models.Model): sealed_at = models.DateTimeField(null=True, blank=True) opened_at = models.DateTimeField(null=True, blank=True) burned_at = models.DateTimeField(null=True, blank=True) + encrypted_dek = models.TextField(null=True, blank=True) def clean(self): # custom validation diff --git a/backend/letters/serializers.py b/backend/letters/serializers.py index 000d2e2..68feafe 100644 --- a/backend/letters/serializers.py +++ b/backend/letters/serializers.py @@ -12,6 +12,7 @@ class LetterSerializer(serializers.ModelSerializer): "status", "encrypted_content", "encrypted_metadata", + "encrypted_dek", "unlock_at", "sealed_at", "created_at", @@ -22,3 +23,10 @@ class LetterSerializer(serializers.ModelSerializer): def create(self, validated_data): user = self.context["request"].user # get user from access token return Letter.objects.create(user=user, **validated_data) + + def validate(self, data): + if (data.get("encrypted_content") or data.get("encrypted_metadata")) and not data.get("encrypted_dek"): + raise serializers.ValidationError( + "encrypted_dek is required when encrypted_content and encrypted_metadata are present" + ) + return data diff --git a/backend/letters/tests.py b/backend/letters/tests.py index 1baddbf..bff4e14 100644 --- a/backend/letters/tests.py +++ b/backend/letters/tests.py @@ -51,7 +51,12 @@ class LetterAPITest(APITestCase): def test_create_draft_letter_api(self): """Test API can successfully create a basic draft letter.""" - payload = {"type": "KEPT", "encrypted_content": "enc_xyz==", "encrypted_metadata": "enc_meta=="} + payload = { + "type": "KEPT", + "encrypted_content": "enc_xyz==", + "encrypted_metadata": "enc_meta==", + "encrypted_dek": "enc_dek==", + } response = self.client.post(self.url, payload) self.assertEqual(response.status_code, 201) @@ -59,3 +64,14 @@ class LetterAPITest(APITestCase): self.assertEqual(Letter.objects.get().status, "DRAFT") self.assertEqual(Letter.objects.get().type, "KEPT") self.assertEqual(Letter.objects.get().user, self.user) + + def test_encrypted_dek_is_required_when_storing_encrypted_content_and_metadata(self): + """encrypted_dek is required when encrypted_content and encrypted_metadata are present""" + payload = {"type": "KEPT", "encrypted_content": "enc_xyz==", "encrypted_metadata": "enc_meta=="} + response = self.client.post(self.url, payload) + self.assertEqual(response.status_code, 400) + self.assertEqual(Letter.objects.count(), 0) + self.assertEqual( + response.data["non_field_errors"], + ["encrypted_dek is required when encrypted_content and encrypted_metadata are present"], + ) diff --git a/frontend/src/pages/Editor.tsx b/frontend/src/pages/Editor.tsx index 2f8b22a..41d6176 100644 --- a/frontend/src/pages/Editor.tsx +++ b/frontend/src/pages/Editor.tsx @@ -49,7 +49,7 @@ export default function Editor() {