PIN authentication error handling
This commit is contained in:
parent
de81d72e60
commit
e910e0b6c4
50
src/Main.tsx
50
src/Main.tsx
|
@ -6,6 +6,7 @@ import DiscoveryScreen from './components/steps/DiscoveryScreen';
|
||||||
import InitializationScreen from './components/steps/InitializationScreen';
|
import InitializationScreen from './components/steps/InitializationScreen';
|
||||||
import MnemonicScreen from './components/steps/MnemonicScreen';
|
import MnemonicScreen from './components/steps/MnemonicScreen';
|
||||||
import AuthenticationScreen from './components/steps/AuthenticationScreen';
|
import AuthenticationScreen from './components/steps/AuthenticationScreen';
|
||||||
|
import FactoryResetScreen from './components/steps/FactoryResetScreen';
|
||||||
import NFCModal from './NFCModal';
|
import NFCModal from './NFCModal';
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
@ -20,8 +21,11 @@ enum Step {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
|
const PIN_MAX_RETRY = 3;
|
||||||
|
|
||||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||||||
const [step, setStep] = useState(Step.Discovery);
|
const [step, setStep] = useState(Step.Discovery);
|
||||||
|
const [pinCounter, setPinCounter] = useState(PIN_MAX_RETRY);
|
||||||
|
|
||||||
const didMount = useRef(false);
|
const didMount = useRef(false);
|
||||||
const stepRef = useRef(step);
|
const stepRef = useRef(step);
|
||||||
|
@ -41,14 +45,18 @@ const Main = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCardLost = (err) => {
|
const isCardLost = (err) => {
|
||||||
return (err == "Tag was lost.") || err.includes("NFCError:100");
|
return (err == "Tag was lost.") || /"NFCError:10[02]"/.test(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrongPINCounter = (err) => {
|
const wrongPINCounter = (err) => {
|
||||||
const matches = /Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)/.exec(err)
|
const matches = /Unexpected error SW, 0x63C(\d)|wrongPIN\(retryCounter: (\d+)\)/.exec(err)
|
||||||
|
|
||||||
if (matches && matches.length == 2) {
|
if (matches) {
|
||||||
return parseInt(matches[1])
|
if (matches[1] !== undefined) {
|
||||||
|
return parseInt(matches[1])
|
||||||
|
} else {
|
||||||
|
return parseInt(matches[2])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -59,12 +67,18 @@ const Main = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newPinCounter = pinCounter;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const appInfo = await Keycard.getApplicationInfo();
|
const appInfo = await Keycard.getApplicationInfo();
|
||||||
if (appInfo["new-pairing"]) {
|
if (appInfo["new-pairing"]) {
|
||||||
await addPairing(appInfo["instance-uid"], appInfo["new-pairing"]);
|
await addPairing(appInfo["instance-uid"], appInfo["new-pairing"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appInfo["pin-retry-counter"] !== null) {
|
||||||
|
newPinCounter = appInfo["pin-retry-counter"];
|
||||||
|
}
|
||||||
|
|
||||||
switch (stepRef.current) {
|
switch (stepRef.current) {
|
||||||
case Step.Discovery:
|
case Step.Discovery:
|
||||||
if (appInfo["initialized?"]) {
|
if (appInfo["initialized?"]) {
|
||||||
|
@ -105,14 +119,20 @@ const Main = () => {
|
||||||
const pinRetryCounter = wrongPINCounter(err.message);
|
const pinRetryCounter = wrongPINCounter(err.message);
|
||||||
|
|
||||||
if (pinRetryCounter !== null) {
|
if (pinRetryCounter !== null) {
|
||||||
//TODO: better handling
|
pinRef.current = ""
|
||||||
|
newPinCounter = pinRetryCounter;
|
||||||
console.log("wrong PIN. Retry counter: " + pinRetryCounter);
|
console.log("wrong PIN. Retry counter: " + pinRetryCounter);
|
||||||
return;
|
} else {
|
||||||
|
console.log(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newPinCounter == 0) {
|
||||||
|
setStep(Step.FactoryReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPinCounter(newPinCounter);
|
||||||
|
|
||||||
await Keycard.stopNFC("");
|
await Keycard.stopNFC("");
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
}
|
}
|
||||||
|
@ -155,10 +175,9 @@ const Main = () => {
|
||||||
setIsModalVisible(true);
|
setIsModalVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const factoryResetCard = async () => {
|
const startFactoryReset = async () => {
|
||||||
stepRef.current = Step.FactoryReset;
|
stepRef.current = Step.FactoryReset;
|
||||||
setStep(Step.FactoryReset);
|
setStep(Step.FactoryReset);
|
||||||
return connectCard();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPin = async (p: string) => {
|
const initPin = async (p: string) => {
|
||||||
|
@ -180,12 +199,17 @@ const Main = () => {
|
||||||
setStep(Step.Discovery);
|
setStep(Step.Discovery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pinDisplayCounter = () => {
|
||||||
|
return pinCounter == PIN_MAX_RETRY ? -1 : pinCounter;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
{step == Step.Discovery && <DiscoveryScreen onPressFunc={connectCard} onFactoryResetFunc={factoryResetCard}></DiscoveryScreen>}
|
{step == Step.Discovery && <DiscoveryScreen onPressFunc={connectCard} onFactoryResetFunc={startFactoryReset}></DiscoveryScreen>}
|
||||||
{step == Step.Initialization && <InitializationScreen onPressFunc={initPin} onCancelFunc={cancel}></InitializationScreen>}
|
{step == Step.Initialization && <InitializationScreen onPressFunc={initPin} onCancelFunc={cancel}></InitializationScreen>}
|
||||||
{step == Step.Loading && <MnemonicScreen onPressFunc={loadMnemonic} pinRequired={pinRef.current ? false : true} onCancelFunc={cancel}></MnemonicScreen>}
|
{step == Step.Loading && <MnemonicScreen pinRequired={pinRef.current ? false : true} pinRetryCounter={pinDisplayCounter()} onPressFunc={loadMnemonic} onCancelFunc={cancel}></MnemonicScreen>}
|
||||||
{step == Step.Authentication && <AuthenticationScreen onPressFunc={() => {} } onCancelFunc={cancel}></AuthenticationScreen>}
|
{step == Step.Authentication && <AuthenticationScreen pinRetryCounter={pinDisplayCounter()} onPressFunc={() => {} } onCancelFunc={cancel}></AuthenticationScreen>}
|
||||||
|
{step == Step.FactoryReset && <FactoryResetScreen pinRetryCounter={pinDisplayCounter()} onPressFunc={connectCard} onCancelFunc={cancel}></FactoryResetScreen>}
|
||||||
<NFCModal isVisible={isModalVisible} onChangeFunc={setIsModalVisible}></NFCModal>
|
<NFCModal isVisible={isModalVisible} onChangeFunc={setIsModalVisible}></NFCModal>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,17 +7,19 @@ import DialpadPin from "./DialpadPin";
|
||||||
const { width } = Dimensions.get("window");
|
const { width } = Dimensions.get("window");
|
||||||
|
|
||||||
type DialpadProps = {
|
type DialpadProps = {
|
||||||
|
pinRetryCounter: number;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
onCancelFunc: () => void;
|
onCancelFunc: () => void;
|
||||||
onNextFunc: (p?: any) => boolean;
|
onNextFunc: (p?: any) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Dialpad: FC<DialpadProps> = props => {
|
const Dialpad: FC<DialpadProps> = props => {
|
||||||
const {prompt, onNextFunc, onCancelFunc} = props;
|
const {pinRetryCounter, prompt, onNextFunc, onCancelFunc} = props;
|
||||||
const dialPadContent = [1, 2, 3, 4, 5, 6, 7, 8, 9, "", 0, "X"];
|
const dialPadContent = [1, 2, 3, 4, 5, 6, 7, 8, 9, "", 0, "X"];
|
||||||
const dialPadSize = width * 0.2;
|
const dialPadSize = width * 0.2;
|
||||||
const dialPadTextSize = dialPadSize * 0.36;
|
const dialPadTextSize = dialPadSize * 0.36;
|
||||||
const [code, setCode] = useState([]);
|
const [code, setCode] = useState([]);
|
||||||
|
const [wrongRepeat, setWrongRepeat] = useState(false);
|
||||||
const pinLength = 6;
|
const pinLength = 6;
|
||||||
const pinContainerSize = width / 2;
|
const pinContainerSize = width / 2;
|
||||||
const pinSize = (pinContainerSize / pinLength) + 8;
|
const pinSize = (pinContainerSize / pinLength) + 8;
|
||||||
|
@ -34,9 +36,8 @@ const Dialpad: FC<DialpadProps> = props => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onNext = () => {
|
const onNext = () => {
|
||||||
if (!onNextFunc(code.join(''))) {
|
setWrongRepeat(!onNextFunc(code.join('')));
|
||||||
setCode([]);
|
setCode([]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -44,6 +45,8 @@ const Dialpad: FC<DialpadProps> = props => {
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<Text style={styles.pinText}>{prompt}</Text>
|
<Text style={styles.pinText}>{prompt}</Text>
|
||||||
<Text style={styles.pinSubText}>Enter your secure six-digit code</Text>
|
<Text style={styles.pinSubText}>Enter your secure six-digit code</Text>
|
||||||
|
{pinRetryCounter >= 0 && <Text style={styles.pinAttempts}>Remaining attempts: {pinRetryCounter}</Text>}
|
||||||
|
{wrongRepeat && <Text style={styles.pinAttempts}>The PINs do not match</Text>}
|
||||||
<DialpadPin pinLength={pinLength} pinSize={pinSize} code={code} dialPadContent={dialPadContent} />
|
<DialpadPin pinLength={pinLength} pinSize={pinSize} code={code} dialPadContent={dialPadContent} />
|
||||||
<DialpadKeypad dialPadContent={dialPadContent} dialPadSize={dialPadSize} dialPadTextSize={dialPadTextSize} updateCodeFunc={updateCode} code={code}/>
|
<DialpadKeypad dialPadContent={dialPadContent} dialPadSize={dialPadSize} dialPadTextSize={dialPadTextSize} updateCodeFunc={updateCode} code={code}/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -76,6 +79,14 @@ const styles = StyleSheet.create({
|
||||||
color: "white",
|
color: "white",
|
||||||
marginVertical: 30,
|
marginVertical: 30,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pinAttempts: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Inconsolata Regular',
|
||||||
|
color: "white",
|
||||||
|
marginTop: -10,
|
||||||
|
marginBottom: 30
|
||||||
|
},
|
||||||
btnContainer: {
|
btnContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '74%',
|
width: '74%',
|
||||||
|
|
|
@ -3,12 +3,13 @@ import { StyleSheet, Text, View } from "react-native";
|
||||||
import Button from "../Button";
|
import Button from "../Button";
|
||||||
|
|
||||||
type AuthenticationScreenProps = {
|
type AuthenticationScreenProps = {
|
||||||
|
pinRetryCounter: number;
|
||||||
onPressFunc: () => void;
|
onPressFunc: () => void;
|
||||||
onCancelFunc: () => void;
|
onCancelFunc: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthenticationScreen: FC<AuthenticationScreenProps> = props => {
|
const AuthenticationScreen: FC<AuthenticationScreenProps> = props => {
|
||||||
const {onPressFunc, onCancelFunc} = props;
|
const {pinRetryCounter, onPressFunc, onCancelFunc} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {FC } from "react";
|
||||||
|
import { StyleSheet, Text, View } from "react-native";
|
||||||
|
import Button from "../Button";
|
||||||
|
|
||||||
|
type FactoryResetScreenProps = {
|
||||||
|
pinRetryCounter: number;
|
||||||
|
onPressFunc: () => void;
|
||||||
|
onCancelFunc: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FactoryResetScreen: FC<FactoryResetScreenProps> = props => {
|
||||||
|
const {pinRetryCounter, onPressFunc, onCancelFunc} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.heading}>Factory reset</Text>
|
||||||
|
<Text style={styles.prompt}>This will remove the keys from your card. Are you sure?</Text>
|
||||||
|
<Button label="Next" disabled={false} btnColor="#199515" btnBorderColor="#199515" btnFontSize={17} btnBorderWidth={1} btnWidth="100%" onChangeFunc={onPressFunc} btnJustifyContent='center'></Button>
|
||||||
|
<Button label="Cancel" disabled={false} btnColor="#199515" btnBorderColor="#199515" btnFontSize={17} btnBorderWidth={1} btnWidth="100%" onChangeFunc={onCancelFunc} btnJustifyContent='center'></Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
heading: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 30,
|
||||||
|
fontFamily: 'Inconsolata Medium',
|
||||||
|
color: '#199515',
|
||||||
|
marginTop: '50%'
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Inconsolata Regular',
|
||||||
|
color: 'white',
|
||||||
|
marginTop: '5%'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default FactoryResetScreen;
|
|
@ -24,23 +24,19 @@ const InitializationScreen: FC<InitializationScreenProps> = props => {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSamePin = (p: string) => {
|
|
||||||
return pin === p;
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitPin = (p: string) => {
|
const submitPin = (p: string) => {
|
||||||
if(!isSamePin(p)) {
|
if(pin == p) {
|
||||||
return false;
|
onPressFunc(pin);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressFunc(pin);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
{ step == PinSteps.InsertPin && <Dialpad prompt={"Choose PIN"} onCancelFunc={onCancelFunc} onNextFunc={insertPin}></Dialpad> }
|
{ step == PinSteps.InsertPin && <Dialpad pinRetryCounter={-1} prompt={"Choose PIN"} onCancelFunc={onCancelFunc} onNextFunc={insertPin}></Dialpad> }
|
||||||
{ step == PinSteps.RepeatPin && <Dialpad prompt={"Repeat PIN"} onCancelFunc={() => setStep(PinSteps.InsertPin)} onNextFunc={submitPin}></Dialpad> }
|
{ step == PinSteps.RepeatPin && <Dialpad pinRetryCounter={-1} prompt={"Repeat PIN"} onCancelFunc={() => setStep(PinSteps.InsertPin)} onNextFunc={submitPin}></Dialpad> }
|
||||||
</View>
|
</View>
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,13 @@ enum LoadMnemonicSteps {
|
||||||
|
|
||||||
type MnemonicScreenProps = {
|
type MnemonicScreenProps = {
|
||||||
pinRequired: boolean;
|
pinRequired: boolean;
|
||||||
|
pinRetryCounter: number;
|
||||||
onPressFunc: (mn: string, pin?: string) => void;
|
onPressFunc: (mn: string, pin?: string) => void;
|
||||||
onCancelFunc: () => void;
|
onCancelFunc: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MnemonicScreen: FC<MnemonicScreenProps> = props => {
|
const MnemonicScreen: FC<MnemonicScreenProps> = props => {
|
||||||
const {pinRequired, onPressFunc, onCancelFunc} = props;
|
const {pinRequired, pinRetryCounter, onPressFunc, onCancelFunc} = props;
|
||||||
const [mnemonic, setMnemonic] = useState('');
|
const [mnemonic, setMnemonic] = useState('');
|
||||||
const [errMessage, setErrMessage] = useState('');
|
const [errMessage, setErrMessage] = useState('');
|
||||||
const [step, setStep] = useState(LoadMnemonicSteps.InsertMnemonic);
|
const [step, setStep] = useState(LoadMnemonicSteps.InsertMnemonic);
|
||||||
|
@ -63,7 +64,7 @@ const MnemonicScreen: FC<MnemonicScreenProps> = props => {
|
||||||
<Text style={styles.errorMessage}>{errMessage}</Text>
|
<Text style={styles.errorMessage}>{errMessage}</Text>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
{ step == LoadMnemonicSteps.InsertPin && <Dialpad prompt={"Enter PIN"} onCancelFunc={() => setStep(LoadMnemonicSteps.InsertMnemonic)} onNextFunc={submitPin}></Dialpad>}
|
{ step == LoadMnemonicSteps.InsertPin && <Dialpad pinRetryCounter={pinRetryCounter} prompt={"Enter PIN"} onCancelFunc={() => setStep(LoadMnemonicSteps.InsertMnemonic)} onNextFunc={submitPin}></Dialpad>}
|
||||||
</View>
|
</View>
|
||||||
)};
|
)};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue