diff --git a/website/server/controllers/api-v3/challenges.js b/website/server/controllers/api-v3/challenges.js index 7db05352c2..1bd9e5f03d 100644 --- a/website/server/controllers/api-v3/challenges.js +++ b/website/server/controllers/api-v3/challenges.js @@ -2,6 +2,11 @@ import _ from 'lodash'; import cloneDeep from 'lodash/cloneDeep'; import { authWithHeaders, authWithSession } from '../../middlewares/auth'; import { model as Challenge } from '../../models/challenge'; +import bannedWords from '../../libs/bannedWords'; +import bannedSlurs from '../../libs/bannedSlurs'; +import { getUserInfo } from '../../libs/email'; +import { getMatchesByWordArray } from '../../libs/stringUtils'; +import * as slack from '../../libs/slack'; import { model as Group, basicFields as basicGroupFields, @@ -12,6 +17,7 @@ import { nameFields, } from '../../models/user'; import { + BadRequest, NotFound, NotAuthorized, } from '../../libs/errors'; @@ -36,6 +42,16 @@ const { MAX_SUMMARY_SIZE_FOR_CHALLENGES } = common.constants; const api = {}; +function textContainsBannedWord (message) { + const bannedWordsMatched = getMatchesByWordArray(message, bannedWords); + return bannedWordsMatched.length > 0; +} + +function textContainsBannedSlur (message) { + const bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs); + return bannedSlursMatched.length > 0; +} + /** * @apiDefine ChallengeLeader Challenge Leader * The leader of the challenge can use this route. @@ -230,6 +246,53 @@ api.createChallenge = { headers: req.headers, }); + // check challenge for slurs + if (group.privacy === 'public' + && textContainsBannedSlur(req.workingChallenge.name + || req.workingChallenge.shortName + || req.workingChallenge.summary + || req.workingChallenge.description)) { + const { message } = req.body; + user.flags.chatRevoked = true; + await user.save(); + + // email mods + const authorEmail = getUserInfo(user, ['email']).email; + + // send Slack message + slack.sendSlurNotification({ + authorEmail, + author: user, + group, + message, + }); + + throw new BadRequest(res.t('bannedSlurUsed')); + } + + // prevent banned words being posted, except in private challenges + if (group.privacy === 'public' + && textContainsBannedWord(req.workingChallenge.name + || req.workingChallenge.shortName + || req.workingChallenge.summary + || req.workingChallenge.description)) { + const { message } = req.body; + await user.save(); + + // email mods + const authorEmail = getUserInfo(user, ['email']).email; + + // send Slack message + slack.sendSlurNotification({ + authorEmail, + author: user, + group, + message, + }); + + throw new BadRequest(res.t('bannedWordUsed')); + } + res.respond(201, response); }, }; diff --git a/website/server/libs/user/validation.js b/website/server/libs/user/validation.js index 3c3defb088..5620f6e2cf 100644 --- a/website/server/libs/user/validation.js +++ b/website/server/libs/user/validation.js @@ -30,8 +30,8 @@ export function nameContainsNewline (username) { } function usernameIsForbidden (username) { - const forbidddenWordsMatched = getMatchesByWordArray(username, forbiddenUsernames); - return forbidddenWordsMatched.length > 0; + const forbiddenWordsMatched = getMatchesByWordArray(username, forbiddenUsernames); + return forbiddenWordsMatched.length > 0; } const invalidCharsRegex = new RegExp('[^a-z0-9_-]', 'i');