mirror of
https://github.com/balzack/databag.git
synced 2025-02-11 19:19:16 +00:00
adding mfa to mobile dashboard
This commit is contained in:
parent
df8d2806e6
commit
f469aff9f6
@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { AppContext } from 'context/AppContext';
|
||||
import { getNodeStatus } from 'api/getNodeStatus';
|
||||
import { setNodeStatus } from 'api/setNodeStatus';
|
||||
import { getNodeConfig } from 'api/getNodeConfig';
|
||||
import { setNodeAccess } from 'api/setNodeAccess';
|
||||
import { getLanguageStrings } from 'constants/Strings';
|
||||
|
||||
export function useAdmin() {
|
||||
@ -22,6 +22,8 @@ export function useAdmin() {
|
||||
version: null,
|
||||
agree: false,
|
||||
showTerms: false,
|
||||
|
||||
mfaCode: '',
|
||||
});
|
||||
|
||||
const updateState = (value) => {
|
||||
@ -80,14 +82,13 @@ export function useAdmin() {
|
||||
try {
|
||||
updateState({ busy: true });
|
||||
const node = state.server.trim();
|
||||
const token = state.token;
|
||||
const unclaimed = await getNodeStatus(node);
|
||||
if (unclaimed) {
|
||||
await setNodeStatus(node, token);
|
||||
await setNodeStatus(node, state.token);
|
||||
}
|
||||
const config = await getNodeConfig(node, token);
|
||||
const session = await setNodeAccess(node, state.token, state.mfaCode);
|
||||
updateState({ server: node, busy: false });
|
||||
navigate('/dashboard', { state: { config, server: node, token }});
|
||||
navigate('/dashboard', { state: { server: node, token: session }});
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
|
11
app/mobile/src/api/addAdminMFAuth.js
Normal file
11
app/mobile/src/api/addAdminMFAuth.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function addAdminMFAuth(server, token) {
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
|
||||
const protocol = insecure ? 'http' : 'https';
|
||||
|
||||
const mfa = await fetchWithTimeout(`${protocol}://${server}/admin/mfauth?token=${token}`, { method: 'POST' })
|
||||
checkResponse(mfa);
|
||||
return mfa.json();
|
||||
}
|
||||
|
11
app/mobile/src/api/getAdminMFAuth.js
Normal file
11
app/mobile/src/api/getAdminMFAuth.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function getAdminMFAuth(server, token) {
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
|
||||
const protocol = insecure ? 'http' : 'https';
|
||||
|
||||
const mfa = await fetchWithTimeout(`${protocol}://${server}/admin/mfauth?token=${encodeURIComponent(token)}`, { method: 'GET' });
|
||||
checkResponse(mfa);
|
||||
return await mfa.json();
|
||||
}
|
||||
|
10
app/mobile/src/api/removeAdminMFAuth.js
Normal file
10
app/mobile/src/api/removeAdminMFAuth.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function removeAdminMFAuth(server, token) {
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
|
||||
const protocol = insecure ? 'http' : 'https';
|
||||
|
||||
const mfa = await fetchWithTimeout(`${protocol}://${server}/admin/mfauth?token=${token}`, { method: 'DELETE' })
|
||||
checkResponse(mfa);
|
||||
}
|
||||
|
10
app/mobile/src/api/setAdminMFAuth.js
Normal file
10
app/mobile/src/api/setAdminMFAuth.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setAdminMFAuth(server, token, code) {
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
|
||||
const protocol = insecure ? 'http' : 'https';
|
||||
|
||||
const mfa = await fetchWithTimeout(`${protocol}://${server}/admin/mfauth?token=${token}&code=${code}`, { method: 'PUT' })
|
||||
checkResponse(mfa);
|
||||
}
|
||||
|
12
app/mobile/src/api/setNodeAccess.js
Normal file
12
app/mobile/src/api/setNodeAccess.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setNodeAccess(server, token, code) {
|
||||
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
|
||||
const protocol = insecure ? 'http' : 'https';
|
||||
const mfa = code ? `&code=${code}` : '';
|
||||
|
||||
const access = await fetchWithTimeout(`${protocol}://${server}/admin/access?token=${encodeURIComponent(token)}${mfa}`, { method: 'PUT' });
|
||||
checkResponse(access);
|
||||
return access.json()
|
||||
}
|
||||
|
@ -210,6 +210,10 @@ const Strings = [
|
||||
mfaDisabled: 'verification temporarily disabled',
|
||||
mfaConfirm: 'Confirm',
|
||||
mfaEnter: 'Enter your verification code',
|
||||
|
||||
disable: 'Disable',
|
||||
confirmDisable: 'Disabling Multi-Factor Authentication',
|
||||
disablePrompt: 'Are you sure you want to disable multi-factor authentication',
|
||||
},
|
||||
{
|
||||
languageCode: 'fr',
|
||||
@ -416,6 +420,10 @@ const Strings = [
|
||||
mfaError: 'erreur de code de vérification',
|
||||
mfaDisabled: 'vérification temporairement désactivée',
|
||||
mfaConfirm: 'Confirmer',
|
||||
|
||||
disable: 'Désactiver',
|
||||
confirmDisable: 'Désactivation de l\'authentification multi-facteurs',
|
||||
disablePrompt: 'Êtes-vous sûr de vouloir désactiver l\'authentification multi-facteurs',
|
||||
},
|
||||
{
|
||||
languageCode: 'es',
|
||||
@ -623,6 +631,10 @@ const Strings = [
|
||||
mfaError: 'error de código de verificación',
|
||||
mfaDisabled: 'verificación temporalmente deshabilitada',
|
||||
mfaConfirm: 'Confirmar',
|
||||
|
||||
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?',
|
||||
},
|
||||
{
|
||||
languageCode: 'de',
|
||||
@ -830,6 +842,10 @@ const Strings = [
|
||||
mfaError: 'Verifizierungscodefehler',
|
||||
mfaDisabled: 'Verifizierung vorübergehend deaktiviert',
|
||||
mfaConfirm: 'Bestätigen',
|
||||
|
||||
disable: 'Deaktivieren',
|
||||
confirmDisable: 'Deaktivierung der Zwei-Faktor-Authentifizierung',
|
||||
disablePrompt: 'Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung deaktivieren möchten?',
|
||||
},
|
||||
{
|
||||
languageCode: 'pt',
|
||||
@ -1022,6 +1038,10 @@ const Strings = [
|
||||
mfaError: 'erro de código de verificação',
|
||||
mfaDisabled: 'verificação temporariamente desativada',
|
||||
mfaConfirm: 'Confirmar',
|
||||
|
||||
disable: 'Desativar',
|
||||
confirmDisable: 'Desativando Autenticação de Dois Fatores',
|
||||
disablePrompt: 'Tem certeza de que deseja desativar a autenticação de dois fatores?',
|
||||
},
|
||||
{
|
||||
languageCode: 'ru',
|
||||
@ -1212,6 +1232,10 @@ const Strings = [
|
||||
mfaError: 'ошибка проверочного кода',
|
||||
mfaDisabled: 'проверка временно отключена',
|
||||
mfaConfirm: 'Подтвердить',
|
||||
|
||||
disable: 'Отключить',
|
||||
confirmDisable: 'Отключение двухфакторной аутентификации',
|
||||
disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?',
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ScrollView, TextInput, Alert, Switch, TouchableOpacity, View, Text, Modal, FlatList, KeyboardAvoidingView } from 'react-native';
|
||||
import { ScrollView, Image, TextInput, Alert, Switch, ActivityIndicator, TouchableOpacity, View, Text, Modal, FlatList, KeyboardAvoidingView } from 'react-native';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||
import AntIcon from 'react-native-vector-icons/AntDesign';
|
||||
@ -9,12 +9,50 @@ import { useDashboard } from './useDashboard.hook';
|
||||
import { Logo } from 'utils/Logo';
|
||||
import { BlurView } from "@react-native-community/blur";
|
||||
import { InputField } from 'utils/InputField';
|
||||
import Colors from 'constants/Colors';
|
||||
import { InputCode } from 'utils/InputCode';
|
||||
|
||||
export function Dashboard(props) {
|
||||
|
||||
const location = useLocation();
|
||||
const { config, server, token } = location.state;
|
||||
const { state, actions } = useDashboard(config, server, token);
|
||||
const { server, token } = location.state;
|
||||
const { state, actions } = useDashboard(server, token);
|
||||
|
||||
const enableMFA = async () => {
|
||||
try {
|
||||
await actions.enableMFA();
|
||||
}
|
||||
catch (err) {
|
||||
Alert.alert(
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const disableMFA = async () => {
|
||||
try {
|
||||
await actions.disableMFA();
|
||||
}
|
||||
catch (err) {
|
||||
Alert.alert(
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const confirmMFA = async () => {
|
||||
try {
|
||||
await actions.confirmMFA();
|
||||
}
|
||||
catch (err) {
|
||||
Alert.alert(
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const saveConfig = async () => {
|
||||
try {
|
||||
@ -24,8 +62,8 @@ export function Dashboard(props) {
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Alert.alert(
|
||||
'Failed to Save Settings',
|
||||
'Please try again.',
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -37,8 +75,8 @@ export function Dashboard(props) {
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Alert.alert(
|
||||
'Failed to Generate Access Token',
|
||||
'Please try again.',
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -50,8 +88,8 @@ export function Dashboard(props) {
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Alert.alert(
|
||||
'Failed to Generate Access Token',
|
||||
'Please try again.',
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -67,8 +105,8 @@ export function Dashboard(props) {
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Alert.alert(
|
||||
'Failed to Update Account',
|
||||
'Please try again.',
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -83,6 +121,16 @@ export function Dashboard(props) {
|
||||
<TouchableOpacity onPress={actions.showEditConfig}>
|
||||
<AntIcon style={styles.icon} name={'setting'} size={20} />
|
||||
</TouchableOpacity>
|
||||
{ !state.mfaEnabled && (
|
||||
<TouchableOpacity onPress={enableMFA}>
|
||||
<MatIcon style={styles.icon} name={'shield-lock-open-outline'} size={20} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{ state.mfaEnabled && (
|
||||
<TouchableOpacity onPress={disableMFA}>
|
||||
<MatIcon style={styles.icon} name={'shield-lock-outline'} size={20} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity onPress={actions.logout}>
|
||||
<AntIcon style={styles.icon} name={'logout'} size={20} />
|
||||
</TouchableOpacity>
|
||||
@ -340,6 +388,60 @@ export function Dashboard(props) {
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
animationType="fade"
|
||||
transparent={true}
|
||||
visible={state.mfaModal}
|
||||
supportedOrientations={['portrait', 'landscape']}
|
||||
onRequestClose={actions.dismissMFA}
|
||||
>
|
||||
<View>
|
||||
<BlurView style={styles.mfaOverlay} blurType={Colors.overlay} blurAmount={2} reducedTransparencyFallbackColor="black" />
|
||||
<KeyboardAvoidingView style={styles.mfaBase} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
|
||||
<View style={styles.mfaContainer}>
|
||||
<Text style={styles.mfaTitle}>{ state.strings.mfaTitle }</Text>
|
||||
<Text style={styles.mfaDescription}>{ state.strings.mfaSteps }</Text>
|
||||
{ state.mfaImage && (
|
||||
<Image source={{ uri: state.mfaImage }} style={{ width: 128, height: 128 }} />
|
||||
)}
|
||||
{ !state.mfaImage && !state.mfaText && (
|
||||
<ActivityIndicator style={styles.modalBusy} animating={true} color={Colors.primaryButton} />
|
||||
)}
|
||||
{ state.mfaText && (
|
||||
<TouchableOpacity style={styles.mfaSecret} onPress={() => Clipboard.setString(state.mfaText)}>
|
||||
<Text style={styles.mfaText}>{ state.mfaText }</Text>
|
||||
<AntIcon style={styles.mfaIcon} name={'copy1'} size={20} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<InputCode style={{ width: '100%' }} onChangeText={actions.setCode} />
|
||||
<View style={styles.mfaError}>
|
||||
{ state.mfaError == '401' && (
|
||||
<Text style={styles.mfaErrorLabel}>{ state.strings.mfaError }</Text>
|
||||
)}
|
||||
{ state.mfaError == '429' && (
|
||||
<Text style={styles.mfaErrorLabel}>{ state.strings.mfaDisabled }</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.mfaControl}>
|
||||
<TouchableOpacity style={styles.mfaCancel} onPress={actions.dismissMFA}>
|
||||
<Text style={styles.mfaCancelLabel}>{ state.strings.cancel }</Text>
|
||||
</TouchableOpacity>
|
||||
{ state.mfaCode != '' && (
|
||||
<TouchableOpacity style={styles.mfaConfirm} onPress={actions.confirmMFA}>
|
||||
<Text style={styles.mfaConfirmLabel}>{ state.strings.mfaConfirm }</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{ state.mfaCode == '' && (
|
||||
<View style={styles.mfaDisabled}>
|
||||
<Text style={styles.mfaDisabledLabel}>{ state.strings.mfaConfirm }</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
@ -302,4 +302,110 @@ export const styles = StyleSheet.create({
|
||||
marginBottom: 8,
|
||||
},
|
||||
},
|
||||
mfaOverlay: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
mfaSecret: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 4,
|
||||
},
|
||||
mfaText: {
|
||||
fontSize: 11,
|
||||
color: Colors.labelText,
|
||||
},
|
||||
mfaIcon: {
|
||||
fontSize: 14,
|
||||
color: Colors.primaryButton,
|
||||
},
|
||||
mfaError: {
|
||||
width: '100%',
|
||||
height: 24,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
mfaErrorLabel: {
|
||||
color: Colors.dangerText,
|
||||
},
|
||||
mfaControl: {
|
||||
height: 32,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 16,
|
||||
},
|
||||
mfaCancel: {
|
||||
width: 72,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: Colors.cancelButton,
|
||||
borderRadius: 4,
|
||||
},
|
||||
mfaCancelLabel: {
|
||||
color: Colors.cancelButtonText,
|
||||
},
|
||||
mfaConfirm: {
|
||||
width: 72,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: Colors.primaryButton,
|
||||
borderRadius: 4,
|
||||
},
|
||||
mfaConfirmLabel: {
|
||||
color: Colors.primaryButtonText,
|
||||
},
|
||||
mfaDisabled: {
|
||||
width: 72,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: Colors.disabledButton,
|
||||
borderRadius: 4,
|
||||
},
|
||||
mfaDisabledLabel: {
|
||||
color: Colors.disabledButtonText,
|
||||
},
|
||||
mfaBase: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
mfaContainer: {
|
||||
backgroundColor: Colors.modalBase,
|
||||
borderColor: Colors.modalBorder,
|
||||
borderWidth: 1,
|
||||
width: '80%',
|
||||
maxWidth: 400,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
},
|
||||
mfaTitle: {
|
||||
fontSize: 20,
|
||||
color: Colors.descriptionText,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
mfaDescription: {
|
||||
fontSize: 14,
|
||||
color: Colors.descriptionText,
|
||||
textAlign: 'center',
|
||||
},
|
||||
mfaCode: {
|
||||
width: 400,
|
||||
borderWidth: 1,
|
||||
borderColor: '#333333',
|
||||
width: '100%',
|
||||
opacity: 0,
|
||||
},
|
||||
});
|
||||
|
@ -15,11 +15,15 @@ import { addAccountAccess } from 'api/addAccountAccess';
|
||||
import { DisplayContext } from 'context/DisplayContext';
|
||||
import { getLanguageStrings } from 'constants/Strings';
|
||||
|
||||
export function useDashboard(config, server, token) {
|
||||
import { getAdminMFAuth } from 'api/getAdminMFAuth';
|
||||
import { addAdminMFAuth } from 'api/addAdminMFAuth';
|
||||
import { setAdminMFAuth } from 'api/setAdminMFAuth';
|
||||
import { removeAdminMFAuth } from 'api/removeAdminMFAuth';
|
||||
|
||||
export function useDashboard(server, token) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
strings: getLanguageStrings(),
|
||||
config: null,
|
||||
accounts: [],
|
||||
editConfig: false,
|
||||
addUser: false,
|
||||
@ -40,6 +44,13 @@ export function useDashboard(config, server, token) {
|
||||
iceUrl: null,
|
||||
iceUsername: null,
|
||||
icePassword: null,
|
||||
|
||||
mfaModal: false,
|
||||
mfaImage: null,
|
||||
mfaText: null,
|
||||
mfaCode: '',
|
||||
mfaError: null,
|
||||
mfaEnabled: false,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
@ -62,19 +73,24 @@ export function useDashboard(config, server, token) {
|
||||
return { logo, name, handle, accountId, disabled };
|
||||
}
|
||||
|
||||
const refreshAccounts = async () => {
|
||||
const accounts = await getNodeAccounts(server, token);
|
||||
updateState({ accounts: accounts.map(setAccountItem) });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const { keyType, accountStorage, domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword } = config;
|
||||
const syncNode = async () => {
|
||||
const mfaEnabled = await getAdminMFAuth(server, token);
|
||||
const config = await getNodeConfig(server, token);
|
||||
const nodeAccounts = await getNodeAccounts(server, token);
|
||||
const accounts = nodeAccounts.map(setAccountItem);
|
||||
const { keyType, accountStorage, domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword } = config || {};
|
||||
const storage = Math.ceil(accountStorage / 1073741824);
|
||||
updateState({ keyType, storage: storage.toString(), domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword });
|
||||
}, [config]);
|
||||
updateState({ keyType, storage: storage.toString(), domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword, accounts, mfaEnabled });
|
||||
}
|
||||
|
||||
const refreshAccounts = async () => {
|
||||
const nodeAccounts = await getNodeAccounts(server, token);
|
||||
const accounts = nodeAccounts.map(setAccountItem);
|
||||
updateState({ accounts });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refreshAccounts();
|
||||
syncNode();
|
||||
}, []);
|
||||
|
||||
const actions = {
|
||||
@ -155,8 +171,8 @@ export function useDashboard(config, server, token) {
|
||||
},
|
||||
promptRemove: (accountId) => {
|
||||
display.actions.showPrompt({
|
||||
title: 'Delete User',
|
||||
ok: { label: 'Delete', action: async () => {
|
||||
title: state.strings.deleteAccount,
|
||||
ok: { label: state.strings.delete, action: async () => {
|
||||
await removeAccount(server, token, accountId);
|
||||
await refreshAccounts();
|
||||
} , failed: () => {
|
||||
@ -168,6 +184,41 @@ export function useDashboard(config, server, token) {
|
||||
cancel: { label: state.strings.cancel },
|
||||
});
|
||||
},
|
||||
enableMFA: async () => {
|
||||
updateState({ mfaModal: true, mfaImage: null, mfaText: null, mfaCode: '', mfaError: '' });
|
||||
const mfa = await addAdminMFAuth(server, token);
|
||||
updateState({ mfaImage: mfa.secretImage, mfaText: mfa.secretText });
|
||||
},
|
||||
disableMFA: async () => {
|
||||
display.actions.showPrompt({
|
||||
title: state.strings.confirmDisable,
|
||||
ok: { label: state.strings.disable, action: async () => {
|
||||
await removeAdminMFAuth(server, token);
|
||||
updateState({ mfaEnabled: false });
|
||||
} , failed: () => {
|
||||
Alert.alert(
|
||||
state.strings.error,
|
||||
state.strings.tryAgain,
|
||||
);
|
||||
}},
|
||||
cancel: { label: state.strings.cancel },
|
||||
});
|
||||
},
|
||||
confirmMFA: async () => {
|
||||
try {
|
||||
await setAdminMFAuth(server, token, state.mfaCode);
|
||||
updateState({ mfaEnabled: true, mfaModal: false });
|
||||
}
|
||||
catch (err) {
|
||||
updateState({ mfaError: err.message});
|
||||
}
|
||||
},
|
||||
dismissMFA: () => {
|
||||
updateState({ mfaModal: false });
|
||||
},
|
||||
setCode: (mfaCode) => {
|
||||
updateState({ mfaCode });
|
||||
},
|
||||
};
|
||||
|
||||
return { state, actions };
|
||||
|
Loading…
Reference in New Issue
Block a user