From 1875d32fba97fc9c0ad4467a409f1988f80dcb37 Mon Sep 17 00:00:00 2001 From: MSG <59928086+MSghais@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:35:32 +0200 Subject: [PATCH] Import account with a Nostr Private key (#234) * import account screen in login * add nav in create account + yarn format * yarn format * yarn lint * change request --- JoyboyCommunity/src/app/Router.tsx | 2 + .../src/screens/Auth/CreateAccount.tsx | 8 +- .../src/screens/Auth/ImportKeys.tsx | 95 +++++++++++++++++++ JoyboyCommunity/src/screens/Auth/Login.tsx | 22 ++++- JoyboyCommunity/src/types/routes.ts | 6 ++ JoyboyCommunity/src/utils/keypair.ts | 15 +++ 6 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 JoyboyCommunity/src/screens/Auth/ImportKeys.tsx diff --git a/JoyboyCommunity/src/app/Router.tsx b/JoyboyCommunity/src/app/Router.tsx index 18f8ace7..3d5408c0 100644 --- a/JoyboyCommunity/src/app/Router.tsx +++ b/JoyboyCommunity/src/app/Router.tsx @@ -7,6 +7,7 @@ import {StyleSheet, View} from 'react-native'; import {Icon} from '../components'; import {useStyles, useTheme} from '../hooks'; import {CreateAccount} from '../screens/Auth/CreateAccount'; +import {ImportKeys} from '../screens/Auth/ImportKeys'; import {Login} from '../screens/Auth/Login'; import {SaveKeys} from '../screens/Auth/SaveKeys'; import {CreatePost} from '../screens/CreatePost'; @@ -117,6 +118,7 @@ const AuthNavigator: React.FC = () => { {publicKey && } + ); }; diff --git a/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx b/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx index 758fb667..96fc4904 100644 --- a/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx +++ b/JoyboyCommunity/src/screens/Auth/CreateAccount.tsx @@ -4,7 +4,7 @@ import {useState} from 'react'; import {Platform} from 'react-native'; import {LockIcon} from '../../assets/icons'; -import {Button, Input} from '../../components'; +import {Button, Input, TextButton} from '../../components'; import {useNostrContext} from '../../context/NostrContext'; import {useTheme} from '../../hooks'; import {useDialog, useToast} from '../../hooks/modals'; @@ -70,6 +70,10 @@ export const CreateAccount: React.FC = ({navigatio navigation.navigate('SaveKeys', {privateKey, publicKey}); }; + const handleImportKey = () => { + navigation.navigate('ImportKeys'); + }; + return ( @@ -90,6 +94,8 @@ export const CreateAccount: React.FC = ({navigatio > Create Account + + Import account ); }; diff --git a/JoyboyCommunity/src/screens/Auth/ImportKeys.tsx b/JoyboyCommunity/src/screens/Auth/ImportKeys.tsx new file mode 100644 index 00000000..7932f5f7 --- /dev/null +++ b/JoyboyCommunity/src/screens/Auth/ImportKeys.tsx @@ -0,0 +1,95 @@ +import {canUseBiometricAuthentication} from 'expo-secure-store'; +import {useState} from 'react'; +import {Platform} from 'react-native'; + +import {LockIcon} from '../../assets/icons'; +import {Button, Input} from '../../components'; +import {useTheme} from '../../hooks'; +import {useDialog, useToast} from '../../hooks/modals'; +import {Auth} from '../../modules/Auth'; +import {AuthImportKeysScreenProps} from '../../types'; +import {getPublicKeyFromSecret, isValidNostrPrivateKey} from '../../utils/keypair'; +import {storePassword, storePrivateKey, storePublicKey} from '../../utils/storage'; + +export const ImportKeys: React.FC = ({navigation}) => { + const {theme} = useTheme(); + + const [password, setPassword] = useState(''); + const [privateKey, setPrivateKey] = useState(''); + const {showToast} = useToast(); + const {showDialog, hideDialog} = useDialog(); + + const handleImportAccount = async () => { + if (!password) { + showToast({type: 'error', title: 'Password is required'}); + return; + } + + if (!privateKey) { + showToast({type: 'error', title: 'Private key to import is required'}); + return; + } + + if (!isValidNostrPrivateKey(privateKey)) { + showToast({type: 'error', title: 'Private key not valid'}); + return; + } + await storePrivateKey(privateKey, password); + const publicKey = getPublicKeyFromSecret(privateKey); + await storePublicKey(publicKey); + + const biometySupported = Platform.OS !== 'web' && canUseBiometricAuthentication(); + if (biometySupported) { + showDialog({ + title: 'Easy login', + description: 'Would you like to use biometrics to login?', + buttons: [ + { + type: 'primary', + label: 'Yes', + onPress: async () => { + await storePassword(password); + hideDialog(); + }, + }, + { + type: 'default', + label: 'No', + onPress: hideDialog, + }, + ], + }); + } + + navigation.navigate('SaveKeys', {privateKey, publicKey}); + }; + + return ( + + } + value={privateKey} + onChangeText={setPrivateKey} + secureTextEntry + placeholder="Private key" + /> + + } + value={password} + onChangeText={setPassword} + secureTextEntry + placeholder="Password" + /> + + + + ); +}; diff --git a/JoyboyCommunity/src/screens/Auth/Login.tsx b/JoyboyCommunity/src/screens/Auth/Login.tsx index 81b2041e..6bb5bd45 100644 --- a/JoyboyCommunity/src/screens/Auth/Login.tsx +++ b/JoyboyCommunity/src/screens/Auth/Login.tsx @@ -79,6 +79,25 @@ export const Login: React.FC = ({navigation}) => { }); }; + const handleImportAccount = () => { + showDialog({ + title: 'WARNING', + description: + 'Creating a new account will delete your current account. Are you sure you want to continue?', + buttons: [ + { + type: 'primary', + label: 'Continue', + onPress: () => { + navigation.navigate('ImportKeys'); + hideDialog(); + }, + }, + {type: 'default', label: 'Cancel', onPress: hideDialog}, + ], + }); + }; + return ( = ({navigation}) => { - Create Account + + Import Account ); }; diff --git a/JoyboyCommunity/src/types/routes.ts b/JoyboyCommunity/src/types/routes.ts index cabd19b8..68225b2c 100644 --- a/JoyboyCommunity/src/types/routes.ts +++ b/JoyboyCommunity/src/types/routes.ts @@ -14,6 +14,7 @@ export type AuthStackParams = { privateKey: string; publicKey: string; }; + ImportKeys: undefined; }; export type MainStackParams = { @@ -49,6 +50,11 @@ export type AuthSaveKeysScreenProps = CompositeScreenProps< NativeStackScreenProps >; +export type AuthImportKeysScreenProps = CompositeScreenProps< + NativeStackScreenProps, + NativeStackScreenProps +>; + // Home Stack export type HomeNavigationProp = NativeStackNavigationProp; diff --git a/JoyboyCommunity/src/utils/keypair.ts b/JoyboyCommunity/src/utils/keypair.ts index 34a39d83..3a6818a0 100644 --- a/JoyboyCommunity/src/utils/keypair.ts +++ b/JoyboyCommunity/src/utils/keypair.ts @@ -28,3 +28,18 @@ export const getPublicKeyFromSecret = (privateKey: string) => { throw new Error('Failed to get public key from secret key'); } }; + +export const isValidNostrPrivateKey = (key: string): boolean => { + // Check if the string is exactly 64 characters long + if (key.length !== 64) { + return false; + } + + // Check if the string contains only hexadecimal characters + const hexRegex = /^[0-9a-fA-F]+$/; + if (!hexRegex.test(key)) { + return false; + } + + return true; +};