From 46e33eb5ba63276836322b11abfbff6432c98ff6 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 6 Jan 2023 12:23:40 -0800 Subject: re-encrypt entropy on password change --- src/components/RegisterOrLogin.svelte | 2 + src/lib/nostr.js | 38 +++++++++ src/lib/utils.js | 13 ++- src/locales/en.json | 6 +- .../[username]/settings/[...tab]/+page.svelte | 92 ++++++++++++---------- .../[username]/settings/[...tab]/_account.svelte | 85 ++------------------ .../(app)/[username]/settings/[...tab]/_pos.svelte | 9 ++- .../[username]/settings/[...tab]/_security.svelte | 29 ++++++- src/routes/(app)/otpsecret/+server.js | 4 +- 9 files changed, 144 insertions(+), 134 deletions(-) (limited to 'src') diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte index cc23d8ad..23a045a0 100644 --- a/src/components/RegisterOrLogin.svelte +++ b/src/components/RegisterOrLogin.svelte @@ -16,6 +16,8 @@ let code = []; let redirect; + $password = undefined; + $: need2fa = form?.message === '2fa'; $: if (need2fa) tokenInput && tick().then(tokenInput.focusFirstInput); diff --git a/src/lib/nostr.js b/src/lib/nostr.js index ae18fbf1..35d843b2 100644 --- a/src/lib/nostr.js +++ b/src/lib/nostr.js @@ -131,3 +131,41 @@ export let sign = async ({ event, user }) => { export let send = (event) => { return post('/events', { event }); }; + +export let reEncryptEntropy = async (user, newPassword) => { + let { cipher, username, salt } = user; + let password = get(pw); + + if (!password) { + passwordPrompt.set(true); + } + + try { + await post('/password', { password }); + } catch (e) { + passwordPrompt.set(true); + } + + await wait(() => !!get(pw)); + password = get(pw); + + let mnemonic, key, seed, entropy, child, privkey; + entropy = Buffer.from( + await crypto.subtle.decrypt( + { name: 'AES-GCM', iv: new Uint8Array(16) }, + await stretch(password, Buffer.from(salt, 'hex')), + Uint8Array.from(fromWords(decode(cipher, 180).words)) + ), + 'hex' + ).toString('hex'); + + let bytes = new Uint8Array( + await crypto.subtle.encrypt( + { name: 'AES-GCM', iv: new Uint8Array(16) }, + await stretch(newPassword, Buffer.from(salt, 'hex')), + Uint8Array.from(Buffer.from(entropy, 'hex')) + ) + ); + + return encode('en', toWords(bytes), 180); +}; diff --git a/src/lib/utils.js b/src/lib/utils.js index d7fba2fc..77ed93bd 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -46,12 +46,12 @@ export const post = (url, body, headers) => { if (r.ok) return r.text(); let text = await r.text(); - let message; + let message; try { ({ message } = JSON.parse(text)); } catch (e) {} - if (message) throw new Error(message); + if (message) throw new Error(message); throw new Error(text); }) .then((body) => { @@ -83,7 +83,14 @@ export function reverseFormat(val, locale) { return Number.isNaN(reversedVal) ? 0 : +reversedVal; } -export let protectedRoutes = [/customers/, /dashboard/, /settings/, /transactions/, /\/ln\//, /request/]; +export let protectedRoutes = [ + /customers/, + /dashboard/, + /settings/, + /transactions/, + /\/ln\//, + /request/ +]; export const success = (m) => { toast.pop(); diff --git a/src/locales/en.json b/src/locales/en.json index b4c8f8ac..a5d0ad02 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -209,16 +209,16 @@ "locale": "Language", "username": "Username", "email": "Email", - "newPassword": "New Password", + "newPassword": "Change Password", "about": "About", - "aboutPlaceholder": "A short description of you or your business (address, bio, etc.)", + "aboutPlaceholder": "A little something about you or your business address and hours", "emailNotifications": "Email Notifications", "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.", "smsNotifications": "SMS Notifications", "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.", "theme": "Color Theme", "themeDescription": "Select from the default color themes.", - "localCurrency": "Local Currency", + "localCurrency": "Currency", "btcUnit": "Display bitcoin in:", "bitcoin": "bitcoin", "satoshis": "Satoshis", diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index b02ddca4..b2602625 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -6,10 +6,10 @@ import { Icon, Spinner, Pin } from '$comp'; import { t } from '$lib/translations'; import { failure, post, success } from '$lib/utils'; - import { avatarUpload, bannerUpload, pin } from '$lib/store'; + import { avatarUpload, bannerUpload, password, pin } from '$lib/store'; import { upload } from '$lib/upload'; import { page } from '$app/stores'; - import { sign, send } from '$lib/nostr'; + import { sign, send, reEncryptEntropy } from '$lib/nostr'; import { invalidateAll } from '$app/navigation'; import Account from './_account.svelte'; @@ -50,50 +50,62 @@ let loading; async function handleSubmit() { - loading = true; - let data = new FormData(formElement); - try { - if ($avatarUpload) { - await upload($avatarUpload.file, $avatarUpload.type, $avatarUpload.progress, token); + loading = true; + let data = new FormData(formElement); + + if (data.get('password')) { + console.log('reencrypting', user.cipher); + data.set('cipher', await reEncryptEntropy(user, data.get('password'))); + console.log(data.get('cipher')); + } + + try { + if ($avatarUpload) { + await upload($avatarUpload.file, $avatarUpload.type, $avatarUpload.progress, token); + } + + if ($bannerUpload) { + await upload($bannerUpload.file, $bannerUpload.type, $bannerUpload.progress, token); + } + } catch (e) { + console.log(e); + failure('Something went wrong'); } - if ($bannerUpload) { - await upload($bannerUpload.file, $bannerUpload.type, $bannerUpload.progress, token); + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 0, + content: JSON.stringify({ + name: user.username, + about: user.address, + picture: `${PUBLIC_COINOS_URL}/public/${user.username}-profile.webp` + }), + tags: [] + }; + + await sign({ event, user }); + await send(event); + + const response = await fetch(formElement.action, { + method: 'POST', + body: data + }); + + const result = deserialize(await response.text()); + + if (result.type === 'success') { + await invalidateAll(); + if (data.get('password')) $password = data.get('password'); } + + applyAction(result); + loading = false; } catch (e) { console.log(e); - failure('Something went wrong'); - } - - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 0, - content: JSON.stringify({ - name: user.username, - about: user.address, - picture: `${PUBLIC_COINOS_URL}/public/${user.username}-profile.webp` - }), - tags: [] - }; - - await sign({ event, user }); - await send(event); - - const response = await fetch(formElement.action, { - method: 'POST', - body: data - }); - - const result = deserialize(await response.text()); - - if (result.type === 'success') { - await invalidateAll(); + failure('Problem submitting form'); } - - applyAction(result); - loading = false; } let loaded; @@ -117,7 +129,7 @@ {$t('user.settings.header')} -
+ + + + +
@@ -105,27 +70,6 @@ />
- - - - - - - - - - - - - - - - - - - - -
{$t('user.settings.profileImage')} @@ -192,7 +136,6 @@
{$t('user.settings.bannerImage')}
- {#if banner} {/if} -
{/if}
- - diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index b4aa3181..bade7a99 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -1,13 +1,18 @@ +
+ + +
+
+ + + +
+ + {#if revealPassword} + + {:else} + + {/if} + +
+
{verifying ? $t('user.settings.verifyPIN') : $t('user.settings.securityPIN')} diff --git a/src/routes/(app)/otpsecret/+server.js b/src/routes/(app)/otpsecret/+server.js index f998fb17..31dbcec1 100644 --- a/src/routes/(app)/otpsecret/+server.js +++ b/src/routes/(app)/otpsecret/+server.js @@ -4,12 +4,12 @@ import { error } from '@sveltejs/kit'; export async function POST({ cookies, request }) { try { - let body = await request.json(); + let body = await request.json(); let { username, secret } = await post('/otpsecret', body, auth(cookies)); let uri = `otpauth://totp/coinos:${username}?secret=${secret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`; return new Response(JSON.stringify({ secret, uri, qr: Qr.drawImg(uri, { size: 600 }) })); } catch (e) { - console.log(e) + console.log(e); throw new error(500, 'Problem saving secret'); } } -- cgit v1.2.3