mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
cc3f3a7141
- Use !== instead of != for null comparisons - Update MFA status indicator from Pill to Badge component
139 lines
5.4 KiB
Svelte
139 lines
5.4 KiB
Svelte
<script context="module" lang="ts">
|
|
let inputDigitFields: InputDigits;
|
|
|
|
export async function verify(challenge: Models.MfaChallenge, code: string) {
|
|
try {
|
|
if (challenge === null) {
|
|
challenge = await sdk.forConsole.account.createMfaChallenge(
|
|
AuthenticationFactor.Totp
|
|
);
|
|
}
|
|
await sdk.forConsole.account.updateMfaChallenge(challenge.$id, code);
|
|
await invalidate(Dependencies.ACCOUNT);
|
|
trackEvent(Submit.AccountLogin, { mfa_used: true });
|
|
} catch (error) {
|
|
inputDigitFields?.clearInputsAndRefocus();
|
|
trackError(error, Submit.AccountLogin);
|
|
throw error;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { invalidate } from '$app/navigation';
|
|
import { Button, InputDigits, InputText } from '$lib/elements/forms';
|
|
import { sdk } from '$lib/stores/sdk';
|
|
import { Dependencies } from '$lib/constants';
|
|
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
|
import { AuthenticationFactor, type Models } from '@appwrite.io/console';
|
|
import { addNotification } from '$lib/stores/notifications';
|
|
import { Icon, Typography } from '@appwrite.io/pink-svelte';
|
|
import { IconChatAlt, IconDeviceMobile, IconMail } from '@appwrite.io/pink-icons-svelte';
|
|
|
|
export let factors: Models.MfaFactors & { recoveryCode: boolean };
|
|
/** If true, the form will be submitted automatically when the code is entered. */
|
|
export let showVerifyButton: boolean = true;
|
|
export let disabled: boolean = false;
|
|
export let challenge: Models.MfaChallenge;
|
|
export let code: string;
|
|
|
|
let challengeType: AuthenticationFactor;
|
|
|
|
const enabledFactors = Object.entries(factors).filter(([_, enabled]) => enabled);
|
|
|
|
async function createChallenge(factor: AuthenticationFactor) {
|
|
disabled = true;
|
|
challengeType = factor;
|
|
try {
|
|
challenge = await sdk.forConsole.account.createMfaChallenge(factor);
|
|
} catch (error) {
|
|
addNotification({
|
|
type: 'error',
|
|
message: error.message
|
|
});
|
|
} finally {
|
|
disabled = false;
|
|
}
|
|
}
|
|
|
|
onMount(async () => {
|
|
const enabledNonRecoveryFactors = enabledFactors.filter(
|
|
([factor, _]) => factor !== 'recoveryCode'
|
|
);
|
|
if (enabledNonRecoveryFactors.length === 1) {
|
|
if (factors.phone) {
|
|
createChallenge(AuthenticationFactor.Phone);
|
|
} else if (factors.email) {
|
|
createChallenge(AuthenticationFactor.Email);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{#if challengeType === AuthenticationFactor.Recoverycode}
|
|
<Typography.Text align="center">
|
|
Enter below one of the recovery codes you received when enabling MFA for this account.
|
|
</Typography.Text>
|
|
<InputText id="recovery-code" bind:value={code} required autofocus />
|
|
{:else}
|
|
{#if factors.totp && (challengeType === AuthenticationFactor.Totp || challengeType === null)}
|
|
<Typography.Text align="center"
|
|
>Enter below a 6-digit one-time code generated by your authentication app.</Typography.Text>
|
|
{:else if challengeType === AuthenticationFactor.Email}
|
|
<Typography.Text align="center"
|
|
>A 6-digit verification code was sent to your email, enter it below.</Typography.Text>
|
|
{:else if challengeType === AuthenticationFactor.Phone}
|
|
<Typography.Text align="center"
|
|
>A 6-digit verification code was sent to your phone, enter it below.</Typography.Text>
|
|
{/if}
|
|
<InputDigits bind:value={code} required autofocus />
|
|
{/if}
|
|
{#if showVerifyButton}
|
|
<Button fullWidth submit {disabled}>Verify</Button>
|
|
{/if}
|
|
{#if enabledFactors.length > 1}
|
|
<span class="with-separators eyebrow-heading-3">or</span>
|
|
<div class="u-flex-vertical u-gap-8">
|
|
{#if factors.totp && challengeType !== null && challengeType !== AuthenticationFactor.Totp}
|
|
<Button
|
|
secondary
|
|
fullWidth
|
|
{disabled}
|
|
on:click={() => createChallenge(AuthenticationFactor.Totp)}>
|
|
<Icon icon={IconDeviceMobile} slot="start" size="s" />
|
|
Authenticator app
|
|
</Button>
|
|
{/if}
|
|
{#if factors.email && challengeType !== AuthenticationFactor.Email}
|
|
<Button
|
|
secondary
|
|
fullWidth
|
|
{disabled}
|
|
on:click={() => createChallenge(AuthenticationFactor.Email)}>
|
|
<Icon icon={IconMail} slot="start" size="s" />
|
|
Email verification
|
|
</Button>
|
|
{/if}
|
|
{#if factors.phone && challengeType !== AuthenticationFactor.Phone}
|
|
<Button
|
|
secondary
|
|
fullWidth
|
|
{disabled}
|
|
on:click={() => createChallenge(AuthenticationFactor.Phone)}>
|
|
<Icon icon={IconChatAlt} slot="start" size="s" />
|
|
Phone verification
|
|
</Button>
|
|
{/if}
|
|
{#if factors.recoveryCode && challengeType !== AuthenticationFactor.Recoverycode}
|
|
<Button
|
|
text
|
|
fullWidth
|
|
{disabled}
|
|
on:click={() => createChallenge(AuthenticationFactor.Recoverycode)}>
|
|
<span class="text">Use recovery code</span>
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
{/if}
|