Skip to content

Commit

Permalink
feat: setup biometrics
Browse files Browse the repository at this point in the history
  • Loading branch information
jimcase committed May 7, 2024
1 parent 0d18b3b commit d1d707c
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 84 deletions.
2 changes: 2 additions & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ android {

apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':aparajita-capacitor-biometric-auth')
implementation project(':aparajita-capacitor-secure-storage')
implementation project(':capacitor-community-barcode-scanner')
implementation project(':capacitor-community-sqlite')
implementation project(':capacitor-app')
implementation project(':capacitor-clipboard')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-preferences')
Expand Down
6 changes: 6 additions & 0 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')

include ':aparajita-capacitor-biometric-auth'
project(':aparajita-capacitor-biometric-auth').projectDir = new File('../node_modules/@aparajita/capacitor-biometric-auth/android')

include ':aparajita-capacitor-secure-storage'
project(':aparajita-capacitor-secure-storage').projectDir = new File('../node_modules/@aparajita/capacitor-secure-storage/android')

Expand All @@ -11,6 +14,9 @@ project(':capacitor-community-barcode-scanner').projectDir = new File('../node_m
include ':capacitor-community-sqlite'
project(':capacitor-community-sqlite').projectDir = new File('../node_modules/@capacitor-community/sqlite/android')

include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')

include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android')

Expand Down
2 changes: 2 additions & 0 deletions ios/App/App/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,7 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSFaceIDUsageDescription</key>
<string>This app uses Face ID to secure your data and provide faster access.</string>
</dict>
</plist>
2 changes: 2 additions & 0 deletions ios/App/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'AparajitaCapacitorBiometricAuth', :path => '../../node_modules/@aparajita/capacitor-biometric-auth'
pod 'AparajitaCapacitorSecureStorage', :path => '../../node_modules/@aparajita/capacitor-secure-storage'
pod 'CapacitorCommunityBarcodeScanner', :path => '../../node_modules/@capacitor-community/barcode-scanner'
pod 'CapacitorCommunitySqlite', :path => '../../node_modules/@capacitor-community/sqlite'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
Expand Down
16 changes: 14 additions & 2 deletions ios/App/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
PODS:
- AparajitaCapacitorBiometricAuth (8.0.0):
- Capacitor
- AparajitaCapacitorSecureStorage (5.2.0):
- Capacitor
- KeychainSwift (~> 21.0)
- Capacitor (5.7.4):
- CapacitorCordova
- CapacitorApp (5.0.7):
- Capacitor
- CapacitorClipboard (5.0.7):
- Capacitor
- CapacitorCommunityBarcodeScanner (4.0.1):
Expand Down Expand Up @@ -34,8 +38,10 @@ PODS:
- ZIPFoundation (0.9.16)

DEPENDENCIES:
- "AparajitaCapacitorBiometricAuth (from `../../node_modules/@aparajita/capacitor-biometric-auth`)"
- "AparajitaCapacitorSecureStorage (from `../../node_modules/@aparajita/capacitor-secure-storage`)"
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
- "CapacitorCommunityBarcodeScanner (from `../../node_modules/@capacitor-community/barcode-scanner`)"
- "CapacitorCommunitySqlite (from `../../node_modules/@capacitor-community/sqlite`)"
Expand All @@ -54,10 +60,14 @@ SPEC REPOS:
- ZIPFoundation

EXTERNAL SOURCES:
AparajitaCapacitorBiometricAuth:
:path: "../../node_modules/@aparajita/capacitor-biometric-auth"
AparajitaCapacitorSecureStorage:
:path: "../../node_modules/@aparajita/capacitor-secure-storage"
Capacitor:
:path: "../../node_modules/@capacitor/ios"
CapacitorApp:
:path: "../../node_modules/@capacitor/app"
CapacitorClipboard:
:path: "../../node_modules/@capacitor/clipboard"
CapacitorCommunityBarcodeScanner:
Expand All @@ -80,8 +90,10 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@capacitor/status-bar"

SPEC CHECKSUMS:
AparajitaCapacitorBiometricAuth: b30db5f1a0d68d14c3a46d2ed95a009703eda3f5
AparajitaCapacitorSecureStorage: 2f3a28fa7cf90053bf5b56834607349cbf9f24ca
Capacitor: 4fe9adf012caceb4c71ffea2f1f4d005cdcbeea7
CapacitorApp: 17fecd0e6cb23feafac7eb0939417389038b0979
CapacitorClipboard: 45e5e25f2271f98712985d422776cdc5a779cca1
CapacitorCommunityBarcodeScanner: 7feb206489c8555a8ca0c74c57ddf49ead774eb8
CapacitorCommunitySqlite: 6e2754dde799d618a8e75e409ccc67ec9c189460
Expand All @@ -96,6 +108,6 @@ SPEC CHECKSUMS:
SQLCipher: f2e96b3822e3006b379181a0e4fd145f6de29b56
ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced

PODFILE CHECKSUM: 90423924fbe85f2efaaf46e65be5f444629b2f3a
PODFILE CHECKSUM: 376fee91162e07308901c11bbed7e5bb1df1add6

COCOAPODS: 1.15.2
COCOAPODS: 1.14.3
57 changes: 47 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"prepare": "husky install"
},
"dependencies": {
"@aparajita/capacitor-biometric-auth": "^8.0.0",
"@aparajita/capacitor-secure-storage": "^5.0.0",
"@capacitor-community/barcode-scanner": "^4.0.1",
"@capacitor-community/sqlite": "^5.5.0",
Expand Down
10 changes: 7 additions & 3 deletions src/ui/components/AppWrapper/hooks/useActivityTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { useEffect, useRef, useState } from "react";
import { App } from "@capacitor/app";
import { useAppDispatch } from "../../../../store/hooks";
import { logout } from "../../../../store/reducers/stateCache";
import { useBiometricAuth } from "../../../hooks/useBiometrics";

const timeout = process.env.NODE_ENV === "development" ? 3600000 : 60000; // 1h/1min
const timeout = process.env.NODE_ENV === "development" ? 6000 : 60000; //3600000 1h/1min
const pauseTimeout = timeout / 2;
const useActivityTimer = () => {
const dispatch = useAppDispatch();
const [pauseTimestamp, setPauseTimestamp] = useState(0);
const [pauseTimestamp, setPauseTimestamp] = useState(new Date().getTime());
const timer = useRef<NodeJS.Timeout | null>(null);

const clearTimer = () => {
Expand Down Expand Up @@ -45,7 +46,7 @@ const useActivityTimer = () => {
resumeListener.remove();
clearTimer();
};
}, []);
}, [pauseTimestamp]);

