Authentication Flow
Multi-Layer Security Model
Dział zatytułowany „Multi-Layer Security Model”graph TB subgraph "Authentication Layers" BIOMETRIC["🔐 Biometric Auth<br/>Touch ID / Face ID"] PASSWORD["🔑 Password Auth<br/>bcrypt + salt"] SESSION["🎫 Session Tokens<br/>JWT with refresh"] API_KEY["🗝️ API Key Storage<br/>System Keychain"] end
subgraph "Authorization Layers" ROLES["👥 Role-Based Access<br/>admin/vet/assistant/viewer"] PERMISSIONS["📋 Resource Permissions<br/>read/write/delete/share"] AUDIT["📝 Audit Trail<br/>All actions logged"] end
BIOMETRIC --> SESSION PASSWORD --> SESSION SESSION --> ROLES ROLES --> PERMISSIONS PERMISSIONS --> AUDIT
API_KEY --> SESSIONLogin Flow
Dział zatytułowany „Login Flow”Metody uwierzytelniania
Dział zatytułowany „Metody uwierzytelniania”const authMethods = { password: 'standard_login', // Email + password biometric: 'biometric_login', // Touch/Face ID invitation: 'invitation_login', // Team invitation token demo: 'demo_login' // Demo mode access};Login Request
Dział zatytułowany „Login Request”interface LoginRequest { email: string; password: string; useBiometric?: boolean;}Login Response
Dział zatytułowany „Login Response”interface LoginResponse { user: User; session_id: string; expires_at: string; // ISO timestamp api_key?: string; // For AI services refresh_token?: string; // For session renewal}Sequence Diagram
Dział zatytułowany „Sequence Diagram”sequenceDiagram participant UI as Frontend participant Auth as AuthContext participant BE as Tauri Backend participant DB as SQLite participant KC as Keychain
UI->>Auth: login(email, password) Auth->>BE: invoke('login', {email, password})
BE->>DB: SELECT user WHERE email = ? DB-->>BE: User record
BE->>BE: bcrypt.verify(password, hash)
alt Password Valid BE->>DB: INSERT session BE->>KC: Store API key (if needed) BE-->>Auth: LoginResponse Auth->>Auth: setUser(user) Auth-->>UI: Success else Password Invalid BE-->>Auth: Error("Invalid credentials") Auth-->>UI: Show error endPassword Hashing
Dział zatytułowany „Password Hashing”Vista używa bcrypt z salt do hashowania haseł.
use bcrypt::{hash, verify, DEFAULT_COST};
pub fn hash_password(password: &str) -> Result<String, Error> { hash(password, DEFAULT_COST) .map_err(|e| Error::Auth(format!("Hashing failed: {}", e)))}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> { verify(password, hash) .map_err(|e| Error::Auth(format!("Verification failed: {}", e)))}Session Management
Dział zatytułowany „Session Management”Token Structure
Dział zatytułowany „Token Structure”pub struct Session { pub session_id: String, // UUID v4 pub user_id: String, pub token: String, // Random 32-byte hex pub created_at: DateTime, pub expires_at: DateTime, // +24h default pub last_activity: DateTime,}Session Lifecycle
Dział zatytułowany „Session Lifecycle”stateDiagram-v2 [*] --> Created: login() Created --> Active: Token validated Active --> Active: refresh_session() Active --> Expired: expires_at passed Active --> Terminated: logout() Expired --> [*] Terminated --> [*]Session Commands
Dział zatytułowany „Session Commands”#[tauri::command]pub async fn get_active_session( db: State<'_, Database>,) -> Result<Option<Session>, String>
#[tauri::command]pub async fn refresh_session( db: State<'_, Database>, session_id: String,) -> Result<Session, String>
#[tauri::command]pub async fn logout( db: State<'_, Database>, session_id: String,) -> Result<(), String>Token Storage
Dział zatytułowany „Token Storage”Secure Storage Locations
Dział zatytułowany „Secure Storage Locations”| Platform | Storage | API |
|---|---|---|
| macOS | Keychain | security-framework |
| Windows | Credential Store | keyring-rs |
| Linux | Secret Service | keyring-rs |
Keychain Integration (macOS)
Dział zatytułowany „Keychain Integration (macOS)”use security_framework::os::macos::keychain::SecKeychain;
pub fn store_api_key(service: &str, key: &str) -> Result<(), Error> { let keychain = SecKeychain::default()?; keychain.set_generic_password(service, "api_key", key.as_bytes())?; Ok(())}
pub fn get_api_key(service: &str) -> Result<String, Error> { let keychain = SecKeychain::default()?; let (password, _) = keychain.find_generic_password(service, "api_key")?; Ok(String::from_utf8(password.to_vec())?)}Password Reset
Dział zatytułowany „Password Reset”sequenceDiagram participant U as User participant FE as Frontend participant BE as Backend participant DB as Database
U->>FE: Request password reset FE->>BE: request_password_reset(email) BE->>DB: INSERT password_reset_token BE-->>FE: Token created (expires 1h)
Note over U: User receives token (display in app)
U->>FE: Enter new password + token FE->>BE: reset_password(token, new_password) BE->>DB: Verify token not expired BE->>BE: bcrypt.hash(new_password) BE->>DB: UPDATE user SET password_hash BE->>DB: DELETE password_reset_token BE-->>FE: SuccessToken Table
Dział zatytułowany „Token Table”CREATE TABLE password_reset_tokens ( token_id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(user_id), token TEXT NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, -- +1 hour used_at TIMESTAMP -- NULL until used);Change Password
Dział zatytułowany „Change Password”#[tauri::command]pub async fn change_password( db: State<'_, Database>, user_id: String, current_password: String, new_password: String,) -> Result<(), String> { // 1. Get current hash let user = get_user(&db, &user_id).await?;
// 2. Verify current password if !verify_password(¤t_password, &user.password_hash)? { return Err("Current password is incorrect".to_string()); }
// 3. Hash new password let new_hash = hash_password(&new_password)?;
// 4. Update in database sqlx::query!( "UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ?", new_hash, user_id ).execute(&db.pool).await?;
// 5. Invalidate all sessions (force re-login) sqlx::query!( "DELETE FROM user_sessions WHERE user_id = ?", user_id ).execute(&db.pool).await?;
Ok(())}Security Best Practices
Dział zatytułowany „Security Best Practices”Validation Rules
Dział zatytułowany „Validation Rules”| Field | Rules |
|---|---|
| Valid format, unique | |
| Password | Min 8 chars, complexity rules (TODO) |
| Session token | 32 random bytes, hex encoded |
| Reset token | UUID v4, 1h expiry |