"""Authentication routes""" from datetime import timedelta from fastapi import APIRouter, HTTPException, Depends, Response from fastapi.responses import JSONResponse from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.auth.dependencies import ( AtprotoAuth, create_access_token, require_user, AuthUser, ACCESS_TOKEN_EXPIRE_MINUTES ) from app.db.base import get_db from app.repositories.user import UserRepository from app.services.atproto import AtprotoService router = APIRouter(prefix="/auth", tags=["auth"]) class LoginRequest(BaseModel): """Login request model""" identifier: str # Handle or DID password: str # App password class LoginResponse(BaseModel): """Login response model""" access_token: str token_type: str = "bearer" did: str handle: str class VerifyResponse(BaseModel): """Verify response model""" did: str handle: str valid: bool = True @router.post("/login", response_model=LoginResponse) async def login( request: LoginRequest, response: Response, db: AsyncSession = Depends(get_db) ): """ Login with atproto credentials - **identifier**: atproto handle or DID - **password**: App password (not main password) """ auth = AtprotoAuth() # Authenticate with atproto user = await auth.authenticate(request.identifier, request.password) if not user: raise HTTPException( status_code=401, detail="Invalid credentials" ) # Create or update user in database user_repo = UserRepository(db) await user_repo.get_or_create(did=user.did, handle=user.handle) await db.commit() # Create access token access_token = create_access_token( data={"did": user.did, "handle": user.handle}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) # Set cookie for web clients response.set_cookie( key="ai_card_token", value=access_token, httponly=True, secure=True, samesite="lax", max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60 ) return LoginResponse( access_token=access_token, did=user.did, handle=user.handle or "" ) @router.post("/logout") async def logout(response: Response): """Logout and clear session""" response.delete_cookie("ai_card_token") return {"message": "Logged out successfully"} @router.get("/verify", response_model=VerifyResponse) async def verify_session( current_user: AuthUser = Depends(require_user) ): """Verify current session is valid""" return VerifyResponse( did=current_user.did, handle=current_user.handle or "", valid=True ) @router.post("/verify-did") async def verify_did(did: str, handle: Optional[str] = None): """ Verify DID is valid (public endpoint) - **did**: DID to verify - **handle**: Optional handle to cross-check """ service = AtprotoService() is_valid = await service.verify_did(did, handle) return { "did": did, "handle": handle, "valid": is_valid } # Import Optional here from typing import Optional