useEffect(() => {
const events = [
Expand All @@ -69,6 +70,9 @@ const useActivityTimer = () => {
clearTimer();
};
}, []);
return {
setPauseTimestamp,
};
};

export { useActivityTimer };
5 changes: 5 additions & 0 deletions src/ui/components/PasscodeModule/PasscodeModule.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
height: 3.125rem;
}
}
.passcode-module-fingerprint-icon {
width: 3.125rem;
height: 3.125rem;
color: var(--ion-color-primary);
}
}

@media screen and (min-height: 300px) and (max-height: 715px) {
Expand Down
23 changes: 21 additions & 2 deletions src/ui/components/PasscodeModule/PasscodeModule.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IonButton, IonCol, IonGrid, IonIcon, IonRow } from "@ionic/react";
import { backspaceSharp } from "ionicons/icons";
import { backspaceSharp, fingerPrintSharp } from "ionicons/icons";
import { PasscodeModuleProps } from "./PasscodeModule.types";
import "./PasscodeModule.scss";
import { PASSCODE_MAPPING } from "../../globals/types";
Expand All @@ -9,6 +9,7 @@ const PasscodeModule = ({
passcode,
handlePinChange,
handleRemove,
handleBiometricButtonClick,
}: PasscodeModuleProps) => {
const numbers = PASSCODE_MAPPING.numbers;
const labels = PASSCODE_MAPPING.labels;
Expand Down Expand Up @@ -46,7 +47,25 @@ const PasscodeModule = ({
className={`passcode-module-numbers-row ${rowIndex}`}
key={rowIndex}
>
{rowIndex === rows.length - 1 && <IonCol />}
{rowIndex === rows.length - 1 && (
<IonCol>
<IonButton
data-testid={"passcode-button-#"}
className="passcode-module-number-button"
onClick={() =>
handleBiometricButtonClick &&
handleBiometricButtonClick()
}
>
<IonIcon
slot="icon-only"
className="passcode-module-fingerprint-icon"
icon={fingerPrintSharp}
/>
</IonButton>
</IonCol>
)}

{row.map((number, colIndex) => (
<IonCol key={colIndex}>
<IonButton
Expand Down
1 change: 1 addition & 0 deletions src/ui/components/PasscodeModule/PasscodeModule.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface PasscodeModuleProps {
passcode: string;
handlePinChange: (digit: number) => void;
handleRemove: () => void;
handleBiometricButtonClick?: () => void;
}

export type { PasscodeModuleProps };
70 changes: 70 additions & 0 deletions src/ui/hooks/useBiometrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// hooks/useBiometricAuth.js
import { useState, useEffect } from "react";
import {
BiometricAuth,
BiometryError,
BiometryErrorType,
} from "@aparajita/capacitor-biometric-auth";
import { useDispatch } from "react-redux";
import { login } from "../../store/reducers/stateCache";
import { useActivityTimer } from "../components/AppWrapper/hooks/useActivityTimer";

const useBiometricAuth = () => {
const [biometricInfo, setBiometricInfo] = useState<any>();
const dispatch = useDispatch();
const { setPauseTimestamp } = useActivityTimer();

const checkBiometry = async () => {
try {
const biometricResult = await BiometricAuth.checkBiometry();
setBiometricInfo(biometricResult);
return biometricResult;
} catch (error) {
//console.error('Error checking biometry:', error);
}
};
useEffect(() => {
//checkBiometry();
}, []);

const handleBiometricAuth = async () => {
const biometricResult = await checkBiometry();
if (!biometricResult?.isAvailable) {
if (biometricResult?.strongReason?.includes("NSFaceIDUsageDescription")) {
alert(
"Please enable Face ID in your device settings or update the app for enhanced security."
);
}
return;
}

try {
await BiometricAuth.authenticate({
reason: "Please authenticate",
cancelTitle: "Cancel",
allowDeviceCredential: true,
iosFallbackTitle: "Use passcode",
androidTitle: "Biometric login",
androidSubtitle: "Log in using biometric authentication",
androidConfirmationRequired: false,
});
setPauseTimestamp(new Date().getTime());
dispatch(login());
} catch (error) {
if (
error instanceof BiometryError &&
error.code !== BiometryErrorType.userCancel
) {
// Handle other biometry errors here
//console.error('Biometry Error:', error.message);
}
}
};

return {
biometricInfo,
handleBiometricAuth,
};
};

export { useBiometricAuth };

0 comments on commit d1d707c

Please sign in to comment.