mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29: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 { AppContext } from 'context/AppContext';
|
||||||
import { getNodeStatus } from 'api/getNodeStatus';
|
import { getNodeStatus } from 'api/getNodeStatus';
|
||||||
import { setNodeStatus } from 'api/setNodeStatus';
|
import { setNodeStatus } from 'api/setNodeStatus';
|
||||||
import { getNodeConfig } from 'api/getNodeConfig';
|
import { setNodeAccess } from 'api/setNodeAccess';
|
||||||
import { getLanguageStrings } from 'constants/Strings';
|
import { getLanguageStrings } from 'constants/Strings';
|
||||||
|
|
||||||
export function useAdmin() {
|
export function useAdmin() {
|
||||||
@ -22,6 +22,8 @@ export function useAdmin() {
|
|||||||
version: null,
|
version: null,
|
||||||
agree: false,
|
agree: false,
|
||||||
showTerms: false,
|
showTerms: false,
|
||||||
|
|
||||||
|
mfaCode: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateState = (value) => {
|
const updateState = (value) => {
|
||||||
@ -80,14 +82,13 @@ export function useAdmin() {
|
|||||||
try {
|
try {
|
||||||
updateState({ busy: true });
|
updateState({ busy: true });
|
||||||
const node = state.server.trim();
|
const node = state.server.trim();
|
||||||
const token = state.token;
|
|
||||||
const unclaimed = await getNodeStatus(node);
|
const unclaimed = await getNodeStatus(node);
|
||||||
if (unclaimed) {
|
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 });
|
updateState({ server: node, busy: false });
|
||||||
navigate('/dashboard', { state: { config, server: node, token }});
|
navigate('/dashboard', { state: { server: node, token: session }});
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(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',
|
mfaDisabled: 'verification temporarily disabled',
|
||||||
mfaConfirm: 'Confirm',
|
mfaConfirm: 'Confirm',
|
||||||
mfaEnter: 'Enter your verification code',
|
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',
|
languageCode: 'fr',
|
||||||
@ -416,6 +420,10 @@ const Strings = [
|
|||||||
mfaError: 'erreur de code de vérification',
|
mfaError: 'erreur de code de vérification',
|
||||||
mfaDisabled: 'vérification temporairement désactivée',
|
mfaDisabled: 'vérification temporairement désactivée',
|
||||||
mfaConfirm: 'Confirmer',
|
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',
|
languageCode: 'es',
|
||||||
@ -623,6 +631,10 @@ const Strings = [
|
|||||||
mfaError: 'error de código de verificación',
|
mfaError: 'error de código de verificación',
|
||||||
mfaDisabled: 'verificación temporalmente deshabilitada',
|
mfaDisabled: 'verificación temporalmente deshabilitada',
|
||||||
mfaConfirm: 'Confirmar',
|
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',
|
languageCode: 'de',
|
||||||
@ -830,6 +842,10 @@ const Strings = [
|
|||||||
mfaError: 'Verifizierungscodefehler',
|
mfaError: 'Verifizierungscodefehler',
|
||||||
mfaDisabled: 'Verifizierung vorübergehend deaktiviert',
|
mfaDisabled: 'Verifizierung vorübergehend deaktiviert',
|
||||||
mfaConfirm: 'Bestätigen',
|
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',
|
languageCode: 'pt',
|
||||||
@ -1022,6 +1038,10 @@ const Strings = [
|
|||||||
mfaError: 'erro de código de verificação',
|
mfaError: 'erro de código de verificação',
|
||||||
mfaDisabled: 'verificação temporariamente desativada',
|
mfaDisabled: 'verificação temporariamente desativada',
|
||||||
mfaConfirm: 'Confirmar',
|
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',
|
languageCode: 'ru',
|
||||||
@ -1212,6 +1232,10 @@ const Strings = [
|
|||||||
mfaError: 'ошибка проверочного кода',
|
mfaError: 'ошибка проверочного кода',
|
||||||
mfaDisabled: 'проверка временно отключена',
|
mfaDisabled: 'проверка временно отключена',
|
||||||
mfaConfirm: 'Подтвердить',
|
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 Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import AntIcon from 'react-native-vector-icons/AntDesign';
|
import AntIcon from 'react-native-vector-icons/AntDesign';
|
||||||
@ -9,12 +9,50 @@ import { useDashboard } from './useDashboard.hook';
|
|||||||
import { Logo } from 'utils/Logo';
|
import { Logo } from 'utils/Logo';
|
||||||
import { BlurView } from "@react-native-community/blur";
|
import { BlurView } from "@react-native-community/blur";
|
||||||
import { InputField } from 'utils/InputField';
|
import { InputField } from 'utils/InputField';
|
||||||
|
import Colors from 'constants/Colors';
|
||||||
|
import { InputCode } from 'utils/InputCode';
|
||||||
|
|
||||||
export function Dashboard(props) {
|
export function Dashboard(props) {
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { config, server, token } = location.state;
|
const { server, token } = location.state;
|
||||||
const { state, actions } = useDashboard(config, server, token);
|
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 () => {
|
const saveConfig = async () => {
|
||||||
try {
|
try {
|
||||||
@ -24,8 +62,8 @@ export function Dashboard(props) {
|
|||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Failed to Save Settings',
|
state.strings.error,
|
||||||
'Please try again.',
|
state.strings.tryAgain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,8 +75,8 @@ export function Dashboard(props) {
|
|||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Failed to Generate Access Token',
|
state.strings.error,
|
||||||
'Please try again.',
|
state.strings.tryAgain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,8 +88,8 @@ export function Dashboard(props) {
|
|||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Failed to Generate Access Token',
|
state.strings.error,
|
||||||
'Please try again.',
|
state.strings.tryAgain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,8 +105,8 @@ export function Dashboard(props) {
|
|||||||
catch (err) {
|
catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Failed to Update Account',
|
state.strings.error,
|
||||||
'Please try again.',
|
state.strings.tryAgain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +121,16 @@ export function Dashboard(props) {
|
|||||||
<TouchableOpacity onPress={actions.showEditConfig}>
|
<TouchableOpacity onPress={actions.showEditConfig}>
|
||||||
<AntIcon style={styles.icon} name={'setting'} size={20} />
|
<AntIcon style={styles.icon} name={'setting'} size={20} />
|
||||||
</TouchableOpacity>
|
</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}>
|
<TouchableOpacity onPress={actions.logout}>
|
||||||
<AntIcon style={styles.icon} name={'logout'} size={20} />
|
<AntIcon style={styles.icon} name={'logout'} size={20} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@ -340,6 +388,60 @@ export function Dashboard(props) {
|
|||||||
</View>
|
</View>
|
||||||
</Modal>
|
</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>
|
</SafeAreaView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -302,4 +302,110 @@ export const styles = StyleSheet.create({
|
|||||||
marginBottom: 8,
|
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 { DisplayContext } from 'context/DisplayContext';
|
||||||
import { getLanguageStrings } from 'constants/Strings';
|
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({
|
const [state, setState] = useState({
|
||||||
strings: getLanguageStrings(),
|
strings: getLanguageStrings(),
|
||||||
config: null,
|
|
||||||
accounts: [],
|
accounts: [],
|
||||||
editConfig: false,
|
editConfig: false,
|
||||||
addUser: false,
|
addUser: false,
|
||||||
@ -40,6 +44,13 @@ export function useDashboard(config, server, token) {
|
|||||||
iceUrl: null,
|
iceUrl: null,
|
||||||
iceUsername: null,
|
iceUsername: null,
|
||||||
icePassword: null,
|
icePassword: null,
|
||||||
|
|
||||||
|
mfaModal: false,
|
||||||
|
mfaImage: null,
|
||||||
|
mfaText: null,
|
||||||
|
mfaCode: '',
|
||||||
|
mfaError: null,
|
||||||
|
mfaEnabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -62,19 +73,24 @@ export function useDashboard(config, server, token) {
|
|||||||
return { logo, name, handle, accountId, disabled };
|
return { logo, name, handle, accountId, disabled };
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshAccounts = async () => {
|
const syncNode = async () => {
|
||||||
const accounts = await getNodeAccounts(server, token);
|
const mfaEnabled = await getAdminMFAuth(server, token);
|
||||||
updateState({ accounts: accounts.map(setAccountItem) });
|
const config = await getNodeConfig(server, token);
|
||||||
};
|
const nodeAccounts = await getNodeAccounts(server, token);
|
||||||
|
const accounts = nodeAccounts.map(setAccountItem);
|
||||||
useEffect(() => {
|
const { keyType, accountStorage, domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword } = config || {};
|
||||||
const { keyType, accountStorage, domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword } = config;
|
|
||||||
const storage = Math.ceil(accountStorage / 1073741824);
|
const storage = Math.ceil(accountStorage / 1073741824);
|
||||||
updateState({ keyType, storage: storage.toString(), domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword });
|
updateState({ keyType, storage: storage.toString(), domain, enableImage, enableAudio, enableVideo, enableBinary, transformSupported, allowUnsealed, pushSupported, enableIce, iceUrl, iceUsername, icePassword, accounts, mfaEnabled });
|
||||||
}, [config]);
|
}
|
||||||
|
|
||||||
|
const refreshAccounts = async () => {
|
||||||
|
const nodeAccounts = await getNodeAccounts(server, token);
|
||||||
|
const accounts = nodeAccounts.map(setAccountItem);
|
||||||
|
updateState({ accounts });
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshAccounts();
|
syncNode();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@ -155,8 +171,8 @@ export function useDashboard(config, server, token) {
|
|||||||
},
|
},
|
||||||
promptRemove: (accountId) => {
|
promptRemove: (accountId) => {
|
||||||
display.actions.showPrompt({
|
display.actions.showPrompt({
|
||||||
title: 'Delete User',
|
title: state.strings.deleteAccount,
|
||||||
ok: { label: 'Delete', action: async () => {
|
ok: { label: state.strings.delete, action: async () => {
|
||||||
await removeAccount(server, token, accountId);
|
await removeAccount(server, token, accountId);
|
||||||
await refreshAccounts();
|
await refreshAccounts();
|
||||||
} , failed: () => {
|
} , failed: () => {
|
||||||
@ -168,6 +184,41 @@ export function useDashboard(config, server, token) {
|
|||||||
cancel: { label: state.strings.cancel },
|
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 };
|
return { state, actions };
|
||||||
|
Loading…
Reference in New Issue
Block a user