forked from Stellar-IndigoPay/Stellar-IndigoPay
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseBiometricAuth.ts
More file actions
116 lines (105 loc) · 3.75 KB
/
Copy pathuseBiometricAuth.ts
File metadata and controls
116 lines (105 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* hooks/useBiometricAuth.ts
*
* Enhanced biometric (Face ID / fingerprint) authentication hook with
* threshold checking, preference storage via AsyncStorage, and fallback configuration.
*/
import { useState, useEffect } from 'react';
import * as LocalAuthentication from 'expo-local-authentication';
import AsyncStorage from '@react-native-async-storage/async-storage';
const BIOMETRIC_THRESHOLD_KEY = '@indigopay:biometric_threshold';
const BIOMETRIC_ENABLED_KEY = '@indigopay:biometric_enabled';
const DEFAULT_THRESHOLD_XLM = 50;
export function useBiometricAuth() {
const [isAvailable, setIsAvailable] = useState(false);
const [biometricType, setBiometricType] = useState<string | null>(null);
const [threshold, setThreshold] = useState(DEFAULT_THRESHOLD_XLM);
const [isEnabled, setIsEnabled] = useState(true);
const [isAuthenticating, setIsAuthenticating] = useState(false);
useEffect(() => {
checkAvailability();
loadPreferences();
}, []);
async function checkAvailability() {
const compatible = await LocalAuthentication.hasHardwareAsync();
const enrolled = await LocalAuthentication.isEnrolledAsync();
setIsAvailable(compatible && enrolled);
if (compatible) {
const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
setBiometricType(
types.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION)
? 'Face ID' : types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT)
? 'Touch ID' : 'Biometric'
);
}
}
async function loadPreferences() {
try {
const stored = await AsyncStorage.getItem(BIOMETRIC_THRESHOLD_KEY);
if (stored) setThreshold(Number(stored));
const enabled = await AsyncStorage.getItem(BIOMETRIC_ENABLED_KEY);
if (enabled !== null) setIsEnabled(enabled === 'true');
} catch (err) {
console.error('Error loading biometric preferences:', err);
}
}
async function confirmDonation(amount: number): Promise<{ success: boolean; error?: string }> {
if (!isEnabled || !isAvailable || amount < threshold) {
return { success: true }; // No confirmation needed
}
setIsAuthenticating(true);
try {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: `Confirm donation of ${amount} XLM`,
fallbackLabel: 'Use device passcode',
cancelLabel: 'Cancel donation',
});
return { success: result.success, error: (result as any).error || undefined };
} catch (err) {
return { success: false, error: 'Biometric authentication failed' };
} finally {
setIsAuthenticating(false);
}
}
async function setBiometricThreshold(newThreshold: number) {
setThreshold(newThreshold);
try {
await AsyncStorage.setItem(BIOMETRIC_THRESHOLD_KEY, String(newThreshold));
} catch (err) {
console.error('Error saving biometric threshold:', err);
}
}
async function updateIsEnabled(value: boolean) {
setIsEnabled(value);
try {
await AsyncStorage.setItem(BIOMETRIC_ENABLED_KEY, String(value));
} catch (err) {
console.error('Error saving biometric enabled status:', err);
}
}
return {
isAvailable,
biometricType,
threshold,
isEnabled,
isAuthenticating,
confirmDonation,
setBiometricThreshold,
setIsEnabled: updateIsEnabled,
};
}
/**
* Standalone authenticate helper exported for non-hook consumers
* (e.g. secureStore.ts) that can't call the React hook directly.
*/
export async function authenticate(reason: string): Promise<boolean> {
try {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: reason,
fallbackLabel: 'Use device passcode',
});
return result.success;
} catch {
return false;
}
}