mirror of
https://github.com/diced/zipline.git
synced 2025-04-04 23:21:17 -05:00
feat: use pininput for 2fa
This commit is contained in:
parent
df013a52d1
commit
2c24cafab8
3 changed files with 93 additions and 58 deletions
|
@ -1,4 +1,4 @@
|
|||
import { Button, Center, Image, Modal, NumberInput, Text, Title } from '@mantine/core';
|
||||
import { Button, Center, Image, Modal, NumberInput, PinInput, Text, Title } from '@mantine/core';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { CheckIcon, CrossIcon } from 'components/icons';
|
||||
|
@ -9,9 +9,7 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
|||
const [secret, setSecret] = useState('');
|
||||
const [qrCode, setQrCode] = useState('');
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [code, setCode] = useState(undefined);
|
||||
const [error, setError] = useState('');
|
||||
const form = useForm();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
@ -34,15 +32,15 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
|||
})();
|
||||
}, [opened]);
|
||||
|
||||
const disableTotp = async () => {
|
||||
const disableTotp = async (code) => {
|
||||
setDisabled(true);
|
||||
const str = code.toString();
|
||||
if (str.length !== 6) {
|
||||
if (code.length !== 6) {
|
||||
setDisabled(false);
|
||||
return setError('Code must be 6 digits');
|
||||
}
|
||||
|
||||
const resp = await useFetch('/api/user/mfa/totp', 'DELETE', {
|
||||
code: str,
|
||||
code,
|
||||
});
|
||||
|
||||
if (resp.error) {
|
||||
|
@ -63,16 +61,16 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
|||
setDisabled(false);
|
||||
};
|
||||
|
||||
const verifyCode = async () => {
|
||||
const verifyCode = async (code) => {
|
||||
setDisabled(true);
|
||||
const str = code.toString();
|
||||
if (str.length !== 6) {
|
||||
if (code.length !== 6) {
|
||||
setDisabled(false);
|
||||
return setError('Code must be 6 digits');
|
||||
}
|
||||
|
||||
const resp = await useFetch('/api/user/mfa/totp', 'POST', {
|
||||
secret,
|
||||
code: str,
|
||||
code,
|
||||
register: true,
|
||||
});
|
||||
|
||||
|
@ -94,6 +92,13 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
|||
setDisabled(false);
|
||||
};
|
||||
|
||||
const handlePinChange = (value) => {
|
||||
if (value.length === 6) {
|
||||
setDisabled(true);
|
||||
deleteTotp ? disableTotp(value) : verifyCode(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
|
@ -112,39 +117,39 @@ export function TotpModal({ opened, onClose, deleteTotp, setTotpEnabled }) {
|
|||
<Center>
|
||||
<Image height={180} width={180} src={qrCode} alt='QR Code' withPlaceholder />
|
||||
</Center>
|
||||
<Text my='sm'>QR Code not working? Try manually entering the code into your app: {secret}</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={form.onSubmit(() => {
|
||||
deleteTotp ? disableTotp() : verifyCode();
|
||||
})}
|
||||
>
|
||||
<NumberInput
|
||||
placeholder='2FA Code'
|
||||
label='Verify'
|
||||
size='xl'
|
||||
hideControls
|
||||
maxLength={6}
|
||||
minLength={6}
|
||||
value={code}
|
||||
onChange={(e) => setCode(e)}
|
||||
<Center my='md'>
|
||||
<PinInput
|
||||
data-autofocus
|
||||
error={error}
|
||||
/>
|
||||
|
||||
<Button
|
||||
length={6}
|
||||
oneTimeCode
|
||||
type='number'
|
||||
placeholder=''
|
||||
onChange={handlePinChange}
|
||||
autoFocus={true}
|
||||
error={!!error}
|
||||
disabled={disabled}
|
||||
size='lg'
|
||||
fullWidth
|
||||
mt='md'
|
||||
rightIcon={<CheckIcon />}
|
||||
onClick={deleteTotp ? disableTotp : verifyCode}
|
||||
>
|
||||
Verify{deleteTotp ? ' and Disable' : ''}
|
||||
</Button>
|
||||
</form>
|
||||
size='xl'
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{error && (
|
||||
<Text my='sm' size='sm' color='red' align='center'>
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{!deleteTotp && (
|
||||
<Text my='sm' size='sm' color='gray' align='center'>
|
||||
QR Code not working? Try manually entering the code into your app: {secret}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Button disabled={disabled} size='lg' fullWidth mt='md' rightIcon={<CheckIcon />} type='submit'>
|
||||
Verify{deleteTotp ? ' and Disable' : ''}
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -413,7 +413,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
|||
<Box my='md'>
|
||||
<Title>Two Factor Authentication</Title>
|
||||
<MutedText size='md'>
|
||||
{user.totpSecret
|
||||
{totpEnabled
|
||||
? 'You have two factor authentication enabled.'
|
||||
: 'You do not have two factor authentication enabled.'}
|
||||
</MutedText>
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
Modal,
|
||||
NumberInput,
|
||||
PasswordInput,
|
||||
PinInput,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
|
@ -23,10 +25,11 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
|
||||
// totp modal
|
||||
const [totpOpen, setTotpOpen] = useState(false);
|
||||
const [code, setCode] = useState(undefined);
|
||||
const [error, setError] = useState('');
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const oauth_providers = JSON.parse(unparsed);
|
||||
|
||||
const icons = {
|
||||
|
@ -46,8 +49,10 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (values) => {
|
||||
const onSubmit = async (values, code = null) => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setDisabled(true);
|
||||
const username = values.username.trim();
|
||||
const password = values.password.trim();
|
||||
|
||||
|
@ -65,20 +70,31 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
} else if (res.totp) {
|
||||
if (res.code === 400) {
|
||||
setError('Invalid code');
|
||||
setDisabled(false);
|
||||
setLoading(false);
|
||||
} else {
|
||||
setError('');
|
||||
setDisabled(false);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
setTotpOpen(true);
|
||||
} else {
|
||||
form.setFieldError('username', 'Invalid username');
|
||||
form.setFieldError('password', 'Invalid password');
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
await router.push((router.query.url as string) || '/dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
const handlePinChange = (value) => {
|
||||
if (value.length === 6) {
|
||||
onSubmit(form.values, value);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const a = await fetch('/api/user');
|
||||
|
@ -98,24 +114,38 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
title={<Title order={3}>Two-Factor Authentication Required</Title>}
|
||||
size='lg'
|
||||
>
|
||||
<form onSubmit={form.onSubmit(() => onSubmit(form.values))}>
|
||||
<NumberInput
|
||||
placeholder='2FA Code'
|
||||
label='Verify'
|
||||
size='xl'
|
||||
hideControls
|
||||
maxLength={6}
|
||||
minLength={6}
|
||||
value={code}
|
||||
onChange={(e) => setCode(e)}
|
||||
<Center my='md'>
|
||||
<PinInput
|
||||
data-autofocus
|
||||
error={error}
|
||||
length={6}
|
||||
oneTimeCode
|
||||
type='number'
|
||||
placeholder=''
|
||||
onChange={handlePinChange}
|
||||
autoFocus={true}
|
||||
error={!!error}
|
||||
disabled={disabled}
|
||||
size='xl'
|
||||
/>
|
||||
</Center>
|
||||
|
||||
<Button disabled={disabled} size='lg' fullWidth mt='md' rightIcon={<CheckIcon />} type='submit'>
|
||||
Verify & Login
|
||||
</Button>
|
||||
</form>
|
||||
{error && (
|
||||
<Text my='sm' size='sm' color='red' align='center'>
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
size='lg'
|
||||
fullWidth
|
||||
mt='md'
|
||||
rightIcon={<CheckIcon />}
|
||||
type='submit'
|
||||
>
|
||||
Verify & Login
|
||||
</Button>
|
||||
</Modal>
|
||||
<Center sx={{ height: '100vh' }}>
|
||||
<div>
|
||||
|
@ -133,7 +163,7 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
{...form.getInputProps('password')}
|
||||
/>
|
||||
|
||||
<Button size='lg' my='sm' fullWidth type='submit'>
|
||||
<Button size='lg' my='sm' fullWidth type='submit' loading={loading}>
|
||||
Login
|
||||
</Button>
|
||||
</form>
|
||||
|
|
Loading…
Add table
Reference in a new issue