Skip to content

Commit

Permalink
webhook: add projectId scoping
Browse files Browse the repository at this point in the history
  • Loading branch information
emranemran committed Mar 25, 2024
1 parent b4b5494 commit ed1df31
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 10 deletions.
52 changes: 42 additions & 10 deletions packages/api/src/controllers/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import sql from "sql-template-strings";
import { UnprocessableEntityError, NotFoundError } from "../store/errors";
import webhookLog from "./webhook-log";

function validateWebhookPayload(id, userId, createdAt, payload) {
function validateWebhookPayload(id, userId, projectId, createdAt, payload) {
try {
new URL(payload.url);
} catch (e) {
Expand All @@ -27,6 +27,7 @@ function validateWebhookPayload(id, userId, createdAt, payload) {
return {
id,
userId,
projectId: projectId ?? "",
createdAt,
kind: "webhook",
name: payload.name,
Expand All @@ -50,6 +51,7 @@ const fieldsMap: FieldsMap = {
createdAt: { val: `webhook.data->'createdAt'`, type: "int" },
userId: `webhook.data->>'userId'`,
"user.email": { val: `users.data->>'email'`, type: "full-text" },
projectId: `webhook.data->>'projectId'`,
sharedSecret: `webhook.data->>'sharedSecret'`,
};

Expand All @@ -65,6 +67,9 @@ app.get("/", authorizer({}), async (req, res) => {
if (!all || all === "false") {
query.push(sql`webhook.data->>'deleted' IS NULL`);
}
query.push(
sql`coalesce(webhook.data->>'projectId', '') = ${req.project?.id || ""}`
);

let fields =
" webhook.id as id, webhook.data as data, users.id as usersId, users.data as usersdata";
Expand Down Expand Up @@ -96,6 +101,9 @@ app.get("/", authorizer({}), async (req, res) => {

const query = parseFilters(fieldsMap, filters);
query.push(sql`webhook.data->>'userId' = ${req.user.id}`);
query.push(
sql`coalesce(webhook.data->>'projectId', '') = ${req.project?.id || ""}`
);

if (!all || all === "false" || !req.user.admin) {
query.push(sql`webhook.data->>'deleted' IS NULL`);
Expand Down Expand Up @@ -145,7 +153,13 @@ app.get("/subscribed/:event", authorizer({}), async (req, res) => {

app.post("/", authorizer({}), validatePost("webhook"), async (req, res) => {
const id = uuid();
const doc = validateWebhookPayload(id, req.user.id, Date.now(), req.body);
const doc = validateWebhookPayload(
id,
req.user.id,
req.project.id,
Date.now(),
req.body
);
try {
await req.store.create(doc);
} catch (e) {
Expand All @@ -163,7 +177,8 @@ app.get("/:id", authorizer({}), async (req, res) => {
const webhook = await db.webhook.get(req.params.id);
if (
!webhook ||
((webhook.deleted || webhook.userId !== req.user.id) && !req.user.admin)
((webhook.deleted || webhook.userId !== req.user.id) && !req.user.admin) ||
webhook.projectId !== req.project?.id
) {
res.status(404);
return res.json({ errors: ["not found"] });
Expand All @@ -176,14 +191,25 @@ app.get("/:id", authorizer({}), async (req, res) => {
app.put("/:id", authorizer({}), validatePost("webhook"), async (req, res) => {
// modify a specific webhook
const webhook = await req.store.get(`webhook/${req.body.id}`);
if ((webhook.userId !== req.user.id || webhook.deleted) && !req.user.admin) {
if (
(webhook.userId !== req.user.id ||
webhook.deleted ||
webhook.projectId !== req.project?.id) &&
!req.user.admin
) {
// do not reveal that webhooks exists
res.status(404);
return res.json({ errors: ["not found"] });
}

const { id, userId, createdAt } = webhook;
const doc = validateWebhookPayload(id, userId, createdAt, req.body);
const { id, userId, projectId, createdAt } = webhook;
const doc = validateWebhookPayload(
id,
userId,
projectId,
createdAt,
req.body
);
try {
await req.store.replace(doc);
} catch (e) {
Expand All @@ -206,14 +232,16 @@ app.patch(
}

if (
(webhook.userId !== req.user.id || webhook.deleted) &&
(webhook.userId !== req.user.id ||
webhook.deleted ||
webhook.projectId !== req.project?.id) &&
!req.user.admin
) {
// do not reveal that webhooks exists
throw new NotFoundError(`webhook not found`);
}

const { id, userId, createdAt, kind } = webhook;
const { id, userId, projectId, createdAt, kind } = webhook;

if (req.body.streamId) {
const stream = await db.stream.get(req.body.streamId);
Expand All @@ -240,7 +268,10 @@ app.delete("/:id", authorizer({}), async (req, res) => {
const webhook = await db.webhook.get(req.params.id);
if (
!webhook ||
((webhook.deleted || webhook.userId !== req.user.id) && !req.isUIAdmin)
((webhook.deleted ||
webhook.userId !== req.user.id ||
webhook.projectId !== req.project?.id) &&
!req.isUIAdmin)
) {
// do not reveal that webhooks exists
res.status(404);
Expand Down Expand Up @@ -270,7 +301,8 @@ app.delete("/", authorizer({}), async (req, res) => {
const webhooks = await db.webhook.getMany(ids);
if (
webhooks.length !== ids.length ||
webhooks.some((s) => s.deleted || s.userId !== req.user.id)
webhooks.some((s) => s.deleted || s.userId !== req.user.id) ||
webhooks.some((s) => s.projectId !== req.project?.id)
) {
res.status(404);
return res.json({ errors: ["not found"] });
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/schema/api-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ components:
example: de7818e7-610a-4057-8f6f-b785dc1e6f88
name:
type: string
projectId:
type: string
description: The ID of the project
example: aac12556-4d65-4d34-9fb6-d1f0985eb0a9
createdAt:
type: number
readOnly: true
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/schema/db-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,10 @@ components:
readOnly: true
type: string
index: true
projectId:
type: string
index: true
example: 66E2161C-7670-4D05-B71D-DA2D6979556F
events:
index: true
indexType: gin
Expand Down

0 comments on commit ed1df31

Please sign in to comment.