Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion apps/api/src/tools/organization/member-update-role.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "bun:test";
import { describe, expect, it, mock } from "bun:test";
import { ORGANIZATION_MEMBER_UPDATE_ROLE } from "./member-update-role";

describe("ORGANIZATION_MEMBER_UPDATE_ROLE outputSchema", () => {
Expand Down Expand Up @@ -31,3 +31,38 @@ describe("ORGANIZATION_MEMBER_UPDATE_ROLE outputSchema", () => {
expect(result.success).toBe(true);
});
});

describe("ORGANIZATION_MEMBER_UPDATE_ROLE handler", () => {
it("rejects an organizationId other than the authenticated one", async () => {
const updateMemberRole = mock(async () => ({}));
const ctx = {
auth: { user: { id: "user-1" } },
access: { check: mock(async () => {}) },
organization: { id: "org-1", slug: "acme", name: "Acme" },
db: {
selectFrom: () => ({
select: () => ({
where: () => ({
where: () => ({
executeTakeFirst: async () => ({ role: "owner" }),
}),
}),
}),
}),
},
boundAuth: { organization: { updateMemberRole } },
} as unknown as Parameters<
typeof ORGANIZATION_MEMBER_UPDATE_ROLE.handler
>[1];

await expect(
ORGANIZATION_MEMBER_UPDATE_ROLE.handler(
{ organizationId: "org-2", memberId: "member-1", role: ["admin"] },
ctx,
),
).rejects.toThrow(
"Organization ID does not match authenticated organization",
);
expect(updateMemberRole.mock.calls.length).toBe(0);
});
});
7 changes: 7 additions & 0 deletions apps/api/src/tools/organization/member-update-role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export const ORGANIZATION_MEMBER_UPDATE_ROLE = defineTool({
);
}

// Validate organization ID matches context (mirrors member-add.ts/update.ts).
if (organizationId !== ctx.organization?.id) {
throw new Error(
"Organization ID does not match authenticated organization",
);
}

// Validate the caller is allowed to assign every target role. `organizationId`
// may be an explicit override (not the session's active org), and
// `ctx.auth.user?.role` only ever reflects the active-org role — using it here
Expand Down
Loading