feat: add secret check (#20)
This commit is contained in:
parent
e96d171cf8
commit
a46b4a25c8
2
App.js
2
App.js
|
@ -17,6 +17,7 @@ import {PaperProvider} from "react-native-paper";
|
||||||
import {NavigationContainer} from "@react-navigation/native";
|
import {NavigationContainer} from "@react-navigation/native";
|
||||||
import {BulletList} from "react-content-loader/native";
|
import {BulletList} from "react-content-loader/native";
|
||||||
import {SQLiteProvider} from "expo-sqlite";
|
import {SQLiteProvider} from "expo-sqlite";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import NavigationBar from "./NavigationBar";
|
import NavigationBar from "./NavigationBar";
|
||||||
import {migrateDb} from "./TotpDatabase";
|
import {migrateDb} from "./TotpDatabase";
|
||||||
|
@ -31,6 +32,7 @@ const App = () => {
|
||||||
<NavigationBar />
|
<NavigationBar />
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
<Toast />
|
||||||
</SQLiteProvider>
|
</SQLiteProvider>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,10 +14,12 @@
|
||||||
|
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {WebView} from "react-native-webview";
|
import {WebView} from "react-native-webview";
|
||||||
import {View} from "react-native";
|
import {Platform, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity} from "react-native";
|
||||||
import {Portal} from "react-native-paper";
|
import {Portal} from "react-native-paper";
|
||||||
import SDK from "casdoor-react-native-sdk";
|
import SDK from "casdoor-react-native-sdk";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
import EnterCasdoorSdkConfig from "./EnterCasdoorSdkConfig";
|
import EnterCasdoorSdkConfig from "./EnterCasdoorSdkConfig";
|
||||||
import useStore from "./useStorage";
|
import useStore from "./useStorage";
|
||||||
// import {LogBox} from "react-native";
|
// import {LogBox} from "react-native";
|
||||||
|
@ -28,6 +30,7 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
|
||||||
CasdoorLoginPage.propTypes = {
|
CasdoorLoginPage.propTypes = {
|
||||||
onWebviewClose: PropTypes.func.isRequired,
|
onWebviewClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [casdoorLoginURL, setCasdoorLoginURL] = useState("");
|
const [casdoorLoginURL, setCasdoorLoginURL] = useState("");
|
||||||
const [showConfigPage, setShowConfigPage] = useState(true);
|
const [showConfigPage, setShowConfigPage] = useState(true);
|
||||||
|
|
||||||
|
@ -45,6 +48,11 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
|
||||||
const handleHideConfigPage = () => {
|
const handleHideConfigPage = () => {
|
||||||
setShowConfigPage(false);
|
setShowConfigPage(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShowConfigPage = () => {
|
||||||
|
setShowConfigPage(true);
|
||||||
|
};
|
||||||
|
|
||||||
const getCasdoorSignInUrl = async() => {
|
const getCasdoorSignInUrl = async() => {
|
||||||
const signinUrl = await sdk.getSigninUrl();
|
const signinUrl = await sdk.getSigninUrl();
|
||||||
setCasdoorLoginURL(signinUrl);
|
setCasdoorLoginURL(signinUrl);
|
||||||
|
@ -68,24 +76,71 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleErrorResponse = (error) => {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Error",
|
||||||
|
text2: error.description,
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
setShowConfigPage(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<View style={{flex: 1}}>
|
<SafeAreaView style={styles.container}>
|
||||||
{showConfigPage && <EnterCasdoorSdkConfig onClose={handleHideConfigPage} onWebviewClose={onWebviewClose} />}
|
{showConfigPage && (
|
||||||
|
<EnterCasdoorSdkConfig
|
||||||
|
onClose={handleHideConfigPage}
|
||||||
|
onWebviewClose={onWebviewClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{!showConfigPage && casdoorLoginURL !== "" && (
|
{!showConfigPage && casdoorLoginURL !== "" && (
|
||||||
|
<>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.backButton}
|
||||||
|
onPress={handleShowConfigPage}
|
||||||
|
>
|
||||||
|
<Text style={styles.backButtonText}>Back to Config</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
<WebView
|
<WebView
|
||||||
source={{uri: casdoorLoginURL}}
|
source={{uri: casdoorLoginURL}}
|
||||||
onNavigationStateChange={onNavigationStateChange}
|
onNavigationStateChange={onNavigationStateChange}
|
||||||
style={{flex: 1}}
|
onError={(syntheticEvent) => {
|
||||||
|
const {nativeEvent} = syntheticEvent;
|
||||||
|
handleErrorResponse(nativeEvent);
|
||||||
|
}}
|
||||||
|
style={styles.webview}
|
||||||
mixedContentMode="always"
|
mixedContentMode="always"
|
||||||
javaScriptEnabled={true}
|
javaScriptEnabled={true}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</SafeAreaView>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "white",
|
||||||
|
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0,
|
||||||
|
},
|
||||||
|
webview: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: "#007AFF",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
backButtonText: {
|
||||||
|
color: "white",
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const CasdoorLogout = () => {
|
export const CasdoorLogout = () => {
|
||||||
if (sdk) {sdk.clearState();}
|
if (sdk) {sdk.clearState();}
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,104 +12,230 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React, {useState} from "react";
|
import React, {useCallback, useState} from "react";
|
||||||
import {Text, TextInput, View} from "react-native";
|
import {View} from "react-native";
|
||||||
import {Button, Divider, IconButton, Menu} from "react-native-paper";
|
import {Button, IconButton, Menu, Text, TextInput} from "react-native-paper";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export default function EnterAccountDetails({onClose, onAdd}) {
|
const EnterAccountDetails = ({onClose, onAdd, validateSecret}) => {
|
||||||
EnterAccountDetails.propTypes = {
|
EnterAccountDetails.propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onAdd: PropTypes.func.isRequired,
|
onAdd: PropTypes.func.isRequired,
|
||||||
|
validateSecret: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [accountName, setAccountName] = useState("");
|
const [accountName, setAccountName] = useState("");
|
||||||
const [secretKey, setSecretKey] = useState("");
|
const [secretKey, setSecretKey] = useState("");
|
||||||
|
const [secretError, setSecretError] = useState("");
|
||||||
|
const [accountNameError, setAccountNameError] = useState("");
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [selectedItem, setSelectedItem] = useState("Time based");
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
const [visible, setVisible] = React.useState(false);
|
|
||||||
const openMenu = () => setVisible(true);
|
const openMenu = () => setVisible(true);
|
||||||
const closeMenu = () => setVisible(false);
|
const closeMenu = () => setVisible(false);
|
||||||
const [selectedItem, setSelectedItem] = useState("Time based");
|
|
||||||
|
|
||||||
const handleMenuItemPress = (item) => {
|
const handleMenuItemPress = useCallback((item) => {
|
||||||
setSelectedItem(item);
|
setSelectedItem(item);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
|
const handleAddAccount = useCallback(() => {
|
||||||
|
if (accountName.trim() === "") {
|
||||||
|
setAccountNameError("Account Name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretKey.trim() === "") {
|
||||||
|
setSecretError("Secret Key is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountName.trim() === "" || secretKey.trim() === "") {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Error",
|
||||||
|
text2: "Please fill in all the fields!",
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretError) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Invalid Secret Key",
|
||||||
|
text2: "Please check your secret key and try again.",
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleAddAccount = () => {
|
|
||||||
onAdd({accountName, secretKey});
|
onAdd({accountName, secretKey});
|
||||||
setAccountName("");
|
setAccountName("");
|
||||||
setSecretKey("");
|
setSecretKey("");
|
||||||
};
|
setAccountNameError("");
|
||||||
|
setSecretError("");
|
||||||
|
}, [accountName, secretKey, secretError, onAdd]);
|
||||||
|
|
||||||
|
const handleSecretKeyChange = useCallback((text) => {
|
||||||
|
setSecretKey(text);
|
||||||
|
if (validateSecret) {
|
||||||
|
const isValid = validateSecret(text);
|
||||||
|
setSecretError(isValid || text.trim() === "" ? "" : "Invalid Secret Key");
|
||||||
|
}
|
||||||
|
}, [validateSecret]);
|
||||||
|
|
||||||
|
const handleAccountNameChange = useCallback((text) => {
|
||||||
|
setAccountName(text);
|
||||||
|
if (accountNameError) {
|
||||||
|
setAccountNameError("");
|
||||||
|
}
|
||||||
|
}, [accountNameError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1, justifyContent: "center", alignItems: "center"}}>
|
<View style={styles.container}>
|
||||||
<Text style={{fontSize: 24, marginBottom: 5}}>Add new 2FA account</Text>
|
<View style={styles.content}>
|
||||||
<View style={{flexDirection: "row", alignItems: "center"}}>
|
<View style={styles.header}>
|
||||||
<IconButton icon="account-details" size={35} />
|
<Text style={styles.title}>Add Account</Text>
|
||||||
|
<IconButton
|
||||||
|
icon="close"
|
||||||
|
size={24}
|
||||||
|
onPress={onClose}
|
||||||
|
style={styles.closeButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Account Name"
|
label="Account Name"
|
||||||
placeholder="Account Name"
|
|
||||||
value={accountName}
|
value={accountName}
|
||||||
autoCapitalize="none"
|
onChangeText={handleAccountNameChange}
|
||||||
onChangeText={(text) => setAccountName(text)}
|
error={!!accountNameError}
|
||||||
style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}}
|
style={styles.input}
|
||||||
|
mode="outlined"
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={{flexDirection: "row", alignItems: "center"}}>
|
|
||||||
<IconButton icon="account-key" size={35} />
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Secret Key"
|
label="Secret Key"
|
||||||
placeholder="Secret Key"
|
|
||||||
value={secretKey}
|
value={secretKey}
|
||||||
autoCapitalize="none"
|
onChangeText={handleSecretKeyChange}
|
||||||
onChangeText={(text) => setSecretKey(text)}
|
secureTextEntry={!showPassword}
|
||||||
secureTextEntry
|
error={!!secretError}
|
||||||
style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}}
|
style={styles.input}
|
||||||
|
mode="outlined"
|
||||||
|
right={
|
||||||
|
<TextInput.Icon
|
||||||
|
icon={showPassword ? "eye-off" : "eye"}
|
||||||
|
onPress={() => setShowPassword(!showPassword)}
|
||||||
/>
|
/>
|
||||||
</View>
|
}
|
||||||
<Button
|
/>
|
||||||
icon="account-plus"
|
<View style={styles.buttonContainer}>
|
||||||
style={{
|
|
||||||
backgroundColor: "#E6DFF3",
|
|
||||||
borderRadius: 5,
|
|
||||||
margin: 10,
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
top: 230,
|
|
||||||
right: 30,
|
|
||||||
width: 90,
|
|
||||||
}}
|
|
||||||
onPress={handleAddAccount}
|
|
||||||
>
|
|
||||||
<Text style={{fontSize: 18}}>Add</Text>
|
|
||||||
</Button>
|
|
||||||
<IconButton icon={"close"} size={30} onPress={onClose} style={{position: "absolute", top: 5, right: 5}} />
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#E6DFF3",
|
|
||||||
borderRadius: 5,
|
|
||||||
position: "absolute",
|
|
||||||
left: 30,
|
|
||||||
top: 240,
|
|
||||||
width: 140,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu
|
<Menu
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onDismiss={closeMenu}
|
onDismiss={closeMenu}
|
||||||
anchor={
|
anchor={
|
||||||
<Button style={{alignItems: "left"}} icon={"chevron-down"} onPress={openMenu}>
|
<Button
|
||||||
|
onPress={openMenu}
|
||||||
|
mode="outlined"
|
||||||
|
icon="chevron-down"
|
||||||
|
contentStyle={styles.menuButtonContent}
|
||||||
|
style={styles.menuButton}
|
||||||
|
>
|
||||||
{selectedItem}
|
{selectedItem}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
contentStyle={styles.menuContent}
|
||||||
>
|
>
|
||||||
<Menu.Item onPress={() => handleMenuItemPress("Time based")} title="Time based" />
|
<Menu.Item onPress={() => handleMenuItemPress("Time based")} title="Time based" />
|
||||||
<Divider />
|
|
||||||
<Menu.Item onPress={() => handleMenuItemPress("Counter based")} title="Counter based" />
|
<Menu.Item onPress={() => handleMenuItemPress("Counter based")} title="Counter based" />
|
||||||
</Menu>
|
</Menu>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={handleAddAccount}
|
||||||
|
style={styles.addButton}
|
||||||
|
labelStyle={styles.buttonLabel}
|
||||||
|
>
|
||||||
|
Add Account
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: "#F5F5F5",
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: {width: 0, height: 2},
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 10,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
position: "relative",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#333",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: -8,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginVertical: 10,
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: "white",
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
menuButton: {
|
||||||
|
flex: 1,
|
||||||
|
marginRight: 10,
|
||||||
|
height: 50,
|
||||||
|
justifyContent: "center",
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
menuButtonContent: {
|
||||||
|
height: 50,
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
menuContent: {
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
borderRadius: 8,
|
||||||
|
elevation: 3,
|
||||||
|
shadowColor: "#000000",
|
||||||
|
shadowOffset: {width: 0, height: 2},
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 3,
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "#8A7DF7",
|
||||||
|
height: 50,
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingHorizontal: 5,
|
||||||
|
},
|
||||||
|
buttonLabel: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: "white",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnterAccountDetails;
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Alert, Text, View} from "react-native";
|
import {ScrollView, Text, View} from "react-native";
|
||||||
import {Button, IconButton, Portal, TextInput} from "react-native-paper";
|
import {Button, IconButton, Portal, TextInput} from "react-native-paper";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig";
|
import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import useStore from "./useStorage";
|
import useStore from "./useStorage";
|
||||||
|
@ -22,6 +23,7 @@ import useStore from "./useStorage";
|
||||||
const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
|
const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
|
||||||
EnterCasdoorSdkConfig.propTypes = {
|
EnterCasdoorSdkConfig.propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onWebviewClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -44,12 +46,26 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!serverUrl || !clientId || !appName || !organizationName || !redirectPath) {
|
if (!serverUrl || !clientId || !appName || !organizationName || !redirectPath) {
|
||||||
Alert.alert("Please fill in all the fields!");
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Error",
|
||||||
|
text2: "Please fill in all the fields!",
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleScanToLogin = () => {
|
||||||
|
Toast.show({
|
||||||
|
type: "info",
|
||||||
|
text1: "Info",
|
||||||
|
text2: "Scan to Login functionality not implemented yet.",
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleUseDefault = () => {
|
const handleUseDefault = () => {
|
||||||
setCasdoorConfig(DefaultCasdoorSdkConfig);
|
setCasdoorConfig(DefaultCasdoorSdkConfig);
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -57,115 +73,155 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<View style={{flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "white"}}>
|
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||||
<View style={{top: -60, flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "white"}}>
|
<View style={styles.content}>
|
||||||
<Text style={{fontSize: 24, marginBottom: 5}}>Casdoor server</Text>
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Casdoor server</Text>
|
||||||
|
<IconButton
|
||||||
|
icon="close"
|
||||||
|
size={24}
|
||||||
|
onPress={closeConfigPage}
|
||||||
|
style={styles.closeButton}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Endpoint"
|
label="Endpoint"
|
||||||
value={serverUrl}
|
value={serverUrl}
|
||||||
onChangeText={setServerUrl}
|
onChangeText={setServerUrl}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
style={{
|
style={styles.input}
|
||||||
borderWidth: 3,
|
mode="outlined"
|
||||||
borderColor: "white",
|
|
||||||
margin: 10,
|
|
||||||
width: 300,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 5,
|
|
||||||
fontSize: 18,
|
|
||||||
color: "gray",
|
|
||||||
paddingLeft: 10,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="ClientID"
|
label="Client ID"
|
||||||
value={clientId}
|
value={clientId}
|
||||||
onChangeText={setClientId}
|
onChangeText={setClientId}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
style={{
|
style={styles.input}
|
||||||
borderWidth: 3,
|
mode="outlined"
|
||||||
borderColor: "white",
|
|
||||||
margin: 10,
|
|
||||||
width: 300,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 5,
|
|
||||||
fontSize: 18,
|
|
||||||
color: "gray",
|
|
||||||
paddingLeft: 10,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="appName"
|
label="App Name"
|
||||||
value={appName}
|
value={appName}
|
||||||
onChangeText={setAppName}
|
onChangeText={setAppName}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
style={{
|
style={styles.input}
|
||||||
borderWidth: 3,
|
mode="outlined"
|
||||||
borderColor: "white",
|
|
||||||
margin: 10,
|
|
||||||
width: 300,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 5,
|
|
||||||
fontSize: 18,
|
|
||||||
color: "gray",
|
|
||||||
paddingLeft: 10,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="organizationName"
|
label="Organization Name"
|
||||||
value={organizationName}
|
value={organizationName}
|
||||||
onChangeText={setOrganizationName}
|
onChangeText={setOrganizationName}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
style={{
|
style={styles.input}
|
||||||
borderWidth: 3,
|
mode="outlined"
|
||||||
borderColor: "white",
|
|
||||||
margin: 10,
|
|
||||||
width: 300,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 5,
|
|
||||||
fontSize: 18,
|
|
||||||
color: "gray",
|
|
||||||
paddingLeft: 10,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<View style={styles.buttonRow}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={handleSave}
|
onPress={handleSave}
|
||||||
style={{
|
style={[styles.button, styles.confirmButton]}
|
||||||
backgroundColor: "#E6DFF3",
|
labelStyle={styles.buttonLabel}
|
||||||
borderRadius: 5,
|
|
||||||
margin: 10,
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
top: 600,
|
|
||||||
width: 300,
|
|
||||||
height: 50,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text style={{fontSize: 21, width: 300, color: "black"}}>Confirm</Text>
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={handleUseDefault}
|
onPress={handleScanToLogin}
|
||||||
style={{
|
style={[styles.button, styles.scanButton]}
|
||||||
backgroundColor: "#E6DFF3",
|
labelStyle={styles.buttonLabel}
|
||||||
borderRadius: 5,
|
|
||||||
margin: 10,
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
top: 660,
|
|
||||||
width: 300,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text style={{fontSize: 18, width: 300, color: "black"}}>Use Casdoor Demo Site</Text>
|
Scan to Login
|
||||||
</Button>
|
</Button>
|
||||||
<IconButton icon={"close"} size={30} onPress={closeConfigPage} style={{position: "absolute", top: 120, right: -30}} />
|
|
||||||
</View>
|
</View>
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={handleUseDefault}
|
||||||
|
style={[styles.button, styles.outlinedButton]}
|
||||||
|
labelStyle={styles.outlinedButtonLabel}
|
||||||
|
>
|
||||||
|
Use Casdoor Demo Site
|
||||||
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
scrollContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.5)",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
width: "95%",
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: "#F5F5F5",
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: {width: 0, height: 2},
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 10,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginVertical: 10,
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: "white",
|
||||||
|
},
|
||||||
|
buttonRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: 14,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
borderRadius: 5,
|
||||||
|
paddingVertical: 8,
|
||||||
|
},
|
||||||
|
confirmButton: {
|
||||||
|
backgroundColor: "#6200EE",
|
||||||
|
flex: 1,
|
||||||
|
marginRight: 5,
|
||||||
|
},
|
||||||
|
scanButton: {
|
||||||
|
backgroundColor: "#03DAC6",
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 5,
|
||||||
|
},
|
||||||
|
buttonLabel: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
outlinedButton: {
|
||||||
|
borderColor: "#6200EE",
|
||||||
|
borderWidth: 1,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
outlinedButtonLabel: {
|
||||||
|
color: "#6200EE",
|
||||||
|
fontSize: 16,
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
position: "relative",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#333",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: -8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default EnterCasdoorSdkConfig;
|
export default EnterCasdoorSdkConfig;
|
||||||
|
|
24
Header.js
24
Header.js
|
@ -15,13 +15,16 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {Dimensions, StyleSheet, View} from "react-native";
|
import {Dimensions, StyleSheet, View} from "react-native";
|
||||||
import {Appbar, Avatar, Menu, Text, TouchableRipple} from "react-native-paper";
|
import {Appbar, Avatar, Menu, Text, TouchableRipple} from "react-native-paper";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
|
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
|
||||||
import useStore from "./useStorage";
|
import useStore from "./useStorage";
|
||||||
|
import useSyncStore from "./useSyncStore";
|
||||||
|
|
||||||
const {width} = Dimensions.get("window");
|
const {width} = Dimensions.get("window");
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const {userInfo, clearAll} = useStore();
|
const {userInfo, clearAll} = useStore();
|
||||||
|
const syncError = useSyncStore(state => state.syncError);
|
||||||
const [showLoginPage, setShowLoginPage] = React.useState(false);
|
const [showLoginPage, setShowLoginPage] = React.useState(false);
|
||||||
const [menuVisible, setMenuVisible] = React.useState(false);
|
const [menuVisible, setMenuVisible] = React.useState(false);
|
||||||
|
|
||||||
|
@ -41,8 +44,27 @@ const Header = () => {
|
||||||
clearAll();
|
clearAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSyncErrorPress = () => {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Sync Error",
|
||||||
|
text2: syncError || "An unknown error occurred during synchronization.",
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Appbar.Header>
|
<Appbar.Header mode="center-aligned">
|
||||||
|
<View style={styles.leftContainer}>
|
||||||
|
{true && syncError && (
|
||||||
|
<Appbar.Action
|
||||||
|
icon="sync-alert"
|
||||||
|
color="#E53935"
|
||||||
|
size={24}
|
||||||
|
onPress={handleSyncErrorPress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
<Appbar.Content
|
<Appbar.Content
|
||||||
title="Casdoor"
|
title="Casdoor"
|
||||||
titleStyle={styles.titleText}
|
titleStyle={styles.titleText}
|
||||||
|
|
34
HomePage.js
34
HomePage.js
|
@ -19,6 +19,7 @@ import {GestureHandlerRootView, Swipeable} from "react-native-gesture-handler";
|
||||||
import {CountdownCircleTimer} from "react-native-countdown-circle-timer";
|
import {CountdownCircleTimer} from "react-native-countdown-circle-timer";
|
||||||
import {useNetInfo} from "@react-native-community/netinfo";
|
import {useNetInfo} from "@react-native-community/netinfo";
|
||||||
import {FlashList} from "@shopify/flash-list";
|
import {FlashList} from "@shopify/flash-list";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
import * as SQLite from "expo-sqlite/next";
|
import * as SQLite from "expo-sqlite/next";
|
||||||
|
|
||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
|
@ -49,11 +50,13 @@ export default function HomePage() {
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const {isConnected} = useNetInfo();
|
const {isConnected} = useNetInfo();
|
||||||
const [canSync, setCanSync] = useState(false);
|
const [canSync, setCanSync] = useState(false);
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
const swipeableRef = useRef(null);
|
const swipeableRef = useRef(null);
|
||||||
|
const db = SQLite.useSQLiteContext();
|
||||||
const {userInfo, serverUrl, token} = useStore();
|
const {userInfo, serverUrl, token} = useStore();
|
||||||
const {startSync} = useSyncStore();
|
const {startSync} = useSyncStore();
|
||||||
const db = SQLite.useSQLiteContext();
|
const syncError = useSyncStore(state => state.syncError);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (db) {
|
if (db) {
|
||||||
|
@ -89,11 +92,29 @@ export default function HomePage() {
|
||||||
|
|
||||||
const onRefresh = async() => {
|
const onRefresh = async() => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
if (canSync) {await startSync(db, userInfo, serverUrl, token);}
|
if (canSync) {
|
||||||
|
await startSync(db, userInfo, serverUrl, token);
|
||||||
|
if (syncError) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Sync error",
|
||||||
|
text2: syncError,
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Toast.show({
|
||||||
|
type: "success",
|
||||||
|
text1: "Sync success",
|
||||||
|
text2: "All your accounts are up to date.",
|
||||||
|
autoHide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAccount = async(accountData) => {
|
const handleAddAccount = async(accountData) => {
|
||||||
|
setKey(prevKey => prevKey + 1);
|
||||||
await TotpDatabase.insertAccount(db, accountData);
|
await TotpDatabase.insertAccount(db, accountData);
|
||||||
closeEnterAccountModal();
|
closeEnterAccountModal();
|
||||||
};
|
};
|
||||||
|
@ -218,6 +239,7 @@ export default function HomePage() {
|
||||||
right={() => (
|
right={() => (
|
||||||
<View style={{justifyContent: "center", alignItems: "center"}}>
|
<View style={{justifyContent: "center", alignItems: "center"}}>
|
||||||
<CountdownCircleTimer
|
<CountdownCircleTimer
|
||||||
|
key={key}
|
||||||
isPlaying={true}
|
isPlaying={true}
|
||||||
duration={30}
|
duration={30}
|
||||||
initialRemainingTime={TotpDatabase.calculateCountdown()}
|
initialRemainingTime={TotpDatabase.calculateCountdown()}
|
||||||
|
@ -226,7 +248,11 @@ export default function HomePage() {
|
||||||
size={60}
|
size={60}
|
||||||
onComplete={() => {
|
onComplete={() => {
|
||||||
TotpDatabase.updateToken(db, item.id);
|
TotpDatabase.updateToken(db, item.id);
|
||||||
return {shouldRepeat: true, delay: 0};
|
return {
|
||||||
|
shouldRepeat: true,
|
||||||
|
delay: 0,
|
||||||
|
newInitialRemainingTime: TotpDatabase.calculateCountdown(),
|
||||||
|
};
|
||||||
}}
|
}}
|
||||||
strokeWidth={5}
|
strokeWidth={5}
|
||||||
>
|
>
|
||||||
|
@ -292,7 +318,7 @@ export default function HomePage() {
|
||||||
transform: [{translateX: -OFFSET_X}, {translateY: -OFFSET_Y}],
|
transform: [{translateX: -OFFSET_X}, {translateY: -OFFSET_Y}],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EnterAccountDetails onClose={closeEnterAccountModal} onAdd={handleAddAccount} />
|
<EnterAccountDetails onClose={closeEnterAccountModal} onAdd={handleAddAccount} validateSecret={TotpDatabase.validateSecret} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {Dimensions, Text, View} from "react-native";
|
import {Text, View} from "react-native";
|
||||||
import {IconButton, Modal, Portal} from "react-native-paper";
|
import {IconButton, Portal} from "react-native-paper";
|
||||||
import {Camera, CameraView} from "expo-camera";
|
import {Camera, CameraView} from "expo-camera";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
@ -54,26 +54,9 @@ const ScanQRCode = ({onClose, showScanner, onAdd}) => {
|
||||||
closeOptions();
|
closeOptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
const {width, height} = Dimensions.get("window");
|
|
||||||
const offsetX = width * 0.5;
|
|
||||||
const offsetY = height * 0.5;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{marginTop: "50%", flex: 1}} >
|
<View style={{marginTop: "50%", flex: 1}} >
|
||||||
<Portal>
|
<Portal>
|
||||||
<Modal
|
|
||||||
visible={showScanner}
|
|
||||||
onDismiss={closeOptions}
|
|
||||||
contentContainerStyle={{
|
|
||||||
backgroundColor: "white",
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
position: "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: [{translateX: -offsetX}, {translateY: -offsetY}],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hasPermission === null ? (
|
{hasPermission === null ? (
|
||||||
<Text style={{marginLeft: "20%", marginRight: "20%"}}>Requesting for camera permission</Text>
|
<Text style={{marginLeft: "20%", marginRight: "20%"}}>Requesting for camera permission</Text>
|
||||||
) : hasPermission === false ? (
|
) : hasPermission === false ? (
|
||||||
|
@ -88,7 +71,6 @@ const ScanQRCode = ({onClose, showScanner, onAdd}) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<IconButton icon={"close"} size={40} onPress={onClose} style={{position: "absolute", top: 30, right: 5}} />
|
<IconButton icon={"close"} size={40} onPress={onClose} style={{position: "absolute", top: 30, right: 5}} />
|
||||||
</Modal>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,14 +13,15 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {Button} from "react-native-paper";
|
import {StyleSheet, View, useWindowDimensions} from "react-native";
|
||||||
import {View} from "react-native";
|
import {Button, Surface, Text} from "react-native-paper";
|
||||||
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
|
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
|
||||||
import useStore from "./useStorage";
|
import useStore from "./useStorage";
|
||||||
|
|
||||||
const SettingPage = () => {
|
const SettingPage = () => {
|
||||||
const [showLoginPage, setShowLoginPage] = React.useState(false);
|
const [showLoginPage, setShowLoginPage] = React.useState(false);
|
||||||
const {userInfo, clearAll} = useStore();
|
const {userInfo, clearAll} = useStore();
|
||||||
|
const {width} = useWindowDimensions();
|
||||||
|
|
||||||
const handleCasdoorLogin = () => setShowLoginPage(true);
|
const handleCasdoorLogin = () => setShowLoginPage(true);
|
||||||
const handleHideLoginPage = () => setShowLoginPage(false);
|
const handleHideLoginPage = () => setShowLoginPage(false);
|
||||||
|
@ -30,16 +31,42 @@ const SettingPage = () => {
|
||||||
clearAll();
|
clearAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
surface: {
|
||||||
|
padding: 16,
|
||||||
|
width: width > 600 ? 400 : "100%",
|
||||||
|
maxWidth: 400,
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 24,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: 16,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={styles.container}>
|
||||||
|
<Surface style={styles.surface} elevation={4}>
|
||||||
|
<Text style={styles.title}>Account Settings</Text>
|
||||||
<Button
|
<Button
|
||||||
style={{marginTop: "50%", marginLeft: "20%", marginRight: "20%"}}
|
style={styles.button}
|
||||||
icon={userInfo === null ? "login" : "logout"}
|
icon={userInfo === null ? "login" : "logout"}
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={userInfo === null ? handleCasdoorLogin : handleCasdoorLogout}
|
onPress={userInfo === null ? handleCasdoorLogin : handleCasdoorLogout}
|
||||||
>
|
>
|
||||||
{userInfo === null ? "Login with Casdoor" : "Logout"}
|
{userInfo === null ? "Login with Casdoor" : "Logout"}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Surface>
|
||||||
{showLoginPage && <CasdoorLoginPage onWebviewClose={handleHideLoginPage} />}
|
{showLoginPage && <CasdoorLoginPage onWebviewClose={handleHideLoginPage} />}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -45,6 +45,17 @@ CREATE TABLE accounts (
|
||||||
await db.execAsync(`PRAGMA user_version = ${DATABASE_VERSION}`);
|
await db.execAsync(`PRAGMA user_version = ${DATABASE_VERSION}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function clearDatabase(db) {
|
||||||
|
try {
|
||||||
|
await db.execAsync("DELETE FROM accounts");
|
||||||
|
await db.execAsync("DELETE FROM sqlite_sequence WHERE name='accounts'");
|
||||||
|
await db.execAsync("PRAGMA user_version = 0");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const generateToken = (secretKey) => {
|
const generateToken = (secretKey) => {
|
||||||
if (secretKey !== null && secretKey !== undefined && secretKey !== "") {
|
if (secretKey !== null && secretKey !== undefined && secretKey !== "") {
|
||||||
try {
|
try {
|
||||||
|
@ -147,7 +158,6 @@ export async function getAllAccounts(db) {
|
||||||
const mappedAccount = {
|
const mappedAccount = {
|
||||||
...account,
|
...account,
|
||||||
accountName: account.account_name,
|
accountName: account.account_name,
|
||||||
secretKey: account.secret,
|
|
||||||
};
|
};
|
||||||
return mappedAccount;
|
return mappedAccount;
|
||||||
});
|
});
|
||||||
|
@ -173,10 +183,18 @@ async function updateSyncTimeForAll(db) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateCountdown() {
|
export function calculateCountdown() {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.round(new Date().getTime() / 1000.0);
|
||||||
return 30 - (now % 30);
|
return 30 - (now % 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateSecret(secret) {
|
||||||
|
const base32Regex = /^[A-Z2-7]+=*$/i;
|
||||||
|
if (!secret || secret.length % 8 !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return base32Regex.test(secret);
|
||||||
|
}
|
||||||
|
|
||||||
async function updateLocalDatabase(db, mergedAccounts) {
|
async function updateLocalDatabase(db, mergedAccounts) {
|
||||||
for (const account of mergedAccounts) {
|
for (const account of mergedAccounts) {
|
||||||
if (account.id) {
|
if (account.id) {
|
||||||
|
|
Loading…
Reference in New Issue