adding mfa to admin login

This commit is contained in:
Roland Osborne 2024-05-21 00:56:25 -07:00
parent 53bfc32d4f
commit 51306e92c4
23 changed files with 621 additions and 22 deletions

View File

@ -87,6 +87,139 @@ paths:
'500':
description: internal server error
/admin/access:
put:
tags:
- admin
description: Acquire new session token for admin endpoints
operationId: set-admin-session
parameters:
- name: token
in: query
description: access token
required: true
schema:
type: string
- name: code
in: query
description: totp code
required: false
schema:
type: string
responses:
'201':
description: generated
content:
application/json:
schema:
type: string
'401':
description: invalid token
'405':
description: totp code required but not set
'429':
description: temporarily locked due to too many failures
'500':
description: internal server error
/admin/mfauth:
get:
tags:
- admin
description: check if multi-factor authentication enabled
operationId: get-admin-mfa
parameters:
- name: token
in: query
description: session token
required: true
schema:
type: string
responses:
'200':
description: success
content:
application/json:
schema:
type: boolean
'401':
description: permission denied
'500':
description: internal server error
post:
tags:
- admin
description: Enable multi-factor authentication
operationId: add-admin-mfa
parameters:
- name: token
in: query
description: session token
required: true
schema:
type: string
responses:
'201':
description: success
content:
application/json:
schema:
type: string
'401':
description: permission denied
'500':
description: internal server error
put:
tags:
- admin
description: Confirm multi-factor authentication
operationId: confirm-admin-mfa
parameters:
- name: token
in: query
description: session token
required: false
schema:
type: string
- name: code
in: query
description: totp code generated from secret
required: true
schema:
type: string
responses:
'200':
description: success
'401':
description: permission denied
'403':
description: totp code not correct
'405':
description: totp code required but not set
'429':
description: temporarily locked due to too many failures
'500':
description: internal server error
delete:
tags:
- admin
description: Disable multi-factor authentication
operationId: remove-admin-mfa
parameters:
- name: token
in: query
description: session token
required: false
schema:
type: string
responses:
'200':
description: success
'401':
description: permission denied
'500':
description: internal server error
/admin/config:
get:
tags:
@ -96,7 +229,7 @@ paths:
parameters:
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -119,7 +252,7 @@ paths:
parameters:
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -146,7 +279,7 @@ paths:
parameters:
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -173,7 +306,7 @@ paths:
parameters:
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -204,7 +337,7 @@ paths:
type: string
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -238,7 +371,7 @@ paths:
type: string
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -261,7 +394,7 @@ paths:
parameters:
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string
@ -298,7 +431,7 @@ paths:
type: string
- name: token
in: query
description: token for admin access
description: session token for admin access
required: true
schema:
type: string

View File

@ -0,0 +1,73 @@
package databag
import (
"bytes"
"net/http"
"image/png"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"databag/internal/store"
"encoding/base64"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
//AddAdminMFAuth enables multi-factor auth on the given account
func AddAdminMFAuth(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}
key, err := totp.Generate(totp.GenerateOpts{
Issuer: APPMFAIssuer,
AccountName: "admin",
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA256,
})
err = store.DB.Transaction(func(tx *gorm.DB) error {
// upsert mfa enabled
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 {
return res
}
// upsert mfa confirmed
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 {
return res
}
// upsert mfa secret
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
}).Create(&store.Config{ConfigID: CNFMFASecret, StrValue: key.Secret()}).Error; res != nil {
return res
}
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
var buf bytes.Buffer
img, err := key.Image(200, 200)
if err != nil {
panic(err)
}
png.Encode(&buf, img)
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
WriteResponse(w, MFASecret{ Image: "data:image/png;base64," + enc, Text: key.Secret() })
}

View File

