aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2022-10-21 13:29:04 -0700
committerAdam Soltys <[email protected]>2022-10-21 13:29:52 -0700
commita005ef51ee46618a1875dee4559ccddfec41dfcd (patch)
treeea30cefa854f45160d88f01417a98707a26873a6
parentdbf440fc4f553e1ac3d02c7941377a67ffb0cd8d (diff)
downloadcoinos-ui-a005ef51ee46618a1875dee4559ccddfec41dfcd.tar.gz
coinos-ui-a005ef51ee46618a1875dee4559ccddfec41dfcd.zip
move 2fa to separate step
-rw-r--r--src/components/RegisterOrLogin.svelte195
-rw-r--r--src/lib/utils.js13
-rw-r--r--src/routes/login/+page.server.js8
-rw-r--r--src/routes/login/+page.svelte1
4 files changed, 130 insertions, 87 deletions
diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte
index 7f254400..bf2d2084 100644
--- a/src/components/RegisterOrLogin.svelte
+++ b/src/components/RegisterOrLogin.svelte
@@ -1,4 +1,6 @@
<script>
+ import { tick } from 'svelte';
+ import { Pincode, PincodeInput } from 'svelte-pincode';
import { fly } from 'svelte/transition';
import { enhance } from '$app/forms';
import { Icon } from '$comp';
@@ -6,13 +8,21 @@
import { loginRedirect } from '$lib/store';
import { t } from '$lib/translations';
+ let token, tokenInput;
+ let code = [];
+
+ $: need2fa = form?.message === '2fa';
+ $: if (need2fa) tokenInput && tick().then(tokenInput.focusFirstInput);
+
export let form;
export let pageID;
- let username,
- password,
- email,
- twofa = '';
+ let username, password, email, submit;
+
+ $: update(form);
+ let update = (form) => form && ({ username, password } = form);
+
+ $: token && token?.length === 6 && tick().then(() => submit.click());
let revealPassword = false;
@@ -31,101 +41,122 @@
<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]">
- <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1>
+ {#if need2fa}
+ <h1 class="text-2xl font-bold text-center">Enter 2FA Code</h1>
- {#if form?.error}
- <div class="text-red-600 text-center" in:fly>
- {form.error}
- </div>
- {/if}
+ {#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={submit}
+ 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>
- <form class="space-y-5" {action} method="POST" use:enhance>
- {#if $loginRedirect}
- <input type="hidden" name="loginRedirect" value={$loginRedirect} />
+ {#if form?.error}
+ <div class="text-red-600 text-center" in:fly>
+ {form.error}
+ </div>
{/if}
- <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>
+ <form class="space-y-5" {action} method="POST" use:enhance>
+ {#if $loginRedirect}
+ <input type="hidden" name="loginRedirect" value={$loginRedirect} />
+ {/if}
- <div class="relative">
- <label for="password" class="block font-semibold">{$t('login.password')}</label>
- {#if revealPassword}
+ <div>
+ <label for="username" class="font-semibold">{$t('login.username')}</label>
<input
- name="password"
+ name="username"
type="text"
required
class="bg-primary"
- bind:value={password}
+ bind:value={username}
+ use:focus
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>
+ </div>
- {#if pageID === 'signIn'}
- <div>
- <label for="token" class="font-semibold"
- >{$t('login.token')}
- <span class="text-secondary">({$t('login.optional')})</span></label
+ <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"
>
- <input name="token" type="text" class="bg-primary" bind:value={twofa} />
+ <Icon icon={revealPassword ? 'eye' : 'eye-off'} />
+ </button>
</div>
- {/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}
+ {#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"
- >{$t('login.' + pageID)}
- </button>
- </form>
+ <button
+ type="submit"
+ class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80"
+ >{$t('login.' + pageID)}
+ </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>
+ <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}
</div>
</div>
</div>
diff --git a/src/lib/utils.js b/src/lib/utils.js
index 76ce5077..7cb09c61 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -36,6 +36,7 @@ export const post = (url, body, headers) => {
return fetch(base + url, { method: 'POST', body: JSON.stringify(body), headers })
.then((r) => r.text())
.then((body) => {
+ console.log('BODY', body);
try {
body = JSON.parse(body);
} catch (e) {
@@ -108,7 +109,17 @@ export const info = (m) => {
export const login = async (user, cookies) => {
let maxAge = 30 * 24 * 60 * 60;
- let { token } = await post('/login', user);
+ let res = await fetch(base + '/login', {
+ method: 'POST',
+ body: JSON.stringify(user),
+ headers: { 'content-type': 'application/json', accept: 'application/json' }
+ });
+ if (res.status === 401) {
+ let text = await res.text();
+ if (text.startsWith('2fa')) throw new Error('2fa');
+ }
+
+ let { token } = await res.json();
if (!token) throw new Error('Login failed');
let expires = new Date();
diff --git a/src/routes/login/+page.server.js b/src/routes/login/+page.server.js
index aa8797f9..b46af49e 100644
--- a/src/routes/login/+page.server.js
+++ b/src/routes/login/+page.server.js
@@ -4,13 +4,13 @@ import { login } from '$lib/utils';
export const actions = {
default: async ({ cookies, request }) => {
let form = Object.fromEntries(await request.formData());
- let { username, password, loginRedirect } = form;
- let user = { username, password };
+ let { username, password, token, loginRedirect } = form;
+ let user = { username, password, token };
try {
- await login(user, cookies);
+ let r = await login(user, cookies);
} catch (e) {
- return invalid(400, { error: 'Login failed' });
+ return invalid(400, { error: 'Login failed', message: e.message, ...form });
}
throw redirect(303, loginRedirect || `/${user.username}/dashboard`);
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
index cf7cb517..75998cd0 100644
--- a/src/routes/login/+page.svelte
+++ b/src/routes/login/+page.svelte
@@ -1,5 +1,6 @@
<script>
import { RegisterOrLogin } from '$comp';
+
export let form;
</script>