""" Biometric Authentication Module Simplified: Use device fingerprint + biometric to unlock stored credentials """ import hashlib import secrets from datetime import datetime from postgresql_models import SessionLocal, BiometricCredential import logging logger = logging.getLogger(__name__) def register_biometric_credential(username, device_name, device_fingerprint, device_info): """Register a biometric-enabled device for a user""" from postgresql_models import User db = SessionLocal() try: # Check if this device is already registered for this user existing = db.query(BiometricCredential).filter_by( username=username, device_fingerprint=device_fingerprint ).first() if existing: # Update existing existing.device_name = device_name or existing.device_name existing.device_info = device_info or existing.device_info existing.enabled = 1 existing.last_used = datetime.now() db.commit() logger.info(f"Updated biometric for {username} on device {device_name}") return {'success': True, 'message': 'Biometric updated'} # Find user in User table user = db.query(User).filter_by(username=username).first() user_id = user.id if user else None # Create new biometric registration credential = BiometricCredential( username=username, user_id=user_id, credential_id=device_fingerprint, # Use device fingerprint as ID device_fingerprint=device_fingerprint, public_key='', # Not used in this simplified approach device_name=device_name or 'Unknown Device', device_info=device_info or '', enabled=1, created_at=datetime.now(), last_used=datetime.now() ) db.add(credential) db.commit() logger.info(f"Registered biometric for {username} on device {device_name}") return { 'success': True, 'message': 'Biometric registered successfully' } except Exception as e: db.rollback() logger.error(f"Error registering biometric: {e}") return {'success': False, 'error': str(e)} finally: db.close() def authenticate_biometric_by_device(username, device_fingerprint): """ Authenticate user by verifying device fingerprint The browser already verified the biometric, we just check if this device is registered If username is None, lookup by device fingerprint alone """ from postgresql_models import User db = SessionLocal() try: logger.info(f"Biometric auth for {username or 'unknown'} from device {device_fingerprint}") # Find biometric registration - either by username+fingerprint or just fingerprint if username: credential = db.query(BiometricCredential).filter_by( username=username, device_fingerprint=device_fingerprint, enabled=1 ).first() else: # No username provided - lookup by device fingerprint alone credential = db.query(BiometricCredential).filter_by( device_fingerprint=device_fingerprint, enabled=1 ).first() if not credential: logger.warning(f"No biometric found for device {device_fingerprint}") return {'success': False, 'error': 'No biometric setup found for this device'} # Get username from credential if not provided if not username: username = credential.username logger.info(f"Found username {username} from device fingerprint") # Get user details user = db.query(User).filter_by(username=username).first() if not user: logger.error(f"User {username} not found in database") return {'success': False, 'error': 'User not found'} # Update last used credential.last_used = datetime.now() db.commit() logger.info(f"Biometric auth successful for {username}") return { 'success': True, 'username': user.username, 'role': user.role, 'permissions': user.get_permissions_list() } except Exception as e: logger.error(f"Error in biometric authentication: {e}", exc_info=True) return {'success': False, 'error': str(e)} finally: db.close() def get_user_biometric_status(username): """Check if user has biometric enabled""" db = SessionLocal() try: credentials = db.query(BiometricCredential).filter_by( username=username, enabled=1 ).all() return { 'success': True, 'has_biometric': len(credentials) > 0, 'device_count': len(credentials), 'devices': [{ 'device_name': c.device_name, 'last_used': c.last_used.isoformat() if c.last_used else None } for c in credentials] } except Exception as e: logger.error(f"Error checking biometric status: {e}") return {'success': False, 'error': str(e)} finally: db.close() """Register a new biometric credential for a user""" from postgresql_models import User # Import here to avoid circular import db = SessionLocal() try: # Check if credential already exists existing = db.query(BiometricCredential).filter_by(credential_id=credential_id).first() if existing: return {'success': False, 'error': 'credential_exists'} # Find user in User table user = db.query(User).filter_by(username=username).first() user_id = user.id if user else None # Create new credential credential = BiometricCredential( username=username, user_id=user_id, # Link to User table if exists credential_id=credential_id, public_key=public_key, device_name=device_name or 'Unknown Device', device_info=device_info or '', enabled=1, created_at=datetime.now(), last_used=datetime.now() ) db.add(credential) db.commit() db.refresh(credential) return { 'success': True, 'credential': { 'id': credential.id, 'device_name': credential.device_name, 'created_at': credential.created_at.isoformat() } } except Exception as e: db.rollback() logger.error(f"Error registering biometric credential: {e}") return {'success': False, 'error': str(e)} finally: db.close() def authenticate_biometric(username, credential_id, signature, challenge): """Authenticate using biometric credential""" db = SessionLocal() try: logger.info(f"Authenticating biometric for user: {username}") logger.info(f"Credential ID (first 40 chars): {credential_id[:40] if credential_id else 'None'}...") logger.info(f"Credential ID length: {len(credential_id) if credential_id else 0}") # First, check if user has any credentials at all all_credentials = db.query(BiometricCredential).filter_by(username=username).all() logger.info(f"User {username} has {len(all_credentials)} total credentials in database") if all_credentials: for idx, cred in enumerate(all_credentials): logger.info(f" Credential {idx+1}: ID (first 40): {cred.credential_id[:40]}..., enabled: {cred.enabled}, device: {cred.device_name}") # Find credential - match by username first, then credential_id credential = db.query(BiometricCredential).filter_by( username=username, credential_id=credential_id, enabled=1 ).first() if not credential: logger.warning(f"No matching enabled credential found for {username}") # Try to find any enabled credential for debugging any_enabled = db.query(BiometricCredential).filter_by(username=username, enabled=1).first() if any_enabled: logger.warning(f"User has enabled credential but ID doesn't match.") logger.warning(f" Expected (first 40): {any_enabled.credential_id[:40]}...") logger.warning(f" Received (first 40): {credential_id[:40]}...") else: logger.warning(f"User has no enabled credentials at all") return {'success': False, 'error': 'credential_not_found'} logger.info(f"Credential found for {username} on device: {credential.device_name}") # Verify signature (currently simplified - accepts all) if not verify_signature(credential.public_key, signature, challenge): logger.error(f"Signature verification failed for {username}") return {'success': False, 'error': 'invalid_signature'} # Update last used timestamp credential.last_used = datetime.now() db.commit() logger.info(f"Biometric authentication successful for {username}") return { 'success': True, 'username': username, 'device_name': credential.device_name } except Exception as e: logger.error(f"Error authenticating biometric: {e}", exc_info=True) return {'success': False, 'error': str(e)} finally: db.close() def authenticate_biometric_by_credential(credential_id, signature, challenge): """ Authenticate using biometric credential - looks up username from credential ID This is the proper WebAuthn flow where the credential identifies the user """ db = SessionLocal() try: logger.info(f"Authenticating by credential ID (first 40): {credential_id[:40]}...") logger.info(f"Credential ID length: {len(credential_id)}") # Find credential by ID only (no username needed) credential = db.query(BiometricCredential).filter_by( credential_id=credential_id, enabled=1 ).first() if not credential: logger.warning(f"No enabled credential found with this ID") # Debug: show all credentials all_creds = db.query(BiometricCredential).filter_by(enabled=1).all() logger.info(f"Total enabled credentials in database: {len(all_creds)}") for idx, cred in enumerate(all_creds[:5]): # Show first 5 logger.info(f" Cred {idx+1}: User={cred.username}, ID (first 40)={cred.credential_id[:40]}...") return {'success': False, 'error': 'credential_not_found'} username = credential.username logger.info(f"Credential found! Belongs to user: {username}, device: {credential.device_name}") # Verify signature if not verify_signature(credential.public_key, signature, challenge): logger.error(f"Signature verification failed for credential") return {'success': False, 'error': 'invalid_signature'} # Update last used timestamp credential.last_used = datetime.now() db.commit() logger.info(f"Biometric authentication successful for {username}") return { 'success': True, 'username': username, 'device_name': credential.device_name } except Exception as e: logger.error(f"Error authenticating biometric by credential: {e}", exc_info=True) return {'success': False, 'error': str(e)} finally: db.close() def get_user_credentials(username): """Get all biometric credentials for a user""" db = SessionLocal() try: credentials = db.query(BiometricCredential).filter_by(username=username).all() return { 'success': True, 'credentials': [{ 'id': c.id, 'device_name': c.device_name, 'device_info': c.device_info, 'enabled': bool(c.enabled), 'created_at': c.created_at.isoformat(), 'last_used': c.last_used.isoformat() if c.last_used else None } for c in credentials] } except Exception as e: logger.error(f"Error fetching credentials: {e}") return {'success': False, 'error': str(e)} finally: db.close() def toggle_credential(credential_id, enabled): """Enable or disable a biometric credential""" db = SessionLocal() try: credential = db.query(BiometricCredential).filter_by(id=credential_id).first() if not credential: return {'success': False, 'error': 'credential_not_found'} credential.enabled = 1 if enabled else 0 db.commit() return {'success': True, 'enabled': bool(credential.enabled)} except Exception as e: db.rollback() logger.error(f"Error toggling credential: {e}") return {'success': False, 'error': str(e)} finally: db.close() def delete_credential(credential_id): """Delete a biometric credential""" db = SessionLocal() try: credential = db.query(BiometricCredential).filter_by(id=credential_id).first() if not credential: return {'success': False, 'error': 'credential_not_found'} db.delete(credential) db.commit() return {'success': True} except Exception as e: db.rollback() logger.error(f"Error deleting credential: {e}") return {'success': False, 'error': str(e)} finally: db.close() def delete_all_user_biometrics(username): """Delete all biometric credentials for a user""" db = SessionLocal() try: # Delete all credentials for this user deleted_count = db.query(BiometricCredential).filter_by(username=username).delete() db.commit() logger.info(f"Deleted {deleted_count} biometric credential(s) for user {username}") return {'success': True, 'deleted_count': deleted_count} except Exception as e: db.rollback() logger.error(f"Error deleting biometrics for {username}: {e}") return {'success': False, 'error': str(e)} finally: db.close()