diff --git a/server/package-lock.json b/server/package-lock.json index 131a3708e..42c2388dc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -30,7 +30,6 @@ "ioredis": "^5.4.2", "isomorphic-dompurify": "^2.26.0", "jmespath": "^0.16.0", - "joi": "^17.13.1", "jsdom": "^26.1.0", "jsonwebtoken": "9.0.2", "mailersend": "^2.2.0", @@ -2231,21 +2230,6 @@ "node": ">=6" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -3460,27 +3444,6 @@ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "license": "MIT" }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, "node_modules/@sinclair/typebox": { "version": "0.34.47", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", @@ -9931,19 +9894,6 @@ "node": ">= 0.6.0" } }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/server/package.json b/server/package.json index 3abadb75f..ee30cccee 100755 --- a/server/package.json +++ b/server/package.json @@ -45,7 +45,6 @@ "ioredis": "^5.4.2", "isomorphic-dompurify": "^2.26.0", "jmespath": "^0.16.0", - "joi": "^17.13.1", "jsdom": "^26.1.0", "jsonwebtoken": "9.0.2", "mailersend": "^2.2.0", diff --git a/server/src/controllers/notificationController.ts b/server/src/controllers/notificationController.ts index 77e332cad..0cee3726e 100644 --- a/server/src/controllers/notificationController.ts +++ b/server/src/controllers/notificationController.ts @@ -42,9 +42,7 @@ class NotificationController { createNotification = async (req: Request, res: Response, next: NextFunction) => { try { - await createNotificationBodyValidation.validateAsync(req.body, { - abortEarly: false, - }); + createNotificationBodyValidation.parse(req.body); const body = req.body; @@ -134,9 +132,7 @@ class NotificationController { editNotification = async (req: Request, res: Response, next: NextFunction) => { try { - await createNotificationBodyValidation.validateAsync(req.body, { - abortEarly: false, - }); + createNotificationBodyValidation.parse(req.body); const teamId = requireTeamId(req.user?.teamId); const notificationId = req.params.id as string; diff --git a/server/src/repositories/notifications/MongoNotificationsRepository.ts b/server/src/repositories/notifications/MongoNotificationsRepository.ts index 730072d8c..3c4225b50 100644 --- a/server/src/repositories/notifications/MongoNotificationsRepository.ts +++ b/server/src/repositories/notifications/MongoNotificationsRepository.ts @@ -3,7 +3,6 @@ import { NotificationModel, type NotificationDocument } from "@/db/models/index. import { INotificationsRepository } from "@/repositories/index.js"; import type { Notification } from "@/types/index.js"; import { AppError } from "@/utils/AppError.js"; -import { not } from "joi"; class MongoNotificationsRepository implements INotificationsRepository { private mapDocuments = (documents: NotificationDocument[]): Notification[] => { diff --git a/server/src/validation/notificationValidation.ts b/server/src/validation/notificationValidation.ts index 43eae5575..d974d873f 100644 --- a/server/src/validation/notificationValidation.ts +++ b/server/src/validation/notificationValidation.ts @@ -1,82 +1,65 @@ -import joi from "joi"; import { z } from "zod"; //**************************************** // Notification Validations //**************************************** -export const createNotificationBodyValidation = joi.object({ - notificationName: joi.string().required().messages({ - "string.empty": "Notification name is required", - "any.required": "Notification name is required", +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(), }), - - type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty", "matrix").required().messages({ - "string.empty": "Notification type is required", - "any.required": "Notification type is required", - "any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or matrix", + // Webhook notification + z.object({ + notificationName: z.string().min(1, "Notification name is required"), + type: z.literal("webhook"), + address: z.string().url("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(), }), - - address: joi.when("type", { - switch: [ - { - is: "email", - then: joi.string().email().required().messages({ - "string.empty": "E-mail address cannot be empty", - "any.required": "E-mail address is required", - "string.email": "Please enter a valid e-mail address", - }), - }, - { - is: "pager_duty", - then: joi.string().required().messages({ - "string.empty": "PagerDuty integration key cannot be empty", - "any.required": "PagerDuty integration key is required", - }), - }, - { - is: joi.string().valid("webhook", "slack", "discord"), - then: joi.string().uri().required().messages({ - "string.empty": "Webhook URL cannot be empty", - "any.required": "Webhook URL is required", - "string.uri": "Please enter a valid Webhook URL", - }), - }, - { - is: "matrix", - then: joi.string().allow("").optional(), - }, - ], + // Slack notification + z.object({ + notificationName: z.string().min(1, "Notification name is required"), + type: z.literal("slack"), + address: z.string().url("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(), }), - - homeserverUrl: joi.when("type", { - is: "matrix", - then: joi.string().uri().required().messages({ - "string.empty": "Homeserver URL cannot be empty", - "any.required": "Homeserver URL is required", - "string.uri": "Please enter a valid Homeserver URL", - }), - otherwise: joi.string().allow("").optional(), + // Discord notification + z.object({ + notificationName: z.string().min(1, "Notification name is required"), + type: z.literal("discord"), + address: z.string().url("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(), }), - - roomId: joi.when("type", { - is: "matrix", - then: joi.string().required().messages({ - "string.empty": "Room ID cannot be empty", - "any.required": "Room ID is required", - }), - otherwise: joi.string().allow("").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(), }), - - accessToken: joi.when("type", { - is: "matrix", - then: joi.string().required().messages({ - "string.empty": "Access Token cannot be empty", - "any.required": "Access Token is required", - }), - otherwise: joi.string().allow("").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.string().url("Please enter a valid Homeserver URL"), + roomId: z.string().min(1, "Room ID is required"), + accessToken: z.string().min(1, "Access Token is required"), }), -}); +]); export const sendTestEmailBodyValidation = z.object({ to: z.string().min(1, "To field is required"),