@ -11,7 +11,7 @@ import (
//AddNodeAccount generate a new token to be used for account creation
func AddNodeAccount(w http.ResponseWriter, r *http.Request) {
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -20,7 +20,7 @@ func AddNodeAccountAccess(w http.ResponseWriter, r *http.Request) {
return
}
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -0,0 +1,20 @@
package databag
import (
"net/http"
)
//GetAdminMFAuth checks if mfa enabled for admin
func GetAdminMFAuth(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}
enabled := getBoolConfigValue(CNFMFAEnabled, false);
confirmed := getBoolConfigValue(CNFMFAConfirmed, false);
WriteResponse(w, enabled && confirmed)
}

View File

@ -23,7 +23,7 @@ func GetNodeAccountImage(w http.ResponseWriter, r *http.Request) {
return
}
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -8,7 +8,7 @@ import (
//GetNodeAccounts retrieves profiles of hosted accounts for the admin
func GetNodeAccounts(w http.ResponseWriter, r *http.Request) {
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -8,7 +8,7 @@ import (
func GetNodeConfig(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -0,0 +1,40 @@
package databag
import (
"net/http"
"databag/internal/store"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
//Disable multi-factor auth for admin
func RemoveAdminMFAuth(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}
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"}),
}).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"}),
}).Create(&store.Config{ConfigID: CNFMFAEnabled, BoolValue: false}).Error; res != nil {
return res
}
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
WriteResponse(w, nil)
}

View File

@ -21,7 +21,7 @@ func RemoveNodeAccount(w http.ResponseWriter, r *http.Request) {
return
}
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -0,0 +1,45 @@
package databag
import (
"encoding/hex"
"github.com/theckman/go-securerandom"
"databag/internal/store"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"net/http"
)
//SetAdminAccess begins a session for admin access
func SetAdminAccess(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamAdminToken(r); err != nil {
ErrResponse(w, code, err)
return
}
// gernate app token
data, err := securerandom.Bytes(APPTokenSize)
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
access := hex.EncodeToString(data)
err = store.DB.Transaction(func(tx *gorm.DB) error {
// upsert mfa enabled
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
}).Create(&store.Config{ConfigID: CNFAdminSession, StrValue: access}).Error; res != nil {
return res
}
return nil
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
WriteResponse(w, access)
}

View File

@ -0,0 +1,92 @@
package databag
import (
"databag/internal/store"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"net/http"
"errors"
"time"
)
//SetMultiFactorAuth
func SetAdminMFAuth(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}
if !getBoolConfigValue(CNFMFAEnabled, false) {
ErrResponse(w, http.StatusMethodNotAllowed, errors.New("totp not enabled"))
return;
}
code := r.FormValue("code")
if code == "" {
ErrResponse(w, http.StatusMethodNotAllowed, errors.New("totp code required"))
return;
}
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;
}
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.StatusUnauthorized, errors.New("invalid code"))
return
}
err := store.DB.Transaction(func(tx *gorm.DB) error {
// upsert mfa confirmed
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"str_value"}),
}).Create(&store.Config{ConfigID: CNFMFAConfirmed, BoolValue: true}).Error; res != nil {
return res
}
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
}
WriteResponse(w, nil)
}

View File

