mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: Implement email OTP authentication
This commit is contained in:
@@ -15,9 +15,33 @@
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let mail: string, pass: string, disabled: boolean;
|
||||
let showPasswordLogin: boolean = false;
|
||||
|
||||
export let data;
|
||||
|
||||
$: showPasswordLogin = pass && pass.length > 0;
|
||||
|
||||
async function sendSignInCode() {
|
||||
try {
|
||||
disabled = true;
|
||||
// use createEmailToken for sign in with code
|
||||
const sessionToken = await sdk.forConsole.account.createEmailToken({
|
||||
userId: 'unique',
|
||||
email: mail
|
||||
});
|
||||
|
||||
await goto(
|
||||
`${base}/login/email-otp?email=${encodeURIComponent(mail)}&userId=${sessionToken.userId}`
|
||||
);
|
||||
} catch (error) {
|
||||
disabled = false;
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
try {
|
||||
disabled = true;
|
||||
@@ -52,7 +76,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// no specific redirect, so redirect will happen through invalidating the account
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
} catch (error) {
|
||||
disabled = false;
|
||||
@@ -92,22 +115,27 @@
|
||||
<Unauthenticated coupon={data?.couponData} campaign={data?.campaign}>
|
||||
<svelte:fragment slot="title">Sign in</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
<Form onSubmit={login}>
|
||||
<Form onSubmit={showPasswordLogin ? login : sendSignInCode}>
|
||||
<Layout.Stack>
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Email"
|
||||
autofocus={true}
|
||||
required={true}
|
||||
bind:value={mail} />
|
||||
<InputPassword
|
||||
id="password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
required={true}
|
||||
required={false}
|
||||
bind:value={pass} />
|
||||
<Button fullWidth submit {disabled}>Sign in</Button>
|
||||
|
||||
{#if showPasswordLogin}
|
||||
<Button fullWidth submit {disabled}>Sign in</Button>
|
||||
{:else}
|
||||
<Button fullWidth submit {disabled}>Get sign in code</Button>
|
||||
{/if}
|
||||
|
||||
{#if isCloud}
|
||||
<span class="with-separators eyebrow-heading-3">or</span>
|
||||
<Button secondary fullWidth on:click={onGithubLogin} {disabled}>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { InputDigits, Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Unauthenticated } from '$lib/layout';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { redirectTo } from '$routes/store';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { Layout, Typography, Link } from '@appwrite.io/pink-svelte';
|
||||
import { page } from '$app/state';
|
||||
|
||||
let otpCode: string = '';
|
||||
let disabled: boolean = false;
|
||||
let email: string = '';
|
||||
let userId: string = '';
|
||||
|
||||
export let data;
|
||||
|
||||
// Get email and userId from URL params
|
||||
$: email = page.url.searchParams.get('email') || '';
|
||||
$: userId = page.url.searchParams.get('userId') || '';
|
||||
|
||||
async function verifyOTP() {
|
||||
try {
|
||||
disabled = true;
|
||||
// Use createSession with the userId from createEmailToken
|
||||
await sdk.forConsole.account.createSession({ userId: userId, secret: otpCode });
|
||||
|
||||
if ($user) {
|
||||
trackEvent(Submit.AccountLogin, { mfa_used: 'none' });
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Successfully logged in.'
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.couponData?.code) {
|
||||
trackEvent(Submit.AccountCreate, {
|
||||
campaign_name: data?.couponData?.code,
|
||||
email: email,
|
||||
name: $user?.name
|
||||
});
|
||||
await goto(`${base}/apply-credit?code=${data?.couponData?.code}`);
|
||||
return;
|
||||
}
|
||||
if (data?.campaign?.$id) {
|
||||
await goto(`${base}/apply-credit?campaign=${data.campaign?.$id}`);
|
||||
return;
|
||||
}
|
||||
if ($redirectTo) {
|
||||
window.location.href = $redirectTo;
|
||||
return;
|
||||
}
|
||||
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
} catch (error) {
|
||||
disabled = false;
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.AccountLogin);
|
||||
}
|
||||
}
|
||||
|
||||
async function resendCode() {
|
||||
try {
|
||||
disabled = true;
|
||||
const sessionToken = await sdk.forConsole.account.createEmailToken({
|
||||
userId: 'unique',
|
||||
email: email
|
||||
});
|
||||
userId = sessionToken.userId;
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'New sign in code sent to your email.'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
} finally {
|
||||
disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Enter your sign-in code - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Unauthenticated coupon={data?.couponData} campaign={data?.campaign} align="center">
|
||||
<svelte:fragment slot="title">Enter your sign-in code</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
<Layout.Stack gap="l" justifyContent="center" alignContent="center" alignItems="center">
|
||||
<Typography.Text align="center">
|
||||
We sent a 6-digit code to {email}
|
||||
</Typography.Text>
|
||||
|
||||
<form on:submit|preventDefault={verifyOTP}>
|
||||
<Layout.Stack align="center">
|
||||
<InputDigits bind:value={otpCode} required />
|
||||
<Button submit {disabled} class="verify-button">Verify</Button>
|
||||
</Layout.Stack>
|
||||
</form>
|
||||
|
||||
<Typography.Text align="center">
|
||||
Didn't get it?
|
||||
<Link.Button on:click={resendCode} {disabled}>Resend code</Link.Button>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Unauthenticated>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { base } from '$app/paths';
|
||||
import type { Campaign } from '$lib/stores/campaigns';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ url }) => {
|
||||
if (!url.searchParams.has('email')) {
|
||||
redirect(303, `${base}/login`);
|
||||
}
|
||||
|
||||
if (url.searchParams.has('code')) {
|
||||
const code = url.searchParams.get('code');
|
||||
let campaign: Campaign;
|
||||
try {
|
||||
const couponData = await sdk.forConsole.billing.getCoupon(code);
|
||||
if (couponData.campaign) {
|
||||
campaign = await sdk.forConsole.billing.getCampaign(couponData.campaign);
|
||||
return {
|
||||
couponData,
|
||||
campaign
|
||||
};
|
||||
} else redirect(303, `${base}/login`);
|
||||
} catch (e) {
|
||||
redirect(303, `${base}/login`);
|
||||
}
|
||||
}
|
||||
if (url.searchParams.has('campaign')) {
|
||||
const campaignId = url.searchParams.get('campaign');
|
||||
let campaign: Campaign;
|
||||
try {
|
||||
campaign = await sdk.forConsole.billing.getCampaign(campaignId);
|
||||
return { campaign };
|
||||
} catch (e) {
|
||||
redirect(303, `${base}/login`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
Reference in New Issue
Block a user