From 51709c97319d44006c2adf65f5b8b0fabc011bc2 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 24 Nov 2025 19:48:46 +0000 Subject: [PATCH] from max --- .../app/__pycache__/__init__.cpython-314.pyc | Bin 157 -> 165 bytes .../app/__pycache__/database.cpython-314.pyc | Bin 915 -> 923 bytes backend/app/__pycache__/main.cpython-314.pyc | Bin 1244 -> 1252 bytes .../app/__pycache__/models.cpython-314.pyc | Bin 5720 -> 5728 bytes .../__pycache__/folders.cpython-314.pyc | Bin 4649 -> 4657 bytes .../routes/__pycache__/notes.cpython-314.pyc | Bin 4165 -> 4173 bytes backend/notes.db | Bin 12288 -> 12288 bytes frontend/package-lock.json | 18 +--- frontend/src/api/encryption.tsx | 95 +++++++++++++++++ frontend/src/api/folders.tsx | 13 ++- frontend/src/api/notes.tsx | 97 +++++------------- .../src/components/sidebar/DraggableNote.tsx | 2 +- frontend/src/pages/Home.tsx | 62 +++++------ 13 files changed, 163 insertions(+), 124 deletions(-) create mode 100644 frontend/src/api/encryption.tsx diff --git a/backend/app/__pycache__/__init__.cpython-314.pyc b/backend/app/__pycache__/__init__.cpython-314.pyc index e78c6d7f60c7048c5fa58cf10def2be49355ca7f..5d59c995b3b43c88d330988e417c235ecdbc86ff 100644 GIT binary patch delta 56 zcmbQsxRjAwn~#@^0SMS#l_zqWDmv+h7N-^!>t`kArWU7VmQ)pI=H}-W>${|urRL-p Kq!vw#bO8X2{t+Ai delta 48 zcmZ3=IG2%In~#@^0SNN-xlmQ?Axq?V=TWtLPGXXfVT73;gCmZj$8 N7o-+#?quX*0sy`#5r6;y delta 51 zcmbQuKAD|cn~#@^0SKmSR@lg$$|z~2pOK%Ns-KmZn^{t&?~+=Unv-9Uo0?a$c_Jeh F6987C4-EhS diff --git a/backend/app/__pycache__/main.cpython-314.pyc b/backend/app/__pycache__/main.cpython-314.pyc index 1c5498b086091b65441ffd4b9d575c7d6ab8149f..a0a2cf33df64c76c2c11baa3334bd1ad2dfca64f 100644 GIT binary patch delta 59 zcmcb^`Gk{On~#@^0SMS#l{a!%GAY{WhZd(673*gu=B5^>WtLPGXXfVT73;gCmZj$8 N7o-+#p37v<1OVfr5^(?k delta 51 zcmaFDd54o*n~#@^0SJsvDs1GgWRf(~&&bbB)z3=I%`B@$_53&FN diff --git a/backend/app/__pycache__/models.cpython-314.pyc b/backend/app/__pycache__/models.cpython-314.pyc index 001efb13a71da49f4ec3560c4f61db24f34c2284..25f616e6405d158c9b8074626dc42153d4f4a92a 100644 GIT binary patch delta 59 zcmcbi^FW7Nn~#@^0SMS#l{a$xu_!v|hZd(673*gu=B5^>WtLPGXXfVT73;gCmZj$8 N7o-+#E@F8m2mtHY6BPge delta 51 zcmaE$b3=z)n~#@^0SMIBDsJTVW0ADb&&bbB)z3=I%`BWtLPGXXfVT73;gCmZj$8 N7o-+#wqbtD2LQ^B5=8(2 delta 51 zcmdm}vQmXxn~#@^0SNThDsJSKV3u^y&&bbB)z3=I%`B(9={4jdB~2-F`y1aSmc;s}Bj`~h_|Y1BLD z+(1{ofu@%;_s}btMQys%^_wqw^6mTk{`c!l_VK-#$^Pp9%)iX!pQ!xP@3(Kh?)kX=liM9WT)d*9 zB_#;9xScgqYvERU4d-E+PKSQTnPUc53mk|hb4D3JS@3ipMmRFc9<5y+!Tr3v7gq2S#sz2EqI2}vxi#vNhT^=BrYrYRoFs!Cd$ zQMtV7ND;bEnqs8ayhvUkTO%&6CywVQ8}m-;nSm3}14hG~;ijgg&Kh2?wQk2N21se6 YdLrXWG$|6Aa0H(f_6ikw79tG(0y!qBN&o-= literal 12288 zcmeI2TW=Fb6vx*IX+kP+r4k{8DvlggNu;o~*IwUVS_2M5gwUF_h=+p4yJLIA-nuhO z@Kdi(t<>rD`l~_A)bP&iS1)XFWN7@Y#V-oJMgP zGDUUkJ*!l*J|VI!%f_*cWBysf#ku(n&ZRTgue!7?`^QTc(7JT-yj3RrF&{&~5HJJ` z0YktLFa!(%L%8G^4 zeqQ*re|Xft-5=25{UbX3{NP}h*3T=U0#0A_1~>2Y2HTG7?kqHWaRi}i-eI9_6bC*} zpPUNT^1PHY#r-GPncjjDNBj5s3xl%6e{K7xgZ+EG!6Ulce?;5D-_bJn?hpF=w-5ER z?UPA&Xwcv55Bi5U`wy@e)~e(K)_1=Bvb1{n%9Yah8#$Jut@yjNfM!1Gm2e^!zJ^W0 zQlNQ5`~S_p6QQrRPw&l_%RgJ?zskSkWIl#~Az%m?0)~JgU+Ak#Ed9~Rw~;#48eO;6ZFKN$#C6-J zUwB?@aeq2`XU)3$>qn4P2pKa-lAopmHKkONh)7L1)!b@TlJrs*qKM_^&yM5Nmo!S_ zkZuZ(r}37g$}LWNA`-()(v3KZ(<~&e=pGC7<<=Il5yVm`o{MOFMhd3k%$txzv4%$; zx9JG8j#wxH!BVPVI1foA67u{QcO-=(RDM*26o0OWhddoavEvDksMb`I>mEd9>6DVW zDa>aHs$Qqc#;F*Khy?^op6=3+<1W&A9Sb$dl}jcmlbkAxQxXbpl6#6nzFk>ghpJOz z!(qyw30*gv_2*%QwnNTxk(GprsIo&Uo*A=Sfvy53dn`=_m#6{KyfbJVFeM_JD*M`G z*qE|N1`sZD2|`I8j39zVg)edjqm+M@!RlZNw|PP4qjzBeT$f=g1_zzdotQ5YAw6v} z4+KzZag|I_z=i;>J(pX~?TYpXNaSk4=WHBeBDqVb=!s`Ac`*!zP?(z9#HaUAFn zg{5K~6*Q(vJWg3MflItJmXgLi;wcn>)9lCF zRK@~8tw=W2jHYU$k>}A7>)v+hro8K zxt8@YJkNAT{v06kp`4N!>A|KOQ_5t%ko6MOGSM%K;9y_Zx@m||3K91Tho;PDz6dB` z3V9%+c!Y2mX93puaVikUuNN}?~p>mVukvI>iJkrc1b2o$LwzI?<9+~f3$n^ zp+zX@)a9`VHD?c*2u*tOXF|e}2z4Y21h1Z)A=IZa)YMB}pLM^&;d+(lW%V&vxni?a zc+94Do}CiOGe5pwxR)gf!kQs<(HMx4I1xA$F{B7gIpQ2yCig8^XUIrn-QhUa@#PDN zgu+x@&jbYP;E7lzH0Ha`u*2t!w2{Pw$TNUo7y=<&)mnNn@9cw@%e=Rcr0e zf^JT?+V0kxu%y+lwtMHGo0gO7w)BFCX1C_rtwyKgF1)o05suxdw_EipUhEe(K0Kd` zIJmn6d9xeXs { + const enc = new TextEncoder(); + const iv = crypto.getRandomValues(new Uint8Array(12)); + + const encrypted = await crypto.subtle.encrypt( + { name: "AES-GCM", iv }, + key, + enc.encode(text), + ); + + const combined = new Uint8Array(iv.length + encrypted.byteLength); + combined.set(iv); + combined.set(new Uint8Array(encrypted), iv.length); + + return btoa(String.fromCharCode(...combined)); +} + +export async function decryptString(encrypted: string, key: CryptoKey) { + const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0)); + const iv = combined.slice(0, 12); + const data = combined.slice(12); + + const decrypted = await crypto.subtle.decrypt( + { name: "AES-GCM", iv }, + key, + data, + ); + + return new TextDecoder().decode(decrypted); +} + +export async function decryptFolderTree( + tree: FolderTreeResponse, + encryptionKey: CryptoKey, +): Promise { + const decryptFolder = async ( + folder: FolderTreeNode, + ): Promise => { + return { + ...folder, + notes: await Promise.all( + folder.notes.map(async (note) => ({ + ...note, + title: await decryptString(note.title, encryptionKey), + content: await decryptString(note.content, encryptionKey), + })), + ), + children: await Promise.all( + folder.children.map((child) => decryptFolder(child)), + ), + }; + }; + + return { + folders: await Promise.all( + tree.folders.map((folder) => decryptFolder(folder)), + ), + orphaned_notes: await Promise.all( + tree.orphaned_notes.map(async (note) => ({ + ...note, + title: await decryptString(note.title, encryptionKey), + content: await decryptString(note.content, encryptionKey), + })), + ), + }; +} diff --git a/frontend/src/api/folders.tsx b/frontend/src/api/folders.tsx index 8ca5409..6e5ab22 100644 --- a/frontend/src/api/folders.tsx +++ b/frontend/src/api/folders.tsx @@ -1,4 +1,5 @@ import axios from "axios"; +import { decryptFolderTree, deriveKey } from "./encryption"; const API_URL = import.meta.env.PROD ? "/api" : "http://localhost:8000/api"; @@ -35,8 +36,18 @@ export interface FolderCreate { parent_id: number | null; } +const getFolderTree = async () => { + const { data } = await axios.get( + `${API_URL}/folders/tree`, + ); + var key = await deriveKey("Test"); + const decryptedFolderTree = await decryptFolderTree(data, key); + + return decryptedFolderTree; +}; + export const folderApi = { - tree: () => axios.get(`${API_URL}/folders/tree`), + tree: () => getFolderTree(), list: () => axios.get(`${API_URL}/folders`), create: (folder: FolderCreate) => axios.post(`${API_URL}/folders`, folder), diff --git a/frontend/src/api/notes.tsx b/frontend/src/api/notes.tsx index 9bb4b5d..8e69c89 100644 --- a/frontend/src/api/notes.tsx +++ b/frontend/src/api/notes.tsx @@ -1,4 +1,6 @@ import axios from "axios"; +import { NoteRead } from "./folders"; +import { deriveKey, encryptString, decryptString } from "./encryption"; const API_URL = import.meta.env.PROD ? "/api" : "http://localhost:8000/api"; @@ -18,82 +20,39 @@ export interface NoteCreate { encrypted: boolean; } -// Derive key from password -async function deriveKey(password: string) { - const enc = new TextEncoder(); - const keyMaterial = await crypto.subtle.importKey( - "raw", - enc.encode(password), - "PBKDF2", - false, - ["deriveKey"], - ); - - return crypto.subtle.deriveKey( - { - name: "PBKDF2", - salt: enc.encode("your-app-salt"), // Store this somewhere consistent - iterations: 100000, - hash: "SHA-256", - }, - keyMaterial, - { name: "AES-GCM", length: 256 }, - false, - ["encrypt", "decrypt"], - ); -} - -// Encrypt content -async function encryptNote(content: string, key: CryptoKey) { - const enc = new TextEncoder(); - const iv = crypto.getRandomValues(new Uint8Array(12)); - - const encrypted = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - key, - enc.encode(content), - ); - - // Return IV + encrypted data as base64 - const combined = new Uint8Array(iv.length + encrypted.byteLength); - combined.set(iv); - combined.set(new Uint8Array(encrypted), iv.length); - - return btoa(String.fromCharCode(...combined)); -} - -// Decrypt content -async function decryptNote(encrypted: string, key: CryptoKey) { - const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0)); - const iv = combined.slice(0, 12); - const data = combined.slice(12); - - const decrypted = await crypto.subtle.decrypt( - { name: "AES-GCM", iv }, - key, - data, - ); - - return new TextDecoder().decode(decrypted); -} - const createNote = async (note: NoteCreate) => { - if (!note.encrypted) { - return axios.post(`${API_URL}/notes`, note); - } else { - var key = await deriveKey("Test"); - var eNote = await encryptNote(note.content, key); + var key = await deriveKey("Test"); + var noteContent = await encryptString(note.content, key); + var noteTitle = await encryptString(note.title, key); - console.log(eNote); + var encryptedNote = { + title: noteTitle, + content: noteContent, + folder_id: note.folder_id, + }; - var unENote = await decryptNote(eNote, key); + console.log(encryptedNote); + return axios.post(`${API_URL}/notes`, encryptedNote); +}; - console.log(unENote); - } +const fetchNotes = async () => { + const { data } = await axios.get(`${API_URL}/notes`); + + console.log(data); + var key = await deriveKey("Test"); + const decryptedNotes = await Promise.all( + data.map(async (note: Note) => ({ + ...note, + title: await decryptString(note.title, key), + content: await decryptString(note.content, key), + })), + ); + + return decryptedNotes; }; export const notesApi = { - list: () => axios.get(`${API_URL}/notes`), + list: () => fetchNotes(), get: (id: number) => axios.get(`${API_URL}/notes/${id}`), create: (note: NoteCreate) => createNote(note), update: (id: number, note: Partial) => diff --git a/frontend/src/components/sidebar/DraggableNote.tsx b/frontend/src/components/sidebar/DraggableNote.tsx index 847c313..0bf33d3 100644 --- a/frontend/src/components/sidebar/DraggableNote.tsx +++ b/frontend/src/components/sidebar/DraggableNote.tsx @@ -27,7 +27,7 @@ export const DraggableNote = ({
selectNote(note)} - className={`ml-5 rounded-md px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${ + className={` rounded-md px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${ selectedNote?.id === note.id ? "bg-ctp-mauve text-ctp-base" : "hover:bg-ctp-surface1" diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 307fa2e..f01076b 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -68,7 +68,7 @@ function Home() { }, []); const loadFolderTree = async () => { - const { data } = await folderApi.tree(); + const data = await folderApi.tree(); setFolderTree(data); }; @@ -113,7 +113,7 @@ function Home() { }; const selectNote = (note: NoteRead) => { - console.log("setting note.... " + note.id); + console.log(note); setSelectedNote(note); setTitle(note.title); setContent(note.content); @@ -146,14 +146,16 @@ function Home() { selectedFolder={selectedFolder} selectedNote={selectedNote} /> - {folder.notes.map((note) => ( - - ))} +
+ {folder.notes.map((note) => ( + + ))} +
{folder.children.map((child) => renderFolder(child, depth + 1))}
); @@ -176,14 +178,9 @@ function Home() {
e.preventDefault()} // Add this - onTouchMove={(e) => e.preventDefault()} // And this for touch devices + className="bg-ctp-mantle border-r-ctp-surface2 border-r overflow-hidden w-[300px] p-4 overflow-y-auto sm:block hidden" + onDragOver={(e) => e.preventDefault()} + onTouchMove={(e) => e.preventDefault()} >

Notes