diff options
| author | Adam Soltys <[email protected]> | 2022-12-21 15:48:14 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-12-21 15:48:14 -0800 |
| commit | 6ae27be919808df935fb140fd56bb6ae010a219b (patch) | |
| tree | ba9f5d862cc48abf5b49acfdbd32b87216eadba1 | |
| parent | 5c0675fa6815a70033c17a9310d0fa1f9c569e92 (diff) | |
| download | coinos-ui-6ae27be919808df935fb140fd56bb6ae010a219b.tar.gz coinos-ui-6ae27be919808df935fb140fd56bb6ae010a219b.zip | |
add nostr message field to user profile page
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 30 | ||||
| -rw-r--r-- | src/lib/utils.js | 24 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/+page.server.js | 9 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/+page.svelte | 60 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/+server.js | 6 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/generate/+page.svelte | 27 |
7 files changed, 129 insertions, 28 deletions
diff --git a/package.json b/package.json index dac10511..8e86dc1d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "date-fns": "^2.29.3", "leaflet": "^1.9.3", "leaflet.markercluster": "^1.5.3", + "nostr": "^0.2.7", "postcss": "^8.4.19", "prettier": "^2.7.1", "prettier-plugin-svelte": "^2.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2353621a..af33679d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,7 @@ specifiers: date-fns: ^2.29.3 leaflet: ^1.9.3 leaflet.markercluster: ^1.5.3 + nostr: ^0.2.7 postcss: ^8.4.19 prettier: ^2.7.1 prettier-plugin-svelte: ^2.8.1 @@ -49,6 +50,7 @@ devDependencies: date-fns: 2.29.3 leaflet: 1.9.3 leaflet.markercluster: [email protected] + nostr: 0.2.7 postcss: 8.4.19 prettier: 2.7.1 prettier-plugin-svelte: 2.8.1_nryolsexf6k3znhuh4uzpugsem @@ -1529,6 +1531,11 @@ packages: hasBin: true dev: true + /noble-secp256k1/1.2.14: + resolution: {integrity: sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==} + deprecated: Switch to namespaced @noble/secp256k1 for security and feature updates + dev: true + /node-addon-api/2.0.2: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} dev: true @@ -1585,6 +1592,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /nostr/0.2.7: + resolution: {integrity: sha512-Yq5tkiCTJtohs7H7Fx+Ki83bR8b3xapdwYi17HunhW5EQfZ9rI0cIXFyihj2ZRkv/0YYvVBiCp/KIaLYt5LyfQ==} + dependencies: + noble-secp256k1: 1.2.14 + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /object-hash/3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} @@ -2372,6 +2389,19 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /ws/8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/src/lib/utils.js b/src/lib/utils.js index f59b6359..14646ad0 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,3 +1,5 @@ +import * as ecc from 'tiny-secp256k1'; +import { BIP32Factory } from 'bip32'; import { browser } from '$app/environment'; import { toast } from '@zerodevx/svelte-toast'; import { goto } from '$app/navigation'; @@ -168,3 +170,25 @@ export const wait = async (f, s = 300) => { if (i >= s) throw new Error('timeout'); return f(); }; + +export const stretch = async (password, salt) => + crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt, + iterations: 100000, + hash: 'SHA-256' + }, + await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(password), + { name: 'PBKDF2' }, + false, + ['deriveBits', 'deriveKey'] + ), + { name: 'AES-GCM', length: 256 }, + true, + ['encrypt', 'decrypt'] + ); + +export const bip32 = await BIP32Factory(ecc); diff --git a/src/routes/(app)/[username]/+page.server.js b/src/routes/(app)/[username]/+page.server.js index 01dfab5a..547f754f 100644 --- a/src/routes/(app)/[username]/+page.server.js +++ b/src/routes/(app)/[username]/+page.server.js @@ -1,14 +1,19 @@ import { get } from '$lib/utils'; import Qr from 'qrcode-base64'; -export async function load({ params, url }) { +export async function load({ params, parent, url }) { + let { user } = await parent(); + let pubkey; + if (user) ({ pubkey } = user); + else pubkey = params.username; + let { username } = params; let text = `${encodeURI(username)}@${url.hostname}`; let src = Qr.drawImg(text, { size: 300 }); let events = []; try { - events = await get(`/${username}/events`); + events = await get(`/${pubkey}/events`); } catch (e) { console.log(e); } diff --git a/src/routes/(app)/[username]/+page.svelte b/src/routes/(app)/[username]/+page.svelte index 78d84653..837257ef 100644 --- a/src/routes/(app)/[username]/+page.svelte +++ b/src/routes/(app)/[username]/+page.svelte @@ -1,12 +1,53 @@ <script> + import { Buffer } from 'buffer'; + import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'; + import { enhance } from '$app/forms'; import { Feed, Icon, PageNotFound } from '$comp'; import { t } from '$lib/translations'; + import { bech32m } from 'bech32'; + import { calculateId, signId } from 'nostr'; + import { bip32, stretch, post } from '$lib/utils'; + + const { decode, fromWords } = bech32m; export let data; + let message; + let { events, user, subject, src, text } = data; let followed = false; let { username: n } = subject; + + let submit = async () => { + let mnemonic, key, password, seed, entropy, child, privkey; + entropy = Buffer.from( + await crypto.subtle.decrypt( + { name: 'AES-GCM', iv: new Uint8Array(16) }, + await stretch(password, Buffer.from(user.salt, 'hex')), + Uint8Array.from(fromWords(decode(user.cipher, 180).words)) + ), + 'hex' + ).toString('hex'); + + mnemonic = entropyToMnemonic(entropy); + seed = mnemonicToSeedSync(mnemonic); + key = bip32.fromSeed(seed); + child = key.derivePath("m/44'/1237'/0'/0/0"); + privkey = child.privateKey; + + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + content: message, + tags: [] + }; + + event.id = await calculateId(event); + event.sig = await signId(privkey, event.id); + await post(`/${user.username}`, { event }); + }; + $: username = n.length > 60 ? n.substr(0, 6) : n; </script> @@ -69,9 +110,24 @@ </div> </div> - <div class="w-full flex md:pt-12 lg:pt-0"> + <div class="w-full md:pt-12 lg:pt-0"> + {#if user.username === n} + <form on:submit|preventDefault={submit}> + <input + name="message" + bind:value={message} + placeholder="What's on your mind?" + class="w-full my-5 max-w-lg mx-auto" + /> + </form> + {/if} + <div class="mx-auto space-y-5 mt-5 lg:max-w-lg xl:max-w-2xl"> - <Feed {events} /> + {#if events.length} + <Feed {events} /> + {:else} + <div class="text-center">You haven't posted anything yet</div> + {/if} </div> </div> </div> diff --git a/src/routes/(app)/[username]/+server.js b/src/routes/(app)/[username]/+server.js new file mode 100644 index 00000000..d0b1242f --- /dev/null +++ b/src/routes/(app)/[username]/+server.js @@ -0,0 +1,6 @@ +import { auth, post } from '$lib/utils'; +import { json } from '@sveltejs/kit'; + +export async function POST({ cookies, request }) { + return json(post('/nostr/send', await request.json(), auth(cookies))); +} diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index ee67237c..fd6b6f7e 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -1,42 +1,20 @@ <script> import { Buffer } from 'buffer'; import { onMount } from 'svelte'; - import { BIP32Factory } from 'bip32'; import { bech32m } from 'bech32'; import { generateMnemonic, mnemonicToEntropy, mnemonicToSeedSync } from 'bip39'; import * as ecc from 'tiny-secp256k1'; - import { post } from '$lib/utils'; + import { bip32, stretch, post } from '$lib/utils'; import { goto } from '$app/navigation'; export let data; let { user } = data; - const { encode, decode, fromWords, toWords } = bech32m; - - const stretch = async (password, salt) => - crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt, - iterations: 100000, - hash: 'SHA-256' - }, - await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(password), - { name: 'PBKDF2' }, - false, - ['deriveBits', 'deriveKey'] - ), - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); + const { encode, toWords } = bech32m; let mnemonic, key, password, seed, entropy, cipher, pubkey, salt, child; onMount(async () => { - let bip32 = await BIP32Factory(ecc); mnemonic = generateMnemonic(); seed = mnemonicToSeedSync(mnemonic); entropy = mnemonicToEntropy(mnemonic); @@ -54,6 +32,7 @@ user.pubkey = Buffer.from(ecc.xOnlyPointFromPoint(child.publicKey)).toString('hex'); user.cipher = encode('en', toWords(bytes), 180); + user.salt = Buffer.from(salt).toString('hex'); await post(`/${user.username}/generate`, user); goto(`/${user.username}/dashboard`); |
