invoke mfa in browser

This commit is contained in:
Roland Osborne 2024-05-15 18:49:14 -07:00
parent 19248eee7c
commit e5fe393b43
8 changed files with 122 additions and 1 deletions

View File

@ -35,6 +35,7 @@ func GetAccountStatus(w http.ResponseWriter, r *http.Request) {
status.Disabled = account.Disabled
status.ForwardingAddress = account.Forward
status.Searchable = account.Searchable
status.MFAEnabled = account.MFAEnabled && account.MFAConfirmed
status.Sealable = true
status.EnableIce = getBoolConfigValue(CNFEnableIce, false)
status.AllowUnsealed = getBoolConfigValue(CNFAllowUnsealed, false)

View File

@ -35,6 +35,8 @@ type AccountStatus struct {
Searchable bool `json:"searchable"`
MFAEnabled bool `json:"mfaEnabled"`
PushEnabled bool `json:"pushEnabled"`
Sealable bool `json:"sealable"`

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addAccountMFA(token) {
const mfa = await fetchWithTimeout(`/account/mfauth?agent=${token}`, { method: 'POST' })
checkResponse(mfa);
return mfa.json();
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeAccountMFA(token) {
let res = await fetchWithTimeout(`/account/mfauth?agent=${token}`, { method: 'DELETE' })
checkResponse(res);
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setAccountMFA(token, code) {
let res = await fetchWithTimeout(`/account/mfauth?agent=${token}&code=${code}`, { method: 'PUT' })
checkResponse(res);
}

View File

@ -3,6 +3,9 @@ import { setAccountSearchable } from 'api/setAccountSearchable';
import { setAccountSeal } from 'api/setAccountSeal';
import { getAccountStatus } from 'api/getAccountStatus';
import { setAccountLogin } from 'api/setAccountLogin';
import { addAccountMFA } from 'api/addAccountMFA';
import { setAccountMFA } from 'api/setAccountMFA';
import { removeAccountMFA } from 'api/removeAccountMFA';
import { StoreContext } from './StoreContext';
export function useAccountContext() {
@ -72,6 +75,17 @@ export function useAccountContext() {
setSearchable: async (flag) => {
await setAccountSearchable(access.current, flag);
},
enableMFA: async () => {
const secret = await addAccountMFA(access.current);
console.log("SECRET ", secret);
return secret;
},
disableMFA: async () => {
await removeAccountMFA(access.current);
},
confirmMFA: async (code) => {
await setAccountMFA(access.current, code);
},
setSeal: async (seal, sealKey) => {
await setAccountSeal(access.current, seal);
await storeContext.actions.setValue("sealKey", sealKey);

View File

@ -39,6 +39,26 @@ export function AccountAccess() {
}
};
const enableMFA = async (enable) => {
console.log("ENABLE: ", enable);
try {
if (enable) {
await actions.enableMFA();
}
else {
await actions.disableMFA();
}
}
catch (err) {
console.log(err);
modal.error({
title: <span style={state.menuStyle}>{state.strings.operationFailed}</span>,
content: <span style={state.menuStyle}>{state.strings.tryAgain}</span>,
bodyStyle: { borderRadius: 8, padding: 16, ...state.menuStyle },
});
}
}
const saveLogin = async () => {
try {
await actions.setLogin();
@ -84,6 +104,12 @@ export function AccountAccess() {
</div>
<div className="switchLabel">{state.strings.registry}</div>
</div>
<div className="switch">
<div className="control">
<Switch size="small" checked={state.mfaEnabled} onChange={enable => enableMFA(enable)} />
</div>
<div className="switchLabel">Multi-Factor Authentication</div>
</div>
<div className="link" onClick={actions.setEditSeal}>
<div className="control">
<SettingOutlined />
@ -238,6 +264,9 @@ export function AccountAccess() {
</div>
</LoginModal>
</Modal>
<Modal centerd closable={false} footer={null} visible={state.mfaModal} bodyStyle={{ borderRadius: 8, padding: 16, ...state.menuStyle }} onCancel={actions.dismissMFA}>
<div>{ state.mfaSecret }</div>
</Modal>
</AccountAccessWrapper>
);
}

View File

@ -40,6 +40,11 @@ export function useAccountAccess() {
videoId: null,
videoInputs: [],
mfaModal: false,
mfaEnabled: null,
mfaSecret: null,
mfaCode: null,
seal: null,
sealKey: null,
});
@ -60,7 +65,7 @@ export function useAccountAccess() {
useEffect(() => {
const { seal, sealKey, status } = account.state;
updateState({ searchable: status?.searchable, seal, sealKey });
updateState({ searchable: status?.searchable, mfaEnabled: status?.mfaEnabled, seal, sealKey });
}, [account.state]);
useEffect(() => {
@ -307,6 +312,54 @@ export function useAccountAccess() {
}
}
},
setCode: async (code) => {
updateState({ mfaCode: code });
},
enableMFA: async () => {
if (!state.busy) {
try {
updateState({ busy: true });
const secret = await account.actions.enableMFA();
updateState({ busy: false, mfaModal: true, mfaSecret: secret, mfaCode: '' });
}
catch (err) {
console.log(err);
updateState({ busy: false });
throw new Error('faild to enable mfa');
}
}
},
disableMFA: async () => {
if (!state.busy) {
try {
updateState({ busy: true });
await account.actions.disableMFA();
updateState({ busy: false });
}
catch (err) {
console.log(err);
updateState({ busy: false });
throw new Error('failed to disable mfa');
}
}
},
confirmMFA: async () => {
if (!state.busy) {
try {
updateState({ busy: true });
await account.actions.confirmMFA(state.code);
updateState({ busy: false });
}
catch (err) {
console.log(err);
updateState({ busy: false });
throw new Error('failed to confirm mfa');
}
}
},
dismissMFA: async () => {
updateState({ mfaModal: false });
},
};
return { state, actions };