@ -18,7 +18,7 @@ func SetNodeAccountStatus(w http.ResponseWriter, r *http.Request) {
return
}
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -11,7 +11,7 @@ import (
func SetNodeConfig(w http.ResponseWriter, r *http.Request) {
// validate login
if code, err := ParamAdminToken(r); err != nil {
if code, err := ParamSessionToken(r); err != nil {
ErrResponse(w, code, err)
return
}

View File

@ -95,6 +95,30 @@ func ParamAdminToken(r *http.Request) (int, error) {
return http.StatusOK, nil
}
//ParamSessionToken compares session token with token query param
func ParamSessionToken(r *http.Request) (int, error) {
// parse authentication token
token := r.FormValue("token")
if token == "" {
return http.StatusUnauthorized, errors.New("token not set")
}
// nothing to do if not configured
if !getBoolConfigValue(CNFConfigured, false) {
return http.StatusUnauthorized, errors.New("node not configured")
}
// compare password
value := getStrConfigValue(CNFAdminSession, "")
if value != token {
return http.StatusUnauthorized, errors.New("invalid session token")
}
return http.StatusOK, nil
}
//GetSessionDetail retrieves account detail specified by agent query param
func GetSessionDetail(r *http.Request) (*store.Session, int, error) {

View File

@ -63,6 +63,23 @@ const CNFIceUsername = "ice_username"
//CNFIceUrl specifies the ice candidate url
const CNFIcePassword = "ice_password"
//CNFMFAFailedTime start of mfa failure window
const CNFMFAFailedTime = "mfa_failed_time"
//CNFMFAFailedCount number of failures in window
const CNFMFAFailedCount = "mfa_failed_count"
//CNFMFARequired specified if mfa enabled for admin
const CNFMFAEnabled = "mfa_enabled"
//CNFMFAConfirmed specified if mfa has been confirmed for admin
const CNFMFAConfirmed = "mfa_confirmed"
//CNFMFASecret specified the mfa secret
const CNFMFASecret = "mfa_secret"
//CNFAdminSession sepcifies the admin session token
const CNFAdminSession = "admin_session"
func getStrConfigValue(configID string, empty string) string {
var config store.Config

View File

@ -279,6 +279,41 @@ var endpoints = routes{
ImportAccount,
},
route{
"SetAdminAccess",
strings.ToUpper("Put"),
"/admin/access",
SetAdminAccess,
},
route{
"GetAdminMFAuth",
strings.ToUpper("Get"),
"/admin/mfauth",
GetAdminMFAuth,
},
route{
"AddAdminMFAuth",
strings.ToUpper("Post"),
"/admin/mfauth",
AddAdminMFAuth,
},
route{
"SetAdminMFAuth",
strings.ToUpper("Put"),
"/admin/mfauth",
SetAdminMFAuth,
},
route{
"RemoveAdminMFAuth",
strings.ToUpper("Delete"),
"/admin/mfauth",
RemoveAdminMFAuth,
},
route{
"RemoveNodeAccount",
strings.ToUpper("Delete"),

View File

@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
import { getNodeStatus } from 'api/getNodeStatus';
import { setNodeStatus } from 'api/setNodeStatus';
import { getNodeConfig } from 'api/getNodeConfig';
import { setNodeAccess } from 'api/setNodeAccess';
import { AppContext } from 'context/AppContext';
import { SettingsContext } from 'context/SettingsContext';
@ -52,9 +53,10 @@ export function useAdmin() {
if (state.unclaimed === true) {
await setNodeStatus(state.password);
}
await getNodeConfig(state.password);
const session = await setNodeAccess(state.password);
updateState({ busy: false });
app.actions.setAdmin(state.password);
app.actions.setAdmin(session);
}
catch(err) {
console.log(err);

View File

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

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setNodeAccess(token) {
const access = await fetchWithTimeout(`/admin/access?token=${encodeURIComponent(token)}`, { method: 'PUT' });
checkResponse(access);
return access.json()
}

View File

@ -195,6 +195,13 @@ export const en = {
mfaDisabled: 'verification temporarily disabled',
mfaConfirm: 'Confirm',
mfaEnter: 'Enter your verification code',
enableMultifactor: 'Enable multi-factor authentication',
disableMultifactor: 'Disable multi-factor authentication',
disable: 'Disable',
confirmDisable: 'Disabling Multi-Factor Authentication',
disablePrompt: 'Are you sure you want to disable multi-factor authentication',
};
export const fr = {
@ -395,6 +402,13 @@ export const fr = {
mfaError: 'erreur de code de vérification',
mfaDisabled: 'vérification temporairement désactivée',
mfaConfirm: 'Confirmer',
enableMultifactor: 'Activer l\'authentification multifacteur',
disableMultifactor: 'Désactiver l\'authentification multifacteur',
disable: 'Désactiver',
confirmDisable: 'Désactivation de l\'authentification multi-facteurs',
disablePrompt: 'Êtes-vous sûr de vouloir désactiver l\'authentification multi-facteurs',
};
export const sp = {
@ -594,6 +608,13 @@ export const sp = {
mfaError: 'error de código de verificación',
mfaDisabled: 'verificación temporalmente deshabilitada',
mfaConfirm: 'Confirmar',
enableMultifactor: 'Habilitar la autenticación multifactor',
disableMultifactor: 'Deshabilitar la autenticación multifactor',
disable: 'Desactivar',
confirmDisable: 'Desactivación de la autenticación de dos factores',
disablePrompt: '¿Estás seguro de que quieres desactivar la autenticación de dos factores?',
};
export const pt = {
@ -793,6 +814,13 @@ export const pt = {
mfaError: 'erro de código de verificação',
mfaDisabled: 'verificação temporariamente desativada',
mfaConfirm: 'Confirmar',
enableMultifactor: 'Habilitar autenticação multifator',
disableMultifactor: 'Desativar autenticação multifator',
disable: 'Desativar',
confirmDisable: 'Desativando Autenticação de Dois Fatores',
disablePrompt: 'Tem certeza de que deseja desativar a autenticação de dois fatores?',
};
export const de = {
@ -992,6 +1020,13 @@ export const de = {
mfaError: 'Verifizierungscodefehler',
mfaDisabled: 'Verifizierung vorübergehend deaktiviert',
mfaConfirm: 'Bestätigen',
enableMultifactor: 'Aktivieren Sie die Multi-Faktor-Authentifizierung',
disableMultifactor: 'Deaktivieren Sie die Multi-Faktor-Authentifizierung',
disable: 'Deaktivieren',
confirmDisable: 'Deaktivierung der Zwei-Faktor-Authentifizierung',
disablePrompt: 'Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung deaktivieren möchten?',
};
export const ru = {
@ -1191,4 +1226,11 @@ export const ru = {
mfaError: 'ошибка проверочного кода',
mfaDisabled: 'проверка временно отключена',
mfaConfirm: 'Подтвердить',
enableMultifactor: 'Включить многофакторную аутентификацию',
disableMultifactor: 'Отключить многофакторную аутентификацию',
disable: 'Отключить',
confirmDisable: 'Отключение двухфакторной аутентификации',
disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?',
};

View File

@ -1,6 +1,6 @@
import { AlertIcon, 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 } from '@ant-design/icons';
import { ExclamationCircleOutlined, SettingOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined, LockOutlined, UnlockOutlined } from '@ant-design/icons';
import { ThemeProvider } from "styled-components";
import { useDashboard } from './useDashboard.hook';
import { AccountItem } from './accountItem/AccountItem';
@ -8,6 +8,7 @@ import { CopyButton } from '../copyButton/CopyButton';
export function Dashboard() {
const [ modal, modalContext ] = Modal.useModal();
const { state, actions } = useDashboard();
const onClipboard = async (value) => {
@ -18,9 +19,26 @@ export function Dashboard() {
return window.location.origin + '/#/create?add=' + state.createToken;
};
const disableMFA = () => {
modal.confirm({
title: <span style={state.menuStyle}>{state.strings.confirmDisable}</span>,
content: <span style={state.menuStyle}>{state.strings.disablePrompt}</span>,
icon: <ExclamationCircleOutlined />,
bodyStyle: { borderRadius: 8, padding: 16, ...state.menuStyle },
okText: state.strings.disable,
cancelText: state.strings.cancel,
onOk() {
actions.disableMFA();
},
onCancel() {},
});
}
return (
<ThemeProvider theme={state.colors}>
<DashboardWrapper>
{ modalContext }
<div className="container">
<div className="header">
<div className="label">{ state.strings.accounts }</div>
@ -34,6 +52,18 @@ export function Dashboard() {
<SettingsButton type="text" size="small" icon={<SettingOutlined />}
onClick={() => actions.setShowSettings(true)}></SettingsButton>
</div>
{ (state.mfAuthSet && state.mfaAuthEnabled) && (
<div className="settings">
<SettingsButton type="text" size="small" icon={<UnlockOutlined />}
onClick={disableMFA}></SettingsButton>
</div>
)}
{ (state.mfAuthSet && !state.mfaAuthEnabled) && (
<div className="settings">
<SettingsButton type="text" size="small" icon={<LockOutlined />}
onClick={actions.enableMFA}></SettingsButton>
</div>
)}
<div className="settings">
<SettingsButton type="text" size="small" icon={<LogoutOutlined />}
onClick={() => actions.logout()}></SettingsButton>
@ -63,6 +93,22 @@ export function Dashboard() {
onClick={() => actions.setShowSettings(true)}></SettingsButton>
</Tooltip>
</div>
{ (state.mfAuthSet && state.mfaAuthEnabled) && (
<div className="settings">
<Tooltip placement="topRight" title={state.strings.disableMultifactor}>
<SettingsButton type="text" size="small" icon={<LockOutlined />}
onClick={disableMFA}></SettingsButton>
</Tooltip>
</div>
)}
{ (state.mfAuthSet && !state.mfaAuthEnabled) && (
<div className="settings">
<Tooltip placement="topRight" title={state.strings.enableMultifactor}>
<SettingsButton type="text" size="small" icon={<UnlockOutlined />}
onClick={actions.enableMFA}></SettingsButton>
</Tooltip>
</div>
)}
<div className="settings">
<Tooltip placement="topRight" title={state.strings.logout}>
<SettingsButton type="text" size="small" icon={<LogoutOutlined />}

View File

@ -8,7 +8,10 @@ import { useNavigate } from 'react-router-dom';
import { AppContext } from 'context/AppContext';
import { SettingsContext } from 'context/SettingsContext';
export function useDashboard() {
import { getAdminMFAuth } from 'api/getAdminMFAuth';
export function useDashboard(token) {
const [state, setState] = useState({
domain: "",
@ -38,6 +41,12 @@ export function useDashboard() {
colors: {},
menuStyle: {},
strings: {},
mfAuthSet: false,
mfAuthEnabled: false,
mfAuthSecretText: null,
mfAuthSecretImage: null,
mfaAuthError: null,
});
const navigate = useNavigate();
@ -140,6 +149,10 @@ export function useDashboard() {
await syncConfig();
await syncAccounts();
},
enableMFA: async () => {
},
disableMFA: async () => {
},
setSettings: async () => {
if (!state.busy) {
updateState({ busy: true });
@ -161,10 +174,11 @@ export function useDashboard() {
const syncConfig = async () => {
try {
const enabled = await getAdminMFAuth(app.state.adminToken);
const config = await getNodeConfig(app.state.adminToken);
const { accountStorage, domain, keyType, pushSupported, transformSupported, allowUnsealed, enableImage, enableAudio, enableVideo, enableBinary, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit } = config;
const storage = Math.ceil(accountStorage / 1073741824);
updateState({ configError: false, domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, enableBinary, pushSupported, transformSupported, allowUnsealed, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit });
updateState({ mfAuthSet: true, mfaAuthEnabled: enabled, configError: false, domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, enableBinary, pushSupported, transformSupported, allowUnsealed, enableIce, iceUrl, iceUsername, icePassword, enableOpenAccess, openAccessLimit });
}
catch(err) {
console.log(err);