Files
infisical/backend/src/server/routes/v1/webhook-router.ts
T

247 lines
6.7 KiB
TypeScript

import { z } from "zod";
import { WebhooksSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { WebhookType } from "@app/services/webhook/webhook-types";
export const sanitizedWebhookSchema = WebhooksSchema.pick({
id: true,
secretPath: true,
lastStatus: true,
lastRunErrorMessage: true,
isDisabled: true,
createdAt: true,
updatedAt: true,
envId: true,
type: true
}).extend({
projectId: z.string(),
environment: z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
});
export const registerWebhookRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z
.object({
type: z.nativeEnum(WebhookType).default(WebhookType.GENERAL),
workspaceId: z.string().trim(),
environment: z.string().trim(),
webhookUrl: z.string().url().trim(),
webhookSecretKey: z.string().trim().optional(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash)
})
.superRefine((data, ctx) => {
if (data.type === WebhookType.SLACK && !data.webhookUrl.includes("hooks.slack.com")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Incoming Webhook URL is invalid.",
path: ["webhookUrl"]
});
}
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.createWebhook({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath
}
}
});
return { message: "Successfully created webhook", webhook };
}
});
server.route({
method: "PATCH",
url: "/:webhookId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
}),
body: z.object({
isDisabled: z.boolean().default(false)
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.updateWebhook({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.webhookId,
isDisabled: req.body.isDisabled
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.UPDATE_WEBHOOK_STATUS,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath
}
}
});
return { message: "Successfully updated webhook", webhook };
}
});
server.route({
method: "DELETE",
url: "/:webhookId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
})
},
handler: async (req) => {
const webhook = await server.services.webhook.deleteWebhook({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.webhookId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.DELETE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath
}
}
});
return { message: "Successfully deleted webhook", webhook };
}
});
server.route({
method: "POST",
url: "/:webhookId/test",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
webhookId: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
webhook: sanitizedWebhookSchema
})
}
},
handler: async (req) => {
const webhook = await server.services.webhook.testWebhook({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.webhookId
});
return { message: "Successfully tested webhook", webhook };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim().optional(),
secretPath: z
.string()
.trim()
.optional()
.transform((val) => (val ? removeTrailingSlash(val) : val))
}),
response: {
200: z.object({
message: z.string(),
webhooks: sanitizedWebhookSchema.extend({ url: z.string() }).array()
})
}
},
handler: async (req) => {
const webhooks = await server.services.webhook.listWebhooks({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
});
return { message: "Successfully fetched webhook", webhooks };
}
});
};