diff options
23 files changed, 395 insertions, 262 deletions
diff --git a/package.json b/package.json index b6b32ee6..908ec0bf 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "screenfull": "^6.0.2", "svelte": "^3.55.0", "svelte-outclick": "^3.3.3", - "svelte-pincode": "^2.2.0", "svelte-preprocess": "^4.10.7", "svelte-virtual-scroll-list": "^1.1.0", "sveltekit-i18n": "^2.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d072ee80..4f0d2ef2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,7 +26,6 @@ specifiers: screenfull: ^6.0.2 svelte: ^3.55.0 svelte-outclick: ^3.3.3 - svelte-pincode: ^2.2.0 svelte-preprocess: ^4.10.7 svelte-virtual-scroll-list: ^1.1.0 sveltekit-i18n: ^2.2.2 @@ -66,7 +65,6 @@ devDependencies: screenfull: 6.0.2 svelte: 3.55.0 svelte-outclick: [email protected] - svelte-pincode: 2.2.0 svelte-preprocess: 4.10.7_qs7rgzvahok4cbkjoy5bgypde4 svelte-virtual-scroll-list: [email protected] sveltekit-i18n: [email protected] @@ -2406,10 +2404,6 @@ packages: svelte: 3.55.0 dev: true - /svelte-pincode/2.2.0: - resolution: {integrity: sha512-zT2jOHffZqQ1dLmFAojfkdssKr8HGq1uSZQK1TCwD1/Mjrw2EGuKppQOh0q2pobm8AAGhvk7LKHnFtRUKsz80A==} - dev: true - /svelte-preprocess/4.10.7_qs7rgzvahok4cbkjoy5bgypde4: resolution: {integrity: sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==} engines: {node: '>= 9.11.2'} diff --git a/src/components/Feed.svelte b/src/components/Feed.svelte index ab757fa1..7de087a7 100644 --- a/src/components/Feed.svelte +++ b/src/components/Feed.svelte @@ -4,7 +4,7 @@ import { Event } from '$comp'; import { browser } from '$app/environment'; import VirtualScroll from 'svelte-virtual-scroll-list'; - import { failure } from '$lib/utils'; + import { fail } from '$lib/utils'; import { sign, send } from '$lib/nostr'; export let events; @@ -34,7 +34,7 @@ events = events; } catch (e) { console.log(e, e.stack); - failure('Problem posting event'); + fail('Problem posting event'); } }; diff --git a/src/components/Password.svelte b/src/components/Password.svelte index fd39441c..a84c0b16 100644 --- a/src/components/Password.svelte +++ b/src/components/Password.svelte @@ -1,6 +1,6 @@ <script> import { Icon } from '$comp'; - import { focus, failure, post } from '$lib/utils'; + import { focus, fail, post } from '$lib/utils'; import { password as pw, passwordPrompt } from '$lib/store'; export let user; @@ -17,7 +17,7 @@ $passwordPrompt = false; $pw = password; } catch (e) { - failure('Invalid password, try again'); + fail('Invalid password, try again'); } }; </script> @@ -51,7 +51,7 @@ <button type="button" on:click={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-5" + class="absolute right-5 top-6" > <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> </button> diff --git a/src/components/Pin.svelte b/src/components/Pin.svelte index 7225ac37..0db831ff 100644 --- a/src/components/Pin.svelte +++ b/src/components/Pin.svelte @@ -1,24 +1,28 @@ <script> + import { t } from '$lib/translations'; import { focus } from '$lib/utils'; - import { pin } from '$lib/store'; + import { Pinpad } from '$comp'; - let pinCode = ''; - let pinInput; + export let value = ''; + export let title = $t('user.settings.verifyPIN'); + export let cancel = () => (pin = ''); - let cancel = () => ($pin = '000000'); + let pin = ''; - $: pinCode.length > 5 && ($pin = pinCode); + $: update(value); + let update = (v) => (pin = v) || console.log('updated', v, pin); + + $: pin.toString().length > 5 && (value = pin.toString()); </script> -<div class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20"> - <div class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5"> - <h1 class="text-center text-2xl font-semibold">Please enter your PIN</h1> - <input bind:value={pinCode} use:focus /> +<div class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> + <div class="relative top-40 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5"> + <h1 class="text-center text-2xl font-semibold">{title}</h1> + <Pinpad bind:v={pin} {cancel} /> <div class="w-full flex"> <button - type="submit" class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" - on:click={cancel} + on:click|preventDefault={cancel} > <div class="my-auto">Cancel</div> </button> diff --git a/src/components/Pinpad.svelte b/src/components/Pinpad.svelte new file mode 100644 index 00000000..bf3778f7 --- /dev/null +++ b/src/components/Pinpad.svelte @@ -0,0 +1,81 @@ +<script> + import { Left, Icon } from '$comp'; + import { pin, selectedRate } from '$lib/store'; + import { f, s, focus, post, warning, sat, sats } from '$lib/utils'; + import { t } from '$lib/translations'; + + export let v; + export let cancel; + + let arrow = '<'; + let loading = false; + + const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', '<']; + + const handleInput = (n) => { + v = (v || '').toString(); + if (n === 'C') return (v = ''); + if (n === '<') return (v = v.length > 1 ? Math.floor(v / 10) : ''); + if (v.length > 5) n = ''; + if (parseInt(n) >= 0) v += n; + }; + + let validate = (e) => { + let k = e.key || e.code; + if (k === 'Escape') cancel(); + if (e.key.length <= 1 && !(e.metaKey || e.ctrlKey || e.altKey) && !(k >= '0' && k <= '9')) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + }; + + let hide = true; +</script> + +<div class="flex justify-center items-center"> + <div class="space-y-5"> + <div class="text-5xl md:text-6xl font-semibold relative w-[300px]"> + {#if hide} + <input + on:keydown={validate} + use:focus + bind:value={v} + type="password" + class="outline-none border-0 text-center py-0" + pattern="[0-9]+" + /> + {:else} + <input + on:keydown={validate} + bind:value={v} + class="outline-none border-0 text-center py-0" + type="text" + pattern="[0-9]+" + /> + {/if} + <button type="button" on:click={() => (hide = !hide)} class="absolute -right-5 top-5"> + <Icon icon={hide ? 'eye-off' : 'eye'} style="w-8" /> + </button> + </div> + + <div class="grid grid-cols-3 gap-2 w-[300px] mx-auto grayscale"> + {#each numPad as value} + {#if value === arrow} + <button + type="button" + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center hover:opacity-80" + on:click={() => handleInput(value)} + > + <Left /> + </button> + {:else} + <button + type="button" + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white hover:opacity-80" + on:click={() => handleInput(value)}>{value}</button + > + {/if} + {/each} + </div> + </div> +</div> diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte index 7ac2fbb3..6c099178 100644 --- a/src/components/RegisterOrLogin.svelte +++ b/src/components/RegisterOrLogin.svelte @@ -1,28 +1,32 @@ <script> import { applyAction, deserialize } from '$app/forms'; import { tick } from 'svelte'; - import { Pincode, PincodeInput } from 'svelte-pincode'; import { fly } from 'svelte/transition'; import { enhance } from '$app/forms'; - import { Icon, Spinner } from '$comp'; - import { focus } from '$lib/utils'; + import { Pin, Icon, Spinner } from '$comp'; + import { focus, fail } from '$lib/utils'; import { password, loginRedirect } from '$lib/store'; import { t } from '$lib/translations'; import { page } from '$app/stores'; import { generate } from '$lib/nostr'; import { invalidateAll } from '$app/navigation'; - let token, tokenInput, formElement; + export let form; + export let pageID; + + let token, formElement; let code = []; let redirect; $password = undefined; $: need2fa = form?.message === '2fa'; - $: if (need2fa) tokenInput && tick().then(tokenInput.focusFirstInput); - - export let form; - export let pageID; + let cancel = () => (need2fa = false); + $: if (form?.message === '2fa' && form.token === token) { + token = ''; + console.log('TOKEN', token); + fail('Invalid token, try again'); + } let username, email, btn; @@ -66,6 +70,10 @@ } </script> +{#if need2fa} + <Pin bind:value={token} title="Enter 2FA Code" {cancel} /> +{/if} + <div class="pt-10"> <div class="w-[243px] mx-auto mb-10"> <a href="/"> @@ -75,126 +83,97 @@ <div class="flex justify-center items-center"> <div class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"> - {#if need2fa} - <h1 class="text-2xl font-bold text-center">Enter 2FA Code</h1> + <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1> - {#if form?.token} - <div class="text-red-600 text-center" in:fly>Invalid token, try again</div> - {/if} - - <form class="space-y-5" {action} method="POST" use:enhance> - <input type="hidden" name="username" value={username} /> - <input type="hidden" name="password" value={$password} /> - <input type="hidden" name="token" value={token} /> - - <div> - <Pincode bind:code bind:value={token} bind:this={tokenInput}> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - </Pincode> - </div> - <button - bind:this={btn} - type="submit" - class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" - >{$t('login.' + pageID)} - </button> - </form> - {:else} - <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1> + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} - {#if form?.error} - <div class="text-red-600 text-center" in:fly> - {form.error} - </div> - {/if} + <form class="space-y-5" on:submit|preventDefault={handleSubmit} method="POST"> + <input type="hidden" name="loginRedirect" value={$loginRedirect} /> + <input type="hidden" name="token" value={token} /> - <form class="space-y-5" on:submit|preventDefault={handleSubmit} method="POST"> - <input type="hidden" name="loginRedirect" value={$loginRedirect} /> + <div> + <label for="username" class="font-semibold">{$t('login.username')}</label> + <input + name="username" + type="text" + required + class="bg-primary" + bind:value={username} + use:focus + autocapitalize="none" + /> + </div> - <div> - <label for="username" class="font-semibold">{$t('login.username')}</label> + <div class="relative"> + <label for="password" class="block font-semibold">{$t('login.password')}</label> + {#if revealPassword} <input - name="username" + name="password" type="text" required class="bg-primary" - bind:value={username} - use:focus + bind:value={$password} autocapitalize="none" /> - </div> - - <div class="relative"> - <label for="password" class="block font-semibold">{$t('login.password')}</label> - {#if revealPassword} - <input - name="password" - type="text" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - /> - {:else} - <input - name="password" - type="password" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - /> - {/if} - <button - type="button" - on:click={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-10" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> - </button> - </div> - - {#if pageID === 'register'} - <p class="text-secondary text-sm"> - {$t('login.passwordRecommendation')} - </p> {:else} - <!-- TODO --> - <!-- <div class="flex justify-end items-center"> --> - <!-- <a href="/forgot" class="underline underline-offset-4 text-black text-sm" --> - <!-- >{$t('login.forgotUserOrPassword')}</a --> - <!-- > --> - <!-- </div> --> + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> {/if} - <button - type="submit" - class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" - disabled={loading} + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" > - {#if loading} - <Spinner /> - {:else} - {$t('login.' + pageID)} - {/if} + <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> </button> - </form> + </div> - <p class="text-secondary text-center font-medium"> - {pageID === 'register' ? $t('login.haveAccount') : $t('login.noAccount')} - <a - href={pageID === 'register' ? '/login' : '/register'} - class="block md:inline text-black underline underline-offset-4 hover:opacity-80" - > - {pageID === 'register' ? $t('login.signIn') : $t('login.register')} - </a> - </p> - {/if} + {#if pageID === 'register'} + <p class="text-secondary text-sm"> + {$t('login.passwordRecommendation')} + </p> + {:else} + <!-- TODO --> + <!-- <div class="flex justify-end items-center"> --> + <!-- <a href="/forgot" class="underline underline-offset-4 text-black text-sm" --> + <!-- >{$t('login.forgotUserOrPassword')}</a --> + <!-- > --> + <!-- </div> --> + {/if} + + <button + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + bind:this={btn} + > + {#if loading} + <Spinner /> + {:else} + {$t('login.' + pageID)} + {/if} + </button> + </form> + + <p class="text-secondary text-center font-medium"> + {pageID === 'register' ? $t('login.haveAccount') : $t('login.noAccount')} + <a + href={pageID === 'register' ? '/login' : '/register'} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {pageID === 'register' ? $t('login.signIn') : $t('login.register')} + </a> + </p> </div> </div> </div> diff --git a/src/components/index.js b/src/components/index.js index 18a821ff..ab54e09f 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,3 +1,4 @@ +export { default as Pinpad } from './Pinpad.svelte'; export { default as Balance } from './Balance.svelte'; export { default as Lnurlp } from './Lnurlp.svelte'; export { default as Lnurlw } from './Lnurlw.svelte'; diff --git a/src/lib/nostr.js b/src/lib/nostr.js index 35d843b2..de19b477 100644 --- a/src/lib/nostr.js +++ b/src/lib/nostr.js @@ -3,7 +3,7 @@ import * as ecc from 'tiny-secp256k1'; import { generateMnemonic, entropyToMnemonic, mnemonicToEntropy, mnemonicToSeedSync } from 'bip39'; import { Buffer } from 'buffer'; import { bech32m } from 'bech32'; -import { bip32, failure, wait, post, stretch } from '$lib/utils'; +import { bip32, fail, wait, post, stretch } from '$lib/utils'; import { calculateId, signId } from 'nostr'; import { password as pw, passwordPrompt, pin, loginRedirect } from '$lib/store'; import { goto, invalidate } from '$app/navigation'; diff --git a/src/lib/utils.js b/src/lib/utils.js index d0b0b1c2..3a644e1f 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -101,7 +101,7 @@ export const warning = (m) => { } }); }; -export const failure = (m) => { +export const fail = (m) => { toast.pop(); toast.push(m, { theme: { diff --git a/src/locales/en.json b/src/locales/en.json index eb0039ac..b39571f6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -241,7 +241,7 @@ "autoLock": "Auto-Lock Timer", "autoLockDescription": "Your account will go into a secure mode, requiring you to re-enter your credentials after a specified time:", "twofa": "2-Factor Authentication", - "twofaDescription": "Add an extra level of security to your device by enableing 2-factor authentication.", + "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.", "twofaSetup": "Set up 2FA", "twofaDisable": "Disable 2FA", "select": "Select", diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 40c73ea4..1cf525a0 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -4,8 +4,11 @@ export let data; let { events, subject, user } = data; + let v; </script> <div class="flex justify-center lg:justify-start"> - <Balance {user} /> + {#if user?.id === subject.id} + <Balance {user} /> + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/messages/+page.svelte b/src/routes/(app)/[username]/(profile)/messages/+page.svelte index 00960534..db7b1ced 100644 --- a/src/routes/(app)/[username]/(profile)/messages/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/messages/+page.svelte @@ -5,7 +5,7 @@ import { scale } from 'svelte/transition'; import { enhance } from '$app/forms'; import { Icon } from '$comp'; - import { back, failure, focus } from '$lib/utils'; + import { back, fail, focus } from '$lib/utils'; import { sign, send, encrypt, decrypt } from '$lib/nostr'; import { messages, password } from '$lib/store'; import { tick, onMount } from 'svelte'; @@ -64,7 +64,7 @@ sent = true; } catch (e) { - failure('Failed to send message'); + fail('Failed to send message'); delete $messages[user.pubkey][subject.pubkey][event.id]; $messages = $messages; } diff --git a/src/routes/(app)/[username]/dashboard/+page.svelte b/src/routes/(app)/[username]/dashboard/+page.svelte index d136943e..499034e6 100644 --- a/src/routes/(app)/[username]/dashboard/+page.svelte +++ b/src/routes/(app)/[username]/dashboard/+page.svelte @@ -1,6 +1,6 @@ <script> import { enhance } from '$app/forms'; - import { f, s, post, failure, sat, sats } from '$lib/utils'; + import { f, s, post, fail, sat, sats } from '$lib/utils'; import { tick } from 'svelte'; import { Avatar, Icon } from '$comp'; import { rate, password, selectedRate } from '$lib/store'; diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index 6336062a..bd72592f 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -1,5 +1,5 @@ <script> - import { failure, post, wait } from '$lib/utils'; + import { fail, post, wait } from '$lib/utils'; import { Buffer } from 'buffer'; import { onMount } from 'svelte'; import { bech32m } from 'bech32'; @@ -41,9 +41,9 @@ } catch (e) { $pin = ''; if (e.message?.startsWith('Pin')) { - failure('Wrong pin, try again'); + fail('Wrong pin, try again'); } else { - failure('Failed to generate keys'); + fail('Failed to generate keys'); goto('/'); } } diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 1e8b31ce..5638a22b 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -5,7 +5,7 @@ import { Icon, Spinner, Pin } from '$comp'; import { t } from '$lib/translations'; - import { failure, post, success } from '$lib/utils'; + import { fail, post, success } from '$lib/utils'; import { avatar, banner, password, pin } from '$lib/store'; import { upload } from '$lib/upload'; import { page } from '$app/stores'; @@ -34,7 +34,7 @@ $: form?.success && success('Settings saved!'); $: if (form?.message?.startsWith('Pin')) { - failure('Wrong pin, try again'); + fail('Wrong pin, try again'); $pin = ''; } @@ -98,7 +98,7 @@ applyAction(result); } catch (e) { console.log(e); - failure('Something went wrong'); + fail('Something went wrong'); } loading = false; @@ -109,7 +109,7 @@ </script> {#if loaded && user.haspin && $pin?.length !== 6} - <Pin /> + <Pin bind:value={$pin} /> {/if} <form diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 94dc8c88..472f4d07 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -1,7 +1,7 @@ <script> import { Icon, LocaleSelector, Toggle } from '$comp'; import { t } from '$lib/translations'; - import { success, failure } from '$lib/utils'; + import { success, fail } from '$lib/utils'; import { page } from '$app/stores'; export let user, rates; diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte index cf969f1c..eb3b405e 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte @@ -1,80 +1,52 @@ <script> import { tick } from 'svelte'; import { t } from '$lib/translations'; - import { Icon, Qr } from '$comp'; - import { Pincode, PincodeInput } from 'svelte-pincode'; - import { post, success, failure } from '$lib/utils'; + import { Pin, Icon, Qr } from '$comp'; + import { post, success, fail } from '$lib/utils'; import { pin as current } from '$lib/store'; import { invalidate } from '$app/navigation'; import { page } from '$app/stores'; export let user, submit; - let settingPin, setting2fa, disabling2fa; + let pin, verify; + let settingPin, setting2fa, disabling2fa, confirming2fa; let password, revealPassword; let token = ''; - let pinCode = []; - let verifyCode = []; - let tokenInput, pinInput, verifyInput; - let verifying = false; let old; let togglePassword = () => (revealPassword = !revealPassword); - let startVerifying = () => { - if (pinCode.join('').length < 6) return; - verifying = true; - }; - - $: pin = pinCode.join(''); - let otp; - $: pinChange($current); - let pinChange = async (pin) => { - try { - otp = await post('/otpsecret', { pin }); - } catch (e) { - console.log(e); - } - }; let locked; - $: verifyCode.join('').length > 5 && checkPin(); + $: verifying = pin?.length > 5; + $: verify && checkPin(pin); let checkPin = async () => { old = $current; - if (locked) return; - locked = true; - - let verify = verifyCode.join(''); - - pinCode = ['', '', '', '', '', '']; - verifyCode = ['', '', '', '', '', '']; - settingPin = false; try { if (pin.length > 5 && pin === verify) { user.haspin = true; $current = pin; + pin = ''; + verify = ''; submit.click(); + settingPin = false; + verifying = false; } else { - failure('Pin mismatch, try again'); + fail('Pin mismatch, try again'); + pin = ''; + verify = ''; + verifying = false; settingPin = true; - pinInput.focusFirstInput(); } } catch (e) { - failure('Problem setting PIN'); + console.log(e); + fail('Problem setting PIN'); } - - verifying = false; - - await new Promise((r) => setTimeout(r, 500)); - locked = false; }; - $: if (verifying) verifyInput && tick().then(verifyInput.focusFirstInput); - $: if (settingPin) pinInput && tick().then(pinInput.focusFirstInput); - $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput?.focusFirstInput); - let reset = () => { token = ''; return true; @@ -88,15 +60,35 @@ submit.click(); } catch (e) { console.log(e); - failure('Failed to disable pin'); + fail('Failed to disable pin'); } } else { settingPin = true; } }; - let toggleEnabling = () => reset() && (setting2fa = !setting2fa); - let toggleDisabling = () => reset() && (disabling2fa = !disabling2fa); + let startEnabling2fa = async () => { + reset(); + try { + if (!otp) otp = await post('/otpsecret', { pin: $current }); + } catch (e) { + console.log(e); + } + + setting2fa = true; + }; + + let startDisabling2fa = () => (disabling2fa = true); + let startConfirming2fa = () => (confirming2fa = true); + let cancel = () => { + pin = null; + token = null; + verifying = false; + settingPin = false; + setting2fa = false; + confirming2fa = false; + disabling2fa = false; + }; $: enable2fa(token); let enable2fa = async (twoFa) => { @@ -105,10 +97,10 @@ await post('/enable2fa', { token }); success('2FA enabled'); user.twofa = 1; - toggleEnabling(); + cancel(); } } catch (e) { - failure('Failed to enable 2FA, try again'); + fail('Failed to enable 2FA, try again'); } }; @@ -118,12 +110,12 @@ if (disabling2fa && token.length === 6) { await post('/disable2fa', { token }); success('2FA disabled'); - user.twofa = 0; - toggleDisabling(); + delete user.twofa; + disabling2fa = false; } } catch (e) { console.log(e); - failure('Failed to disable 2FA, try again'); + fail('Failed to disable 2FA, try again'); } }; </script> @@ -155,31 +147,10 @@ <p class="text-secondary mb-1"> {$t('user.settings.securityPINDescription')} </p> - {#if settingPin} - <div class="flex my-4"> - <div class="mx-auto"> - <div class:hidden={verifying}> - <Pincode on:complete={startVerifying} bind:code={pinCode} bind:this={pinInput}> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - </Pincode> - </div> - <div class:hidden={!verifying}> - <Pincode bind:code={verifyCode} bind:this={verifyInput}> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - </Pincode> - </div> - </div> - </div> + {#if verifying} + <Pin bind:value={verify} {cancel} /> + {:else if settingPin} + <Pin bind:value={pin} title={$t('user.settings.setPIN')} {cancel} /> {:else} <button type="button" class="primary" on:click={togglePin} ><Icon icon="lock" style="mr-1" /> @@ -193,6 +164,7 @@ <p class="text-secondary mb-4"> {$t('user.settings.twofaDescription')} </p> + {#if setting2fa} <a href={otp.uri}> <Qr src={otp.qr} /> @@ -203,40 +175,25 @@ <b>{otp.secret}</b> </div> - <div class="text-center my-4"> - {$t('user.settings.oneTimeCode')}<br /> - <Pincode bind:value={token} bind:this={tokenInput}> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - </Pincode> - </div> - {:else if disabling2fa} - <div class="text-center my-4"> - {$t('user.settings.oneTimeCode')}<br /> - <Pincode bind:value={token} bind:this={tokenInput}> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - </Pincode> - </div> + <button type="button" class="primary" on:click={startConfirming2fa}> + <Icon icon="mobile" style="mr-1" /> + Confirm + </button> {:else if user.twofa} - <button type="button" class="primary" on:click={toggleDisabling}> + <button type="button" class="primary" on:click={startDisabling2fa}> <Icon icon="mobile" style="mr-1" /> {$t('user.settings.twofaDisable')} </button> {:else} - <button type="button" class="primary" on:click={toggleEnabling}> + <button type="button" class="primary" on:click={startEnabling2fa}> <Icon icon="mobile" style="mr-1" /> {$t('user.settings.twofaSetup')} </button> {/if} + + {#if confirming2fa || disabling2fa} + <Pin bind:value={token} title="Enter 2FA Code" {cancel} /> + {/if} </div> <div> diff --git a/src/routes/(app)/disable2fa/+server.js b/src/routes/(app)/disable2fa/+server.js index 13fcb2b9..b2bb07ec 100644 --- a/src/routes/(app)/disable2fa/+server.js +++ b/src/routes/(app)/disable2fa/+server.js @@ -1,4 +1,4 @@ -import { error } from '@sveltejs/kit'; +import { error, json } from '@sveltejs/kit'; import { auth, post } from '$lib/utils'; export async function POST({ cookies, request }) { @@ -6,7 +6,7 @@ export async function POST({ cookies, request }) { let body = await request.json(); let res = await post('/disable2fa', body, auth(cookies)); - return new Response(JSON.stringify(res)); + return json(res); } catch (e) { throw error(500, 'Problem disabling 2fa'); } diff --git a/src/routes/(app)/otpsecret/+server.js b/src/routes/(app)/otpsecret/+server.js index 31dbcec1..7d5c1ad9 100644 --- a/src/routes/(app)/otpsecret/+server.js +++ b/src/routes/(app)/otpsecret/+server.js @@ -9,7 +9,7 @@ export async function POST({ cookies, request }) { 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); + if (e.message.includes('pin')) throw e; throw new error(500, 'Problem saving secret'); } } diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index e3d8091f..46f5e56a 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -6,7 +6,7 @@ import { browser } from '$app/environment'; import { t } from '$lib/translations'; import { AppHeader, Avatar, Icon, Spinner } from '$comp'; - import { back, failure } from '$lib/utils'; + import { back, fail } from '$lib/utils'; import { format, parseISO } from 'date-fns'; export let data; @@ -90,7 +90,7 @@ <h1 class="px-3 md:px-0 text-xl font-semibold mt-10">{$t('user.send.contacts')}</h1> <div> {#each contacts as c} - <a href={`/${c.username}`}> + <a href={`/${c.username}/receive`}> <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> <div class="flex"> <div> diff --git a/static/icons/eye-off.svg b/static/icons/eye-off.svg index 88504dd3..0e52158a 100644 --- a/static/icons/eye-off.svg +++ b/static/icons/eye-off.svg @@ -1,3 +1,72 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M3 3L6.58916 6.58916M21 21L17.4112 17.4112M13.8749 18.8246C13.2677 18.9398 12.6411 19 12.0005 19C7.52281 19 3.73251 16.0571 2.45825 12C2.80515 10.8955 3.33851 9.87361 4.02143 8.97118M9.87868 9.87868C10.4216 9.33579 11.1716 9 12 9C13.6569 9 15 10.3431 15 12C15 12.8284 14.6642 13.5784 14.1213 14.1213M9.87868 9.87868L14.1213 14.1213M9.87868 9.87868L6.58916 6.58916M14.1213 14.1213L6.58916 6.58916M14.1213 14.1213L17.4112 17.4112M6.58916 6.58916C8.14898 5.58354 10.0066 5 12.0004 5C16.4781 5 20.2684 7.94291 21.5426 12C20.8357 14.2507 19.3545 16.1585 17.4112 17.4112" stroke="#858A92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> + +<svg + fill="#000000" + width="32" + height="25.580051" + viewBox="0 0 20.480001 16.371233" + version="1.1" + id="svg679" + sodipodi:docname="eye-off.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs683" /> + <sodipodi:namedview + id="namedview681" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="5.9750523" + inkscape:cx="-49.20459" + inkscape:cy="-11.966422" + inkscape:window-width="1920" + inkscape:window-height="1048" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="svg679" /> + <title + id="title667">ionicons-v5-i</title> + <path + d="M 17.749699,16.371232 A 0.67923596,0.67923596 0 0 1 17.267158,16.17113 L 2.2488725,1.1528484 A 0.6826492,0.6826492 0 0 1 3.2139683,0.18775264 L 18.232249,15.206035 a 0.6826492,0.6826492 0 0 1 -0.48255,1.165197 z" + id="path669" + style="stroke-width:0.0426655" /> + <path + d="m 10.226055,13.640635 c -1.7701939,0 -3.4772444,-0.523933 -5.0737896,-1.557294 C 3.698649,11.144699 2.3900958,9.8003072 1.3678283,8.2007742 v -0.0034 C 2.2185799,6.9783796 3.1503966,5.9475793 4.1513306,5.1164533 a 0.08533115,0.08533115 0 0 0 0.006,-0.1254362 L 3.3074318,4.1423984 A 0.08533115,0.08533115 0 0 0 3.1918084,4.1372784 C 2.128582,5.0332561 1.1417271,6.1323208 0.24447016,7.4191149 a 1.3618851,1.3618851 0 0 0 -0.027304,1.5163343 C 1.3439644,10.698817 2.7941672,12.184006 4.4103395,13.229739 c 1.8196865,1.17885 3.7780366,1.776168 5.8157435,1.776168 a 10.202619,10.202619 0 0 0 3.234055,-0.536733 0.08533115,0.08533115 0 0 0 0.03288,-0.141223 l -0.920729,-0.920723 a 0.1706623,0.1706623 0 0 0 -0.163409,-0.04266 8.7379096,8.7379096 0 0 1 -2.182772,0.276046 z" + id="path671" + style="stroke-width:0.0426655" /> + <path + d="M 20.260142,7.4370604 C 19.131212,5.6911855 17.666508,4.20813 16.024732,3.1478908 14.208462,1.9737337 12.203178,1.3529497 10.226055,1.3529497 a 9.6995917,9.6995917 0 0 0 -3.1952251,0.5473996 0.08533115,0.08533115 0 0 0 -0.032,0.1412224 l 0.9194432,0.9194432 a 0.1706623,0.1706623 0 0 0 0.165542,0.042664 8.2267762,8.2267762 0 0 1 2.1422389,-0.2854329 c 1.736063,0 3.437995,0.5303332 5.058002,1.5786262 1.480928,0.9557086 2.804841,2.2988208 3.829668,3.882567 a 0.00554652,0.00554652 0 0 1 0,0.0068 13.257048,13.257048 0 0 1 -2.735722,3.1030668 0.08533115,0.08533115 0 0 0 -0.0064,0.125864 l 0.849044,0.848619 a 0.08533115,0.08533115 0 0 0 0.115199,0.0055 14.655199,14.655199 0 0 0 2.928569,-3.3483948 1.3738315,1.3738315 0 0 0 -0.004,-1.4839086 z" + id="path673" + style="stroke-width:0.0426655" /> + <path + d="m 10.240561,4.0835467 a 4.0907753,4.0907753 0 0 0 -0.9117629,0.102397 0.08533115,0.08533115 0 0 0 -0.04266,0.1442097 L 14.08985,9.1325902 a 0.08533115,0.08533115 0 0 0 0.144216,-0.04266 4.0958952,4.0958952 0 0 0 -3.9935,-5.0063789 z" + id="path675" + style="stroke-width:0.0426655" /> + <path + d="m 6.3912733,7.2262927 a 0.08533115,0.08533115 0 0 0 -0.1442097,0.042664 4.0958952,4.0958952 0 0 0 4.9065414,4.9065413 0.08533115,0.08533115 0 0 0 0.04266,-0.14421 z" + id="path677" + style="stroke-width:0.0426655" /> + <metadata + id="metadata796"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:title>ionicons-v5-i</dc:title> + </cc:Work> + </rdf:RDF> + </metadata> </svg> diff --git a/static/icons/eye.svg b/static/icons/eye.svg index 9cf04eb9..3869fee6 100644 --- a/static/icons/eye.svg +++ b/static/icons/eye.svg @@ -1,4 +1,50 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="#858A92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> -<path d="M2.45825 12C3.73253 7.94288 7.52281 5 12.0004 5C16.4781 5 20.2684 7.94291 21.5426 12C20.2684 16.0571 16.4781 19 12.0005 19C7.52281 19 3.73251 16.0571 2.45825 12Z" stroke="#858A92" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> + +<svg + fill="#000000" + height="21.76594" + width="32" + version="1.1" + id="Capa_1" + viewBox="0 0 8.98196 6.1094" + xml:space="preserve" + sodipodi:docname="eye.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs339" /><sodipodi:namedview + id="namedview337" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="1.05625" + inkscape:cx="-300.11834" + inkscape:cy="1.4201183" + inkscape:window-width="1920" + inkscape:window-height="1048" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="Capa_1" /> +<g + id="g334" + transform="matrix(0.04,0,0,0.04,2e-5,-1.43628)"> + <path + d="m 223.476,108.41 c -1.779,-2.96 -44.35,-72.503 -111.202,-72.503 -66.852,0 -109.423,69.543 -111.202,72.503 -1.43,2.378 -1.43,5.351 0,7.729 1.779,2.96 44.35,72.503 111.202,72.503 66.852,0 109.423,-69.543 111.202,-72.503 1.43,-2.378 1.43,-5.351 0,-7.729 z m -111.202,65.232 c -49.925,0 -86.176,-47.359 -95.808,-61.374 9.614,-14.032 45.761,-61.36 95.808,-61.36 49.925,0 86.176,47.359 95.808,61.374 -9.614,14.031 -45.761,61.36 -95.808,61.36 z" + id="path328" /> + <path + d="m 112.274,61.731 c -27.869,0 -50.542,22.674 -50.542,50.543 0,27.868 22.673,50.54 50.542,50.54 27.868,0 50.541,-22.672 50.541,-50.54 0,-27.869 -22.672,-50.543 -50.541,-50.543 z m 0,86.083 c -19.598,0 -35.542,-15.943 -35.542,-35.54 0,-19.599 15.944,-35.543 35.542,-35.543 19.598,0 35.541,15.944 35.541,35.543 0,19.597 -15.943,35.54 -35.541,35.54 z" + id="path330" /> + <path + d="m 112.274,92.91 c -10.702,0 -19.372,8.669 -19.372,19.364 0,10.694 8.67,19.363 19.372,19.363 10.703,0 19.373,-8.669 19.373,-19.363 0,-10.695 -8.67,-19.364 -19.373,-19.364 z" + id="path332" /> +</g> </svg> |
