address comments

This commit is contained in:
Sasha 2023-11-30 02:03:27 +01:00
parent 63a92cf825
commit ce693cfdf1
No known key found for this signature in database
11 changed files with 1110 additions and 934 deletions

View File

@ -1,36 +1,16 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). ## Waku dependencies
- @waku/interfaces
- @waku/message-encryption
- @waku/sdk
- @waku/utils
## Getting Started ## Description
Exchange encrypted or plain notes by link.
First, run the development server: This example shows how symmetric encryption can be used to encrypt only part of Waku message.
## How to run
```bash ```bash
npm run dev npm start
# or # or
yarn dev yarn start
# or
pnpm dev
# or
bun dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

File diff suppressed because it is too large Load Diff

View File

@ -8,11 +8,10 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@waku/interfaces": "^0.0.20", "@waku/interfaces": "0.0.21-7eb3375.0",
"@waku/message-encryption": "^0.0.23", "@waku/message-encryption": "0.0.24-7eb3375.0",
"@waku/sdk": "^0.0.21", "@waku/sdk": "0.0.22-7eb3375.0",
"@waku/utils": "^0.0.13", "@waku/utils": "0.0.14-7eb3375.0",
"ethereum-cryptography": "^2.1.2",
"next": "14.0.2", "next": "14.0.2",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",

View File

@ -21,10 +21,8 @@
align-items: center; align-items: center;
} }
.password-value { .to-encrypt {
font-size: 1rem; font-size: 1rem;
height: 2rem;
width: 250px;
padding: 5px; padding: 5px;
} }

View File

@ -2,7 +2,7 @@
import React from "react"; import React from "react";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { WakuProvider } from "@/app/WakuProvider"; import { WakuProvider } from "@/components/WakuProvider";
import "./globals.css"; import "./globals.css";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });

View File

