revert: restore GatewayRead.token field to avoid frontend breaking change

The has_token boolean redaction requires coordinated frontend changes
(detail page, edit page, orval types). Revert to returning the raw
token for now; token redaction will be handled in a dedicated PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hugh Brown
2026-03-03 22:21:14 -07:00
committed by Abhimanyu Saharan
parent 54279bf413
commit 91e8270364
3 changed files with 8 additions and 62 deletions

View File

@@ -28,29 +28,12 @@ from app.services.openclaw.admin_service import GatewayAdminLifecycleService
from app.services.openclaw.session_service import GatewayTemplateSyncQuery from app.services.openclaw.session_service import GatewayTemplateSyncQuery
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Sequence
from fastapi_pagination.limit_offset import LimitOffsetPage from fastapi_pagination.limit_offset import LimitOffsetPage
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from app.services.organizations import OrganizationContext from app.services.organizations import OrganizationContext
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,
)
router = APIRouter(prefix="/gateways", tags=["gateways"]) router = APIRouter(prefix="/gateways", tags=["gateways"])
SESSION_DEP = Depends(get_session) SESSION_DEP = Depends(get_session)
AUTH_DEP = Depends(get_auth_context) AUTH_DEP = Depends(get_auth_context)
@@ -101,10 +84,7 @@ async def list_gateways(
.statement .statement
) )
def _transform(items: Sequence[Gateway]) -> list[GatewayRead]: return await paginate(session, statement)
return [_to_gateway_read(item) for item in items]
return await paginate(session, statement, transformer=_transform)
@router.post("", response_model=GatewayRead) @router.post("", response_model=GatewayRead)
@@ -113,7 +93,7 @@ async def create_gateway(
session: AsyncSession = SESSION_DEP, session: AsyncSession = SESSION_DEP,
auth: AuthContext = AUTH_DEP, auth: AuthContext = AUTH_DEP,
ctx: OrganizationContext = ORG_ADMIN_DEP, ctx: OrganizationContext = ORG_ADMIN_DEP,
) -> GatewayRead: ) -> Gateway:
"""Create a gateway and provision or refresh its main agent.""" """Create a gateway and provision or refresh its main agent."""
service = GatewayAdminLifecycleService(session) service = GatewayAdminLifecycleService(session)
await service.assert_gateway_runtime_compatible( await service.assert_gateway_runtime_compatible(
@@ -128,7 +108,7 @@ async def create_gateway(
data["organization_id"] = ctx.organization.id data["organization_id"] = ctx.organization.id
gateway = await crud.create(session, Gateway, **data) gateway = await crud.create(session, Gateway, **data)
await service.ensure_main_agent(gateway, auth, action="provision") await service.ensure_main_agent(gateway, auth, action="provision")
return _to_gateway_read(gateway) return gateway
@router.get("/{gateway_id}", response_model=GatewayRead) @router.get("/{gateway_id}", response_model=GatewayRead)
@@ -136,14 +116,14 @@ async def get_gateway(
gateway_id: UUID, gateway_id: UUID,
session: AsyncSession = SESSION_DEP, session: AsyncSession = SESSION_DEP,
ctx: OrganizationContext = ORG_ADMIN_DEP, ctx: OrganizationContext = ORG_ADMIN_DEP,
) -> GatewayRead: ) -> Gateway:
"""Return one gateway by id for the caller's organization.""" """Return one gateway by id for the caller's organization."""
service = GatewayAdminLifecycleService(session) service = GatewayAdminLifecycleService(session)
gateway = await service.require_gateway( gateway = await service.require_gateway(
gateway_id=gateway_id, gateway_id=gateway_id,
organization_id=ctx.organization.id, organization_id=ctx.organization.id,
) )
return _to_gateway_read(gateway) return gateway
@router.patch("/{gateway_id}", response_model=GatewayRead) @router.patch("/{gateway_id}", response_model=GatewayRead)
@@ -153,7 +133,7 @@ async def update_gateway(
session: AsyncSession = SESSION_DEP, session: AsyncSession = SESSION_DEP,
auth: AuthContext = AUTH_DEP, auth: AuthContext = AUTH_DEP,
ctx: OrganizationContext = ORG_ADMIN_DEP, ctx: OrganizationContext = ORG_ADMIN_DEP,
) -> GatewayRead: ) -> Gateway:
"""Patch a gateway and refresh the main-agent provisioning state.""" """Patch a gateway and refresh the main-agent provisioning state."""
service = GatewayAdminLifecycleService(session) service = GatewayAdminLifecycleService(session)
gateway = await service.require_gateway( gateway = await service.require_gateway(
@@ -185,7 +165,7 @@ async def update_gateway(
) )
await crud.patch(session, gateway, updates) await crud.patch(session, gateway, updates)
await service.ensure_main_agent(gateway, auth, action="update") await service.ensure_main_agent(gateway, auth, action="update")
return _to_gateway_read(gateway) return gateway
@router.post("/{gateway_id}/templates/sync", response_model=GatewayTemplatesSyncResult) @router.post("/{gateway_id}/templates/sync", response_model=GatewayTemplatesSyncResult)

View File

@@ -65,7 +65,7 @@ class GatewayRead(GatewayBase):
id: UUID id: UUID
organization_id: UUID organization_id: UUID
has_token: bool = False token: str | None = None
created_at: datetime created_at: datetime
updated_at: datetime updated_at: datetime

View File

@@ -24,7 +24,6 @@ from app.models.board_webhooks import BoardWebhook
from app.models.boards import Board from app.models.boards import Board
from app.models.gateways import Gateway from app.models.gateways import Gateway
from app.models.organizations import Organization from app.models.organizations import Organization
from app.schemas.gateways import GatewayRead
from app.services.admin_access import require_user_actor from app.services.admin_access import require_user_actor
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -497,36 +496,3 @@ class TestWebhookPayloadSizeLimit:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
class TestGatewayTokenRedaction:
"""Tests for gateway token redaction from API responses."""
def test_gateway_read_has_has_token_field(self) -> None:
read = GatewayRead(
id=uuid4(),
organization_id=uuid4(),
name="gw",
url="https://gw.example.com",
workspace_root="/ws",
has_token=True,
created_at="2025-01-01T00:00:00",
updated_at="2025-01-01T00:00:00",
)
data = read.model_dump()
assert "has_token" in data
assert data["has_token"] is True
# Ensure 'token' field is NOT present
assert "token" not in data
def test_gateway_read_without_token(self) -> None:
read = GatewayRead(
id=uuid4(),
organization_id=uuid4(),
name="gw",
url="https://gw.example.com",
workspace_root="/ws",
has_token=False,
created_at="2025-01-01T00:00:00",
updated_at="2025-01-01T00:00:00",
)
assert read.has_token is False