adding mfa modal

This commit is contained in:
balzack 2024-09-10 23:29:25 -07:00
parent 65cbf70528
commit 4161f60648
5 changed files with 97 additions and 11 deletions

View File

@ -1,3 +1,27 @@
.mfa {
.secret {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding-bottom: 24px;
}
.secretImage {
width: 192px;
height: 192px;
}
.secretText {
display: flex;
gap: 16px;
}
.copyIcon {
cursor: pointer;
}
}
.change {
display: flex;
flex-direction: column;

View File

@ -9,6 +9,7 @@ import {
Select,
Switch,
Text,
PinInput,
Image,
Button,
UnstyledButton,
@ -33,6 +34,8 @@ import {
IconMapPin,
IconLogout,
IconLogin,
IconCopy,
IconCheck,
} from '@tabler/icons-react'
import { modals } from '@mantine/modals'
import { useDisclosure } from '@mantine/hooks'
@ -60,6 +63,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
const [savingNotifications, setSavingNotifications] = useState(false)
const [savingSeal, setSavingSeal] = useState(false)
const [savingMfa, setSavingMfa] = useState(false)
const [addingMfa, setAddingMfa] = useState(false)
const logout = () =>
modals.openConfirmModal({
@ -114,7 +118,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
}
}
const setSeal = async (checked: boolean) => {
const setSeal = async () => {
if (!savingSeal) {
sealOpen();
setSavingSeal(true);
@ -122,10 +126,26 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
}
}
const setMfa = async () => {
const setMfa = async (checked: boolean) => {
if (!addingMfa) {
setAddingMfa(true);
try {
await actions.enableMFA();
mfaOpen();
} catch (err) {
console.log(err)
showError()
}
setAddingMfa(false);
}
}
const confirmMfa = async () => {
if (!savingMfa) {
mfaOpen();
setSavingMfa(true);
mfaClose();
setSavingMfa(false);
}
}
@ -615,7 +635,35 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
centered
>
<div className={classes.mfa} />
<div className={classes.mfa}>
<div className={classes.secret}>
<Text>{state.strings.mfaSteps}</Text>
<Image radius="md" className={classes.secretImage} src={state.secretImage} />
<div className={classes.secretText}>
<Text>{state.secretText}</Text>
{ state.secretCopied && (
<IconCheck />
)}
{ !state.secretCopied && (
<IconCopy className={classes.copyIcon} onClick={actions.copySecret} />
)}
</div>
<PinInput
value={state.code}
length={6}
className={classes.mfaPin}
onChange={(event) => actions.setCode(event)}
/>
</div>
<div className={classes.control}>
<Button variant="default" onClick={mfaClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={confirmMfa} disabled={state.code.length != 6} loading={savingMfa}>
{state.strings.save}
</Button>
</div>
</div>
</Modal>
<Modal
title={state.strings.sealedTopics}

View File

@ -48,7 +48,10 @@ export function useSettings() {
clip: { w: 0, h: 0, x: 0, y: 0 },
crop: { x: 0, y: 0 },
zoom: 1,
editImage: undefined,
secretText: '',
secretImage: '',
code: '',
editImage: '',
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -149,7 +152,8 @@ export function useSettings() {
},
enableMFA: async () => {
const { settings } = getSession()
return await settings.enableMFA()
const { secretImage, secretText } = await settings.enableMFA()
updateState({ secretImage, secretText });
},
disableMFA: async () => {
const { settings } = getSession()
@ -159,6 +163,16 @@ export function useSettings() {
const { settings } = getSession()
await settings.confirmMFA(code)
},
setCode: (code: string) => {
updateState({ code });
},
copySecret: () => {
navigator.clipboard.writeText(state.secretText);
updateState({ secretCopied: true });
setTimeout(() => {
updateState({ secretCopied: false });
}, 1000);
},
setSeal: async (password: string) => {
const { settings } = getSession()
await settings.setSeal(password)
@ -279,8 +293,8 @@ export function useSettings() {
img,
state.clip.x,
state.clip.y,
state.clip.width,
state.clip.height,
state.clip.w,
state.clip.h,
0,
0,
IMAGE_DIM,

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addAccountMFAuth(node: string, secure: boolean, token: string): Promise<{ text: string, image: string }> {
const endpoint = `http${secure ? 's' : ''}://${node}/account/mfauth=${token}`;
const endpoint = `http${secure ? 's' : ''}://${node}/account/mfauth?agent=${token}`;
const auth = await fetchWithTimeout(endpoint, { method: 'POST' })
checkResponse(auth.status);
return await auth.json();

View File

@ -145,8 +145,8 @@ export class SettingsModule implements Settings {
public async enableMFA(): Promise<{ secretImage: string, secretText: string }> {
const { node, secure, token } = this;
const { image, text } = await addAccountMFAuth(node, secure, token);
return { secretImage: image, secretText: text };
const { secretImage, secretText } = await addAccountMFAuth(node, secure, token);
return { secretImage, secretText };
}
public async disableMFA(): Promise<void> {