diff --git a/apps/dashboard/src/components/forms/notifications/form-pushover.tsx b/apps/dashboard/src/components/forms/notifications/form-pushover.tsx index 62aeba7a..b11e10b8 100644 --- a/apps/dashboard/src/components/forms/notifications/form-pushover.tsx +++ b/apps/dashboard/src/components/forms/notifications/form-pushover.tsx @@ -35,10 +35,18 @@ import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { CheckboxTree } from "@/components/ui/checkbox-tree"; import { useTRPC } from "@/lib/trpc/client"; +const pushoverKeySchema = z + .string() + .regex(/^[A-Za-z0-9]{30}$/, "Must be exactly 30 alphanumeric characters"); + const schema = z.object({ name: z.string(), provider: z.literal("pushover"), - data: z.record(z.string(), z.string()), + data: z.object({ + token: pushoverKeySchema, + user: pushoverKeySchema, + priority: z.string(), + }), monitors: z.array(z.number()), }); @@ -115,6 +123,8 @@ export function FormPushover({ if (isPending) return; startTransition(async () => { + const valid = await form.trigger(["data.token", "data.user"]); + if (!valid) return; try { const provider = form.getValues("provider"); const data = form.getValues("data"); @@ -173,6 +183,7 @@ export function FormPushover({ Application API Token @@ -192,6 +203,7 @@ export function FormPushover({ User / Group Key diff --git a/packages/db/src/schema/notifications/validation.ts b/packages/db/src/schema/notifications/validation.ts index 5a3ad883..3aa0ad98 100644 --- a/packages/db/src/schema/notifications/validation.ts +++ b/packages/db/src/schema/notifications/validation.ts @@ -92,6 +92,8 @@ export const pushoverDataSchema = z.object({ pushover: z.object({ token: pushoverKeySchema, user: pushoverKeySchema, + // Emergency priority (2) is excluded: it requires extra retry/expire + // params and acknowledgement handling not surfaced here. priority: z.coerce.number().int().min(-2).max(1).prefault(0), }), }); diff --git a/packages/notifications/pushover/src/index.ts b/packages/notifications/pushover/src/index.ts index c64e328a..b66cdf3a 100644 --- a/packages/notifications/pushover/src/index.ts +++ b/packages/notifications/pushover/src/index.ts @@ -42,13 +42,16 @@ async function send({ } } +const parsePushover = (data: string) => + pushoverDataSchema.parse(JSON.parse(data)).pushover; + export const sendAlert = async ({ monitor, notification, statusCode, message, }: NotificationContext) => { - const { pushover } = pushoverDataSchema.parse(JSON.parse(notification.data)); + const pushover = parsePushover(notification.data); await send({ ...pushover, title: `${monitor.name} is down`, @@ -63,7 +66,7 @@ export const sendRecovery = async ({ monitor, notification, }: NotificationContext) => { - const { pushover } = pushoverDataSchema.parse(JSON.parse(notification.data)); + const pushover = parsePushover(notification.data); // Recovery is never urgent: force normal priority so "up again" can't // bypass the user's Pushover quiet hours. await send({ @@ -79,7 +82,7 @@ export const sendDegraded = async ({ monitor, notification, }: NotificationContext) => { - const { pushover } = pushoverDataSchema.parse(JSON.parse(notification.data)); + const pushover = parsePushover(notification.data); await send({ ...pushover, title: `${monitor.name} is degraded`,