PIN authentication error handling

This commit is contained in:
Michele Balistreri 2024-09-24 12:28:16 +02:00
parent de81d72e60
commit e910e0b6c4
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
6 changed files with 105 additions and 30 deletions

View File

@ -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<boolean>(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) {
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,13 +119,19 @@ 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);
}
}
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 (
<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.Loading && <MnemonicScreen onPressFunc={loadMnemonic} pinRequired={pinRef.current ? false : true} onCancelFunc={cancel}></MnemonicScreen>}
{step == Step.Authentication && <AuthenticationScreen onPressFunc={() => {} } onCancelFunc={cancel}></AuthenticationScreen>}
{step == Step.Loading && <MnemonicScreen pinRequired={pinRef.current ? false : true} pinRetryCounter={pinDisplayCounter()} onPressFunc={loadMnemonic} onCancelFunc={cancel}></MnemonicScreen>}
{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>
</SafeAreaView>
);

View File

@ -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<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 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,16 +36,17 @@ const Dialpad: FC<DialpadProps> = props => {
}
const onNext = () => {
if (!onNextFunc(code.join(''))) {
setWrongRepeat(!onNextFunc(code.join('')));
setCode([]);
}
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.textContainer}>
<Text style={styles.pinText}>{prompt}</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} />
<DialpadKeypad dialPadContent={dialPadContent} dialPadSize={dialPadSize} dialPadTextSize={dialPadTextSize} updateCodeFunc={updateCode} code={code}/>
</View>
@ -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%',

View File

@ -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<AuthenticationScreenProps> = props => {
const {onPressFunc, onCancelFunc} = props;
const {pinRetryCounter, onPressFunc, onCancelFunc} = props;
return (
<View>

View File

@ -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;

View File

@ -24,23 +24,19 @@ const InitializationScreen: FC<InitializationScreenProps> = 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;
}
return false;
}
return (
<View>
{ step == PinSteps.InsertPin && <Dialpad prompt={"Choose PIN"} onCancelFunc={onCancelFunc} onNextFunc={insertPin}></Dialpad> }
{ step == PinSteps.RepeatPin && <Dialpad prompt={"Repeat PIN"} onCancelFunc={() => setStep(PinSteps.InsertPin)} onNextFunc={submitPin}></Dialpad> }
{ step == PinSteps.InsertPin && <Dialpad pinRetryCounter={-1} prompt={"Choose PIN"} onCancelFunc={onCancelFunc} onNextFunc={insertPin}></Dialpad> }
{ step == PinSteps.RepeatPin && <Dialpad pinRetryCounter={-1} prompt={"Repeat PIN"} onCancelFunc={() => setStep(PinSteps.InsertPin)} onNextFunc={submitPin}></Dialpad> }
</View>
)};

View File

@ -11,12 +11,13 @@ enum LoadMnemonicSteps {
type MnemonicScreenProps = {
pinRequired: boolean;
pinRetryCounter: number;
onPressFunc: (mn: string, pin?: string) => void;
onCancelFunc: () => void;
};
const MnemonicScreen: FC<MnemonicScreenProps> = 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<MnemonicScreenProps> = props => {
<Text style={styles.errorMessage}>{errMessage}</Text>
</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>
)};