Open dsebastien opened 1 week ago
Other:
import React, { useRef, useState } from 'react';
import { Head, useForm } from '@inertiajs/react';
import AuthenticationCard from '@/Components/AuthenticationCard';
import AuthenticationCardLogo from '@/Components/AuthenticationCardLogo';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { FormEventHandler } from 'react';
const TwoFactorConfirmation: React.FC = () => {
const [recovery, setRecovery] = useState(false);
const form = useForm({
code: '',
recovery_code: '',
});
const recoveryCodeInput = useRef<HTMLInputElement>(null);
const codeInput = useRef<HTMLInputElement>(null);
const toggleRecovery = async () => {
setRecovery(!recovery);
if (recovery) {
recoveryCodeInput.current?.focus();
form.setData('code', '');
} else {
codeInput.current?.focus();
form.setData('recovery_code', '');
}
};
const submit: FormEventHandler = (e) => {
e.preventDefault();
form.post(route('two-factor.login'));
};
return (
<>
<Head title="Two-factor Confirmation" />
<AuthenticationCard logo={<AuthenticationCardLogo />}>
<div className="mb-4 text-sm text-gray-600 dark:text-gray-400">
{recovery ? (
<>Please confirm access to your account by entering one of your emergency recovery codes.</>
) : (
<>Please confirm access to your account by entering the authentication code provided by your authenticator application.</>
)}
</div>
<form onSubmit={submit}>
{!recovery ? (
<>
<InputLabel htmlFor="code" value="Code" />
<TextInput
id="code"
ref={codeInput}
value={form.data.code}
onChange={(e) => form.setData('code', e.target.value)}
type="text"
inputMode="numeric"
className="block w-full mt-1"
autoFocus
autoComplete="one-time-code"
/>
<InputError className="mt-2" message={form.errors.code} />
</>
) : (
<>
<InputLabel htmlFor="recovery_code" value="Recovery Code" />
<TextInput
id="recovery_code"
ref={recoveryCodeInput}
value={form.data.recovery_code}
onChange={(e) => form.setData('recovery_code', e.target.value)}
type="text"
className="block w-full mt-1"
autoComplete="one-time-code"
/>
<InputError className="mt-2" message={form.errors.recovery_code} />
</>
)}
<div className="flex items-center justify-end mt-4">
<button
type="button"
className="text-sm text-gray-600 underline cursor-pointer dark:text-gray-400 hover:text-gray-900"
onClick={toggleRecovery}
>
{recovery ? <>Use an authentication code</> : <>Use a recovery code</>}
</button>
<PrimaryButton className={`ms-4 ${form.processing ? 'opacity-25' : ''}`} disabled={form.processing}>
Log in
</PrimaryButton>
</div>
</form>
</AuthenticationCard>
</>
);
};
export default TwoFactorConfirmation;
Vue version:
<script lang="ts" setup>
import { nextTick, ref } from 'vue';
import { Head, useForm } from '@inertiajs/vue3';
import AuthenticationCard from '@/Components/AuthenticationCard.vue';
import AuthenticationCardLogo from '@/Components/AuthenticationCardLogo.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
const recovery = ref(false);
const form = useForm({
code: '',
recovery_code: '',
});
const recoveryCodeInput = ref(null);
const codeInput = ref(null);
const toggleRecovery = async () => {
recovery.value ^= true;
await nextTick();
if (recovery.value) {
recoveryCodeInput.value.focus();
form.code = '';
} else {
codeInput.value.focus();
form.recovery_code = '';
}
};
const submit = () => {
form.post(route('two-factor.login'));
};
</script>
<template>
<Head title="Two-factor Confirmation" />
<AuthenticationCard>
<template #logo>
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-gray-600">
<template v-if="!recovery">
Please confirm access to your account by entering the authentication code provided by your authenticator application.
</template>
<template v-else> Please confirm access to your account by entering one of your emergency recovery codes. </template>
</div>
<form @submit.prevent="submit">
<div v-if="!recovery">
<InputLabel for="code" value="Code" />
<TextInput
id="code"
ref="codeInput"
v-model="form.code"
type="text"
inputmode="numeric"
class="mt-1 block w-full"
autofocus
autocomplete="one-time-code"
/>
<InputError class="mt-2" :message="form.errors.code" />
</div>
<div v-else>
<InputLabel for="recovery_code" value="Recovery Code" />
<TextInput
id="recovery_code"
ref="recoveryCodeInput"
v-model="form.recovery_code"
type="text"
class="mt-1 block w-full"
autocomplete="one-time-code"
/>
<InputError class="mt-2" :message="form.errors.recovery_code" />
</div>
<div class="flex items-center justify-end mt-4">
<button type="button" class="text-sm text-gray-600 hover:text-gray-900 underline cursor-pointer" @click.prevent="toggleRecovery">
<template v-if="!recovery"> Use a recovery code </template>
<template v-else> Use an authentication code </template>
</button>
<PrimaryButton class="ms-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing"> Log in </PrimaryButton>
</div>
</form>
</AuthenticationCard>
</template>
Profile/Partials/TwoFactoryAuthenticationForm.tsx:
import { router } from '@inertiajs/core';
import { useForm } from '@inertiajs/react';
import axios from 'axios';
import classNames from 'classnames';
import React, { useState } from 'react';
import ActionSection from '@/Components/ActionSection';
import ConfirmsPassword from '@/Components/ConfirmsPassword';
import DangerButton from '@/Components/DangerButton';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import SecondaryButton from '@/Components/SecondaryButton';
import TextInput from '@/Components/TextInput';
import useTypedPage from '@/Hooks/useTypedPage';
interface Props {
requiresConfirmation: boolean;
}
export default function TwoFactorAuthenticationForm({ requiresConfirmation }: Props) {
const page = useTypedPage();
const [enabling, setEnabling] = useState(false);
const [disabling, setDisabling] = useState(false);
const [qrCode, setQrCode] = useState<string | null>(null);
const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);
const [confirming, setConfirming] = useState(false);
const [setupKey, setSetupKey] = useState<string | null>(null);
const confirmationForm = useForm({
code: '',
});
const twoFactorEnabled = !enabling && page.props?.auth?.user?.two_factor_enabled;
function enableTwoFactorAuthentication() {
setEnabling(true);
router.post(
'/user/two-factor-authentication',
{},
{
preserveScroll: true,
onSuccess() {
return Promise.all([showQrCode(), showSetupKey(), showRecoveryCodes()]);
},
onFinish() {
setEnabling(false);
setConfirming(requiresConfirmation);
},
},
);
}
function showSetupKey() {
return axios.get('/user/two-factor-secret-key').then((response) => {
setSetupKey(response.data.secretKey);
});
}
function confirmTwoFactorAuthentication() {
confirmationForm.post('/user/confirmed-two-factor-authentication', {
preserveScroll: true,
preserveState: true,
errorBag: 'confirmTwoFactorAuthentication',
onSuccess: () => {
setConfirming(false);
setQrCode(null);
setSetupKey(null);
},
});
}
function showQrCode() {
return axios.get('/user/two-factor-qr-code').then((response) => {
setQrCode(response.data.svg);
});
}
function showRecoveryCodes() {
return axios.get('/user/two-factor-recovery-codes').then((response) => {
setRecoveryCodes(response.data);
});
}
function regenerateRecoveryCodes() {
axios.post('/user/two-factor-recovery-codes').then(() => {
showRecoveryCodes();
});
}
function disableTwoFactorAuthentication() {
setDisabling(true);
router.delete('/user/two-factor-authentication', {
preserveScroll: true,
onSuccess() {
setDisabling(false);
setConfirming(false);
},
});
}
return (
<ActionSection
title={'Two Factor Authentication'}
description={'Add additional security to your account using two factor authentication.'}
>
{(() => {
if (twoFactorEnabled && !confirming) {
return <h3 className="text-lg font-medium text-gray-900">You have enabled two factor authentication.</h3>;
}
if (confirming) {
return <h3 className="text-lg font-medium text-gray-900">Finish enabling two factor authentication.</h3>;
}
return <h3 className="text-lg font-medium text-gray-900">You have not enabled two factor authentication.</h3>;
})()}
<div className="mt-3 max-w-xl text-sm text-gray-600">
<p>
When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve
this token from your phone's Google Authenticator application.
</p>
</div>
{twoFactorEnabled || confirming ? (
<div>
{qrCode ? (
<div>
<div className="mt-4 max-w-xl text-sm text-gray-600">
{confirming ? (
<p className="font-semibold">
To finish enabling two factor authentication, scan the following QR code using your phone's authenticator application or
enter the setup key and provide the generated OTP code.
</p>
) : (
<p>
Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application or
enter the setup key.
</p>
)}
</div>
<div className="mt-4" dangerouslySetInnerHTML={{ __html: qrCode || '' }} />
{setupKey && (
<div className="mt-4 max-w-xl text-sm text-gray-600">
<p className="font-semibold">
Setup Key: <span dangerouslySetInnerHTML={{ __html: setupKey || '' }} />
</p>
</div>
)}
{confirming && (
<div className="mt-4">
<InputLabel htmlFor="code" value="Code" />
<TextInput
id="code"
type="text"
name="code"
className="block mt-1 w-1/2"
inputMode="numeric"
autoFocus={true}
autoComplete="one-time-code"
value={confirmationForm.data.code}
onChange={(e) => confirmationForm.setData('code', e.currentTarget.value)}
/>
<InputError message={confirmationForm.errors.code} className="mt-2" />
</div>
)}
</div>
) : null}
{recoveryCodes.length > 0 && !confirming ? (
<div>
<div className="mt-4 max-w-xl text-sm text-gray-600">
<p className="font-semibold">
Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two
factor authentication device is lost.
</p>
</div>
<div className="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg">
{recoveryCodes.map((code) => (
<div key={code}>{code}</div>
))}
</div>
</div>
) : null}
</div>
) : null}
<div className="mt-5">
{twoFactorEnabled || confirming ? (
<div>
{confirming ? (
<ConfirmsPassword onConfirm={confirmTwoFactorAuthentication}>
<PrimaryButton className={classNames('mr-3', { 'opacity-25': enabling })} disabled={enabling}>
Confirm
</PrimaryButton>
</ConfirmsPassword>
) : null}
{recoveryCodes.length > 0 && !confirming ? (
<ConfirmsPassword onConfirm={regenerateRecoveryCodes}>
<SecondaryButton className="mr-3">Regenerate Recovery Codes</SecondaryButton>
</ConfirmsPassword>
) : null}
{recoveryCodes.length === 0 && !confirming ? (
<ConfirmsPassword onConfirm={showRecoveryCodes}>
<SecondaryButton className="mr-3">Show Recovery Codes</SecondaryButton>
</ConfirmsPassword>
) : null}
{confirming ? (
<ConfirmsPassword onConfirm={disableTwoFactorAuthentication}>
<SecondaryButton className={classNames('mr-3', { 'opacity-25': disabling })} disabled={disabling}>
Cancel
</SecondaryButton>
</ConfirmsPassword>
) : (
<ConfirmsPassword onConfirm={disableTwoFactorAuthentication}>
<DangerButton className={classNames({ 'opacity-25': disabling })} disabled={disabling}>
Disable
</DangerButton>
</ConfirmsPassword>
)}
</div>
) : (
<div>
<ConfirmsPassword onConfirm={enableTwoFactorAuthentication}>
<PrimaryButton type="button" className={classNames({ 'opacity-25': enabling })} disabled={enabling}>
Enable
</PrimaryButton>
</ConfirmsPassword>
</div>
)}
</div>
</ActionSection>
);
}
Other implementation:
import React, { useEffect, useRef, useState, FormEventHandler } from 'react';
import { useForm, usePage, router } from '@inertiajs/react';
import axios from 'axios';
import ActionSection from '@/Components/ActionSection';
import ConfirmsPassword from '@/Components/ConfirmsPassword';
import DangerButton from '@/Components/DangerButton';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import SecondaryButton from '@/Components/SecondaryButton';
import TextInput from '@/Components/TextInput';
interface TwoFactorAuthenticationFormProps {
requiresConfirmation: boolean;
}
const TwoFactorAuthenticationForm: React.FC<TwoFactorAuthenticationFormProps> = ({ requiresConfirmation }) => {
const { props: page } = usePage<any>();
const [enabling, setEnabling] = useState(false);
const [confirming, setConfirming] = useState(false);
const [disabling, setDisabling] = useState(false);
const [qrCode, setQrCode] = useState<string | null>(null);
const [setupKey, setSetupKey] = useState<string | null>(null);
const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);
const confirmationForm = useForm({
code: '',
});
const twoFactorEnabled = !enabling && page.auth.user?.two_factor_enabled;
useEffect(() => {
if (!twoFactorEnabled) {
confirmationForm.reset();
confirmationForm.clearErrors();
}
}, [twoFactorEnabled]);
const enableTwoFactorAuthentication = (e: any) => {
e.preventDefault();
setEnabling(true);
router.post(
route('two-factor.enable'),
{},
{
preserveScroll: true,
onSuccess: () => {
Promise.all([showQrCode(), showSetupKey(), showRecoveryCodes()]);
},
onFinish: () => {
setEnabling(false);
setConfirming(requiresConfirmation);
},
},
);
};
const showQrCode = () => {
return axios.get(route('two-factor.qr-code')).then((response) => {
setQrCode(response.data.svg);
});
};
const showSetupKey = () => {
return axios.get(route('two-factor.secret-key')).then((response) => {
setSetupKey(response.data.secretKey);
});
};
const showRecoveryCodes = () => {
return axios.get(route('two-factor.recovery-codes')).then((response) => {
setRecoveryCodes(response.data);
});
};
const confirmTwoFactorAuthentication: FormEventHandler = (e) => {
e.preventDefault();
confirmationForm.post(route('two-factor.confirm'), {
errorBag: 'confirmTwoFactorAuthentication',
preserveScroll: true,
preserveState: true,
onSuccess: () => {
setConfirming(false);
setQrCode(null);
setSetupKey(null);
},
});
};
const regenerateRecoveryCodes: FormEventHandler = (e) => {
e.preventDefault();
axios.post(route('two-factor.recovery-codes')).then(() => showRecoveryCodes());
};
const disableTwoFactorAuthentication = () => {
setDisabling(true);
router.delete(route('two-factor.disable'), {
preserveScroll: true,
onSuccess: () => {
setDisabling(false);
setConfirming(false);
},
});
};
return (
<ActionSection
title="Two Factor Authentication"
description="Add additional security to your account using two factor authentication."
content={
<>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
{twoFactorEnabled && !confirming && 'You have enabled two factor authentication.'}
{twoFactorEnabled && confirming && 'Finish enabling two factor authentication.'}
{!twoFactorEnabled && 'You have not enabled two factor authentication.'}
</h3>
<div className="max-w-xl mt-3 text-sm text-gray-600 dark:text-gray-400">
<p>
When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may
retrieve this token from your phone's Google Authenticator application.
</p>
</div>
{twoFactorEnabled && (
<div>
{qrCode && (
<>
<div className="max-w-xl mt-4 text-sm text-gray-600 dark:text-gray-400">
<p className="font-semibold">
{confirming
? "To finish enabling two factor authentication, scan the following QR code using your phone's authenticator application or enter the setup key and provide the generated OTP code."
: "Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application or enter the setup key."}
</p>
</div>
<div
className="inline-block p-2 mt-4 bg-white"
dangerouslySetInnerHTML={{
__html: qrCode,
}}
/>
{setupKey && (
<div className="max-w-xl mt-4 text-sm text-gray-600 dark:text-gray-400">
<p className="font-semibold">
Setup Key: <span>{setupKey}</span>
</p>
</div>
)}
{confirming && (
<div className="mt-4">
<InputLabel htmlFor="code" value="Code" />
<TextInput
id="code"
value={confirmationForm.data.code}
onChange={(e) => confirmationForm.setData('code', e.target.value)}
type="text"
name="code"
className="block w-1/2 mt-1"
inputMode="numeric"
autoFocus
autoComplete="one-time-code"
onKeyUp={(e) => e.key === 'Enter' && confirmTwoFactorAuthentication(e)}
/>
<InputError message={confirmationForm.errors.code} className="mt-2" />
</div>
)}
</>
)}
{recoveryCodes.length > 0 && !confirming && (
<>
<div className="max-w-xl mt-4 text-sm text-gray-600 dark:text-gray-400">
<p className="font-semibold">
Store these recovery codes in a secure password manager. They can be used to recover access to your account if your
two factor authentication device is lost.
</p>
</div>
<div className="grid max-w-xl gap-1 px-4 py-4 mt-4 font-mono text-sm bg-gray-100 rounded-lg dark:bg-gray-900 dark:text-gray-100">
{recoveryCodes.map((code) => (
<div key={code}>{code}</div>
))}
</div>
</>
)}
</div>
)}
<div className="mt-5">
{!twoFactorEnabled ? (
<ConfirmsPassword onConfirmed={enableTwoFactorAuthentication}>
<PrimaryButton type="button" className={enabling ? 'opacity-25' : ''} disabled={enabling}>
Enable
</PrimaryButton>
</ConfirmsPassword>
) : (
<>
{confirming && (
<ConfirmsPassword onConfirmed={confirmTwoFactorAuthentication}>
<PrimaryButton type="button" className={`me-3 ${enabling ? 'opacity-25' : ''}`} disabled={enabling}>
Confirm
</PrimaryButton>
</ConfirmsPassword>
)}
{recoveryCodes.length > 0 && !confirming && (
<ConfirmsPassword onConfirmed={regenerateRecoveryCodes}>
<SecondaryButton className="me-3">Regenerate Recovery Codes</SecondaryButton>
</ConfirmsPassword>
)}
{recoveryCodes.length === 0 && !confirming && (
<ConfirmsPassword onConfirmed={showRecoveryCodes}>
<SecondaryButton className="me-3">Show Recovery Codes</SecondaryButton>
</ConfirmsPassword>
)}
{confirming && (
<ConfirmsPassword onConfirmed={disableTwoFactorAuthentication}>
<SecondaryButton className={disabling ? 'opacity-25' : ''} disabled={disabling}>
Cancel
</SecondaryButton>
</ConfirmsPassword>
)}
{!confirming && (
<ConfirmsPassword onConfirmed={disableTwoFactorAuthentication}>
<DangerButton className={disabling ? 'opacity-25' : ''} disabled={disabling}>
Disable
</DangerButton>
</ConfirmsPassword>
)}
</>
)}
</div>
</>
}
></ActionSection>
);
};
export default TwoFactorAuthenticationForm;
Vue implementation:
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import { router, useForm, usePage } from '@inertiajs/vue3';
import ActionSection from '@/Components/ActionSection.vue';
import ConfirmsPassword from '@/Components/ConfirmsPassword.vue';
import DangerButton from '@/Components/DangerButton.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
const props = defineProps({
requiresConfirmation: Boolean,
});
const page = usePage();
const enabling = ref(false);
const confirming = ref(false);
const disabling = ref(false);
const qrCode = ref(null);
const setupKey = ref(null);
const recoveryCodes = ref([]);
const confirmationForm = useForm({
code: '',
});
const twoFactorEnabled = computed(() => !enabling.value && page.props.auth.user?.two_factor_enabled);
watch(twoFactorEnabled, () => {
if (!twoFactorEnabled.value) {
confirmationForm.reset();
confirmationForm.clearErrors();
}
});
const enableTwoFactorAuthentication = () => {
enabling.value = true;
router.post(
route('two-factor.enable'),
{},
{
preserveScroll: true,
onSuccess: () => Promise.all([showQrCode(), showSetupKey(), showRecoveryCodes()]),
onFinish: () => {
enabling.value = false;
confirming.value = props.requiresConfirmation;
},
},
);
};
const showQrCode = () => {
return axios.get(route('two-factor.qr-code')).then((response) => {
qrCode.value = response.data.svg;
});
};
const showSetupKey = () => {
return axios.get(route('two-factor.secret-key')).then((response) => {
setupKey.value = response.data.secretKey;
});
};
const showRecoveryCodes = () => {
return axios.get(route('two-factor.recovery-codes')).then((response) => {
recoveryCodes.value = response.data;
});
};
const confirmTwoFactorAuthentication = () => {
confirmationForm.post(route('two-factor.confirm'), {
errorBag: 'confirmTwoFactorAuthentication',
preserveScroll: true,
preserveState: true,
onSuccess: () => {
confirming.value = false;
qrCode.value = null;
setupKey.value = null;
},
});
};
const regenerateRecoveryCodes = () => {
axios.post(route('two-factor.recovery-codes')).then(() => showRecoveryCodes());
};
const disableTwoFactorAuthentication = () => {
disabling.value = true;
router.delete(route('two-factor.disable'), {
preserveScroll: true,
onSuccess: () => {
disabling.value = false;
confirming.value = false;
},
});
};
</script>
<template>
<ActionSection>
<template #title> Two Factor Authentication </template>
<template #description> Add additional security to your account using two factor authentication. </template>
<template #content>
<h3 v-if="twoFactorEnabled && !confirming" class="text-lg font-medium text-gray-900">You have enabled two factor authentication.</h3>
<h3 v-else-if="twoFactorEnabled && confirming" class="text-lg font-medium text-gray-900">
Finish enabling two factor authentication.
</h3>
<h3 v-else class="text-lg font-medium text-gray-900">You have not enabled two factor authentication.</h3>
<div class="mt-3 max-w-xl text-sm text-gray-600">
<p>
When two factor authentication is enabled, you will be prompted for a secure, random token during authentication. You may retrieve
this token from your phone's Google Authenticator application.
</p>
</div>
<div v-if="twoFactorEnabled">
<div v-if="qrCode">
<div class="mt-4 max-w-xl text-sm text-gray-600">
<p v-if="confirming" class="font-semibold">
To finish enabling two factor authentication, scan the following QR code using your phone's authenticator application or enter
the setup key and provide the generated OTP code.
</p>
<p v-else>
Two factor authentication is now enabled. Scan the following QR code using your phone's authenticator application or enter the
setup key.
</p>
</div>
<div class="mt-4 p-2 inline-block bg-white" v-html="qrCode" />
<div v-if="setupKey" class="mt-4 max-w-xl text-sm text-gray-600">
<p class="font-semibold">Setup Key: <span v-html="setupKey"></span></p>
</div>
<div v-if="confirming" class="mt-4">
<InputLabel for="code" value="Code" />
<TextInput
id="code"
v-model="confirmationForm.code"
type="text"
name="code"
class="block mt-1 w-1/2"
inputmode="numeric"
autofocus
autocomplete="one-time-code"
@keyup.enter="confirmTwoFactorAuthentication"
/>
<InputError :message="confirmationForm.errors.code" class="mt-2" />
</div>
</div>
<div v-if="recoveryCodes.length > 0 && !confirming">
<div class="mt-4 max-w-xl text-sm text-gray-600">
<p class="font-semibold">
Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor
authentication device is lost.
</p>
</div>
<div class="grid gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 rounded-lg">
<div v-for="code in recoveryCodes" :key="code">
{{ code }}
</div>
</div>
</div>
</div>
<div class="mt-5">
<div v-if="!twoFactorEnabled">
<ConfirmsPassword @confirmed="enableTwoFactorAuthentication">
<PrimaryButton type="button" :class="{ 'opacity-25': enabling }" :disabled="enabling"> Enable </PrimaryButton>
</ConfirmsPassword>
</div>
<div v-else>
<ConfirmsPassword @confirmed="confirmTwoFactorAuthentication">
<PrimaryButton v-if="confirming" type="button" class="me-3" :class="{ 'opacity-25': enabling }" :disabled="enabling">
Confirm
</PrimaryButton>
</ConfirmsPassword>
<ConfirmsPassword @confirmed="regenerateRecoveryCodes">
<SecondaryButton v-if="recoveryCodes.length > 0 && !confirming" class="me-3"> Regenerate Recovery Codes </SecondaryButton>
</ConfirmsPassword>
<ConfirmsPassword @confirmed="showRecoveryCodes">
<SecondaryButton v-if="recoveryCodes.length === 0 && !confirming" class="me-3"> Show Recovery Codes </SecondaryButton>
</ConfirmsPassword>
<ConfirmsPassword @confirmed="disableTwoFactorAuthentication">
<SecondaryButton v-if="confirming" :class="{ 'opacity-25': disabling }" :disabled="disabling"> Cancel </SecondaryButton>
</ConfirmsPassword>
<ConfirmsPassword @confirmed="disableTwoFactorAuthentication">
<DangerButton v-if="!confirming" :class="{ 'opacity-25': disabling }" :disabled="disabling"> Disable </DangerButton>
</ConfirmsPassword>
</div>
</div>
</template>
</ActionSection>
</template>
Also, add this in Profile/Show.tsx, before logout other browser sessions form
{jetstream.canManageTwoFactorAuthentication && (
<>
<div className="mt-10 sm:mt-0">
<TwoFactorAuthenticationForm requiresConfirmation={confirmsTwoFactorAuthentication} />
</div>
<SectionBorder />
</>
)}
Pages of Jetstream: Pages/Auth/TwoFactorChallenge.tsx