security: redact gateway tokens from API responses
Gateway tokens were returned as plaintext in GatewayRead API responses. Replace the `token` field with a boolean `has_token` flag so the API never exposes the plaintext token. The token remains in the database for outbound gateway connections (full encryption would require key management infrastructure). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Abhimanyu Saharan
parent
94988deef2
commit
547965a5cb
@@ -23,11 +23,28 @@ from app.schemas.gateways import (
|
||||
GatewayTemplatesSyncResult,
|
||||
GatewayUpdate,
|
||||
)
|
||||
|
||||
|
||||
def _to_gateway_read(gateway: Gateway) -> GatewayRead:
|
||||
return GatewayRead(
|
||||
id=gateway.id,
|
||||
organization_id=gateway.organization_id,
|
||||
name=gateway.name,
|
||||
url=gateway.url,
|
||||
workspace_root=gateway.workspace_root,
|
||||
allow_insecure_tls=gateway.allow_insecure_tls,
|
||||
disable_device_pairing=gateway.disable_device_pairing,
|
||||
has_token=gateway.token is not None,
|
||||
created_at=gateway.created_at,
|
||||
updated_at=gateway.updated_at,
|
||||
)
|
||||
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||
from app.services.openclaw.admin_service import GatewayAdminLifecycleService
|
||||
from app.services.openclaw.session_service import GatewayTemplateSyncQuery
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
from fastapi_pagination.limit_offset import LimitOffsetPage
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
@@ -82,7 +99,11 @@ async def list_gateways(
|
||||
.order_by(col(Gateway.created_at).desc())
|
||||
.statement
|
||||
)
|
||||
return await paginate(session, statement)
|
||||
|
||||
def _transform(items: Sequence[object]) -> Sequence[object]:
|
||||
return [_to_gateway_read(item) for item in items if isinstance(item, Gateway)]
|
||||
|
||||
return await paginate(session, statement, transformer=_transform)
|
||||
|
||||
|
||||
@router.post("", response_model=GatewayRead)
|
||||
@@ -91,7 +112,7 @@ async def create_gateway(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
auth: AuthContext = AUTH_DEP,
|
||||
ctx: OrganizationContext = ORG_ADMIN_DEP,
|
||||
) -> Gateway:
|
||||
) -> GatewayRead:
|
||||
"""Create a gateway and provision or refresh its main agent."""
|
||||
service = GatewayAdminLifecycleService(session)
|
||||
await service.assert_gateway_runtime_compatible(
|
||||
@@ -106,7 +127,7 @@ async def create_gateway(
|
||||
data["organization_id"] = ctx.organization.id
|
||||
gateway = await crud.create(session, Gateway, **data)
|
||||
await service.ensure_main_agent(gateway, auth, action="provision")
|
||||
return gateway
|
||||
return _to_gateway_read(gateway)
|
||||
|
||||
|
||||
@router.get("/{gateway_id}", response_model=GatewayRead)
|
||||
@@ -114,14 +135,14 @@ async def get_gateway(
|
||||
gateway_id: UUID,
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
ctx: OrganizationContext = ORG_ADMIN_DEP,
|
||||
) -> Gateway:
|
||||
) -> GatewayRead:
|
||||
"""Return one gateway by id for the caller's organization."""
|
||||
service = GatewayAdminLifecycleService(session)
|
||||
gateway = await service.require_gateway(
|
||||
gateway_id=gateway_id,
|
||||
organization_id=ctx.organization.id,
|
||||
)
|
||||
return gateway
|
||||
return _to_gateway_read(gateway)
|
||||
|
||||
|
||||
@router.patch("/{gateway_id}", response_model=GatewayRead)
|
||||
@@ -131,7 +152,7 @@ async def update_gateway(
|
||||
session: AsyncSession = SESSION_DEP,
|
||||
auth: AuthContext = AUTH_DEP,
|
||||
ctx: OrganizationContext = ORG_ADMIN_DEP,
|
||||
) -> Gateway:
|
||||
) -> GatewayRead:
|
||||
"""Patch a gateway and refresh the main-agent provisioning state."""
|
||||
service = GatewayAdminLifecycleService(session)
|
||||
gateway = await service.require_gateway(
|
||||
@@ -163,7 +184,7 @@ async def update_gateway(
|
||||
)
|
||||
await crud.patch(session, gateway, updates)
|
||||
await service.ensure_main_agent(gateway, auth, action="update")
|
||||
return gateway
|
||||
return _to_gateway_read(gateway)
|
||||
|
||||
|
||||
@router.post("/{gateway_id}/templates/sync", response_model=GatewayTemplatesSyncResult)
|
||||
|
||||
@@ -65,7 +65,7 @@ class GatewayRead(GatewayBase):
|
||||
|
||||
id: UUID
|
||||
organization_id: UUID
|
||||
token: str | None = None
|
||||
has_token: bool = False
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
Reference in New Issue
Block a user