mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 10:26:26 +01:00
fix(secrets): require explicit responsible users
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7c7f9b1767
commit
f3727a6fb0
3 files changed
+21
-5
No files matched your search
@@ -156,6 +156,23 @@ describe("secret routes", () => {
|
||||
expect(JSON.stringify(mockLogActivity.mock.calls)).not.toContain("secret-value");
|
||||
});
|
||||
|
||||
it("rejects current-user secret values without a concrete user identity", async () => {
|
||||
const res = await request(createApp({
|
||||
type: "board",
|
||||
source: "local_implicit",
|
||||
companyIds: ["company-1"],
|
||||
memberships: [{ companyId: "company-1", status: "active", membershipRole: "admin" }],
|
||||
})).post("/api/companies/company-1/me/user-secrets").send({
|
||||
definitionKey: "github_token",
|
||||
value: "secret-value",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toMatchObject({ error: "User identity required for user-specific secrets" });
|
||||
expect(mockSecretService.createCurrentUserSecretValue).not.toHaveBeenCalled();
|
||||
expect(mockLogActivity).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects empty current-user secret rotation payloads", async () => {
|
||||
const res = await request(createApp())
|
||||
.post("/api/companies/company-1/me/user-secrets/secret-1/rotate")
|
||||
|
||||
@@ -19,7 +19,7 @@ import { validate } from "../middleware/validate.js";
|
||||
import { assertBoard, assertCompanyAccess } from "./authz.js";
|
||||
import { logActivity, secretService } from "../services/index.js";
|
||||
import { getConfiguredSecretProvider } from "../secrets/configured-provider.js";
|
||||
import { forbidden } from "../errors.js";
|
||||
import { forbidden, unauthorized } from "../errors.js";
|
||||
|
||||
function assertSecretDefinitionAdmin(req: Parameters<typeof assertBoard>[0], companyId: string) {
|
||||
assertBoard(req);
|
||||
@@ -34,7 +34,8 @@ function assertSecretDefinitionAdmin(req: Parameters<typeof assertBoard>[0], com
|
||||
|
||||
function currentUserId(req: Parameters<typeof assertBoard>[0]) {
|
||||
assertBoard(req);
|
||||
return req.actor.userId ?? "board";
|
||||
if (req.actor.userId) return req.actor.userId;
|
||||
throw unauthorized("User identity required for user-specific secrets");
|
||||
}
|
||||
|
||||
function isCompanyScopedSecret(secret: { scope?: string | null }) {
|
||||
|
||||
@@ -4614,17 +4614,15 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
if (input.run.responsibleUserId) return input.run.responsibleUserId;
|
||||
if (input.routineEnvContext.responsibleUserId) return input.routineEnvContext.responsibleUserId;
|
||||
if (input.issueContext?.responsibleUserId) return input.issueContext.responsibleUserId;
|
||||
if (input.issueContext?.createdByUserId) return input.issueContext.createdByUserId;
|
||||
if (input.issueContext?.parentId) {
|
||||
const parent = await db
|
||||
.select({
|
||||
responsibleUserId: issues.responsibleUserId,
|
||||
createdByUserId: issues.createdByUserId,
|
||||
})
|
||||
.from(issues)
|
||||
.where(and(eq(issues.companyId, input.run.companyId), eq(issues.id, input.issueContext.parentId)))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
return parent?.responsibleUserId ?? parent?.createdByUserId ?? null;
|
||||
return parent?.responsibleUserId ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user