Spaces:
Sleeping
Sleeping
| from datetime import datetime, timezone | |
| from typing import Optional | |
| from fastapi import APIRouter, Depends, HTTPException, Security | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| from pydantic import BaseModel, Field | |
| from sqlalchemy.orm import Session | |
| from app.core.security import verify_api_key | |
| from app.db.session import get_db | |
| from app.models.api_key import ApiKey | |
| from app.models.user import User | |
| router = APIRouter(tags=["auth"]) | |
| security = HTTPBearer(auto_error=False) | |
| class UserResponse(BaseModel): | |
| """User response model.""" | |
| id: int = Field(..., description="User ID") | |
| email: str = Field(..., description="User email") | |
| created_at: str = Field(..., description="ISO 8601 timestamp in UTC") | |
| class Config: | |
| json_schema_extra = { | |
| "example": { | |
| "id": 1, | |
| "email": "user@example.com", | |
| "created_at": "2025-12-31T00:00:00Z" | |
| } | |
| } | |
| def get_current_user( | |
| credentials: Optional[HTTPAuthorizationCredentials] = Security(security), | |
| db: Session = Depends(get_db) | |
| ) -> User: | |
| """ | |
| Authenticate user via API key from Authorization header. | |
| Raises 401 if authentication fails. | |
| """ | |
| if credentials is None: | |
| raise HTTPException( | |
| status_code=401, | |
| detail="Authorization header missing" | |
| ) | |
| if db is None: | |
| raise HTTPException( | |
| status_code=503, | |
| detail="Database not available" | |
| ) | |
| # Extract API key from Bearer token | |
| api_key = credentials.credentials | |
| # Find API key in database | |
| api_keys = db.query(ApiKey).all() | |
| matching_key: Optional[ApiKey] = None | |
| for key in api_keys: | |
| if verify_api_key(api_key, key.key_hash): | |
| # Check if key is expired | |
| if key.expires_at and key.expires_at < datetime.now(timezone.utc): | |
| continue | |
| matching_key = key | |
| # Update last_used_at | |
| key.last_used_at = datetime.now(timezone.utc) | |
| db.commit() | |
| break | |
| if matching_key is None: | |
| raise HTTPException( | |
| status_code=401, | |
| detail="Invalid or expired API key" | |
| ) | |
| # Get user | |
| user = db.query(User).filter(User.id == matching_key.user_id).first() | |
| if user is None: | |
| raise HTTPException( | |
| status_code=404, | |
| detail="User not found" | |
| ) | |
| return user | |
| def get_me(current_user: User = Depends(get_current_user)) -> UserResponse: | |
| """ | |
| Get current authenticated user information. | |
| Requires valid API key in Authorization header. | |
| Returns 401 if authentication fails. | |
| """ | |
| return UserResponse( | |
| id=current_user.id, | |
| email=current_user.email, | |
| created_at=current_user.created_at.isoformat() + "Z" | |
| ) | |
| def get_admin_api_key(db: Session = Depends(get_db)): | |
| """ | |
| Get the seeded admin API key. | |
| This endpoint tries to read from file first, if not found, | |
| it will generate a new one from the database (if seed ran successfully). | |
| """ | |
| import os | |
| from pathlib import Path | |
| # Try multiple file paths | |
| possible_paths = [ | |
| Path("admin_api_key.txt"), | |
| Path("/tmp/admin_api_key.txt"), | |
| Path("/app/admin_api_key.txt"), | |
| Path("./admin_api_key.txt"), | |
| ] | |
| key_file_path = None | |
| for path in possible_paths: | |
| if path.exists(): | |
| key_file_path = path | |
| break | |
| # If file exists, read from it | |
| if key_file_path: | |
| try: | |
| with open(key_file_path, "r") as f: | |
| content = f.read().strip() | |
| lines = content.split("\n") | |
| api_key = None | |
| email = None | |
| for line in lines: | |
| if line.startswith("API Key:"): | |
| api_key = line.replace("API Key:", "").strip() | |
| elif line.startswith("Email:"): | |
| email = line.replace("Email:", "").strip() | |
| if api_key: | |
| return { | |
| "email": email or "admin@forgeflow.local", | |
| "api_key": api_key, | |
| "source": "file", | |
| "message": "⚠️ IMPORTANT: Copy this API key. It will not be shown again after you use it!" | |
| } | |
| except Exception as e: | |
| pass # Fall through to DB check | |
| # If file not found, try to get from database (if seed ran) | |
| if db is not None: | |
| try: | |
| # Check if admin user exists | |
| admin_user = db.query(User).filter(User.email == "admin@forgeflow.local").first() | |
| if admin_user: | |
| # Get the first API key for admin user | |
| api_key_obj = db.query(ApiKey).filter(ApiKey.user_id == admin_user.id).first() | |
| if api_key_obj: | |
| return { | |
| "email": admin_user.email, | |
| "api_key": "⚠️ API key found in database but plaintext is not available. Check logs or seed again.", | |
| "source": "database", | |
| "message": "API key was seeded but plaintext is not stored. Check application logs for the original key." | |
| } | |
| except Exception as e: | |
| pass | |
| raise HTTPException( | |
| status_code=404, | |
| detail="Admin API key not found. Seed may not have run yet or encountered an error. Check application logs." | |
| ) | |