from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.database import get_db from app.core.security import verify_password, get_password_hash, create_access_token from app.models.models import User from app.schemas.schemas import LoginRequest, TokenResponse, UserResponse from app.api.deps import get_current_user from app.services.event_service import log_event router = APIRouter(prefix="/auth", tags=["Authentication"]) import logging logger = logging.getLogger("uvicorn.error") from app.core.radius import authenticate_radius from app.services.settings_service import get_setting @router.post("/login", response_model=TokenResponse) async def login(credentials: LoginRequest, db: AsyncSession = Depends(get_db)): email_clean = credentials.email.strip().lower() auth_mode = await get_setting(db, "auth_mode") radius_host = await get_setting(db, "radius_host") radius_port = await get_setting(db, "radius_port") radius_secret = await get_setting(db, "radius_secret") is_authenticated = False user = None # 1. RADIUS Authentication Path (except for default admin fallback) if auth_mode == "radius" and email_clean != "admin@oneverdrive.local": username_radius = email_clean.split("@")[0] if "@" in email_clean else email_clean try: port_num = int(radius_port) if radius_port else 1812 except ValueError: port_num = 1812 radius_ok = authenticate_radius(username_radius, credentials.password, radius_host, radius_secret, port=port_num) if radius_ok: result = await db.execute(select(User).where(User.email == email_clean)) user = result.scalar_one_or_none() if not user: # Auto-provision (JIT) RADIUS User user = User( email=email_clean, hashed_password=get_password_hash(credentials.password), full_name=username_radius.capitalize(), role="OPERATOR", auth_source="radius", is_active=True ) db.add(user) await db.commit() await db.refresh(user) is_authenticated = True else: # 2. Local Authentication Path (or Admin local login fallback) result = await db.execute(select(User).where(User.email == email_clean)) user = result.scalar_one_or_none() if user: if verify_password(credentials.password, user.hashed_password): is_authenticated = True if not is_authenticated: logger.warning(f"Login failed: Invalid credentials for '{email_clean}'") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password" ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is deactivated" ) access_token = create_access_token(subject=user.id, role=user.role) await log_event( db=db, event_type="LOGIN", message=f"User {user.email} logged in successfully.", severity="INFO", user_email=user.email ) return { "access_token": access_token, "token_type": "bearer", "user": { "id": user.id, "email": user.email, "full_name": user.full_name, "role": user.role, "auth_source": user.auth_source } } @router.get("/me", response_model=UserResponse) async def get_me(current_user: User = Depends(get_current_user)): return current_user @router.post("/seed-admin") async def seed_admin(db: AsyncSession = Depends(get_db)): """Creates default admin if no users exist in the database.""" result = await db.execute(select(User)) first_user = result.scalar_one_or_none() if first_user: return {"message": "Admin already exists"} admin = User( email="admin@oneverdrive.local", hashed_password=get_password_hash("Admin1234!"), full_name="System Administrator", role="ADMIN", is_active=True ) db.add(admin) await db.commit() return {"message": "Default admin created: admin@oneverdrive.local / Admin1234!"}