"""Authentication routes""" from datetime import timedelta from typing import Optional 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_session 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_session) ): """ 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