From e910e0b6c497a08c430a50307d2b218747f6d52a Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Tue, 24 Sep 2024 12:28:16 +0200 Subject: [PATCH] PIN authentication error handling --- src/Main.tsx | 50 ++++++++++++++----- src/components/Dialpad.tsx | 19 +++++-- src/components/steps/AuthenticationScreen.tsx | 3 +- src/components/steps/FactoryResetScreen.tsx | 42 ++++++++++++++++ src/components/steps/InitializationScreen.tsx | 16 +++--- src/components/steps/MnemonicScreen.tsx | 5 +- 6 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 src/components/steps/FactoryResetScreen.tsx diff --git a/src/Main.tsx b/src/Main.tsx index 12d4db7..724bc0d 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -6,6 +6,7 @@ import DiscoveryScreen from './components/steps/DiscoveryScreen'; import InitializationScreen from './components/steps/InitializationScreen'; import MnemonicScreen from './components/steps/MnemonicScreen'; import AuthenticationScreen from './components/steps/AuthenticationScreen'; +import FactoryResetScreen from './components/steps/FactoryResetScreen'; import NFCModal from './NFCModal'; //@ts-ignore @@ -20,8 +21,11 @@ enum Step { } const Main = () => { + const PIN_MAX_RETRY = 3; + const [isModalVisible, setIsModalVisible] = useState(false); const [step, setStep] = useState(Step.Discovery); + const [pinCounter, setPinCounter] = useState(PIN_MAX_RETRY); const didMount = useRef(false); const stepRef = useRef(step); @@ -41,14 +45,18 @@ const Main = () => { } 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 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) { - return parseInt(matches[1]) + if (matches) { + if (matches[1] !== undefined) { + return parseInt(matches[1]) + } else { + return parseInt(matches[2]) + } } return null @@ -59,12 +67,18 @@ const Main = () => { return; } + var newPinCounter = pinCounter; + try { const appInfo = await Keycard.getApplicationInfo(); if (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) { case Step.Discovery: if (appInfo["initialized?"]) { @@ -105,14 +119,20 @@ const Main = () => { const pinRetryCounter = wrongPINCounter(err.message); if (pinRetryCounter !== null) { - //TODO: better handling + pinRef.current = "" + newPinCounter = 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(""); setIsModalVisible(false); } @@ -155,10 +175,9 @@ const Main = () => { setIsModalVisible(true); } - const factoryResetCard = async () => { + const startFactoryReset = async () => { stepRef.current = Step.FactoryReset; setStep(Step.FactoryReset); - return connectCard(); } const initPin = async (p: string) => { @@ -180,12 +199,17 @@ const Main = () => { setStep(Step.Discovery); } + const pinDisplayCounter = () => { + return pinCounter == PIN_MAX_RETRY ? -1 : pinCounter; + } + return ( - {step == Step.Discovery && } + {step == Step.Discovery && } {step == Step.Initialization && } - {step == Step.Loading && } - {step == Step.Authentication && {} } onCancelFunc={cancel}>} + {step == Step.Loading && } + {step == Step.Authentication && {} } onCancelFunc={cancel}>} + {step == Step.FactoryReset && } ); diff --git a/src/components/Dialpad.tsx b/src/components/Dialpad.tsx index 96b452d..d892e8c 100644 --- a/src/components/Dialpad.tsx +++ b/src/components/Dialpad.tsx @@ -7,17 +7,19 @@ import DialpadPin from "./DialpadPin"; const { width } = Dimensions.get("window"); type DialpadProps = { + pinRetryCounter: number; prompt: string; onCancelFunc: () => void; onNextFunc: (p?: any) => boolean; }; const Dialpad: FC = 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 dialPadSize = width * 0.2; const dialPadTextSize = dialPadSize * 0.36; const [code, setCode] = useState([]); + const [wrongRepeat, setWrongRepeat] = useState(false); const pinLength = 6; const pinContainerSize = width / 2; const pinSize = (pinContainerSize / pinLength) + 8; @@ -34,9 +36,8 @@ const Dialpad: FC = props => { } const onNext = () => { - if (!onNextFunc(code.join(''))) { - setCode([]); - } + setWrongRepeat(!onNextFunc(code.join(''))); + setCode([]); } return ( @@ -44,6 +45,8 @@ const Dialpad: FC = props => { {prompt} Enter your secure six-digit code + {pinRetryCounter >= 0 && Remaining attempts: {pinRetryCounter}} + {wrongRepeat && The PINs do not match} @@ -76,6 +79,14 @@ const styles = StyleSheet.create({ color: "white", marginVertical: 30, }, + + pinAttempts: { + fontSize: 18, + fontFamily: 'Inconsolata Regular', + color: "white", + marginTop: -10, + marginBottom: 30 + }, btnContainer: { flexDirection: 'row', width: '74%', diff --git a/src/components/steps/AuthenticationScreen.tsx b/src/components/steps/AuthenticationScreen.tsx index 1451d6c..f08759f 100644 --- a/src/components/steps/AuthenticationScreen.tsx +++ b/src/components/steps/AuthenticationScreen.tsx @@ -3,12 +3,13 @@ import { StyleSheet, Text, View } from "react-native"; import Button from "../Button"; type AuthenticationScreenProps = { + pinRetryCounter: number; onPressFunc: () => void; onCancelFunc: () => void; }; const AuthenticationScreen: FC = props => { - const {onPressFunc, onCancelFunc} = props; + const {pinRetryCounter, onPressFunc, onCancelFunc} = props; return ( diff --git a/src/components/steps/FactoryResetScreen.tsx b/src/components/steps/FactoryResetScreen.tsx new file mode 100644 index 0000000..138baac --- /dev/null +++ b/src/components/steps/FactoryResetScreen.tsx @@ -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 = props => { + const {pinRetryCounter, onPressFunc, onCancelFunc} = props; + + return ( + + + Factory reset + This will remove the keys from your card. Are you sure? + + + + + )}; + +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; \ No newline at end of file diff --git a/src/components/steps/InitializationScreen.tsx b/src/components/steps/InitializationScreen.tsx index 45ccaf6..c9ef62b 100644 --- a/src/components/steps/InitializationScreen.tsx +++ b/src/components/steps/InitializationScreen.tsx @@ -24,23 +24,19 @@ const InitializationScreen: FC = props => { return true; } - const isSamePin = (p: string) => { - return pin === p; - } - const submitPin = (p: string) => { - if(!isSamePin(p)) { - return false; + if(pin == p) { + onPressFunc(pin); + return true; } - onPressFunc(pin); - return true; + return false; } return ( - { step == PinSteps.InsertPin && } - { step == PinSteps.RepeatPin && setStep(PinSteps.InsertPin)} onNextFunc={submitPin}> } + { step == PinSteps.InsertPin && } + { step == PinSteps.RepeatPin && setStep(PinSteps.InsertPin)} onNextFunc={submitPin}> } )}; diff --git a/src/components/steps/MnemonicScreen.tsx b/src/components/steps/MnemonicScreen.tsx index 026681a..bd3d09b 100644 --- a/src/components/steps/MnemonicScreen.tsx +++ b/src/components/steps/MnemonicScreen.tsx @@ -11,12 +11,13 @@ enum LoadMnemonicSteps { type MnemonicScreenProps = { pinRequired: boolean; + pinRetryCounter: number; onPressFunc: (mn: string, pin?: string) => void; onCancelFunc: () => void; }; const MnemonicScreen: FC = props => { - const {pinRequired, onPressFunc, onCancelFunc} = props; + const {pinRequired, pinRetryCounter, onPressFunc, onCancelFunc} = props; const [mnemonic, setMnemonic] = useState(''); const [errMessage, setErrMessage] = useState(''); const [step, setStep] = useState(LoadMnemonicSteps.InsertMnemonic); @@ -63,7 +64,7 @@ const MnemonicScreen: FC = props => { {errMessage} } - { step == LoadMnemonicSteps.InsertPin && setStep(LoadMnemonicSteps.InsertMnemonic)} onNextFunc={submitPin}>} + { step == LoadMnemonicSteps.InsertPin && setStep(LoadMnemonicSteps.InsertMnemonic)} onNextFunc={submitPin}>} )};