aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-01-06 12:23:40 -0800
committerAdam Soltys <[email protected]>2023-01-06 12:23:40 -0800
commit46e33eb5ba63276836322b11abfbff6432c98ff6 (patch)
tree64c1ad3760ed5c6c5f86dda8a0ec624197a75ab1 /src
parente398ccf8b21e38c5fd0bc445acce4d6542551713 (diff)
downloadcoinos-ui-46e33eb5ba63276836322b11abfbff6432c98ff6.tar.gz
coinos-ui-46e33eb5ba63276836322b11abfbff6432c98ff6.zip
re-encrypt entropy on password change
Diffstat (limited to 'src')
-rw-r--r--src/components/RegisterOrLogin.svelte2
-rw-r--r--src/lib/nostr.js38
-rw-r--r--src/lib/utils.js13
-rw-r--r--src/locales/en.json6
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.svelte86
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_account.svelte85
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_pos.svelte9
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_security.svelte29
-rw-r--r--src/routes/(app)/otpsecret/+server.js4
9 files changed, 141 insertions, 131 deletions
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'));
}
- if ($bannerUpload) {
- await upload($bannerUpload.file, $bannerUpload.type, $bannerUpload.progress, token);
+ 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');
}
- } 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: []
- };
+ 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);
+ await sign({ event, user });
+ await send(event);
- const response = await fetch(formElement.action, {
- method: 'POST',
- body: data
- });
+ const response = await fetch(formElement.action, {
+ method: 'POST',
+ body: data
+ });
- const result = deserialize(await response.text());
+ const result = deserialize(await response.text());
- if (result.type === 'success') {
- await invalidateAll();
- }
+ if (result.type === 'success') {
+ await invalidateAll();
+ if (data.get('password')) $password = data.get('password');
+ }
- applyAction(result);
- loading = false;
+ applyAction(result);
+ loading = false;
+ } catch (e) {
+ console.log(e);
+ failure('Problem submitting form');
+ }
}
let loaded;
@@ -117,7 +129,7 @@
{$t('user.settings.header')}
</h1>
- <div class="font-bold flex justify-between items-center border-b pb-3 text-secondary">
+ <div class="font-bold flex justify-around items-center border-b pb-3 text-secondary">
{#each tabs as { name, key }}
<a href={`/${user.username}/settings/${name}`}>
<button class="hover:opacity-80" class:text-black={tab === name}
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte
index 0359f074..6b9fb2c6 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte
+++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte
@@ -1,22 +1,19 @@
<script>
import { tick } from 'svelte';
import { colorTheme, avatarUpload, bannerUpload } from '$lib/store';
- import { Icon, Toggle, LocaleSelector } from '$comp';
+ import { Icon } from '$comp';
import { t } from '$lib/translations';
import { upload } from '$lib/upload';
export let user;
let selectedTheme = 1;
- let revealPassword = false;
- let password;
let profileFile;
let select = (type) => files[type].el.click();
$: profile = files.profile.src;
$: banner = files.banner.src;
- let togglePassword = () => (revealPassword = !revealPassword);
let colorThemes = [
{ theme: 1, color1: 'from-[#F5F7FA]', color2: 'to-[#C3CFE2]' },
{ theme: 2, color1: 'from-[#FDFCFB]', color2: 'to-[#E2D1C3]' },
@@ -56,44 +53,12 @@
$bannerUpload = { file, type, progress };
}
};
-
- $: user.password = user.confirm = password;
</script>
-<div>
- <label for="language" class="font-bold block mb-1">{$t('user.settings.locale')}</label>
- <LocaleSelector style="select-styles block py-3 w-full" />
-</div>
-
-<!-- TODO
-<div>
- <label for="email" class="font-bold mb-1 block">{$t('user.settings.email')}</label>
- <input type="email" name="email" bind:value={user.email} />
-</div> -->
-
-<!-- TODO fix breaking changes when user updates this field -->
-<div>
- <label for="username" class="font-bold mb-1 block">{$t('user.settings.username')}</label>
- <input disabled type="text" name="username" bind:value={user.username} />
- <input type="hidden" name="username" bind:value={user.username} />
-</div>
-
-<div class="relative">
- <label for="password" class="block font-bold block mb-1">{$t('user.settings.newPassword')}</label>
- {#if revealPassword}
- <input name="password" type="text" bind:value={password} />
- {:else}
- <input name="password" type="password" bind:value={password} />
- {/if}
- <button
- type="button"
- on:click={togglePassword}
- on:keydown={togglePassword}
- class="absolute right-5 top-11"
- >
- <Icon icon={revealPassword ? 'eye' : 'eye-off'} />
- </button>
-</div>
+<!-- <div> -->
+<!-- <label for="username" class="font-bold mb-1 block">{$t('user.settings.username')}</label> -->
+<!-- <input type="text" name="username" bind:value={user.username} /> -->
+<!-- </div> -->
<div>
<label for="address" class="font-bold mb-1 block">{$t('user.settings.about')}</label>
@@ -105,27 +70,6 @@
/>
</div>
-<!-- TODO -->
-<!-- <div> -->
-<!-- <div class="flex justify-between items-center"> -->
-<!-- <span class="font-bold">{$t('user.settings.emailNotifications')}</span> -->
-<!-- <Toggle id="email-notify" /> -->
-<!-- </div> -->
-<!-- <p class="text-secondary mt-1 w-9/12"> -->
-<!-- {$t('user.settings.emailNotificationsDescription')} -->
-<!-- </p> -->
-<!-- </div> -->
-<!-- -->
-<!-- <div> -->
-<!-- <div class="flex justify-between items-center"> -->
-<!-- <span class="font-bold">{$t('user.settings.smsNotifications')}</span> -->
-<!-- <Toggle id="sms-notify" /> -->
-<!-- </div> -->
-<!-- <p class="text-secondary mt-1 w-9/12"> -->
-<!-- {$t('user.settings.smsNotificationsDescription')} -->
-<!-- </p> -->
-<!-- </div> -->
-
<div>
<span class="font-bold">{$t('user.settings.profileImage')}</span>
@@ -192,7 +136,6 @@
<div class="flex justify-between items-center">
<span class="font-bold">{$t('user.settings.bannerImage')}</span>
</div>
- <!-- found missing translation -->
{#if banner}
<img
@@ -219,7 +162,6 @@
/>
{/if}
- <!-- found missing translation -->
<button
type="button"
class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80"
@@ -240,20 +182,3 @@
<div class="text-red-600">Max file size 10MB</div>
{/if}
</div>
-
-<!-- TODO
-<div>
- <span class="font-bold mb-1 block">{$t('user.settings.theme')}</span>
- <p class="text-secondary mb-1">{$t('user.settings.themeDescription')}</p>
- {#each colorThemes as colors}
- <button
- type="button"
- class="hover:opacity-80 mr-2 w-10 h-10 bg-gradient-to-r {colors.color1} {colors.color2} rounded-xl border-2 {selectedTheme ===
- colors.theme
- ? 'border-black'
- : ''}"
- on:click={() => handleThemeClick(colors)}
- on:keydown={() => handleThemeClick(colors)}
- />
- {/each}
-</div> -->
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,14 +1,19 @@
<script>
- import { Icon, Toggle } from '$comp';
+ import { Icon, LocaleSelector } from '$comp';
import { t } from '$lib/translations';
import { success, failure } from '$lib/utils';
import { page } from '$app/stores';
export let user, rates;
- const fiats = Object.keys(rates);
+ let fiats = Object.keys(rates);
</script>
<div>
+ <label for="language" class="font-bold block mb-1">{$t('user.settings.locale')}</label>
+ <LocaleSelector style="select-styles block py-3 w-full" />
+</div>
+
+<div>
<label for="currency" class="font-bold block mb-1">{$t('user.settings.localCurrency')}</label>
<select name="currency" class="select-styles block py-3 w-full" value={user.currency}>
{#each fiats as fiat}
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
index 2d880d44..ef4b9fba 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
+++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
@@ -11,6 +11,7 @@
export let user, submit;
let settingPin, setting2fa, disabling2fa;
+ let password, revealPassword;
let token = '';
let pinCode = [];
let verifyCode = [];
@@ -18,8 +19,10 @@
let verifying = false;
let old;
+ let togglePassword = () => (revealPassword = !revealPassword);
+
let startVerifying = () => {
- if (pinCode.join("").length < 6) return;
+ if (pinCode.join('').length < 6) return;
verifying = true;
};
@@ -81,7 +84,7 @@
if (user.haspin) {
try {
pin = null;
- await tick();
+ await tick();
submit.click();
} catch (e) {
console.log(e);
@@ -125,9 +128,27 @@
};
</script>
-<div>
- <input type="hidden" name="newpin" value={pin} />
+<input type="hidden" name="newpin" value={pin} />
+<input type="hidden" name="confirm" bind:value={password} />
+
+<div class="relative mb-5">
+ <label for="password" class="block font-bold block mb-1">{$t('user.settings.newPassword')}</label>
+ {#if revealPassword}
+ <input name="password" type="text" bind:value={password} />
+ {:else}
+ <input name="password" type="password" bind:value={password} />
+ {/if}
+ <button
+ type="button"
+ on:click={togglePassword}
+ on:keydown={togglePassword}
+ class="absolute right-5 top-11"
+ >
+ <Icon icon={revealPassword ? 'eye' : 'eye-off'} />
+ </button>
+</div>
+<div>
<span class="font-bold mb-1"
>{verifying ? $t('user.settings.verifyPIN') : $t('user.settings.securityPIN')}</span
>
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');
}
}