diff --git a/internal/cloud/access-control.test.ts b/internal/cloud/access-control.test.ts index b8fd0820..24ae1e8a 100644 --- a/internal/cloud/access-control.test.ts +++ b/internal/cloud/access-control.test.ts @@ -214,6 +214,22 @@ test("cloud api access control grants visible device pods and requires device-po assert.equal(bobLeaseConflict.body.error.code, "device_lease_conflict"); assert.equal(bobLeaseConflict.body.lease.holderUserId, userCreate.body.user.id); + const bobListWithoutPrivateGrant = await getJson(port, "/v1/device-pods", bobLogin.cookie); + assert.equal(bobListWithoutPrivateGrant.status, 200); + assert.equal(bobListWithoutPrivateGrant.body.devicePods.length, 1); + + const privatePodCreate = await postJson(port, "/v1/admin/device-pods", { + devicePodId: "device-pod-private", + profile: { schemaVersion: 1, target: { id: "target-private" }, route: { gatewaySessionId: "gws_private" } } + }, adminCookie); + assert.equal(privatePodCreate.status, 201); + const bobPrivateStatus = await getJson(port, "/v1/device-pods/device-pod-private/status", bobLogin.cookie); + assert.equal(bobPrivateStatus.status, 403); + assert.equal(bobPrivateStatus.body.error.code, "device_pod_forbidden"); + const bobMissingStatus = await getJson(port, "/v1/device-pods/device-pod-missing/status", bobLogin.cookie); + assert.equal(bobMissingStatus.status, 404); + assert.equal(bobMissingStatus.body.error.code, "device_pod_not_found"); + const mutatingWithLease = await postJson(port, "/v1/device-pods/device-pod-71-freq/jobs", { intent: "debug.reset", reason: "reset smoke with lease", diff --git a/internal/cloud/access-control.ts b/internal/cloud/access-control.ts index c8afc539..c77514e0 100644 --- a/internal/cloud/access-control.ts +++ b/internal/cloud/access-control.ts @@ -397,7 +397,13 @@ class AccessController { const parsed = parseDevicePodPath(url.pathname); if (!parsed) return sendJson(response, 404, errorPayload("not_found", "Device Pod route is not implemented", 404)); const pod = await this.store.getVisibleDevicePod(auth.actor, parsed.devicePodId); - if (!pod) return sendJson(response, 404, errorPayload("device_pod_not_found", `Device Pod ${parsed.devicePodId} is not visible to the current actor`, 404)); + if (!pod) { + const existing = await this.store.getDevicePod(parsed.devicePodId); + if (existing?.status === "active") { + return sendJson(response, 403, errorPayload("device_pod_forbidden", `Device Pod ${parsed.devicePodId} is not authorized for the current actor`, 403)); + } + return sendJson(response, 404, errorPayload("device_pod_not_found", `Device Pod ${parsed.devicePodId} was not found`, 404)); + } if (request.method === "GET" && parsed.route === "status") return sendJson(response, 200, this.devicePodStatus(pod, auth.actor)); if (request.method === "GET" && parsed.route === "events") return sendJson(response, 200, await this.devicePodEvents(pod, url.searchParams));