2024-09-24 14:39:40 +03:30
|
|
|
const { isAfter, subDays, subHours, addMilliseconds, differenceInHours } = require("date-fns");
|
2024-11-19 07:58:57 +03:30
|
|
|
const { body, param, query: queryValidator } = require("express-validator");
|
2024-09-12 14:26:39 +03:30
|
|
|
const promisify = require("util").promisify;
|
2024-08-11 18:41:03 +03:30
|
|
|
const bcrypt = require("bcryptjs");
|
|
|
|
const dns = require("dns");
|
|
|
|
const URL = require("url");
|
|
|
|
const ms = require("ms");
|
|
|
|
|
2024-11-19 07:58:57 +03:30
|
|
|
const { ROLES } = require("../consts");
|
2024-08-11 18:41:03 +03:30
|
|
|
const query = require("../queries");
|
2024-09-09 18:43:12 +03:30
|
|
|
const utils = require("../utils");
|
2024-08-11 18:41:03 +03:30
|
|
|
const knex = require("../knex");
|
|
|
|
const env = require("../env");
|
|
|
|
|
|
|
|
const dnsLookup = promisify(dns.lookup);
|
|
|
|
|
|
|
|
const checkUser = (value, { req }) => !!req.user;
|
2024-08-31 12:19:39 +03:30
|
|
|
const sanitizeCheckbox = value => value === true || value === "on" || value;
|
2024-08-11 18:41:03 +03:30
|
|
|
|
|
|
|
const createLink = [
|
|
|
|
body("target")
|
|
|
|
.exists({ checkNull: true, checkFalsy: true })
|
|
|
|
.withMessage("Target is missing.")
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 1, max: 2040 })
|
|
|
|
.withMessage("Maximum URL length is 2040.")
|
2024-09-09 18:43:12 +03:30
|
|
|
.customSanitizer(utils.addProtocol)
|
|
|
|
.custom(value => utils.urlRegex.test(value) || /^(?!https?|ftp)(\w+:|\/\/)/.test(value))
|
2024-08-11 18:41:03 +03:30
|
|
|
.withMessage("URL is not valid.")
|
2024-09-09 18:43:12 +03:30
|
|
|
.custom(value => utils.removeWww(URL.parse(value).host) !== env.DEFAULT_DOMAIN)
|
2024-08-11 18:41:03 +03:30
|
|
|
.withMessage(`${env.DEFAULT_DOMAIN} URLs are not allowed.`),
|
|
|
|
body("password")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.custom(checkUser)
|
|
|
|
.withMessage("Only users can use this field.")
|
|
|
|
.isString()
|
|
|
|
.isLength({ min: 3, max: 64 })
|
|
|
|
.withMessage("Password length must be between 3 and 64."),
|
|
|
|
body("customurl")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.custom(checkUser)
|
|
|
|
.withMessage("Only users can use this field.")
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 1, max: 64 })
|
|
|
|
.withMessage("Custom URL length must be between 1 and 64.")
|
|
|
|
.custom(value => /^[a-zA-Z0-9-_]+$/g.test(value))
|
2024-08-21 21:22:59 +03:30
|
|
|
.withMessage("Custom URL is not valid.")
|
2024-09-09 18:43:12 +03:30
|
|
|
.custom(value => !utils.preservedURLs.some(url => url.toLowerCase() === value))
|
2024-08-11 18:41:03 +03:30
|
|
|
.withMessage("You can't use this custom URL."),
|
|
|
|
body("reuse")
|
|
|
|
.optional({ nullable: true })
|
|
|
|
.custom(checkUser)
|
|
|
|
.withMessage("Only users can use this field.")
|
|
|
|
.isBoolean()
|
|
|
|
.withMessage("Reuse must be boolean."),
|
|
|
|
body("description")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.isString()
|
|
|
|
.trim()
|
2024-08-21 21:22:59 +03:30
|
|
|
.isLength({ min: 1, max: 2040 })
|
|
|
|
.withMessage("Description length must be between 1 and 2040."),
|
2024-08-11 18:41:03 +03:30
|
|
|
body("expire_in")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.custom(value => {
|
|
|
|
try {
|
|
|
|
return !!ms(value);
|
|
|
|
} catch {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.withMessage("Expire format is invalid. Valid examples: 1m, 8h, 42 days.")
|
|
|
|
.customSanitizer(ms)
|
|
|
|
.custom(value => value >= ms("1m"))
|
2024-08-21 21:22:59 +03:30
|
|
|
.withMessage("Expire time should be more than 1 minute.")
|
2024-10-07 09:08:40 +03:30
|
|
|
.customSanitizer(value => utils.dateToUTC(addMilliseconds(new Date(), value))),
|
2024-08-11 18:41:03 +03:30
|
|
|
body("domain")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
2024-09-15 07:52:56 +03:30
|
|
|
.customSanitizer(value => value === env.DEFAULT_DOMAIN ? null : value)
|
2024-08-11 18:41:03 +03:30
|
|
|
.custom(checkUser)
|
|
|
|
.withMessage("Only users can use this field.")
|
|
|
|
.isString()
|
|
|
|
.withMessage("Domain should be string.")
|
|
|
|
.customSanitizer(value => value.toLowerCase())
|
|
|
|
.custom(async (address, { req }) => {
|
|
|
|
const domain = await query.domain.find({
|
|
|
|
address,
|
|
|
|
user_id: req.user.id
|
|
|
|
});
|
2024-08-21 21:22:59 +03:30
|
|
|
req.body.fetched_domain = domain || null;
|
2024-08-11 18:41:03 +03:30
|
|
|
|
|
|
|
if (!domain) return Promise.reject();
|
|
|
|
})
|
|
|
|
.withMessage("You can't use this domain.")
|
|
|
|
];
|
|
|
|
|
2024-08-21 21:22:59 +03:30
|
|
|
const editLink = [
|
|
|
|
body("target")
|
|
|
|
.optional({ checkFalsy: true, nullable: true })
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 1, max: 2040 })
|
|
|
|
.withMessage("Maximum URL length is 2040.")
|
2024-09-09 18:43:12 +03:30
|
|
|
.customSanitizer(utils.addProtocol)
|
2024-09-12 14:26:39 +03:30
|
|
|
.custom(value => utils.urlRegex.test(value) || /^(?!https?|ftp)(\w+:|\/\/)/.test(value))
|
2024-08-21 21:22:59 +03:30
|
|
|
.withMessage("URL is not valid.")
|
2024-09-09 18:43:12 +03:30
|
|
|
.custom(value => utils.removeWww(URL.parse(value).host) !== env.DEFAULT_DOMAIN)
|
2024-08-21 21:22:59 +03:30
|
|
|
.withMessage(`${env.DEFAULT_DOMAIN} URLs are not allowed.`),
|
|
|
|
body("password")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.isString()
|
|
|
|
.isLength({ min: 3, max: 64 })
|
|
|
|
.withMessage("Password length must be between 3 and 64."),
|
|
|
|
body("address")
|
|
|
|
.optional({ checkFalsy: true, nullable: true })
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 1, max: 64 })
|
|
|
|
.withMessage("Custom URL length must be between 1 and 64.")
|
|
|
|
.custom(value => /^[a-zA-Z0-9-_]+$/g.test(value))
|
|
|
|
.withMessage("Custom URL is not valid")
|
2024-09-09 18:43:12 +03:30
|
|
|
.custom(value => !utils.preservedURLs.some(url => url.toLowerCase() === value))
|
2024-08-21 21:22:59 +03:30
|
|
|
.withMessage("You can't use this custom URL."),
|
|
|
|
body("expire_in")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.custom(value => {
|
|
|
|
try {
|
|
|
|
return !!ms(value);
|
|
|
|
} catch {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.withMessage("Expire format is invalid. Valid examples: 1m, 8h, 42 days.")
|
|
|
|
.customSanitizer(ms)
|
|
|
|
.custom(value => value >= ms("1m"))
|
|
|
|
.withMessage("Expire time should be more than 1 minute.")
|
2024-10-07 09:08:40 +03:30
|
|
|
.customSanitizer(value => utils.dateToUTC(addMilliseconds(new Date(), value))),
|
2024-08-21 21:22:59 +03:30
|
|
|
body("description")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.isString()
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 0, max: 2040 })
|
|
|
|
.withMessage("Description length must be between 0 and 2040."),
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 36, max: 36 })
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-09-08 14:10:02 +03:30
|
|
|
const redirectProtected = [
|
|
|
|
body("password", "Password is invalid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isString()
|
|
|
|
.isLength({ min: 3, max: 64 })
|
|
|
|
.withMessage("Password length must be between 3 and 64."),
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 36, max: 36 })
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-08-31 12:19:39 +03:30
|
|
|
const addDomain = [
|
|
|
|
body("address", "Domain is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 3, max: 64 })
|
|
|
|
.withMessage("Domain length must be between 3 and 64.")
|
|
|
|
.trim()
|
2024-09-12 14:26:39 +03:30
|
|
|
.customSanitizer(utils.addProtocol)
|
|
|
|
.custom(value => utils.urlRegex.test(value))
|
2024-08-31 12:19:39 +03:30
|
|
|
.customSanitizer(value => {
|
|
|
|
const parsed = URL.parse(value);
|
2024-09-09 18:43:12 +03:30
|
|
|
return utils.removeWww(parsed.hostname || parsed.href);
|
2024-08-31 12:19:39 +03:30
|
|
|
})
|
|
|
|
.custom(value => value !== env.DEFAULT_DOMAIN)
|
|
|
|
.withMessage("You can't use the default domain.")
|
|
|
|
.custom(async value => {
|
|
|
|
const domain = await query.domain.find({ address: value });
|
|
|
|
if (domain?.user_id || domain?.banned) return Promise.reject();
|
|
|
|
})
|
|
|
|
.withMessage("You can't add this domain."),
|
|
|
|
body("homepage")
|
|
|
|
.optional({ checkFalsy: true, nullable: true })
|
2024-09-09 18:43:12 +03:30
|
|
|
.customSanitizer(utils.addProtocol)
|
2024-09-12 14:26:39 +03:30
|
|
|
.custom(value => utils.urlRegex.test(value) || /^(?!https?|ftp)(\w+:|\/\/)/.test(value))
|
2024-08-31 12:19:39 +03:30
|
|
|
.withMessage("Homepage is not valid.")
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-11-19 07:58:57 +03:30
|
|
|
const addDomainAdmin = [
|
|
|
|
body("address", "Domain is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 3, max: 64 })
|
|
|
|
.withMessage("Domain length must be between 3 and 64.")
|
|
|
|
.trim()
|
|
|
|
.customSanitizer(utils.addProtocol)
|
|
|
|
.custom(value => utils.urlRegex.test(value))
|
|
|
|
.customSanitizer(value => {
|
|
|
|
const parsed = URL.parse(value);
|
|
|
|
return utils.removeWww(parsed.hostname || parsed.href);
|
|
|
|
})
|
|
|
|
.custom(value => value !== env.DEFAULT_DOMAIN)
|
|
|
|
.withMessage("You can't add the default domain.")
|
|
|
|
.custom(async value => {
|
|
|
|
const domain = await query.domain.find({ address: value });
|
|
|
|
if (domain) return Promise.reject();
|
|
|
|
})
|
|
|
|
.withMessage("Domain already exists."),
|
|
|
|
body("homepage")
|
|
|
|
.optional({ checkFalsy: true, nullable: true })
|
|
|
|
.customSanitizer(utils.addProtocol)
|
|
|
|
.custom(value => utils.urlRegex.test(value) || /^(?!https?|ftp)(\w+:|\/\/)/.test(value))
|
|
|
|
.withMessage("Homepage is not valid."),
|
|
|
|
body("banned")
|
|
|
|
.optional({ nullable: true })
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
]
|
|
|
|
|
2024-08-31 12:19:39 +03:30
|
|
|
const removeDomain = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isLength({ min: 36, max: 36 })
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-11-19 07:58:57 +03:30
|
|
|
const removeDomainAdmin = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isNumeric(),
|
|
|
|
queryValidator("links")
|
|
|
|
.optional({ nullable: true })
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
];
|
|
|
|
|
2024-08-21 21:22:59 +03:30
|
|
|
const deleteLink = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isLength({ min: 36, max: 36 })
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-09-08 14:10:02 +03:30
|
|
|
const reportLink = [
|
|
|
|
body("link", "No link has been provided.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
2024-09-09 18:43:12 +03:30
|
|
|
.customSanitizer(utils.addProtocol)
|
2024-09-08 14:10:02 +03:30
|
|
|
.custom(
|
2024-09-09 18:43:12 +03:30
|
|
|
value => utils.removeWww(URL.parse(value).host) === env.DEFAULT_DOMAIN
|
2024-09-08 14:10:02 +03:30
|
|
|
)
|
|
|
|
.withMessage(`You can only report a ${env.DEFAULT_DOMAIN} link.`)
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-08-31 12:19:39 +03:30
|
|
|
const banLink = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isLength({ min: 36, max: 36 }),
|
|
|
|
body("host", '"host" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("user", '"user" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("userLinks", '"userLinks" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("domain", '"domain" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean()
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-11-19 07:58:57 +03:30
|
|
|
const banUser = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isNumeric(),
|
|
|
|
body("links", '"links" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("domains", '"domains" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean()
|
|
|
|
];
|
|
|
|
|
|
|
|
const banDomain = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isNumeric(),
|
|
|
|
body("links", '"links" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("domains", '"domains" should be a boolean.')
|
|
|
|
.optional({
|
|
|
|
nullable: true
|
|
|
|
})
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean()
|
|
|
|
];
|
|
|
|
|
|
|
|
const createUser = [
|
|
|
|
body("password", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64."),
|
|
|
|
body("email", "Email is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.trim()
|
2024-11-23 14:43:18 +03:30
|
|
|
.isLength({ min: 1, max: 255 })
|
2024-11-19 07:58:57 +03:30
|
|
|
.withMessage("Email length must be max 255.")
|
2024-11-23 14:43:18 +03:30
|
|
|
.isEmail()
|
2024-11-19 07:58:57 +03:30
|
|
|
.custom(async (value, { req }) => {
|
|
|
|
const user = await query.user.find({ email: value });
|
|
|
|
if (user)
|
|
|
|
return Promise.reject();
|
|
|
|
})
|
|
|
|
.withMessage("User already exists."),
|
|
|
|
body("role", "Role is not valid.")
|
|
|
|
.optional({ nullable: true, checkFalsy: true })
|
|
|
|
.trim()
|
|
|
|
.isIn([ROLES.USER, ROLES.ADMIN]),
|
|
|
|
body("verified")
|
|
|
|
.optional({ nullable: true })
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("banned")
|
|
|
|
.optional({ nullable: true })
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
body("verification_email")
|
|
|
|
.optional({ nullable: true })
|
|
|
|
.customSanitizer(sanitizeCheckbox)
|
|
|
|
.isBoolean(),
|
|
|
|
];
|
|
|
|
|
2024-09-08 14:10:02 +03:30
|
|
|
const getStats = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({
|
|
|
|
checkFalsy: true,
|
|
|
|
checkNull: true
|
|
|
|
})
|
|
|
|
.isLength({ min: 36, max: 36 })
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
|
|
|
const signup = [
|
|
|
|
body("password", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64."),
|
|
|
|
body("email", "Email is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 0, max: 255 })
|
|
|
|
.withMessage("Email length must be max 255.")
|
2024-11-23 14:43:18 +03:30
|
|
|
.isEmail()
|
|
|
|
];
|
|
|
|
|
|
|
|
const signupEmailTaken = [
|
|
|
|
body("email", "Email is not valid.")
|
2024-08-11 18:41:03 +03:30
|
|
|
.custom(async (value, { req }) => {
|
|
|
|
const user = await query.user.find({ email: value });
|
|
|
|
|
2024-11-23 14:43:18 +03:30
|
|
|
if (user) {
|
2024-08-11 18:41:03 +03:30
|
|
|
req.user = user;
|
2024-11-23 14:43:18 +03:30
|
|
|
}
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-11-23 14:43:18 +03:30
|
|
|
if (user?.verified) {
|
2024-09-12 14:26:39 +03:30
|
|
|
return Promise.reject();
|
2024-11-23 14:43:18 +03:30
|
|
|
}
|
2024-08-11 18:41:03 +03:30
|
|
|
})
|
|
|
|
.withMessage("You can't use this email address.")
|
|
|
|
];
|
|
|
|
|
|
|
|
const login = [
|
|
|
|
body("password", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64."),
|
|
|
|
body("email", "Email is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.trim()
|
2024-08-31 12:19:39 +03:30
|
|
|
.isLength({ min: 1, max: 255 })
|
2024-08-11 18:41:03 +03:30
|
|
|
.withMessage("Email length must be max 255.")
|
2024-11-23 14:43:18 +03:30
|
|
|
.isEmail()
|
2024-08-11 18:41:03 +03:30
|
|
|
];
|
|
|
|
|
2024-11-20 19:02:02 +03:30
|
|
|
const createAdmin = [
|
|
|
|
body("password", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64."),
|
|
|
|
body("email", "Email is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 0, max: 255 })
|
|
|
|
.withMessage("Email length must be max 255.")
|
2024-11-23 14:43:18 +03:30
|
|
|
.isEmail()
|
2024-11-20 19:02:02 +03:30
|
|
|
];
|
|
|
|
|
2024-08-31 12:19:39 +03:30
|
|
|
const changePassword = [
|
|
|
|
body("currentpassword", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64."),
|
|
|
|
body("newpassword", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64.")
|
|
|
|
];
|
|
|
|
|
|
|
|
const changeEmail = [
|
|
|
|
body("password", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.withMessage("Password length must be between 8 and 64."),
|
|
|
|
body("email", "Email address is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 1, max: 255 })
|
|
|
|
.withMessage("Email length must be max 255.")
|
2024-11-23 14:43:18 +03:30
|
|
|
.isEmail()
|
2024-08-31 12:19:39 +03:30
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-09-08 14:10:02 +03:30
|
|
|
const resetPassword = [
|
|
|
|
body("email", "Email is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.trim()
|
|
|
|
.isLength({ min: 0, max: 255 })
|
|
|
|
.withMessage("Email length must be max 255.")
|
2024-11-23 14:43:18 +03:30
|
|
|
.isEmail()
|
2024-09-08 14:10:02 +03:30
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-08-31 12:19:39 +03:30
|
|
|
const deleteUser = [
|
|
|
|
body("password", "Password is not valid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isLength({ min: 8, max: 64 })
|
|
|
|
.custom(async (password, { req }) => {
|
|
|
|
const isMatch = await bcrypt.compare(password, req.user.password);
|
|
|
|
if (!isMatch) return Promise.reject();
|
|
|
|
})
|
|
|
|
.withMessage("Password is not correct.")
|
|
|
|
];
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-11-19 07:58:57 +03:30
|
|
|
const deleteUserByAdmin = [
|
|
|
|
param("id", "ID is invalid.")
|
|
|
|
.exists({ checkFalsy: true, checkNull: true })
|
|
|
|
.isNumeric()
|
|
|
|
];
|
|
|
|
|
2024-08-11 18:41:03 +03:30
|
|
|
// TODO: if user has posted malware should do something better
|
|
|
|
function cooldown(user) {
|
2024-09-24 14:39:40 +03:30
|
|
|
|
|
|
|
if (!user?.cooldown) return;
|
2024-08-11 18:41:03 +03:30
|
|
|
|
2024-09-12 00:18:03 +02:00
|
|
|
// If user has active cooldown then throw error
|
2024-10-07 09:08:40 +03:30
|
|
|
const hasCooldownNow = differenceInHours(new Date(), utils.parseDatetime(user.cooldown)) < 12;
|
2024-08-11 18:41:03 +03:30
|
|
|
|
|
|
|
if (hasCooldownNow) {
|
2024-09-09 18:43:12 +03:30
|
|
|
throw new utils.CustomError("Cooldown because of a malware URL. Wait 12h");
|
2024-08-11 18:41:03 +03:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: if user or non-user has posted malware should do something better
|
|
|
|
async function malware(user, target) {
|
|
|
|
if (!env.GOOGLE_SAFE_BROWSING_KEY) return;
|
|
|
|
|
2024-09-12 14:26:39 +03:30
|
|
|
const isMalware = await fetch(
|
2024-08-11 18:41:03 +03:30
|
|
|
`https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${env.GOOGLE_SAFE_BROWSING_KEY}`,
|
|
|
|
{
|
2024-09-12 14:26:39 +03:30
|
|
|
method: "post",
|
|
|
|
body: JSON.stringify({
|
|
|
|
client: {
|
|
|
|
clientId: env.DEFAULT_DOMAIN.toLowerCase().replace(".", ""),
|
|
|
|
clientVersion: "1.0.0"
|
|
|
|
},
|
|
|
|
threatInfo: {
|
|
|
|
threatTypes: [
|
|
|
|
"THREAT_TYPE_UNSPECIFIED",
|
|
|
|
"MALWARE",
|
|
|
|
"SOCIAL_ENGINEERING",
|
|
|
|
"UNWANTED_SOFTWARE",
|
|
|
|
"POTENTIALLY_HARMFUL_APPLICATION"
|
|
|
|
],
|
|
|
|
platformTypes: ["ANY_PLATFORM", "PLATFORM_TYPE_UNSPECIFIED"],
|
|
|
|
threatEntryTypes: [
|
|
|
|
"EXECUTABLE",
|
|
|
|
"URL",
|
|
|
|
"THREAT_ENTRY_TYPE_UNSPECIFIED"
|
|
|
|
],
|
|
|
|
threatEntries: [{ url: target }]
|
|
|
|
}
|
|
|
|
})
|
2024-08-11 18:41:03 +03:30
|
|
|
}
|
2024-09-12 14:26:39 +03:30
|
|
|
).then(res => res.json());
|
2024-09-24 17:25:06 +03:30
|
|
|
|
2024-08-11 18:41:03 +03:30
|
|
|
if (!isMalware.data || !isMalware.data.matches) return;
|
|
|
|
|
|
|
|
if (user) {
|
2024-11-24 14:56:58 +03:30
|
|
|
const updatedUser = await query.user.update(
|
2024-08-11 18:41:03 +03:30
|
|
|
{ id: user.id },
|
2024-10-07 09:08:40 +03:30
|
|
|
{ cooldown: utils.dateToUTC(new Date()) },
|
2024-09-24 17:25:06 +03:30
|
|
|
{ increments: ["malicious_attempts"] }
|
2024-08-11 18:41:03 +03:30
|
|
|
);
|
|
|
|
|
|
|
|
// Ban if too many cooldowns
|
2024-09-24 12:49:35 +03:30
|
|
|
if (updatedUser.malicious_attempts > 2) {
|
2024-08-11 18:41:03 +03:30
|
|
|
await query.user.update({ id: user.id }, { banned: true });
|
2024-09-09 18:43:12 +03:30
|
|
|
throw new utils.CustomError("Too much malware requests. You are now banned.");
|
2024-08-11 18:41:03 +03:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 18:43:12 +03:30
|
|
|
throw new utils.CustomError(
|
2024-08-11 18:41:03 +03:30
|
|
|
user ? "Malware detected! Cooldown for 12h." : "Malware detected!"
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
async function bannedDomain(domain) {
|
|
|
|
const isBanned = await query.domain.find({
|
|
|
|
address: domain,
|
|
|
|
banned: true
|
|
|
|
});
|
|
|
|
|
|
|
|
if (isBanned) {
|
2024-11-19 07:58:57 +03:30
|
|
|
throw new utils.CustomError("Domain is banned.", 400);
|
2024-08-11 18:41:03 +03:30
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
async function bannedHost(domain) {
|
|
|
|
let isBanned;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const dnsRes = await dnsLookup(domain);
|
|
|
|
|
|
|
|
if (!dnsRes || !dnsRes.address) return;
|
|
|
|
|
|
|
|
isBanned = await query.host.find({
|
|
|
|
address: dnsRes.address,
|
|
|
|
banned: true
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
isBanned = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isBanned) {
|
2024-09-09 18:43:12 +03:30
|
|
|
throw new utils.CustomError("URL is containing malware/scam.", 400);
|
2024-08-11 18:41:03 +03:30
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
2024-08-31 12:19:39 +03:30
|
|
|
addDomain,
|
2024-11-19 07:58:57 +03:30
|
|
|
addDomainAdmin,
|
|
|
|
banDomain,
|
2024-08-31 12:19:39 +03:30
|
|
|
banLink,
|
2024-11-19 07:58:57 +03:30
|
|
|
banUser,
|
2024-08-11 18:41:03 +03:30
|
|
|
bannedDomain,
|
|
|
|
bannedHost,
|
2024-08-31 12:19:39 +03:30
|
|
|
changeEmail,
|
|
|
|
changePassword,
|
2024-08-11 18:41:03 +03:30
|
|
|
checkUser,
|
|
|
|
cooldown,
|
2024-11-20 19:02:02 +03:30
|
|
|
createAdmin,
|
2024-08-11 18:41:03 +03:30
|
|
|
createLink,
|
2024-11-19 07:58:57 +03:30
|
|
|
createUser,
|
2024-08-21 21:22:59 +03:30
|
|
|
deleteLink,
|
2024-08-31 12:19:39 +03:30
|
|
|
deleteUser,
|
2024-11-19 07:58:57 +03:30
|
|
|
deleteUserByAdmin,
|
2024-08-21 21:22:59 +03:30
|
|
|
editLink,
|
2024-09-08 14:10:02 +03:30
|
|
|
getStats,
|
2024-08-11 18:41:03 +03:30
|
|
|
login,
|
|
|
|
malware,
|
2024-09-08 14:10:02 +03:30
|
|
|
redirectProtected,
|
2024-08-31 12:19:39 +03:30
|
|
|
removeDomain,
|
2024-11-19 07:58:57 +03:30
|
|
|
removeDomainAdmin,
|
2024-09-08 14:10:02 +03:30
|
|
|
reportLink,
|
|
|
|
resetPassword,
|
2024-08-11 18:41:03 +03:30
|
|
|
signup,
|
2024-11-23 14:43:18 +03:30
|
|
|
signupEmailTaken,
|
2024-08-11 18:41:03 +03:30
|
|
|
}
|