Files
Checkmate/server/validation/joi.js
T
Michal Šmahel 74915f6840 fix: Strict password validation in login form
Strict password validation is for registration, change password,
reset password and similar forms. In login form, we don't want
to interrupt user with unnecessary validation as it's completely
irrelevant what they actually entered. The one thing it's about
is the correctness of the email and the password.

Disabling strict validation for password is a kind of security
through obscurity, which could filter some untargeted attempts
for trying passwords by bots. If the bot immediatelly receives
notes about the requirements, it has more imformation for better
attacks.
2025-06-09 21:54:47 +02:00

656 lines
18 KiB
JavaScript
Executable File

import joi from "joi";
//****************************************
// Custom Validators
//****************************************
const roleValidatior = (role) => (value, helpers) => {
const hasRole = role.some((role) => value.includes(role));
if (!hasRole) {
throw new joi.ValidationError(
`You do not have the required authorization. Required roles: ${role.join(", ")}`
);
}
return value;
};
//****************************************
// Auth
//****************************************
const passwordPattern =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/;
const loginValidation = joi.object({
email: joi
.string()
.email()
.required()
.lowercase(),
password: joi
.string()
.required(),
});
const nameValidation = joi
.string()
.trim()
.max(50)
.pattern(/^(?=.*[\p{L}\p{Sc}])[\p{L}\p{Sc}\s']+$/u)
.messages({
"string.empty": "Name is required",
"string.max": "Name must be less than 50 characters",
"string.pattern.base":
"Name must contain at least 1 letter or currency symbol and only allow letters, spaces, apostrophes, and currency symbols",
});
const registrationBodyValidation = joi.object({
firstName: nameValidation.required(),
lastName: nameValidation.required(),
email: joi
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
}),
password: joi.string().min(8).required().pattern(passwordPattern),
profileImage: joi.any(),
role: joi.array().items(joi.string().valid("superadmin", "admin", "user", "demo")),
teamId: joi.string().allow("").required(),
inviteToken: joi.string().allow("").required(),
});
const editUserParamValidation = joi.object({
userId: joi.string().required(),
});
const editUserBodyValidation = joi.object({
firstName: nameValidation.required(),
lastName: nameValidation.required(),
profileImage: joi.any(),
newPassword: joi.string().min(8).pattern(passwordPattern),
password: joi.string().min(8).pattern(passwordPattern),
deleteProfileImage: joi.boolean(),
});
const recoveryValidation = joi.object({
email: joi
.string()
.email({ tlds: { allow: false } })
.required(),
});
const recoveryTokenValidation = joi.object({
recoveryToken: joi.string().required(),
});
const newPasswordValidation = joi.object({
recoveryToken: joi.string().required(),
password: joi.string().min(8).required().pattern(passwordPattern),
confirm: joi.string(),
});
const deleteUserParamValidation = joi.object({
email: joi.string().email().required(),
});
const inviteRoleValidation = joi.object({
roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(),
});
const inviteBodyValidation = joi.object({
email: joi.string().trim().email().required().messages({
"string.empty": "Email is required",
"string.email": "Must be a valid email address",
}),
role: joi.array().required(),
teamId: joi.string().required(),
});
const inviteVerificationBodyValidation = joi.object({
token: joi.string().required(),
});
//****************************************
// Monitors
//****************************************
const getMonitorByIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getMonitorByIdQueryValidation = joi.object({
status: joi.boolean(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
const getMonitorsByTeamIdParamValidation = joi.object({
teamId: joi.string().required(),
});
const getMonitorsByTeamIdQueryValidation = joi.object({
limit: joi.number(),
type: joi
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"),
joi
.array()
.items(
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port")
)
),
page: joi.number(),
rowsPerPage: joi.number(),
filter: joi.string(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
});
const getMonitorStatsByIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getMonitorStatsByIdQueryValidation = joi.object({
status: joi.string(),
limit: joi.number(),
sortOrder: joi.string().valid("asc", "desc"),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
const getCertificateParamValidation = joi.object({
monitorId: joi.string().required(),
});
const createMonitorBodyValidation = joi.object({
_id: joi.string(),
userId: joi.string().required(),
teamId: joi.string().required(),
name: joi.string().required(),
description: joi.string().required(),
type: joi.string().required(),
url: joi.string().required(),
ignoreTlsErrors: joi.boolean().default(false),
port: joi.number(),
isActive: joi.boolean(),
interval: joi.number(),
thresholds: joi.object().keys({
usage_cpu: joi.number(),
usage_memory: joi.number(),
usage_disk: joi.number(),
usage_temperature: joi.number(),
}),
notifications: joi.array().items(joi.object()),
secret: joi.string(),
jsonPath: joi.string().allow(""),
expectedValue: joi.string().allow(""),
matchMethod: joi.string(),
});
const createMonitorsBodyValidation = joi.array().items(createMonitorBodyValidation);
const editMonitorBodyValidation = joi.object({
name: joi.string(),
description: joi.string(),
interval: joi.number(),
notifications: joi.array().items(joi.object()),
secret: joi.string(),
ignoreTlsErrors: joi.boolean(),
jsonPath: joi.string().allow(""),
expectedValue: joi.string().allow(""),
matchMethod: joi.string(),
port: joi.number().min(1).max(65535),
thresholds: joi.object().keys({
usage_cpu: joi.number(),
usage_memory: joi.number(),
usage_disk: joi.number(),
usage_temperature: joi.number(),
}),
});
const pauseMonitorParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getMonitorURLByQueryValidation = joi.object({
monitorURL: joi.string().uri().required(),
});
const getHardwareDetailsByIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getHardwareDetailsByIdQueryValidation = joi.object({
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
});
//****************************************
// Alerts
//****************************************
const createAlertParamValidation = joi.object({
monitorId: joi.string().required(),
});
const createAlertBodyValidation = joi.object({
checkId: joi.string().required(),
monitorId: joi.string().required(),
userId: joi.string().required(),
status: joi.boolean(),
message: joi.string(),
notifiedStatus: joi.boolean(),
acknowledgeStatus: joi.boolean(),
});
const getAlertsByUserIdParamValidation = joi.object({
userId: joi.string().required(),
});
const getAlertsByMonitorIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getAlertByIdParamValidation = joi.object({
alertId: joi.string().required(),
});
const editAlertParamValidation = joi.object({
alertId: joi.string().required(),
});
const editAlertBodyValidation = joi.object({
status: joi.boolean(),
message: joi.string(),
notifiedStatus: joi.boolean(),
acknowledgeStatus: joi.boolean(),
});
const deleteAlertParamValidation = joi.object({
alertId: joi.string().required(),
});
//****************************************
// Checks
//****************************************
const createCheckParamValidation = joi.object({
monitorId: joi.string().required(),
});
const createCheckBodyValidation = joi.object({
monitorId: joi.string().required(),
status: joi.boolean().required(),
responseTime: joi.number().required(),
statusCode: joi.number().required(),
message: joi.string().required(),
});
const getChecksParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getChecksQueryValidation = joi.object({
type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port"),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
status: joi.boolean(),
});
const getTeamChecksParamValidation = joi.object({
teamId: joi.string().required(),
});
const getTeamChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
status: joi.boolean(),
});
const deleteChecksParamValidation = joi.object({
monitorId: joi.string().required(),
});
const deleteChecksByTeamIdParamValidation = joi.object({
teamId: joi.string().required(),
});
const updateChecksTTLBodyValidation = joi.object({
ttl: joi.number().required(),
});
//****************************************
// PageSpeedCheckValidation
//****************************************
const getPageSpeedCheckParamValidation = joi.object({
monitorId: joi.string().required(),
});
//Validation schema for the monitorId parameter
const createPageSpeedCheckParamValidation = joi.object({
monitorId: joi.string().required(),
});
//Validation schema for the monitorId body
const createPageSpeedCheckBodyValidation = joi.object({
url: joi.string().required(),
});
const deletePageSpeedCheckParamValidation = joi.object({
monitorId: joi.string().required(),
});
//****************************************
// MaintenanceWindowValidation
//****************************************
const createMaintenanceWindowBodyValidation = joi.object({
monitors: joi.array().items(joi.string()).required(),
name: joi.string().required(),
active: joi.boolean(),
start: joi.date().required(),
end: joi.date().required(),
repeat: joi.number().required(),
expiry: joi.date(),
});
const getMaintenanceWindowByIdParamValidation = joi.object({
id: joi.string().required(),
});
const getMaintenanceWindowsByTeamIdQueryValidation = joi.object({
active: joi.boolean(),
page: joi.number(),
rowsPerPage: joi.number(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
});
const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
const deleteMaintenanceWindowByIdParamValidation = joi.object({
id: joi.string().required(),
});
const editMaintenanceWindowByIdParamValidation = joi.object({
id: joi.string().required(),
});
const editMaintenanceByIdWindowBodyValidation = joi.object({
active: joi.boolean(),
name: joi.string(),
repeat: joi.number(),
start: joi.date(),
end: joi.date(),
expiry: joi.date(),
monitors: joi.array(),
});
//****************************************
// SettingsValidation
//****************************************
const updateAppSettingsBodyValidation = joi.object({
checkTTL: joi.number().allow(""),
pagespeedApiKey: joi.string().allow(""),
language: joi.string().allow(""),
timezone: joi.string().allow(""),
// showURL: joi.bool().required(),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
systemEmailUser: joi.string().allow(""),
systemEmailConnectionHost: joi.string().allow(""),
});
//****************************************
// Status Page Validation
//****************************************
const getStatusPageParamValidation = joi.object({
url: joi.string().required(),
});
const getStatusPageQueryValidation = joi.object({
type: joi.string().valid("uptime").required(),
timeFrame: joi.number().optional(),
});
const createStatusPageBodyValidation = joi.object({
userId: joi.string().required(),
teamId: joi.string().required(),
type: joi.string().valid("uptime").required(),
companyName: joi.string().required(),
url: joi
.string()
.pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen
.required()
.messages({
"string.pattern.base":
"URL can only contain letters, numbers, underscores, and hyphens",
}),
timezone: joi.string().optional(),
color: joi.string().optional(),
monitors: joi
.array()
.items(joi.string().pattern(/^[0-9a-fA-F]{24}$/))
.required()
.messages({
"string.pattern.base": "Must be a valid monitor ID",
"array.base": "Monitors must be an array",
"array.empty": "At least one monitor is required",
"any.required": "Monitors are required",
}),
subMonitors: joi
.array()
.items(joi.string().pattern(/^[0-9a-fA-F]{24}$/))
.optional(),
deleteSubmonitors: joi.boolean().optional(),
isPublished: joi.boolean(),
showCharts: joi.boolean().optional(),
showUptimePercentage: joi.boolean(),
});
const imageValidation = joi
.object({
fieldname: joi.string().required(),
originalname: joi.string().required(),
encoding: joi.string().required(),
mimetype: joi
.string()
.valid("image/jpeg", "image/png", "image/jpg")
.required()
.messages({
"string.valid": "File must be a valid image (jpeg, jpg, or png)",
}),
size: joi.number().max(3145728).required().messages({
"number.max": "File size must be less than 3MB",
}),
buffer: joi.binary().required(),
destination: joi.string(),
filename: joi.string(),
path: joi.string(),
})
.messages({
"any.required": "Image file is required",
});
const webhookConfigValidation = joi
.object({
webhookUrl: joi
.string()
.uri()
.when("$platform", {
switch: [
{
is: "telegram",
then: joi.optional(),
},
{
is: "discord",
then: joi.required().messages({
"string.empty": "Discord webhook URL is required",
"string.uri": "Discord webhook URL must be a valid URL",
"any.required": "Discord webhook URL is required",
}),
},
{
is: "slack",
then: joi.required().messages({
"string.empty": "Slack webhook URL is required",
"string.uri": "Slack webhook URL must be a valid URL",
"any.required": "Slack webhook URL is required",
}),
},
],
}),
botToken: joi.string().when("$platform", {
is: "telegram",
then: joi.required().messages({
"string.empty": "Telegram bot token is required",
"any.required": "Telegram bot token is required",
}),
otherwise: joi.optional(),
}),
chatId: joi.string().when("$platform", {
is: "telegram",
then: joi.required().messages({
"string.empty": "Telegram chat ID is required",
"any.required": "Telegram chat ID is required",
}),
otherwise: joi.optional(),
}),
})
.required();
const triggerNotificationBodyValidation = joi.object({
monitorId: joi.string().required().messages({
"string.empty": "Monitor ID is required",
"any.required": "Monitor ID is required",
}),
type: joi.string().valid("webhook").required().messages({
"string.empty": "Notification type is required",
"any.required": "Notification type is required",
"any.only": "Notification type must be webhook",
}),
platform: joi.string().valid("telegram", "discord", "slack").required().messages({
"string.empty": "Platform type is required",
"any.required": "Platform type is required",
"any.only": "Platform must be telegram, discord, or slack",
}),
config: webhookConfigValidation.required().messages({
"any.required": "Webhook configuration is required",
}),
});
//****************************************
// Announcetment Page Validation
//****************************************
const createAnnouncementValidation = joi.object({
title: joi.string().required().messages({
"string.empty": "Title cannot be empty",
"any.required": "Title is required",
}),
message: joi.string().required().messages({
"string.empty": "Message cannot be empty",
"any.required": "Message is required",
}),
userId: joi.string().required(),
});
const sendTestEmailBodyValidation = joi.object({
to: joi.string().required(),
systemEmailHost: joi.string(),
systemEmailPort: joi.number(),
systemEmailAddress: joi.string(),
systemEmailPassword: joi.string(),
systemEmailUser: joi.string(),
systemEmailConnectionHost: joi.string(),
});
export {
roleValidatior,
loginValidation,
registrationBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
createMonitorBodyValidation,
createMonitorsBodyValidation,
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
getMonitorsByTeamIdParamValidation,
getMonitorsByTeamIdQueryValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
getCertificateParamValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
getMonitorURLByQueryValidation,
editUserParamValidation,
editUserBodyValidation,
createAlertParamValidation,
createAlertBodyValidation,
getAlertsByUserIdParamValidation,
getAlertsByMonitorIdParamValidation,
getAlertByIdParamValidation,
editAlertParamValidation,
editAlertBodyValidation,
deleteAlertParamValidation,
createCheckParamValidation,
createCheckBodyValidation,
getChecksParamValidation,
getChecksQueryValidation,
getTeamChecksParamValidation,
getTeamChecksQueryValidation,
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
deleteUserParamValidation,
getPageSpeedCheckParamValidation,
createPageSpeedCheckParamValidation,
deletePageSpeedCheckParamValidation,
createPageSpeedCheckBodyValidation,
createMaintenanceWindowBodyValidation,
getMaintenanceWindowByIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
deleteMaintenanceWindowByIdParamValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
updateAppSettingsBodyValidation,
createStatusPageBodyValidation,
getStatusPageParamValidation,
getStatusPageQueryValidation,
imageValidation,
triggerNotificationBodyValidation,
webhookConfigValidation,
createAnnouncementValidation,
sendTestEmailBodyValidation,
};