add claude
This commit is contained in:
132
api/app/routes/auth.py
Normal file
132
api/app/routes/auth.py
Normal file
@ -0,0 +1,132 @@
|
||||
"""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
|
Reference in New Issue
Block a user