""" Authentication API routes """ from fastapi import APIRouter, Depends, HTTPException, status, Request from fastapi.security import HTTPBearer from datetime import datetime, timedelta from typing import Optional from jose import jwt from pydantic import BaseModel from sqlalchemy.orm import Session from ...config.settings import settings from ...database import SessionLocal from ...models import User from ...services.auth_service import authenticate_user, create_access_token, get_oidc_config, handle_oidc_callback router = APIRouter() security = HTTPBearer() # Pydantic models for auth class Token(BaseModel): access_token: str token_type: str class Config: json_schema_extra = { "example": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "bearer" } } class TokenData(BaseModel): username: Optional[str] = None tenant_id: Optional[str] = None class UserLogin(BaseModel): username: str password: str class Config: json_schema_extra = { "example": { "username": "johndoe", "password": "securepassword" } } def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt @router.post("/token", response_model=Token) async def login_for_access_token(form_data: UserLogin, db: Session = Depends(SessionLocal)): user = authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "tenant_id": user.tenant_id}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} @router.get("/oidc-config") async def get_oidc_config_endpoint(): """Get OIDC configuration""" return await get_oidc_config() @router.get("/oidc-login") async def oidc_login(): """Initiate OIDC login flow""" if not settings.OIDC_ISSUER or not settings.OIDC_CLIENT_ID or not settings.OIDC_REDIRECT_URI: raise HTTPException(status_code=500, detail="OIDC not properly configured") # Construct the authorization URL auth_url = ( f"{settings.OIDC_ISSUER}/authorize?" f"client_id={settings.OIDC_CLIENT_ID}&" f"response_type=code&" f"redirect_uri={settings.OIDC_REDIRECT_URI}&" f"scope=openid profile email&" f"state=random_state_string" # In real app, generate and store a proper state parameter ) return {"auth_url": auth_url} @router.get("/oidc-callback") async def oidc_callback(code: str, state: str = None): """Handle OIDC callback""" # Verify state parameter in a real implementation # Handle the OIDC callback and return a user try: user = await handle_oidc_callback(code) # Create access token for the user access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "tenant_id": user.tenant_id}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} except NotImplementedError: # For demo purposes, return a mock response return { "message": "OIDC callback received. In a real implementation, this would complete the login process.", "code": code, "state": state } @router.get("/social-login/{provider}") async def social_login(provider: str): """Initiate social media login flow (Google, Facebook, etc.)""" if provider not in ["google", "facebook", "github"]: raise HTTPException(status_code=400, detail="Unsupported social provider") # In a real implementation, redirect to the provider's OAuth URL # For demo purposes, return a mock URL auth_url = f"https://{provider}.com/oauth/authorize" # This is just a placeholder return { "message": f"Redirect user to {provider} for authentication", "auth_url": auth_url, "provider": provider } @router.post("/social-login/{provider}/callback") async def social_login_callback(provider: str, access_token: str): """Handle social media login callback""" if provider not in ["google", "facebook", "github"]: raise HTTPException(status_code=400, detail="Unsupported social provider") # In a real implementation, validate the access token and fetch user info # For demo purposes, return a mock response try: user = await handle_social_login(provider, access_token) # Create access token for the user access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username, "tenant_id": user.tenant_id}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} except NotImplementedError: # For demo purposes, return a mock response return { "message": "Social login callback received. In a real implementation, this would complete the login process.", "provider": provider, "access_token": access_token }