feat: add Firebase Auth ID token verification + /api/me endpoint; support static password + Firebase ID tokens
bbc9c59
| import os | |
| import json | |
| from typing import Optional | |
| try: | |
| import firebase_admin | |
| from firebase_admin import credentials, auth | |
| # App Check verification is available in newer firebase_admin versions | |
| try: | |
| from firebase_admin import app_check | |
| except ImportError: | |
| app_check = None | |
| except ImportError: | |
| firebase_admin = None | |
| credentials = None | |
| auth = None | |
| app_check = None | |
| _initialized = False | |
| def initialize_firebase() -> bool: | |
| """Initialize Firebase Admin SDK from env. | |
| Supports two ways to provide credentials: | |
| 1. FIREBASE_CREDENTIALS_JSON: Full JSON content as string | |
| 2. FIREBASE_CREDENTIALS: Path to service account JSON file | |
| Optional: FIREBASE_PROJECT_ID to override project ID | |
| """ | |
| global _initialized | |
| if _initialized: | |
| return True | |
| if firebase_admin is None or credentials is None: | |
| return False | |
| # Skip if already has a default app | |
| if firebase_admin._apps: | |
| _initialized = True | |
| return True | |
| # Try JSON content first (for Hugging Face secrets) | |
| cred_json = os.getenv("FIREBASE_CREDENTIALS_JSON") | |
| if cred_json: | |
| try: | |
| cred_dict = json.loads(cred_json) | |
| cred = credentials.Certificate(cred_dict) | |
| firebase_admin.initialize_app(cred, { | |
| "projectId": os.getenv("FIREBASE_PROJECT_ID") or cred_dict.get("project_id") | |
| }) | |
| _initialized = True | |
| return True | |
| except Exception as e: | |
| print(f"Warning: Failed to initialize Firebase from JSON: {e}") | |
| # Fallback to file path | |
| cred_path = os.getenv("FIREBASE_CREDENTIALS") | |
| if cred_path and os.path.exists(cred_path): | |
| try: | |
| cred = credentials.Certificate(cred_path) | |
| firebase_admin.initialize_app(cred, { | |
| "projectId": os.getenv("FIREBASE_PROJECT_ID") | |
| }) | |
| _initialized = True | |
| return True | |
| except Exception as e: | |
| print(f"Warning: Failed to initialize Firebase from file: {e}") | |
| return False | |
| def verify_app_check_token(token: Optional[str]) -> bool: | |
| """Verify Firebase App Check token. | |
| Returns True if verification succeeds. If App Check is not available or | |
| not initialized, returns False to indicate verification not possible. | |
| """ | |
| if not token: | |
| return False | |
| # If App Check module is not available, skip verification | |
| if app_check is None: | |
| print("Warning: Firebase App Check module not available") | |
| return False | |
| if not initialize_firebase(): | |
| print("Warning: Firebase not initialized, skipping App Check verification") | |
| return False | |
| try: | |
| # Verify the App Check token | |
| # Note: verify_token() returns decoded token claims if valid, raises exception if invalid | |
| decoded_token = app_check.verify_token(token) | |
| return decoded_token is not None | |
| except Exception as e: | |
| print(f"Warning: App Check token verification failed: {e}") | |
| return False | |
| def verify_firebase_id_token(id_token: Optional[str]) -> Optional[dict]: | |
| """Verify Firebase ID token and return decoded claims. | |
| Returns: | |
| dict: Decoded token claims (user info) if valid, None if invalid/not available | |
| """ | |
| if not id_token: | |
| return None | |
| if auth is None: | |
| return None | |
| if not initialize_firebase(): | |
| return None | |
| try: | |
| # Verify the ID token | |
| decoded_token = auth.verify_id_token(id_token) | |
| return decoded_token | |
| except Exception as e: | |
| print(f"Warning: Firebase ID token verification failed: {e}") | |
| return None | |