forgeflow-core / app /api /auth.py
Caner Giden
Fix: Improve /admin/api-key endpoint with multiple file paths and DB fallback
92bf88b
Raw
History Blame Contribute Delete
5.71 kB
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
@router.get("/me", response_model=UserResponse)
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"
)
@router.get("/admin/api-key")
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."
)