mirror of
https://github.com/balzack/databag.git
synced 2025-02-11 19:19:16 +00:00
integrated admin login with mfa
This commit is contained in:
parent
51306e92c4
commit
0001f6c8c9
@ -34,7 +34,7 @@ func AddAdminMFAuth(w http.ResponseWriter, r *http.Request) {
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAEnabled, BoolValue: false}).Error; res != nil {
|
||||
}).Create(&store.Config{ConfigID: CNFMFAEnabled, BoolValue: true}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ func AddAdminMFAuth(w http.ResponseWriter, r *http.Request) {
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAConfirmed, BoolValue: true}).Error; res != nil {
|
||||
}).Create(&store.Config{ConfigID: CNFMFAConfirmed, BoolValue: false}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ func AddMultiFactorAuth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: APPMFAIssuer,
|
||||
AccountName: account.GUID,
|
||||
AccountName: account.Handle,
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
})
|
||||
|
@ -19,13 +19,13 @@ func RemoveAdminMFAuth(w http.ResponseWriter, r *http.Request) {
|
||||
err := store.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
|
||||
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAConfirmed, BoolValue: false}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
|
||||
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAEnabled, BoolValue: false}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"net/http"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SetAdminAccess begins a session for admin access
|
||||
@ -18,6 +22,60 @@ func SetAdminAccess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// check mfa
|
||||
curTime := time.Now().Unix()
|
||||
failedTime := getNumConfigValue(CNFMFAFailedTime, 0);
|
||||
failedCount := getNumConfigValue(CNFMFAFailedCount, 0);
|
||||
if failedTime + APPMFAFailPeriod > curTime && failedCount > APPMFAFailCount {
|
||||
ErrResponse(w, http.StatusTooManyRequests, errors.New("temporarily locked"))
|
||||
return;
|
||||
}
|
||||
|
||||
mfaEnabled := getBoolConfigValue(CNFMFAEnabled, false);
|
||||
mfaConfirmed := getBoolConfigValue(CNFMFAConfirmed, false);
|
||||
if mfaEnabled && mfaConfirmed {
|
||||
code := r.FormValue("code")
|
||||
if code == "" {
|
||||
ErrResponse(w, http.StatusMethodNotAllowed, errors.New("totp code required"))
|
||||
return;
|
||||
}
|
||||
|
||||
secret := getStrConfigValue(CNFMFASecret, "");
|
||||
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
|
||||
if valid, _ := totp.ValidateCustom(code, secret, time.Now(), opts); !valid {
|
||||
err := store.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if failedTime + APPMFAFailPeriod > curTime {
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAFailedCount, NumValue: failedCount + 1}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
} else {
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAFailedTime, NumValue: curTime}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAFailedCount, NumValue: failedCount + 1}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
LogMsg("failed to increment fail count");
|
||||
}
|
||||
|
||||
ErrResponse(w, http.StatusForbidden, errors.New("invalid code"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// gernate app token
|
||||
data, err := securerandom.Bytes(APPTokenSize)
|
||||
if err != nil {
|
||||
|
@ -77,7 +77,7 @@ func SetAdminMFAuth(w http.ResponseWriter, r *http.Request) {
|
||||
// upsert mfa confirmed
|
||||
if res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "config_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
|
||||
DoUpdates: clause.AssignmentColumns([]string{"bool_value"}),
|
||||
}).Create(&store.Config{ConfigID: CNFMFAConfirmed, BoolValue: true}).Error; res != nil {
|
||||
return res
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button, Modal, Form, Input } from 'antd';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { AdminWrapper } from './Admin.styled';
|
||||
import { AdminWrapper, MFAModal } from './Admin.styled';
|
||||
import { useAdmin } from './useAdmin.hook';
|
||||
|
||||
export function Admin() {
|
||||
@ -55,6 +55,26 @@ export function Admin() {
|
||||
|
||||
</Form>
|
||||
</div>
|
||||
<Modal centerd closable={false} footer={null} visible={state.mfaModal} destroyOnClose={true} bodyStyle={{ borderRadius: 8, padding: 16, ...state.menuStyle }} onCancel={actions.dismissMFA}>
|
||||
<MFAModal>
|
||||
<div className="title">{state.strings.mfaTitle}</div>
|
||||
<div className="description">{state.strings.mfaEnter}</div>
|
||||
<Input.OTP onChange={actions.setCode} />
|
||||
<div className="alert">
|
||||
{ state.mfaError === '403' && (
|
||||
<span>{state.strings.mfaError}</span>
|
||||
)}
|
||||
{ state.mfaError === '429' && (
|
||||
<span>{state.strings.mfaDisabled}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="controls">
|
||||
<Button key="back" onClick={actions.dismissMFA}>{state.strings.cancel}</Button>
|
||||
<Button key="save" type="primary" className={state.mfaCode ? 'saveEnabled' : 'saveDisabled'} onClick={login}
|
||||
disabled={!state.mfaCode} loading={state.busy}>{state.strings.login}</Button>
|
||||
</div>
|
||||
</MFAModal>
|
||||
</Modal>
|
||||
</AdminWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -72,3 +72,53 @@ export const AdminWrapper = styled.div`
|
||||
`;
|
||||
|
||||
|
||||
export const MFAModal = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.0rem;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.code {
|
||||
padding-top: 4px;
|
||||
border-bottom: 1px solid ${props => props.theme.sectionBorder};
|
||||
}
|
||||
|
||||
.alert {
|
||||
height: 24px;
|
||||
color: ${props => props.theme.alertText};
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
|
||||
.saveDisabled {
|
||||
background-color: ${props => props.theme.disabledArea};
|
||||
|
||||
button {
|
||||
color: ${props => props.theme.idleText};
|
||||
}
|
||||
}
|
||||
|
||||
.saveEnabled {
|
||||
background-color: ${props => props.theme.enabledArea};
|
||||
|
||||
button {
|
||||
color: ${props => props.theme.activeText};
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -16,6 +16,9 @@ export function useAdmin() {
|
||||
busy: false,
|
||||
strings: {},
|
||||
menuStyle: {},
|
||||
mfaModal: false,
|
||||
mfaCode: null,
|
||||
mfaError: null,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
@ -53,10 +56,22 @@ export function useAdmin() {
|
||||
if (state.unclaimed === true) {
|
||||
await setNodeStatus(state.password);
|
||||
}
|
||||
const session = await setNodeAccess(state.password);
|
||||
|
||||
try {
|
||||
const session = await setNodeAccess(state.password, state.mfaCode);
|
||||
app.actions.setAdmin(session);
|
||||
}
|
||||
catch (err) {
|
||||
const msg = err?.message;
|
||||
if (msg === '405' || msg === '403' || msg === '429') {
|
||||
updateState({ busy: false, mfaModal: true, mfaError: msg });
|
||||
}
|
||||
else {
|
||||
console.log(err);
|
||||
updateState({ busy: false })
|
||||
throw new Error('login failed: check your username and password');
|
||||
}
|
||||
}
|
||||
updateState({ busy: false });
|
||||
app.actions.setAdmin(session);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
@ -65,6 +80,12 @@ export function useAdmin() {
|
||||
}
|
||||
}
|
||||
},
|
||||
setCode: (mfaCode) => {
|
||||
updateState({ mfaCode });
|
||||
},
|
||||
dismissMFA: () => {
|
||||
updateState({ mfaModal: false, mfaCode: null });
|
||||
},
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -93,7 +93,6 @@ console.log(state.mfaError);
|
||||
<Button key="save" type="primary" className={state.mfaCode ? 'saveEnabled' : 'saveDisabled'} onClick={login}
|
||||
disabled={!state.mfaCode} loading={state.busy}>{state.strings.login}</Button>
|
||||
</div>
|
||||
|
||||
</MFAModal>
|
||||
</Modal>
|
||||
</LoginWrapper>
|
||||
|
8
net/web/src/api/addAdminMFAuth.js
Normal file
8
net/web/src/api/addAdminMFAuth.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function addAdminMFAuth(token) {
|
||||
const mfa = await fetchWithTimeout(`/admin/mfauth?token=${token}`, { method: 'POST' })
|
||||
checkResponse(mfa);
|
||||
return mfa.json();
|
||||
}
|
||||
|
7
net/web/src/api/removeAdminMFAuth.js
Normal file
7
net/web/src/api/removeAdminMFAuth.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function removeAdminMFAuth(token) {
|
||||
const mfa = await fetchWithTimeout(`/admin/mfauth?token=${token}`, { method: 'DELETE' })
|
||||
checkResponse(mfa);
|
||||
}
|
||||
|
7
net/web/src/api/setAdminMFAuth.js
Normal file
7
net/web/src/api/setAdminMFAuth.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setAdminMFAuth(token, code) {
|
||||
const mfa = await fetchWithTimeout(`/admin/mfauth?token=${token}&code=${code}`, { method: 'PUT' })
|
||||
checkResponse(mfa);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setNodeAccess(token) {
|
||||
const access = await fetchWithTimeout(`/admin/access?token=${encodeURIComponent(token)}`, { method: 'PUT' });
|
||||
export async function setNodeAccess(token, code) {
|
||||
const mfa = code ? `&code=${code}` : '';
|
||||
const access = await fetchWithTimeout(`/admin/access?token=${encodeURIComponent(token)}${mfa}`, { method: 'PUT' });
|
||||
checkResponse(access);
|
||||
return access.json()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AlertIcon, DashboardWrapper, SettingsButton, AddButton, SettingsLayout, CreateLayout } from './Dashboard.styled';
|
||||
import { AlertIcon, MFAModal, DashboardWrapper, SettingsButton, AddButton, SettingsLayout, CreateLayout } from './Dashboard.styled';
|
||||
import { Tooltip, Switch, Select, Button, Space, Modal, Input, InputNumber, List } from 'antd';
|
||||
import { ExclamationCircleOutlined, SettingOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined, LockOutlined, UnlockOutlined } from '@ant-design/icons';
|
||||
import { ThemeProvider } from "styled-components";
|
||||
@ -19,7 +19,7 @@ export function Dashboard() {
|
||||
return window.location.origin + '/#/create?add=' + state.createToken;
|
||||
};
|
||||
|
||||
const disableMFA = () => {
|
||||
const confirmDisableMFA = () => {
|
||||
modal.confirm({
|
||||
title: <span style={state.menuStyle}>{state.strings.confirmDisable}</span>,
|
||||
content: <span style={state.menuStyle}>{state.strings.disablePrompt}</span>,
|
||||
@ -28,12 +28,53 @@ export function Dashboard() {
|
||||
okText: state.strings.disable,
|
||||
cancelText: state.strings.cancel,
|
||||
onOk() {
|
||||
actions.disableMFA();
|
||||
disableMFA();
|
||||
},
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
const disableMFA = async () => {
|
||||
try {
|
||||
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 enableMFA = async () => {
|
||||
try {
|
||||
await actions.enableMFA();
|
||||
}
|
||||
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 confirmMFA = async () => {
|
||||
try {
|
||||
await actions.confirmMFA();
|
||||
}
|
||||
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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={state.colors}>
|
||||
@ -55,13 +96,13 @@ export function Dashboard() {
|
||||
{ (state.mfAuthSet && state.mfaAuthEnabled) && (
|
||||
<div className="settings">
|
||||
<SettingsButton type="text" size="small" icon={<UnlockOutlined />}
|
||||
onClick={disableMFA}></SettingsButton>
|
||||
onClick={confirmDisableMFA}></SettingsButton>
|
||||
</div>
|
||||
)}
|
||||
{ (state.mfAuthSet && !state.mfaAuthEnabled) && (
|
||||
<div className="settings">
|
||||
<SettingsButton type="text" size="small" icon={<LockOutlined />}
|
||||
onClick={actions.enableMFA}></SettingsButton>
|
||||
onClick={enableMFA}></SettingsButton>
|
||||
</div>
|
||||
)}
|
||||
<div className="settings">
|
||||
@ -97,7 +138,7 @@ export function Dashboard() {
|
||||
<div className="settings">
|
||||
<Tooltip placement="topRight" title={state.strings.disableMultifactor}>
|
||||
<SettingsButton type="text" size="small" icon={<LockOutlined />}
|
||||
onClick={disableMFA}></SettingsButton>
|
||||
onClick={confirmDisableMFA}></SettingsButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
@ -105,7 +146,7 @@ export function Dashboard() {
|
||||
<div className="settings">
|
||||
<Tooltip placement="topRight" title={state.strings.enableMultifactor}>
|
||||
<SettingsButton type="text" size="small" icon={<UnlockOutlined />}
|
||||
onClick={actions.enableMFA}></SettingsButton>
|
||||
onClick={enableMFA}></SettingsButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
@ -264,6 +305,32 @@ export function Dashboard() {
|
||||
</div>
|
||||
</CreateLayout>
|
||||
</Modal>
|
||||
<Modal bodyStyle={{ borderRadius: 8, padding: 16, ...state.menuStyle }} closable={false} visible={state.mfaModal} centered width="fitContent"
|
||||
destroyOnClose={true} footer={null} onCancel={actions.dismissMFA}>
|
||||
<MFAModal>
|
||||
<div className="title">{state.strings.mfaTitle}</div>
|
||||
<div className="description">{state.strings.mfaSteps}</div>
|
||||
<img src={state.mfaImage} alt="QRCode" />
|
||||
<div className="secret">
|
||||
<div className="label">{ state.mfaText }</div>
|
||||
<CopyButton onCopy={async () => await navigator.clipboard.writeText(state.mfaText)} />
|
||||
</div>
|
||||
<Input.OTP onChange={actions.setCode} />
|
||||
<div className="alert">
|
||||
{ state.mfaError === '401' && (
|
||||
<span>{state.strings.mfaError}</span>
|
||||
)}
|
||||
{ state.mfaError === '429' && (
|
||||
<span>{state.strings.mfaDisabled}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="controls">
|
||||
<Button key="back" onClick={actions.dismissMFA}>{state.strings.cancel}</Button>
|
||||
<Button key="save" type="primary" className={state.mfaCode ? 'saveEnabled' : 'saveDisabled'} onClick={confirmMFA}
|
||||
disabled={!state.mfaCode} loading={state.busy}>{state.strings.mfaConfirm}</Button>
|
||||
</div>
|
||||
</MFAModal>
|
||||
</Modal>
|
||||
</DashboardWrapper>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -163,3 +163,72 @@ export const CreateLayout = styled.div`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const MFAModal = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-aling: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.0rem;
|
||||
padding-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.secret {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
padding-top: 4px;
|
||||
border-bottom: 1px solid ${props => props.theme.sectionBorder};
|
||||
}
|
||||
|
||||
.codeLabel {
|
||||
padding-top: 4px;
|
||||
font-size: 0.9.rem;
|
||||
color: ${props => props.theme.mainText};
|
||||
}
|
||||
|
||||
.alert {
|
||||
height: 24px;
|
||||
color: ${props => props.theme.alertText};
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
|
||||
.saveDisabled {
|
||||
background-color: ${props => props.theme.disabledArea};
|
||||
|
||||
button {
|
||||
color: ${props => props.theme.idleText};
|
||||
}
|
||||
}
|
||||
|
||||
.saveEnabled {
|
||||
background-color: ${props => props.theme.enabledArea};
|
||||
|
||||
button {
|
||||
color: ${props => props.theme.activeText};
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -9,7 +9,9 @@ import { AppContext } from 'context/AppContext';
|
||||
import { SettingsContext } from 'context/SettingsContext';
|
||||
|
||||
import { getAdminMFAuth } from 'api/getAdminMFAuth';
|
||||
|
||||
import { addAdminMFAuth } from 'api/addAdminMFAuth';
|
||||
import { setAdminMFAuth } from 'api/setAdminMFAuth';
|
||||
import { removeAdminMFAuth } from 'api/removeAdminMFAuth';
|
||||
|
||||
export function useDashboard(token) {
|
||||
|
||||
@ -42,11 +44,13 @@ export function useDashboard(token) {
|
||||
menuStyle: {},
|
||||
strings: {},
|
||||
|
||||
mfaModal: false,
|
||||
mfAuthSet: false,
|
||||
mfAuthEnabled: false,
|
||||
mfAuthSecretText: null,
|
||||
mfAuthSecretImage: null,
|
||||
mfaAuthError: null,
|
||||
mfaCode: '',
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
@ -149,9 +153,29 @@ export function useDashboard(token) {
|
||||
await syncConfig();
|
||||
await syncAccounts();
|
||||
},
|
||||
setCode: async (code) => {
|
||||
updateState({ mfaCode: code });
|
||||
},
|
||||
enableMFA: async () => {
|
||||
const mfa = await addAdminMFAuth(app.state.adminToken);
|
||||
updateState({ mfaModal: true, mfaError: false, mfaText: mfa.secretText, mfaImage: mfa.secretImage, mfaCode: '' });
|
||||
},
|
||||
disableMFA: async () => {
|
||||
const mfa = await removeAdminMFAuth(app.state.adminToken);
|
||||
updateState({ mfaAuthEnabled: false });
|
||||
},
|
||||
confirmMFA: async () => {
|
||||
try {
|
||||
await setAdminMFAuth(app.state.adminToken, state.mfaCode);
|
||||
updateState({ mfaAuthEnabled: true, mfaModal: false });
|
||||
}
|
||||
catch (err) {
|
||||
const msg = err?.message;
|
||||
updateState({ mfaError: msg });
|
||||
}
|
||||
},
|
||||
dismissMFA: async () => {
|
||||
updateState({ mfaModal: false });
|
||||
},
|
||||
setSettings: async () => {
|
||||
if (!state.busy) {
|
||||
|
Loading…
Reference in New Issue
Block a user