configuring admin mfa

This commit is contained in:
balzack 2025-02-21 12:34:35 -08:00
parent c471f8defc
commit 8c336c5857
2 changed files with 108 additions and 1 deletions

View File

@ -79,3 +79,39 @@
}
}
}
.mfa {
.secret {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.secretImage {
width: 192px;
height: 192px;
}
.secretText {
display: flex;
gap: 16px;
}
.authMessage {
height: 24px;
color: var(--mantine-color-red-3);
}
.copyIcon {
cursor: pointer;
}
.control {
display: flex;
gap: 16px;
padding-top: 8px;
justify-content: flex-end;
flex-grow: 1;
}
}

View File

@ -1,9 +1,53 @@
import { useState } from 'react'
import classes from './Setup.module.css'
import { useSetup } from './useSetup.hook'
import { Radio, Group, Loader, Modal, Divider, Text, TextInput, Switch, ActionIcon } from '@mantine/core'
import { PinInput, Image, Button, Radio, Group, Loader, Modal, Divider, Text, TextInput, Switch, ActionIcon } from '@mantine/core'
import { modals } from '@mantine/modals'
import { useDisclosure } from '@mantine/hooks'
import { IconCheck, IconCopy } from '@tabler/icons-react'
export function Setup() {
const { state, actions } = useSetup();
const [confirmingAuth, setConfirmingAuth] = useState(false);
const [secretCopy, setSecretCopy] = useState(false);
const [updating, setUpdating] = useState(false);
const [mfaOpened, { open: mfaOpen, close: mfaClose }] = useDisclosure(false)
const confirmAuth = async () => {
if (!confirmingAuth) {
setConfirmingAuth(true);
await actions.confirmMFAuth();
mfaClose();
setConfirmingAuth(false);
}
}
const copySecret = async () => {
if (!secretCopy) {
try {
navigator.clipboard.writeText(state.secretText)
setSecretCopy(true);
setTimeout(() => {
setSecretCopy(false);
}, 2000);
} catch (err) {
console.log(err);
}
}
};
const toggleAuth = async () => {
if (!updating) {
setUpdating(true);
if (state.mfaEnabled) {
await actions.disableMFAuth();
} else {
await actions.enableMFAuth();
mfaOpen();
}
setUpdating(false);
}
}
return (
<div className={classes.setup}>
@ -76,6 +120,10 @@ export function Setup() {
<Text className={classes.label}>{state.strings.allowUnsealed}:</Text>
<Switch className={classes.switch} disabled={state.loading} checked={state.setup?.allowUnsealed} onChange={(ev) => actions.setAllowUnsealed(ev.currentTarget.checked)} />
</div>
<div className={classes.option}>
<Text className={classes.label}>{state.strings.mfaTitle}:</Text>
<Switch className={classes.switch} disabled={state.loading} checked={state.mfaEnabled} onChange={toggleAuth} />
</div>
<Divider />
<div className={classes.option}>
<Text className={classes.label}>{state.strings.enableImage}:</Text>
@ -172,6 +220,29 @@ export function Setup() {
)}
</div>
</div>
<Modal title={state.strings.mfaTitle} opened={mfaOpened} onClose={mfaClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
<div className={classes.mfa}>
<div className={classes.secret}>
<Text>{state.strings.mfaSteps}</Text>
<Image radius="md" className={classes.secretImage} src={state.confirmMFAuthImage} />
<div className={classes.secretText}>
<Text>{state.confirmMFAuthText}</Text>
{secretCopy && <IconCheck />}
{!secretCopy && <IconCopy className={classes.copyIcon} onClick={copySecret} />}
</div>
<PinInput value={state.mfAuthCode} length={6} className={classes.mfaPin} onChange={(event) => actions.setMFAuthCode(event)} />
<Text className={classes.authMessage}>{state.mfaMessage}</Text>
</div>
<div className={classes.control}>
<Button variant="default" onClick={mfaClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={confirmAuth} disabled={state.mfaCode.length != 6} loading={confirmingAuth}>
{state.strings.save}
</Button>
</div>
</div>
</Modal>
</div>
);
}