fix(api): scope Team Computer export to exporting bot workspace
- Add teamBotWorkspaceDirectory import to router.ts - Add exportOptions to scope team exports to one bot workspace - Add authorization test: member export only sees their workspace files - Add LocalAgentHomeStore and computerHomeKey imports to testkit
This commit is contained in:
parent
c8ea27c239
commit
ec597a5c68
@ -74,6 +74,7 @@ import {
|
||||
screenLeaseIdForRun,
|
||||
scriptedCatalogEntry,
|
||||
serializeModelSecret,
|
||||
teamBotWorkspaceDirectory,
|
||||
takeoverLeaseMs,
|
||||
toComputerRef,
|
||||
touchRunningComputer,
|
||||
@ -4477,6 +4478,10 @@ export function createRouter(deps: RouterDeps) {
|
||||
userId: context.actor.userId,
|
||||
signal: new AbortController().signal,
|
||||
};
|
||||
const exportOptions =
|
||||
bot.computer.scope === "team"
|
||||
? { prefix: teamBotWorkspaceDirectory(input.botId) }
|
||||
: undefined;
|
||||
const [memory, routines, files, history] = await Promise.all([
|
||||
deps.prisma.memoryDocument.findMany({
|
||||
where: { botId: input.botId, spaceId: context.actor.spaceId },
|
||||
@ -4486,7 +4491,7 @@ export function createRouter(deps: RouterDeps) {
|
||||
}),
|
||||
(async () => {
|
||||
const exported: Array<{ path: string; content: string }> = [];
|
||||
for await (const file of deps.home.exportHome(homeKey, exportContext)) {
|
||||
for await (const file of deps.home.exportHome(homeKey, exportContext, exportOptions)) {
|
||||
exported.push({
|
||||
path: file.path,
|
||||
content: new TextDecoder().decode(file.content),
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { ComposioEmulator } from "@rakazo/adapters";
|
||||
import { ComposioEmulator, LocalAgentHomeStore, teamBotWorkspaceDirectory } from "@rakazo/adapters";
|
||||
import type { AiConsentStatus, appContract, Space, SpaceNavigation } from "@rakazo/contracts";
|
||||
import {
|
||||
claimEmptySpaceDeletionForMember,
|
||||
computerHomeKey,
|
||||
deleteEmptySpaceForMember,
|
||||
releaseSpaceDeletionClaim,
|
||||
renewSpaceDeletionClaim,
|
||||
@ -544,6 +545,77 @@ describeWithDatabase("API authorization and resource isolation", () => {
|
||||
expect(computer.controlBotId).toBe(ownerBot.id);
|
||||
});
|
||||
|
||||
it("scopes Team Computer export to the exporting member's bot workspace in a shared Space", async () => {
|
||||
const owner = await signup(app, `export-owner-${stamp}@rakazo.test`, "Export Owner");
|
||||
const member = await signup(app, `export-member-${stamp}@rakazo.test`, "Export Member");
|
||||
const ownerActor = await rpc<Actor>(app, owner, "me");
|
||||
const memberActor = await rpc<Actor>(app, member, "me");
|
||||
|
||||
const shared = await rpc<Space>(app, owner, "spaces/create", { name: "Shared export" });
|
||||
const sharedRow = await handles.prisma.space.findUniqueOrThrow({
|
||||
where: { id: shared.id },
|
||||
select: { organizationId: true },
|
||||
});
|
||||
|
||||
await handles.prisma.member.deleteMany({ where: { userId: memberActor.userId } });
|
||||
await handles.prisma.member.create({
|
||||
data: {
|
||||
id: `export-member-org-${stamp}`,
|
||||
organizationId: sharedRow.organizationId,
|
||||
userId: memberActor.userId,
|
||||
role: "member",
|
||||
createdAt: new Date(),
|
||||
},
|
||||
});
|
||||
await handles.prisma.spaceMember.create({
|
||||
data: {
|
||||
id: `export-member-space-${stamp}`,
|
||||
spaceId: shared.id,
|
||||
organizationId: sharedRow.organizationId,
|
||||
userId: memberActor.userId,
|
||||
role: "member",
|
||||
createdAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
const ownerBot = await rpc<Bot>(app, owner, "bots/create", botInput("Owner Bot"), shared.id);
|
||||
const memberBot = await rpc<Bot>(app, member, "bots/create", botInput("Member Bot"), shared.id);
|
||||
const home = new LocalAgentHomeStore(dataDir);
|
||||
const homeKey = computerHomeKey("team", shared.id);
|
||||
const homeContext = {
|
||||
operationId: "test",
|
||||
traceId: "test",
|
||||
spaceId: shared.id,
|
||||
userId: ownerActor.userId,
|
||||
signal: new AbortController().signal,
|
||||
};
|
||||
await home.writeFile(
|
||||
homeKey,
|
||||
`${teamBotWorkspaceDirectory(ownerBot.id)}/PRIVATE.md`,
|
||||
"owner-workspace-secret",
|
||||
homeContext,
|
||||
);
|
||||
await home.writeFile(
|
||||
homeKey,
|
||||
`${teamBotWorkspaceDirectory(memberBot.id)}/notes.txt`,
|
||||
"member-notes",
|
||||
homeContext,
|
||||
);
|
||||
|
||||
const manifest = await rpc<{ files: Array<{ path: string; content: string }> }>(
|
||||
app,
|
||||
member,
|
||||
"export/bot",
|
||||
{ botId: memberBot.id },
|
||||
shared.id,
|
||||
);
|
||||
|
||||
expect(manifest.files.some((file) => file.content.includes("owner-workspace-secret"))).toBe(
|
||||
false,
|
||||
);
|
||||
expect(manifest.files.some((file) => file.content.includes("member-notes"))).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps approval rules private to each user in a shared Space", async () => {
|
||||
const owner = await signup(app, `approval-owner-${stamp}@rakazo.test`, "Approval Owner");
|
||||
const member = await signup(app, `approval-member-${stamp}@rakazo.test`, "Approval Member");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user