E2E Test and setup
This commit is contained in:
parent
9645f411f3
commit
50d7f41248
6 changed files with 111 additions and 3058 deletions
BIN
backend/notes.db
BIN
backend/notes.db
Binary file not shown.
|
|
@ -15,13 +15,88 @@ export interface NoteCreate {
|
|||
title: string;
|
||||
content: string;
|
||||
folder_id: number | null;
|
||||
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);
|
||||
|
||||
console.log(eNote);
|
||||
|
||||
var unENote = await decryptNote(eNote, key);
|
||||
|
||||
console.log(unENote);
|
||||
}
|
||||
};
|
||||
|
||||
export const notesApi = {
|
||||
list: () => axios.get(`${API_URL}/notes`),
|
||||
get: (id: number) => axios.get(`${API_URL}/notes/${id}`),
|
||||
create: (note: NoteCreate) => axios.post(`${API_URL}/notes`, note),
|
||||
update: (id: number, note: Partial) =>
|
||||
create: (note: NoteCreate) => createNote(note),
|
||||
update: (id: number, note: Partial<Note>) =>
|
||||
axios.patch(`${API_URL}/notes/${id}`, note),
|
||||
delete: (id: number) => axios.delete(`${API_URL}/notes/${id}`),
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
6
frontend/src/assets/fontawesome/js/fontawesome.min.js
vendored
Normal file
6
frontend/src/assets/fontawesome/js/fontawesome.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -2,8 +2,8 @@ import React from "react";
|
|||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./main.css";
|
||||
import "./assets/fontawesome/js/fontawesome.min.js";
|
||||
import "./assets/fontawesome/js/duotone-regular.js";
|
||||
import "./assets/fontawesome/js/fontawesome.js ";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,13 @@ import {
|
|||
} from "../api/folders";
|
||||
import { NoteCreate, notesApi } from "../api/notes";
|
||||
import "../main.css";
|
||||
import { DndContext, DragEndEvent } from "@dnd-kit/core";
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
|
||||
import "@mdxeditor/editor/style.css";
|
||||
import { DroppableFolder } from "../components/sidebar/DroppableFolder";
|
||||
|
|
@ -50,7 +56,13 @@ function Home() {
|
|||
const [newFolder, setNewFolder] = useState(false);
|
||||
const [newFolderText, setNewFolderText] = useState("");
|
||||
const [selectedFolder, setSelectedFolder] = useState<number | null>(null);
|
||||
|
||||
const [encrypted, setEncrypted] = useState(false);
|
||||
const pointer = useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 30,
|
||||
},
|
||||
});
|
||||
const sensors = useSensors(pointer);
|
||||
useEffect(() => {
|
||||
loadFolderTree();
|
||||
}, []);
|
||||
|
|
@ -62,7 +74,12 @@ function Home() {
|
|||
|
||||
const handleCreate = async () => {
|
||||
if (!title.trim()) return;
|
||||
const newNote: NoteCreate = { title, content, folder_id: selectedFolder };
|
||||
const newNote: NoteCreate = {
|
||||
title,
|
||||
content,
|
||||
folder_id: selectedFolder,
|
||||
encrypted,
|
||||
};
|
||||
await notesApi.create(newNote);
|
||||
setTitle("");
|
||||
setContent("#");
|
||||
|
|
@ -96,6 +113,7 @@ function Home() {
|
|||
};
|
||||
|
||||
const selectNote = (note: NoteRead) => {
|
||||
console.log("setting note.... " + note.id);
|
||||
setSelectedNote(note);
|
||||
setTitle(note.title);
|
||||
setContent(note.content);
|
||||
|
|
@ -155,7 +173,7 @@ function Home() {
|
|||
};
|
||||
|
||||
return (
|
||||
<DndContext onDragEnd={handleDragEnd} autoScroll={false}>
|
||||
<DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}>
|
||||
<div className="flex bg-ctp-base min-h-screen text-ctp-text">
|
||||
<div
|
||||
className="bg-ctp-mantle border-r-ctp-surface2 border-r overflow-hidden"
|
||||
|
|
@ -292,6 +310,11 @@ function Home() {
|
|||
) : (
|
||||
<button onClick={handleCreate}>Create Note</button>
|
||||
)}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={encrypted}
|
||||
onChange={() => setEncrypted(!encrypted)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue