feat: implement encrypted metadata support and fix public_id handling in letter serialization

This commit is contained in:
Your Name
2026-04-12 05:14:02 +05:30
parent 99ed561570
commit 998ad848b0
4 changed files with 28 additions and 9 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ class LetterSerializer(serializers.ModelSerializer):
"updated_at", "updated_at",
"images", "images",
] # user to be fetched from request ] # user to be fetched from request
read_only_fields = ["public_id", "created_at", "updated_at"] read_only_fields = ["created_at", "updated_at"]
def validate(self, data): def validate(self, data):
if (data.get("encrypted_content") or data.get("encrypted_metadata")) and not data.get("encrypted_dek"): if (data.get("encrypted_content") or data.get("encrypted_metadata")) and not data.get("encrypted_dek"):
+5 -1
View File
@@ -16,7 +16,11 @@ class LetterView(generics.ListCreateAPIView):
return Letter.objects.filter(user=self.request.user) return Letter.objects.filter(user=self.request.user)
def put(self, request, public_id): def put(self, request, public_id):
serializer = self.get_serializer(data=request.data) data = request.data.copy()
# remove public_id from data to avoid UniqueValidator firing
# since we use it from the URL for update_or_create anyway
data.pop("public_id", None)
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
+11 -4
View File
@@ -5,11 +5,15 @@ const PAD = 36;
export type CanvasTools = { export type CanvasTools = {
addImage: (url: string, file: File) => void; addImage: (url: string, file: File) => void;
getData: () => { objects: any }; getData: () => { objects: any[] };
getJsonData: () => string; getJsonData: () => string;
getImages: () => { src: string; file: File }[]; getImages: () => { src: string; file: File }[];
}; };
export interface FabricImageWithFile extends fabric.FabricImage {
_customRawFile: File;
}
export const ComposeCanvas = forwardRef<CanvasTools>((_props, ref) => { export const ComposeCanvas = forwardRef<CanvasTools>((_props, ref) => {
const wrapperRef = useRef<HTMLDivElement>(null); const wrapperRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -150,7 +154,7 @@ export const ComposeCanvas = forwardRef<CanvasTools>((_props, ref) => {
}); });
}, },
getData: () => { getData: () => {
if (!fabricRef.current) return ""; if (!fabricRef.current) return { objects: [] };
return fabricRef.current.toJSON(); return fabricRef.current.toJSON();
}, },
getJsonData: () => { getJsonData: () => {
@@ -159,8 +163,11 @@ export const ComposeCanvas = forwardRef<CanvasTools>((_props, ref) => {
}, },
getImages: () => { getImages: () => {
if (!fabricRef.current) return []; if (!fabricRef.current) return [];
return fabricRef.current.getObjects("Image").map((img: any) => ({ const images = fabricRef.current.getObjects(
src: img._element.currentSrc, "Image",
) as FabricImageWithFile[];
return images.map((img) => ({
src: (img.getElement() as HTMLImageElement).currentSrc,
file: img._customRawFile, file: img._customRawFile,
})); }));
}, },
+11 -3
View File
@@ -19,9 +19,11 @@ import { useKeyStore } from "../store/useKeyStore";
import { CryptoUtils } from "../utils/crypto"; import { CryptoUtils } from "../utils/crypto";
export default function Editor() { export default function Editor() {
const navigate = useNavigate();
// check for existing letter
const { public_id } = useParams(); const { public_id } = useParams();
const letterIdRef = useRef<string>(public_id ?? ""); const letterIdRef = useRef<string>(public_id ?? "");
const navigate = useNavigate();
const [isSealing, setIsSealing] = useState(false); const [isSealing, setIsSealing] = useState(false);
const [isSaveSuccess, setIsSaveSuccess] = useState(false); const [isSaveSuccess, setIsSaveSuccess] = useState(false);
@@ -40,9 +42,11 @@ export default function Editor() {
async function handleSeal(): Promise<void> { async function handleSeal(): Promise<void> {
if (!public_id) { if (!public_id) {
// if no uuid slug, then generate a new one and update params
letterIdRef.current = crypto.randomUUID(); letterIdRef.current = crypto.randomUUID();
navigate(ROUTES.WRITE(letterIdRef.current), { replace: true }); navigate(ROUTES.WRITE(letterIdRef.current), { replace: true });
} }
if (isSealing) return; if (isSealing) return;
setIsSealing(true); setIsSealing(true);
const cryptoUtils = new CryptoUtils(); const cryptoUtils = new CryptoUtils();
@@ -79,7 +83,11 @@ export default function Editor() {
JSON.stringify(canvasData), JSON.stringify(canvasData),
masterKey, masterKey,
); );
const encrypted_metadata = "";
const encrypted_metadata = await cryptoUtils.encryptMetadata(
{ recipient },
masterKey,
);
// upload to server // upload to server
/* /*
@@ -98,7 +106,7 @@ export default function Editor() {
formData.append("status", "SEALED"); formData.append("status", "SEALED");
formData.append("encrypted_content", encrypted_letter.encrypted_content); formData.append("encrypted_content", encrypted_letter.encrypted_content);
formData.append("encrypted_dek", encrypted_letter.encrypted_dek); formData.append("encrypted_dek", encrypted_letter.encrypted_dek);
formData.append("encrypted_metadata", encrypted_metadata); formData.append("encrypted_metadata", encrypted_metadata.encrypted_content);
encImageFilesMap.forEach((image, filename) => { encImageFilesMap.forEach((image, filename) => {
formData.append("image_files", image, filename); formData.append("image_files", image, filename);
}); });