vat_wms/resources/js/Pages/Profile/Partials/TwoFactorAuthenticationForm.tsx
2025-05-13 12:32:55 +02:00

294 lines
9.3 KiB
TypeScript

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 dark:text-gray-100">
You have enabled two factor authentication.
</h3>
);
}
if (confirming) {
return (
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
Finish enabling two factor authentication.
</h3>
);
}
return (
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
You have not enabled two factor authentication.
</h3>
);
})()}
<div className="mt-3 max-w-xl 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 || confirming ? (
<div>
{qrCode ? (
<div>
<div className="mt-4 max-w-xl text-sm text-gray-600 dark:text-gray-400">
{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 dark:text-gray-400">
<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 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 gap-1 max-w-xl mt-4 px-4 py-4 font-mono text-sm bg-gray-100 dark:bg-gray-900 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>
);
}