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:
committed by
Abhimanyu Saharan
parent
54279bf413
commit
91e8270364
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user