fix: totp 2fa QOL #1073

This commit is contained in:
diced
2026-05-11 16:18:37 -07:00
parent 41b63e6f25
commit 4776d9e85f
3 changed files with 45 additions and 30 deletions
+7 -1
View File
@@ -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);
+32 -25
View File
@@ -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
View File
@@ -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,