Biometria & PIN
Touch ID / Face ID
Dział zatytułowany „Touch ID / Face ID”Vista wspiera natywną biometrię na macOS przez LocalAuthentication framework.
Wymagania
Dział zatytułowany „Wymagania”| Platform | Biometria | Wymagania |
|---|---|---|
| macOS | Touch ID | MacBook Pro/Air z Touch ID, macOS 10.12+ |
| macOS | Face ID | Zewnętrzna kamera (przyszłość) |
| Windows | Windows Hello | Windows 10+, kompatybilny sprzęt |
| Linux | - | Brak wsparcia |
Flow uwierzytelniania biometrycznego
Dział zatytułowany „Flow uwierzytelniania biometrycznego”sequenceDiagram participant U as User participant FE as Frontend participant Bio as useBiometric Hook participant BE as Tauri Backend participant LA as LocalAuthentication participant DB as SQLite
U->>FE: Click "Login with Touch ID" FE->>Bio: loginWithBiometric() Bio->>BE: invoke('authenticate_biometric')
BE->>LA: evaluate_policy(DeviceOwnerAuthentication) LA->>U: 🔐 Touch ID prompt
alt Success LA-->>BE: Authentication successful BE->>DB: Get user by biometric_key_id BE->>DB: Create session BE-->>FE: LoginResponse FE-->>U: Logged in! else Failure LA-->>BE: Error (cancelled/failed) BE-->>FE: Biometric auth failed FE-->>U: Show password login endRust Implementation
Dział zatytułowany „Rust Implementation”use security_framework::os::macos::keychain::SecKeychain;
#[tauri::command]pub async fn authenticate_biometric( reason: String,) -> Result<BiometricAuthResult, String> { let context = LAContext::new();
// Check availability if !context.can_evaluate_policy(LAPolicy::DeviceOwnerAuthenticationWithBiometrics) { return Err("Biometric authentication not available".to_string()); }
// Perform authentication let result = context.evaluate_policy( LAPolicy::DeviceOwnerAuthenticationWithBiometrics, &reason ).await;
match result { Ok(_) => Ok(BiometricAuthResult::Success), Err(e) => Ok(BiometricAuthResult::Failed(e.to_string())) }}
#[tauri::command]pub async fn check_biometric_availability() -> Result<BiometricStatus, String> { let context = LAContext::new();
let can_use_biometrics = context.can_evaluate_policy( LAPolicy::DeviceOwnerAuthenticationWithBiometrics );
let biometric_type = if can_use_biometrics { context.biometry_type() } else { BiometryType::None };
Ok(BiometricStatus { available: can_use_biometrics, biometric_type: biometric_type.into(), })}Frontend Hook
Dział zatytułowany „Frontend Hook”export const useBiometric = () => { const [isAvailable, setIsAvailable] = useState(false); const [isEnabled, setIsEnabled] = useState(false); const [biometricType, setBiometricType] = useState<'touchId' | 'faceId' | null>(null);
useEffect(() => { checkAvailability(); }, []);
const checkAvailability = async () => { const status = await invoke<BiometricStatus>('check_biometric_availability'); setIsAvailable(status.available); setBiometricType(status.biometric_type); };
const authenticate = async (reason: string): Promise<BiometricResult> => { if (!isAvailable) { return { success: false, error: 'Biometrics not available' }; }
try { const result = await invoke<BiometricAuthResult>('authenticate_biometric', { reason }); return { success: result === 'Success', error: null }; } catch (error) { return { success: false, error: String(error) }; } };
const enable = async (userId: string): Promise<void> => { // Authenticate first const authResult = await authenticate('Enable biometric login for Vista'); if (!authResult.success) { throw new Error('Biometric authentication required'); }
// Store biometric key in database await invoke('enable_biometric_login', { userId }); setIsEnabled(true); };
const disable = async (userId: string): Promise<void> => { await invoke('disable_biometric_login', { userId }); setIsEnabled(false); };
return { isAvailable, isEnabled, biometricType, authenticate, enable, disable, };};Database Schema
Dział zatytułowany „Database Schema”-- Biometric enabled flag in users tableALTER TABLE users ADD COLUMN biometric_enabled INTEGER DEFAULT 0;ALTER TABLE users ADD COLUMN biometric_key_id TEXT;
-- PIN lockout trackingCREATE TABLE pin_lockout ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, failed_attempts INTEGER DEFAULT 0, locked_until TIMESTAMP, last_attempt TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);PIN Fallback
Dział zatytułowany „PIN Fallback”PIN Setup Flow
Dział zatytułowany „PIN Setup Flow”sequenceDiagram participant U as User participant FE as Settings participant BE as Backend participant DB as SQLite
U->>FE: Setup PIN FE->>FE: Validate PIN (4-6 digits) FE->>BE: set_pin(userId, pin) BE->>BE: bcrypt.hash(pin) BE->>DB: UPDATE users SET pin_hash = ? BE-->>FE: Success FE-->>U: PIN configured!PIN Lockout
Dział zatytułowany „PIN Lockout”interface PinLockoutConfig { maxAttempts: 5, // Lock after 5 failed attempts lockoutDuration: 300, // 5 minutes resetAfterSuccess: true, // Reset counter on success}-- Check lockout statusSELECT failed_attempts, locked_until, CASE WHEN locked_until > CURRENT_TIMESTAMP THEN 'locked' ELSE 'unlocked' END as statusFROM pin_lockoutWHERE user_id = ?;Security Considerations
Dział zatytułowany „Security Considerations”Biometric Data Flow
Dział zatytułowany „Biometric Data Flow”┌─────────────────────────────────────────────────────────────┐│ SECURITY BOUNDARIES │├─────────────────────────────────────────────────────────────┤│ ││ ✅ Biometric data NEVER leaves Secure Enclave ││ ✅ Vista only receives success/failure result ││ ✅ No biometric templates stored in app ││ ✅ PIN hash stored with bcrypt (same as password) ││ │└─────────────────────────────────────────────────────────────┘Error Handling
Dział zatytułowany „Error Handling”| Error Code | Description | User Action |
|---|---|---|
LAErrorBiometryNotAvailable | No biometric hardware | Use password |
LAErrorBiometryNotEnrolled | No fingerprints registered | Setup in System Preferences |
LAErrorBiometryLockout | Too many failed attempts | Wait or use password |
LAErrorUserCancel | User cancelled | Retry or use password |
LAErrorUserFallback | User chose password | Show password input |
Configuration
Dział zatytułowany „Configuration”// User can enable/disable in settingsinterface BiometricSettings { enabled: boolean; useForLogin: boolean; useForSensitiveActions: boolean; // e.g., delete patient requirePasswordEvery: '24h' | '7d' | 'never';}