@ -7,7 +7,7 @@ import { notes } from "@/services/notes";
export default function Create() { export default function Create() {
const router = useRouter(); const router = useRouter();
const { note, onNoteChange } = useEditNote(); const { note, onNoteChange } = useEditNote();
const { password, onPasswordChange } = usePassword(); const { toEncrypt, onEncryptChange } = useEncryptedState();
const onSave = async () => { const onSave = async () => {
if (!note) { if (!note) {
@ -15,10 +15,12 @@ export default function Create() {
} }
try { try {
const id = await notes.createNote(note, password); const { id, password } = await notes.createNote(note, toEncrypt);
router.push(`/view/${id}`); const passwordParam = password ? `?password=${password}` : "";
router.push(`/view/${id}${passwordParam}`);
} catch (error) { } catch (error) {
console.log("Failed to create a note:", error); console.error("Failed to create a note:", error);
} }
}; };
@ -28,13 +30,17 @@ export default function Create() {
Your record will be stored for couple of days. Markdown is supported. Your record will be stored for couple of days. Markdown is supported.
</p> </p>
<div className="create-header"> <div className="create-header">
<div>
<input <input
className="password-value" type="checkbox"
type="password" id="isEncrypted"
onChange={onPasswordChange} name="isEncrypted"
placeholder="Optional password" onChange={onEncryptChange}
autoComplete="off"
/> />
<label htmlFor="isEncrypted" className="to-encrypt">
Private (only those that have link will read the note)
</label>
</div>
<button onClick={onSave} className="save-note"> <button onClick={onSave} className="save-note">
Save note Save note
</button> </button>
@ -61,15 +67,15 @@ const useEditNote = () => {
}; };
}; };
const usePassword = () => { const useEncryptedState = () => {
const [password, setPassword] = React.useState<string>(); const [toEncrypt, setToEncrypt] = React.useState<string>();
const onPasswordChange = (event: React.FormEvent<HTMLInputElement>) => { const onEncryptChange = (event: React.FormEvent<HTMLSelectElement>) => {
setPassword(event?.currentTarget?.value); setToEncrypt(event?.currentTarget?.value);
}; };
return { return {
password, toEncrypt,
onPasswordChange, onEncryptChange,
}; };
}; };

View File

@ -5,7 +5,7 @@ import Markdown from "react-markdown";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useNoteURL } from "@/hooks/useNoteURL"; import { useNoteURL } from "@/hooks/useNoteURL";
import { notes } from "@/services/notes"; import { notes } from "@/services/notes";
import { Loading } from "../Loading"; import { Loading } from "@/components/Loading";
const View = () => { const View = () => {
const router = useRouter(); const router = useRouter();

View File

@ -3,7 +3,7 @@
import React from "react"; import React from "react";
import { waku, Waku, WakuEvents } from "@/services/waku"; import { waku, Waku, WakuEvents } from "@/services/waku";
import { WakuStatus } from "@/const"; import { WakuStatus } from "@/const";
import { Loading } from "./Loading"; import { Loading } from "@/components/Loading";
type WakuContextProps = { type WakuContextProps = {
status: WakuStatus; status: WakuStatus;

View File

@ -1,12 +1,13 @@
"use client"; "use client";
import { usePathname } from "next/navigation"; import { usePathname, useSearchParams } from "next/navigation";
export const useNoteURL = (): undefined | string => { export const useNoteURL = (): undefined | string => {
const pathname = usePathname(); const pathname = usePathname();
const urlParams = new URLSearchParams(window.location.search); const params = useSearchParams();
const segments = pathname.split("/"); const segments = pathname.split("/");
const viewIndex = segments.indexOf("view"); const viewIndex = segments.indexOf("view");
const password = urlParams.get("password"); const password = params.get("password");
return { return {
password, password,

View File

@ -2,9 +2,10 @@
import { waku } from "@/services/waku"; import { waku } from "@/services/waku";
import { CONTENT_TOPIC } from "@/const"; import { CONTENT_TOPIC } from "@/const";
import { encrypt, decrypt } from "ethereum-cryptography/aes"; import {
import { getRandomBytes } from "ethereum-cryptography/random"; symmetric,
import { pbkdf2 } from "ethereum-cryptography/pbkdf2"; generateSymmetricKey,
} from "@waku/message-encryption/crypto";
import { import {
createDecoder, createDecoder,
createEncoder, createEncoder,
@ -21,14 +22,12 @@ import { bytesToHex, hexToBytes } from "@waku/utils/bytes";
type Note = { type Note = {
id: string; id: string;
content: string; content: string;
kdf:
| undefined
| {
iv: string; iv: string;
dklen: number; };
c: number;
salt: string; type NoteResult = {
}; id: string;
password?: string;
}; };
export class Notes { export class Notes {
@ -42,16 +41,23 @@ export class Notes {
this.encoder = createEncoder({ contentTopic: CONTENT_TOPIC }); this.encoder = createEncoder({ contentTopic: CONTENT_TOPIC });
} }
public async createNote(content: string, password?: string): Promise<string> { public async createNote(
const note = password content: string,
? await this.encryptNote(content, password) toEncrypt?: boolean
: { id: generateRandomString(), content, kdf: undefined }; ): Promise<NoteResult> {
const symmetricKey = toEncrypt ? generateSymmetricKey() : undefined;
const note = toEncrypt
? await this.encryptNote(content, symmetricKey)
: { id: generateRandomString(), content, iv: undefined };
await waku.send(this.encoder, { await waku.send(this.encoder, {
payload: utf8ToBytes(JSON.stringify(note)), payload: utf8ToBytes(JSON.stringify(note)),
}); });
return note.id; return {
id: note.id,
password: symmetricKey ? bytesToHex(symmetricKey) : undefined,
};
} }
public async readNote( public async readNote(
@ -74,7 +80,7 @@ export class Notes {
} }
}); });
if (!message?.kdf) { if (!message?.iv) {
return message?.content; return message?.content;
} }
@ -100,48 +106,36 @@ export class Notes {
}); });
} }
private async encryptNote(content: string, password: string): Promise<Note> { private async encryptNote(
const iv = await getRandomBytes(16); content: string,
const salt = await getRandomBytes(32); symmetricKey: Uint8Array
const c = 131072; ): Promise<Note> {
const dklen = 16; const iv = symmetric.generateIv();
const kdf = await pbkdf2( const encryptedContent = await symmetric.encrypt(
utf8ToBytes(password.normalize("NFKC")), iv,
salt, symmetricKey,
c, utf8ToBytes(content)
dklen,
"sha256"
); );
const encryptedContent = await encrypt(utf8ToBytes(content), kdf, iv);
return { return {
id: generateRandomString(), id: generateRandomString(),
content: bytesToHex(encryptedContent), content: bytesToHex(encryptedContent),
kdf: {
c,
dklen,
iv: bytesToHex(iv), iv: bytesToHex(iv),
salt: bytesToHex(salt),
},
}; };
} }
private async decryptNote(note: Note, password: string): Promise<string> { private async decryptNote(note: Note, password: string): Promise<string> {
if (!note?.kdf) { if (!note?.iv) {
throw Error("Failed to decrypt a note, no kdf params found."); throw Error("Failed to decrypt a note, no IV params found.");
} }
const iv = hexToBytes(note.kdf.iv); const iv = hexToBytes(note.iv);
const salt = hexToBytes(note.kdf.salt); const symmetricKey = hexToBytes(password);
const decryptedContent = await symmetric.decrypt(
const kdf = await pbkdf2( iv,
utf8ToBytes(password.normalize("NFKC")), symmetricKey,
salt, hexToBytes(note.content)
note.kdf.c,
note.kdf.dklen,
"sha256"
); );
const decryptedContent = await decrypt(hexToBytes(note.content), kdf, iv);
return bytesToUtf8(decryptedContent); return bytesToUtf8(decryptedContent);
} }