mirror of
https://github.com/diced/zipline.git
synced 2026-06-12 10:51:17 -07:00
fix: totp 2fa QOL #1073
This commit is contained in:
@@ -128,6 +128,12 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleTotpChange = async (val: string) => {
|
||||
setTotp('pin', val);
|
||||
|
||||
if (val.length === 6) await handleLoginSubmit(form.values, val);
|
||||
};
|
||||
|
||||
if (configLoading || !config) return <LoadingOverlay visible />;
|
||||
if (configError) return <GenericError title='Error' message='Config load failed' details={configError} />;
|
||||
|
||||
@@ -139,7 +145,7 @@ export default function Login() {
|
||||
|
||||
<TotpModal
|
||||
state={totp}
|
||||
onPinChange={(val) => setTotp('pin', val)}
|
||||
onPinChange={(val) => handleTotpChange(val)}
|
||||
onVerify={() => handleLoginSubmit(form.values, totp.pin)}
|
||||
onCancel={() => {
|
||||
setTotp('open', false);
|
||||
|
||||
@@ -14,32 +14,39 @@ export default function TotpModal({
|
||||
}) {
|
||||
return (
|
||||
<Modal onClose={onCancel} title='Enter code' opened={state.open} withCloseButton={false}>
|
||||
<Center>
|
||||
<PinInput
|
||||
length={6}
|
||||
oneTimeCode
|
||||
type='number'
|
||||
onChange={onPinChange}
|
||||
error={!!state.error}
|
||||
disabled={state.disabled}
|
||||
size='xl'
|
||||
autoFocus
|
||||
/>
|
||||
</Center>
|
||||
{state.error && (
|
||||
<Text ta='center' size='sm' c='red' mt='xs'>
|
||||
{state.error}
|
||||
</Text>
|
||||
)}
|
||||
<form onSubmit={onVerify}>
|
||||
<Center>
|
||||
<PinInput
|
||||
length={6}
|
||||
oneTimeCode
|
||||
type='number'
|
||||
onChange={onPinChange}
|
||||
error={!!state.error}
|
||||
disabled={state.disabled}
|
||||
size='xl'
|
||||
autoFocus
|
||||
/>
|
||||
</Center>
|
||||
{state.error && (
|
||||
<Text ta='center' size='sm' c='red' mt='xs'>
|
||||
{state.error}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Group mt='sm' grow>
|
||||
<Button leftSection={<IconX size='1rem' />} color='red' variant='outline' onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button leftSection={<IconShieldQuestion size='1rem' />} loading={state.disabled} onClick={onVerify}>
|
||||
Verify
|
||||
</Button>
|
||||
</Group>
|
||||
<Group mt='sm' grow>
|
||||
<Button leftSection={<IconX size='1rem' />} color='red' variant='outline' onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<IconShieldQuestion size='1rem' />}
|
||||
loading={state.disabled}
|
||||
onClick={onVerify}
|
||||
type='submit'
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,18 +1,20 @@
|
||||
import { generateSecret, generateURI, verify } from 'otplib';
|
||||
import { toDataURL } from 'qrcode';
|
||||
|
||||
export function generateKey() {
|
||||
export function generateKey(): string {
|
||||
return generateSecret({
|
||||
length: 16,
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyTotpCode(code: string, secret: string) {
|
||||
return verify({
|
||||
export async function verifyTotpCode(code: string, secret: string): Promise<boolean> {
|
||||
const result = await verify({
|
||||
secret,
|
||||
token: code,
|
||||
epochTolerance: 30,
|
||||
});
|
||||
|
||||
return result.valid;
|
||||
}
|
||||
|
||||
export function totpQrcode({
|
||||
@@ -23,7 +25,7 @@ export function totpQrcode({
|
||||
issuer?: string;
|
||||
username: string;
|
||||
secret: string;
|
||||
}) {
|
||||
}): Promise<string> {
|
||||
return toDataURL(
|
||||
generateURI({
|
||||
secret,
|
||||
|
||||
Reference in New Issue
Block a user