205 lines
8.0 KiB
TypeScript
205 lines
8.0 KiB
TypeScript
import { z } from "zod";
|
|
import { NtfyAuthTypes } from "@/domain/notifications/notification.type.js";
|
|
|
|
//****************************************
|
|
// Notification Validations
|
|
//****************************************
|
|
|
|
// ntfy stores its secret in accessToken for both auth types: the bearer token for
|
|
// "token", the password for "basic". Reject partially filled credentials so a channel
|
|
// can't be saved looking authenticated while posting anonymously.
|
|
const refineNtfyAuth = (body: { ntfyAuthType?: string; ntfyUsername?: string; accessToken?: string }, ctx: z.RefinementCtx) => {
|
|
const authType = body.ntfyAuthType ?? "none";
|
|
|
|
if (authType === "none") {
|
|
if (body.accessToken) {
|
|
ctx.addIssue({ code: "custom", path: ["accessToken"], message: "Select an authentication type to use an access token" });
|
|
}
|
|
if (body.ntfyUsername) {
|
|
ctx.addIssue({ code: "custom", path: ["ntfyUsername"], message: "Select an authentication type to use a username" });
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (authType === "token") {
|
|
if (!body.accessToken) {
|
|
ctx.addIssue({ code: "custom", path: ["accessToken"], message: "Access token is required for token authentication" });
|
|
}
|
|
if (body.ntfyUsername) {
|
|
ctx.addIssue({ code: "custom", path: ["ntfyUsername"], message: "Username is only used with basic authentication" });
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!body.ntfyUsername) {
|
|
ctx.addIssue({ code: "custom", path: ["ntfyUsername"], message: "Username is required for basic authentication" });
|
|
}
|
|
if (!body.accessToken) {
|
|
ctx.addIssue({ code: "custom", path: ["accessToken"], message: "Password is required for basic authentication" });
|
|
}
|
|
};
|
|
|
|
export const createNotificationBodyValidation = z.discriminatedUnion("type", [
|
|
// Email notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("email"),
|
|
address: z.email("Please enter a valid e-mail address"),
|
|
homeserverUrl: z.union([z.string(), z.literal("")]).optional(),
|
|
roomId: z.union([z.string(), z.literal("")]).optional(),
|
|
accessToken: z.union([z.string(), z.literal("")]).optional(),
|
|
}),
|
|
// Webhook notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("webhook"),
|
|
address: z.url({ message: "Please enter a valid Webhook URL" }),
|
|
homeserverUrl: z.union([z.string(), z.literal("")]).optional(),
|
|
roomId: z.union([z.string(), z.literal("")]).optional(),
|
|
accessToken: z.union([z.string(), z.literal("")]).optional(),
|
|
}),
|
|
// Rocket.Chat notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("rocket_chat"),
|
|
address: z.url({
|
|
protocol: /^https?$/,
|
|
message: "Please enter a valid Rocket.Chat webhook URL",
|
|
}),
|
|
}),
|
|
// Slack notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("slack"),
|
|
address: z.url({ message: "Please enter a valid Webhook URL" }),
|
|
homeserverUrl: z.union([z.string(), z.literal("")]).optional(),
|
|
roomId: z.union([z.string(), z.literal("")]).optional(),
|
|
accessToken: z.union([z.string(), z.literal("")]).optional(),
|
|
}),
|
|
// Discord notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("discord"),
|
|
address: z.url({ message: "Please enter a valid Webhook URL" }),
|
|
homeserverUrl: z.union([z.string(), z.literal("")]).optional(),
|
|
roomId: z.union([z.string(), z.literal("")]).optional(),
|
|
accessToken: z.union([z.string(), z.literal("")]).optional(),
|
|
}),
|
|
// PagerDuty notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("pager_duty"),
|
|
address: z.string().min(1, "PagerDuty integration key is required"),
|
|
homeserverUrl: z.union([z.string(), z.literal("")]).optional(),
|
|
roomId: z.union([z.string(), z.literal("")]).optional(),
|
|
accessToken: z.union([z.string(), z.literal("")]).optional(),
|
|
}),
|
|
// Matrix notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("matrix"),
|
|
address: z.union([z.string(), z.literal("")]).optional(),
|
|
homeserverUrl: z.url({ message: "Please enter a valid Homeserver URL" }),
|
|
roomId: z.string().min(1, "Room ID is required"),
|
|
accessToken: z.string().min(1, "Access Token is required"),
|
|
}),
|
|
// Teams notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("teams"),
|
|
address: z.url({ message: "Please enter a valid Webhook URL" }),
|
|
}),
|
|
// Telegram notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("telegram"),
|
|
address: z.string().min(1, "Chat ID is required"),
|
|
accessToken: z.string().min(1, "Bot token is required"),
|
|
}),
|
|
// Pushover notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("pushover"),
|
|
address: z.string().min(1, "User key is required"),
|
|
accessToken: z.string().min(1, "App token is required"),
|
|
}),
|
|
// Signalgrid notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("signalgrid"),
|
|
address: z.string().min(1, "Channel is required"),
|
|
accessToken: z.string().min(1, "Client key is required"),
|
|
}),
|
|
// Twilio SMS notification
|
|
z.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("twilio"),
|
|
accountSid: z.string().min(1, "Account SID is required"),
|
|
accessToken: z.string().min(1, "Auth token is required"),
|
|
phone: z.string().min(1, "Recipient phone number is required"),
|
|
twilioPhoneNumber: z.string().min(1, "Twilio phone number is required"),
|
|
}),
|
|
// ntfy notification
|
|
z
|
|
.object({
|
|
notificationName: z.string().min(1, "Notification name is required"),
|
|
type: z.literal("ntfy"),
|
|
address: z.url({ message: "Please enter a valid ntfy server URL" }),
|
|
topic: z.string().min(1, "Topic is required"),
|
|
ntfyAuthType: z.enum(NtfyAuthTypes).optional(),
|
|
ntfyUsername: z.union([z.string(), z.literal("")]).optional(),
|
|
accessToken: z.union([z.string(), z.literal("")]).optional(),
|
|
})
|
|
.superRefine(refineNtfyAuth),
|
|
]);
|
|
|
|
export const testNotificationBodyValidation = createNotificationBodyValidation;
|
|
|
|
export const deleteNotificationParamValidation = z.object({
|
|
id: z.string().min(1, "Notification ID is required"),
|
|
});
|
|
export const getNotificationByIdParamValidation = z.object({
|
|
id: z.string().min(1, "Notification ID is required"),
|
|
});
|
|
export const editNotificationParamValidation = z.object({
|
|
id: z.string().min(1, "Notification ID is required"),
|
|
});
|
|
|
|
export const testAllNotificationsBodyValidation = z.object({
|
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
|
});
|
|
|
|
export const sendTestEmailBodyValidation = z.object({
|
|
to: z.string().min(1, "To field is required"),
|
|
systemEmailHost: z.string().optional(),
|
|
systemEmailPort: z.number().optional(),
|
|
systemEmailSecure: z.boolean().optional(),
|
|
systemEmailPool: z.boolean().optional(),
|
|
systemEmailAddress: z.string().optional(),
|
|
systemEmailDisplayName: z.string().optional(),
|
|
systemEmailPassword: z.string().optional(),
|
|
systemEmailUser: z.string().optional(),
|
|
systemEmailConnectionHost: z.union([z.string(), z.literal("")]).optional(),
|
|
systemEmailIgnoreTLS: z.boolean().optional(),
|
|
systemEmailRequireTLS: z.boolean().optional(),
|
|
systemEmailRejectUnauthorized: z.boolean().optional(),
|
|
systemEmailTLSServername: z.union([z.string(), z.literal("")]).optional(),
|
|
});
|
|
|
|
export const updateNotificationsValidation = z
|
|
.object({
|
|
monitorIds: z.array(z.string()).min(1, "At least one monitor ID is required").max(100, "Cannot update more than 100 monitors at once"),
|
|
notificationIds: z.array(z.string()).max(100, "Cannot specify more than 100 notification IDs at once"),
|
|
action: z.enum(["add", "remove", "set"] as const),
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
if (data.action !== "set" && data.notificationIds.length === 0) return false;
|
|
return true;
|
|
},
|
|
{
|
|
message: "Notification IDs cannot be empty unless action is 'set'",
|
|
path: ["notificationIds"],
|
|
}
|
|
);
|