from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.auth.dependencies import get_current_active_user from app.db import get_db_session from app.encryption import encrypt_value from app.models.project import Project from app.models.secret import Secret from app.models.tool_instance import ToolInstance from app.models.user import User from app.schemas.secret import SecretCreate, SecretRead, SecretUpdate router = APIRouter(tags=["secrets"]) async def _verify_secret_ownership( secret_obj: Secret, user: User, session: AsyncSession ) -> None: if secret_obj.scope_type == "user": if secret_obj.scope_id != user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") elif secret_obj.scope_type == "project": project = await session.get(Project, secret_obj.scope_id) if not project or project.owner_id != user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") elif secret_obj.scope_type == "tool_instance": ti = await session.get(ToolInstance, secret_obj.scope_id) if not ti: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") project = await session.get(Project, ti.project_id) if not project or project.owner_id != user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") elif secret_obj.scope_type == "global": pass else: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope_type") @router.post("/secrets", response_model=SecretRead, status_code=status.HTTP_201_CREATED) async def create_secret( secret_in: SecretCreate, current_user: User = Depends(get_current_active_user), session: AsyncSession = Depends(get_db_session), ) -> SecretRead: secret = Secret( scope_type=secret_in.scope_type, scope_id=secret_in.scope_id, key=secret_in.key, encrypted_value=encrypt_value(secret_in.value), ) await _verify_secret_ownership(secret, current_user, session) session.add(secret) await session.commit() await session.refresh(secret) return SecretRead.from_secret(secret) @router.get("/secrets", response_model=list[SecretRead]) async def list_secrets( scope_type: str | None = None, scope_id: UUID | None = None, current_user: User = Depends(get_current_active_user), session: AsyncSession = Depends(get_db_session), ) -> list[SecretRead]: stmt = select(Secret) if scope_type: stmt = stmt.where(Secret.scope_type == scope_type) if scope_id: stmt = stmt.where(Secret.scope_id == scope_id) result = await session.execute(stmt) secrets = list(result.scalars().all()) allowed = [] for s in secrets: try: await _verify_secret_ownership(s, current_user, session) allowed.append(SecretRead.from_secret(s)) except HTTPException: pass return allowed @router.get("/secrets/{secret_id}", response_model=SecretRead) async def get_secret( secret_id: UUID, current_user: User = Depends(get_current_active_user), session: AsyncSession = Depends(get_db_session), ) -> SecretRead: s = await session.get(Secret, secret_id) if not s: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found") await _verify_secret_ownership(s, current_user, session) return SecretRead.from_secret(s) @router.put("/secrets/{secret_id}", response_model=SecretRead) async def update_secret( secret_id: UUID, secret_in: SecretUpdate, current_user: User = Depends(get_current_active_user), session: AsyncSession = Depends(get_db_session), ) -> SecretRead: s = await session.get(Secret, secret_id) if not s: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found") await _verify_secret_ownership(s, current_user, session) if secret_in.key is not None: s.key = secret_in.key if secret_in.value is not None: s.encrypted_value = encrypt_value(secret_in.value) await session.commit() await session.refresh(s) return SecretRead.from_secret(s) @router.delete("/secrets/{secret_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_secret( secret_id: UUID, current_user: User = Depends(get_current_active_user), session: AsyncSession = Depends(get_db_session), ) -> None: s = await session.get(Secret, secret_id) if not s: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found") await _verify_secret_ownership(s, current_user, session) await session.delete(s) await session.commit()