import React, { useEffect, useState } from "react"; import { ProgressIndicator, Header2, Button, IconArrow, Label, FileInput, TextArea, CenteredContainer, Spacer, TextAlignWrapper, GlobalStyle, } from "@intended/intended-ui"; import HexMix from "../utils/hexmix"; type JustPageProps = { csrf: string; }; interface AESKey { key: CryptoKey; iv: Uint8Array; exported: ArrayBuffer; keyHex: string; ivHex: string; } const JustPage = (props: JustPageProps) => { const [secretInput, setSecretInput] = useState(""); const [fileInput, setFileInput] = useState(null); const [fileName, setFileName] = useState(""); const [fileType, setFileType] = useState(""); useEffect(() => { sessionStorage.clear(); }, []); const handleChange = (e: React.ChangeEvent) => { setSecretInput(e.target.value); }; const handleFile = (file: File) => { setFileName(file.name); setFileType(file.type); if (file instanceof File) { if (file.size > 2097152) { // TODO: may not need this check alert("Error: Max file size is 2mb."); return; } const reader = new FileReader(); reader.onloadend = loadFile; reader.readAsArrayBuffer(file); } }; const loadFile = (fileEvent: ProgressEvent) => { // Make sure we actually got a binary file if ( fileEvent && fileEvent.target && fileEvent.target.result instanceof ArrayBuffer ) { const data = fileEvent.target.result as ArrayBuffer; HexMix.arrayBufferToString(data, (result: string) => { setFileInput(result); }); } else { alert("File is either missing or corrupt."); } }; const fileFormData = async ( form: FormData, aesKey: AESKey ): Promise => { if (!fileInput) return form; const encoded = HexMix.stringToArrayBuffer(fileInput as string); const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: aesKey.iv, }, aesKey.key, encoded ); const blobData = new Blob([encrypted]); form.append("file_content", blobData, fileName); form.append("filename", fileName); form.append("filetype", fileType); HexMix.arrayBufferToString(encrypted, (result: string) => { sessionStorage.setItem("encoded_file", result); }); return form; }; const textFormData = async ( form: FormData, aesKey: AESKey ): Promise => { if (!secretInput) return form; const encoded = HexMix.stringToArrayBuffer(secretInput); const encrypted = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: aesKey.iv, }, aesKey.key, encoded ); const blobData = new Blob([encrypted]); form.append("text_content", blobData, "secret_message.txt"); HexMix.arrayBufferToString(encrypted, (result: string) => { sessionStorage.setItem("encoded_message", result); }); return form; }; const createKey = async (): Promise => { const key = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256, }, true, ["encrypt", "decrypt"] ); const iv = window.crypto.getRandomValues(new Uint8Array(16)); const exported = await window.crypto.subtle.exportKey("raw", key); const keyHex = HexMix.uint8ToHex(new Uint8Array(exported)); const ivHex = HexMix.uint8ToHex(iv); return { key, iv, exported, keyHex, ivHex, }; }; const postContents = async () => { if (!window.crypto.subtle) { alert("Browser does not support SubtleCrypto"); return; } const key = await createKey(); let formData = new FormData(); formData = await fileFormData(formData, key); formData = await textFormData(formData, key); try { const link: Response = await fetch(`${window.location.origin}/just`, { headers: { "X-CSRF-Token": props.csrf, }, body: formData, method: "POST", }); const { id: link_id } = await link.json(); sessionStorage.setItem("link_id", link_id); sessionStorage.setItem("key_hex", key.keyHex); sessionStorage.setItem("iv_hex", key.ivHex); window.location.href = `${window.location.origin}/just/for`; } catch (err: any) { alert(err.message); } }; return ( Create a secret