From a8d7b2959e87c30fbafdb12af7ffa093385dcc60 Mon Sep 17 00:00:00 2001 From: Hugues Chocart Date: Mon, 23 Sep 2024 11:42:43 +0100 Subject: [PATCH] fix: security patch (#573) --- packages/backend/src/api/v1/checklists.ts | 30 +++++++++++-------- .../src/api/v1/data-warehouse/index.ts | 7 +++++ packages/backend/src/api/v1/users.ts | 7 ++++- .../frontend/components/layout/Sidebar.tsx | 2 +- packages/shared/access-control/roles.ts | 4 +-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/backend/src/api/v1/checklists.ts b/packages/backend/src/api/v1/checklists.ts index 5f10bea1..7155e89d 100644 --- a/packages/backend/src/api/v1/checklists.ts +++ b/packages/backend/src/api/v1/checklists.ts @@ -72,17 +72,20 @@ checklists.post( }, ); -checklists.patch("/:id", async (ctx: Context) => { - const { projectId } = ctx.state; - const paramsSchema = z.object({ id: z.string().uuid() }); - const bodySchema = z.object({ - slug: z.string(), - data: z.any() as z.ZodType, - }); - const { slug, data } = bodySchema.parse(ctx.request.body); - const { id } = paramsSchema.parse(ctx.params); - - const [updatedCheck] = await sql` +checklists.patch( + "/:id", + checkAccess("checklists", "update"), + async (ctx: Context) => { + const { projectId } = ctx.state; + const paramsSchema = z.object({ id: z.string().uuid() }); + const bodySchema = z.object({ + slug: z.string(), + data: z.any() as z.ZodType, + }); + const { slug, data } = bodySchema.parse(ctx.request.body); + const { id } = paramsSchema.parse(ctx.params); + + const [updatedCheck] = await sql` update checklist set @@ -92,8 +95,9 @@ checklists.patch("/:id", async (ctx: Context) => { and id = ${id} returning * `; - ctx.body = updatedCheck; -}); + ctx.body = updatedCheck; + }, +); checklists.delete( "/:id", diff --git a/packages/backend/src/api/v1/data-warehouse/index.ts b/packages/backend/src/api/v1/data-warehouse/index.ts index 3bc6e056..9ef93909 100644 --- a/packages/backend/src/api/v1/data-warehouse/index.ts +++ b/packages/backend/src/api/v1/data-warehouse/index.ts @@ -23,6 +23,13 @@ dataWarehouse.post("/bigquery", async (ctx: Context) => { apiKey: z.string().transform((apiKey) => JSON.parse(apiKey)), }); const { apiKey } = bodySchema.parse(ctx.request.body); + const { userId } = ctx.state; + + const [user] = await sql`select * from account where id = ${userId}`; + + if (user.role !== "owner") { + ctx.throw(403, "Forbidden"); + } if (config.DATA_WAREHOUSE_EXPORTS_ALLOWED) { await createNewDatastream(apiKey, process.env.DATABASE_URL!, ctx); diff --git a/packages/backend/src/api/v1/users.ts b/packages/backend/src/api/v1/users.ts index 4f26de2f..68b5bba9 100644 --- a/packages/backend/src/api/v1/users.ts +++ b/packages/backend/src/api/v1/users.ts @@ -188,7 +188,7 @@ users.post("/", checkAccess("teamMembers", "create"), async (ctx: Context) => { projects: z.array(z.string()).min(1), }); const { email, role, projects } = bodySchema.parse(ctx.request.body); - const { orgId } = ctx.state; + const { orgId, userId } = ctx.state; const FIFTEEN_DAYS = 60 * 60 * 24 * 15; @@ -207,6 +207,11 @@ users.post("/", checkAccess("teamMembers", "create"), async (ctx: Context) => { ); } + const [currentUser] = await sql`select * from account where id = ${userId}`; + if (currentUser.role !== "owner" && role === "billing") { + ctx.throw(403, "Only owners can add billing members to the organization."); + } + const token = await signJWT({ email, orgId }, FIFTEEN_DAYS); const userToInsert = { email, diff --git a/packages/frontend/components/layout/Sidebar.tsx b/packages/frontend/components/layout/Sidebar.tsx index f566469f..6a185bb4 100644 --- a/packages/frontend/components/layout/Sidebar.tsx +++ b/packages/frontend/components/layout/Sidebar.tsx @@ -470,7 +470,7 @@ export default function Sidebar() { - {hasAccess(user.role, "billing", "read") && ( + {hasAccess(user.role, "settings", "read") && (