mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
test(auth): require existing membership and verify grant audit rollback
Refuse grants without an existing active membership. Verify audit failures roll back permission changes. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
a72aa691be
commit
338c58fb24
2 files changed
+16
-4
No files matched your search
@@ -2,13 +2,14 @@ import { randomUUID } from "node:crypto";
|
||||
import express from "express";
|
||||
import request from "supertest";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { activityLog, agents, authUsers, companies, companyMemberships, createDb,
|
||||
principalPermissionGrants, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db";
|
||||
import { agentPermissionGrantRoutes } from "../routes/agent-permission-grants.js";
|
||||
import { agentCommentaryRoutes } from "../routes/agent-commentary.js";
|
||||
import { errorHandler } from "../middleware/error-handler.js";
|
||||
import { startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
|
||||
import * as activityLogging from "../services/activity-log.js";
|
||||
|
||||
describe("Board agent permission grants", () => {
|
||||
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
|
||||
@@ -103,6 +104,9 @@ describe("Board agent permission grants", () => {
|
||||
eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.agentId)));
|
||||
expect((await request(api).patch(path(f)).send(grant)).status).toBe(409);
|
||||
expect(await grants(f)).toEqual([]);
|
||||
await db.delete(companyMemberships).where(and(
|
||||
eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, f.agentId)));
|
||||
expect((await request(api).patch(path(f)).send(grant)).status).toBe(409);
|
||||
});
|
||||
|
||||
it("serializes concurrent additions of the same grant", async () => {
|
||||
@@ -111,4 +115,13 @@ describe("Board agent permission grants", () => {
|
||||
expect(responses.map(response => response.status)).toEqual([200, 200]);
|
||||
expect(await grants(f)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("rolls back the grant if its audit record cannot be saved", async () => {
|
||||
const f = await seed();
|
||||
const failure = vi.spyOn(activityLogging, "logActivity").mockRejectedValueOnce(new Error("Audit storage unavailable"));
|
||||
try {
|
||||
expect((await request(owner(f)).patch(path(f)).send(grant)).status).toBe(500);
|
||||
expect(await grants(f)).toEqual([]);
|
||||
} finally { failure.mockRestore(); }
|
||||
});
|
||||
});
|
||||
@@ -31,17 +31,16 @@ export function agentPermissionGrantRoutes(db: Db) {
|
||||
}
|
||||
if (enabled) {
|
||||
if (agent.status === "pending_approval" || agent.status === "terminated") {
|
||||
throw conflict("Approve or restore the agent before granting permissions");
|
||||
throw conflict("Cannot grant permissions to an unapproved or terminated agent");
|
||||
}
|
||||
const membership = await tx.select().from(companyMemberships).where(and(
|
||||
eq(companyMemberships.companyId, agent.companyId),
|
||||
eq(companyMemberships.principalType, "agent"),
|
||||
eq(companyMemberships.principalId, agent.id),
|
||||
)).for("update").then(rows => rows[0]);
|
||||
if (membership && membership.status !== "active") {
|
||||
if (!membership || membership.status !== "active") {
|
||||
throw conflict("Agent company membership is not active");
|
||||
}
|
||||
if (!membership) await access.ensureMembership(agent.companyId, "agent", agent.id);
|
||||
await tx.insert(principalPermissionGrants).values({
|
||||
companyId: agent.companyId, principalType: "agent", principalId: agent.id,
|
||||
permissionKey, scope: null, grantedByUserId: req.actor.userId ?? null,
|
||||
|
||||
Reference in new issue
Block a user