address comments
This commit is contained in:
parent
63a92cf825
commit
ce693cfdf1
|
@ -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
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"] });
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue