From b7def686e981c4728bc67b5943d94ad7644caf43 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 10 Jan 2024 15:14:11 -0600 Subject: [PATCH] Squashed commit of the following: commit 83bcd07e20385d58f69f9490bf168307e454b2cc Author: SabreCat Date: Fri Dec 22 17:24:45 2023 -0600 fix(profile): revert state on error commit 6aa6278727eb22c6133994f648ef0df3b0e7e866 Author: SabreCat Date: Fri Dec 22 14:37:28 2023 -0600 fix(test): no longer care about swears in profile commit 0882c77038f43fdd237e8f75d52bef256fa97d42 Author: SabreCat Date: Fri Dec 22 14:15:42 2023 -0600 fix(lint): remove unused functions commit 9b275ef72d5315eb1ccd50c44a349d5b7d902bd3 Author: SabreCat Date: Fri Dec 22 14:09:11 2023 -0600 fix(profiles): restore reporting functionality Also remove unused and/or unrelated code and clean up comments commit f4ed8c14619456aa264543cd077f73db3fc374b8 Merge: da16aa9c75 f8ba191eea Author: SabreCat Date: Fri Dec 22 12:11:00 2023 -0600 Merge branch 'release' into slur-swear-blocker commit da16aa9c75afefe2929e25bca0d47720cb1dd16a Author: CuriousMagpie Date: Thu Dec 21 13:20:28 2023 -0500 feat(s/s blocker): challenge updates to slack commit 51bed61c4c21241d5f0af1729cd9065e80a334cd Author: CuriousMagpie Date: Tue Dec 19 15:36:59 2023 -0500 feat(s/s blocker): work on challenges commit 139cbcb21c47c86c760722c876358c2b6372e72f Author: CuriousMagpie Date: Wed Dec 13 13:20:45 2023 -0500 fix(slack): update Slack notification to include authorEmail and remove undefined commit 805b287721ede4a3a3a9f950afceebe55caf7d23 Author: SabreCat Date: Tue Dec 12 16:35:54 2023 -0600 fix(profiles): improve profanity check logic commit 02ef7e8822a8b9e2680423fdebf5f03a2c51262f Author: CuriousMagpie Date: Tue Dec 12 17:22:29 2023 -0500 feat(slur blocker): arghhhhhh commit 949dee9b1ef659ee285fc603d30d4bdd48894ffd Author: CuriousMagpie Date: Tue Dec 12 13:57:29 2023 -0500 feat(slur blocker): more refactoring commit bf953998f41ae7f2e7a25465321430f181f09769 Merge: d21aa687b7 f572aa442e Author: CuriousMagpie Date: Mon Dec 11 15:20:06 2023 -0500 Merge branch 'release' into slur-swear-blocker commit d21aa687b7b493a06a4079cf0b2ef90ad6565138 Author: CuriousMagpie Date: Thu Dec 7 18:00:29 2023 -0500 feat(slur blocker): refactoring code commit f2db90c494b6c2705721e1b9fdbf503aec7ad7d8 Author: CuriousMagpie Date: Wed Dec 6 12:12:55 2023 -0500 feat(slur/swear blocker): work on Profiles commit 8f9822ffe8c820918f1ff59c3fd97faded9ad1f9 Author: CuriousMagpie Date: Mon Dec 4 17:25:17 2023 -0500 feat(profiles/challenges): work on profile block & slack report commit bdb2e06e5e4fc9c34c0abc27139737be05d1320b Author: CuriousMagpie Date: Fri Dec 1 16:11:27 2023 -0500 feat(slur/swear): working on it commit 7277b5cad5ceb17c97fda22d871bf3e2edd104b7 Merge: 24d14277ab 941f1f976c Author: CuriousMagpie Date: Fri Dec 1 15:04:46 2023 -0500 Merge branch 'profile-slur-swear-blocker' into slur-swear-blocker commit 941f1f976c8a04f22a6843e70caea9e071e409b3 Author: CuriousMagpie Date: Thu Nov 30 14:34:30 2023 -0500 feat(profiles/PMs): slur/swear blocker upgrade commit 0863017efc0f7a1832a296addabe68ba16278cf6 Author: CuriousMagpie Date: Tue Nov 28 16:21:21 2023 -0500 feat(profiles): slur/swear blocker commit e9937d864ff35e9ac70c4c3c58d172187cdb3c3f Author: CuriousMagpie Date: Mon Nov 27 15:24:37 2023 -0500 feat(profiles): slur/swear blocker commit 24d14277ab84f7fb3c67aa666ef8ae4312c68cb5 Author: CuriousMagpie Date: Mon Nov 27 14:12:46 2023 -0500 feat(challenges): update behavior based on public/private groups commit 1251f5b6a7ccc57f76b9979dc57c5dc3c712520e Author: CuriousMagpie Date: Tue Nov 14 16:28:29 2023 -0500 feat(challenges): swear and slur blocker commit a771045ca7dde5bd4edea83dcb5572e093da95f1 Author: CuriousMagpie Date: Tue Nov 14 15:46:16 2023 -0500 feat(challenges): even more slur blocker commit e5e91aa78ad244a55e6f43f35bef5ef7b3254d9b Author: CuriousMagpie Date: Tue Nov 14 14:31:25 2023 -0500 feat(challenges): more slur blocker commit 50e824e4e3ebf1479deb17fd0ecf6d293c56db62 Author: CuriousMagpie Date: Mon Nov 13 15:12:14 2023 -0500 feat(challenges): slur blocker commit 315ea24ef4ab846ddcc8a20ea09a58c6eb523a24 Author: CuriousMagpie Date: Fri Nov 3 12:31:12 2023 -0400 feat(challenges): slur blocker work commit 0f742d219fd5bcbb47b29e3c80d73284688490a7 Author: CuriousMagpie Date: Thu Nov 2 16:22:31 2023 -0400 feat(challenges): add banned words & slur blocker to challenges commit 40d6b60ee3c8675b95646132d85f47b44a0bdbb4 Author: CuriousMagpie Date: Mon Oct 23 13:00:46 2023 -0400 update packages on local/origin repo --- test/api/v3/integration/user/PUT-user.test.js | 8 - .../src/components/userMenu/profile.vue | 10 +- website/common/locales/en/groups.json | 2 +- website/server/controllers/api-v3/chat.js | 45 ------ website/server/libs/bannedSlurs.js | 2 +- website/server/libs/bannedWords.js | 150 ++++++++---------- website/server/libs/slack.js | 35 ++-- website/server/libs/user/index.js | 42 +++-- website/server/libs/user/validation.js | 5 +- 9 files changed, 119 insertions(+), 180 deletions(-) diff --git a/test/api/v3/integration/user/PUT-user.test.js b/test/api/v3/integration/user/PUT-user.test.js index ced94b2232..315c8026e2 100644 --- a/test/api/v3/integration/user/PUT-user.test.js +++ b/test/api/v3/integration/user/PUT-user.test.js @@ -94,14 +94,6 @@ describe('PUT /user', () => { message: t('bannedSlurUsedInProfile'), }); - await expect(user.put('/user', { - 'profile.name': 'TESTPLACEHOLDERSWEARWORDHERE', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedWordUsedInProfile'), - }); - await expect(user.put('/user', { 'profile.name': 'namecontainsnewline\n', })).to.eventually.be.rejected.and.eql({ diff --git a/website/client/src/components/userMenu/profile.vue b/website/client/src/components/userMenu/profile.vue index d1b6a85aff..95c18aaff1 100644 --- a/website/client/src/components/userMenu/profile.vue +++ b/website/client/src/components/userMenu/profile.vue @@ -1222,9 +1222,10 @@ export default { const { nextRewardAt } = currentLoginDay; return ((nextRewardAt - previousRewardDay)); }, - save () { + async save () { const values = {}; const edits = cloneDeep(this.editingProfile); + const oldProfile = cloneDeep(this.user.profile); each(edits, (value, key) => { // Using toString because we need to compare two arrays (websites) @@ -1235,7 +1236,12 @@ export default { } }); - this.$store.dispatch('user:set', values); + await this.$store.dispatch('user:set', values).catch(() => { + this.user.profile = oldProfile; + this.editingProfile.name = this.user.profile.name; + this.editingProfile.imageUrl = this.user.profile.imageUrl; + this.editingProfile.blurb = this.user.profile.blurb; + }); this.editing = false; }, diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index bc7c6eee03..777cb6a908 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -16,7 +16,7 @@ "wiki": "Wiki", "resources": "Resources", "communityGuidelines": "Community Guidelines", - "bannedWordUsed": "Oops! Looks like this post contains a swearword or reference to an addictive substance or adult topic (<%= swearWordsUsed %>). Habitica keeps our chat very clean. Feel free to edit your message so you can post it! You must remove the word, not just censor it.", + "bannedWordUsed": "Oops! Looks like this post contains a swear word or reference to an addictive substance or adult topic (<%= swearWordsUsed %>). Habitica keeps our chat very clean. Feel free to edit your message so you can post it! You must remove the word, not just censor it.", "bannedSlurUsed": "Your post contained inappropriate language, and your chat privileges have been revoked.", "party": "Party", "usernameCopied": "Username copied to clipboard.", diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index c94dbc8add..a2614c1ee3 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -17,9 +17,6 @@ import { removeFromArray } from '../../libs/collectionManipulators'; import { getUserInfo } from '../../libs/email'; import * as slack from '../../libs/slack'; import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory'; -import bannedWords from '../../libs/bannedWords'; -import { getMatchesByWordArray } from '../../libs/stringUtils'; -import bannedSlurs from '../../libs/bannedSlurs'; import apiError from '../../libs/apiError'; import highlightMentions from '../../libs/highlightMentions'; import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService'; @@ -50,11 +47,6 @@ const ACCOUNT_MIN_CHAT_AGE = Number(nconf.get('ACCOUNT_MIN_CHAT_AGE')); const api = {}; -function textContainsBannedSlur (message) { - const bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs); - return bannedSlursMatched.length > 0; -} - /** * @api {get} /api/v3/groups/:groupId/chat Get chat messages from a group * @apiName GetChat @@ -90,10 +82,6 @@ api.getChat = { }, }; -function getBannedWordsFromText (message) { - return getMatchesByWordArray(message, bannedWords); -} - /** * @api {post} /api/v3/groups/:groupId/chat Post chat message to a group * @apiName PostChat @@ -137,39 +125,6 @@ api.postChat = { throw new BadRequest(res.t('featureRetired')); } - // Check message for banned slurs - if (group && group.privacy !== 'private' && textContainsBannedSlur(req.body.message)) { - const { message } = req.body; - user.flags.chatRevoked = true; - await user.save(); - - // Email the mods - const authorEmail = getUserInfo(user, ['email']).email; - - // Slack the mods - slack.sendSlurNotification({ - authorEmail, - author: user, - group, - message, - }); - - throw new BadRequest(res.t('bannedSlurUsed')); - } - - if (group.privacy === 'public' && user.flags.chatRevoked) { - throw new NotAuthorized(res.t('chatPrivilegesRevoked')); - } - - // prevent banned words being posted, except in private guilds/parties - // and in certain public guilds with specific topics - if (group.privacy === 'public' && !group.bannedWordsAllowed) { - const matchedBadWords = getBannedWordsFromText(req.body.message); - if (matchedBadWords.length > 0) { - throw new BadRequest(res.t('bannedWordUsed', { swearWordsUsed: matchedBadWords.join(', ') })); - } - } - const chatRes = await Group.toJSONCleanChat(group, user); const lastClientMsg = req.query.previousMsg; const chatUpdated = !!( diff --git a/website/server/libs/bannedSlurs.js b/website/server/libs/bannedSlurs.js index 5dcdeb669c..c74c651b26 100644 --- a/website/server/libs/bannedSlurs.js +++ b/website/server/libs/bannedSlurs.js @@ -15,7 +15,7 @@ // Email admin@habitica.com to discuss the change you want made. // // All updates to this file must be done through a direct commit to limit -// the words visibility in GitHub to protect our coders, socialites, and +// the words visibility in GitHub to protect our coders and // wiki editors who look through PRs for information. // // When adding words that contain asterisks, put two backslashes before them. diff --git a/website/server/libs/bannedWords.js b/website/server/libs/bannedWords.js index 2b16f1ffdf..09d44a4fcc 100644 --- a/website/server/libs/bannedWords.js +++ b/website/server/libs/bannedWords.js @@ -2,8 +2,7 @@ // CONTENT WARNING: // -// This file contains slurs, swear words, religious oaths, and words related -// to addictive substance and adult topics. +// This file contains slurs, swear words, and religious oaths. // Do not read this file if you do not want to be exposed to those words. // // The words are stored in an array called `bannedWords` which is then @@ -18,7 +17,7 @@ // Email admin@habitica.com to discuss the change you want made. // // All updates to this file must be done through a direct commit to limit -// the words visibility in GitHub to protect our coders, socialites, and +// the words visibility in GitHub to protect our coders and // wiki editors who look through PRs for information. // // When adding words that contain asterisks, put two backslashes before them. @@ -85,13 +84,20 @@ const bannedWords = [ - 'damn', - 'goddamn', - 'damnit', - 'dammit', - 'damned', - 'omfg', - + 'ass', + 'arse', + 'arsehole', + 'asshole', + 'badarse', + 'badass', + 'bastard', + 'bastards', + 'bitch', + 'bitchy', + 'bitches', + 'bitching', + 'b\\*tch', + 'blowjob', 'bugger', 'buggery', 'buggering', @@ -101,6 +107,57 @@ const bannedWords = [ 'bullshitter', 'bullshiting', 'bullshitting', + 'cocksucker', + 'cocksucking', + 'cunnilingus', + 'dafuq', + 'fag', + 'fap', + 'fapping', + 'fellatio', + 'fuck', + 'fucks', + 'fucking', + 'fucked', + 'fuckwit', + 'fucker', + 'fuckers', + 'f\\*ck', + 'fuckhead', + 'fuckheads', + 'goddamn', + 'handjob', + 'kickarse', + 'kickass', + 'lmao', + 'lmfao', + 'omfg', + 'masturbate', + 'masturbates', + 'masturbating', + 'masturbation', + 'milf', + 'motherfucker', + 'motherfuckers', + 'motherfucking', + 'muthafucka', + 'nigga', + 'niggas', + 'nofap', + 'no fap', + 'no-fap', + 'no nut', + 'no-nut', + 'no-nut-november', + 'nutting', + 'nuttin', + 'rape', + 'raped', + 'raping', + 'r\\*pe', + 'r\\*ped', + 'r\\*ping', + 'rimjob', 'shiz', 'shit', 'shite', @@ -114,81 +171,12 @@ const bannedWords = [ 'sh\\*t', 'sh\\*tty', 'sh\\*tting', - 'fuck', - 'fucks', - 'fucking', - 'fucked', - 'fuckwit', - 'fucker', - 'fuckers', - 'f\\*ck', - 'fuckhead', - 'fuckheads', - 'motherfucker', - 'motherfuckers', - 'motherfucking', - 'muthafucka', - 'dafuq', - 'wtf', - 'stfu', - - 'ass', - 'arse', - 'asshole', - 'badass', - 'kickass', - 'arsehole', - 'badarse', - 'kickarse', - 'lmao', - 'lmfao', - - 'bitch', - 'bitchy', - 'bitches', - 'bitching', - 'b\\*tch', - - 'fag', 'slut', 'sluts', - 'nigga', - 'niggas', - 'bastard', - 'bastards', - - 'rape', - 'raped', - 'raping', - 'r\\*pe', - 'r\\*ped', - 'r\\*ping', - 'blowjob', - 'rimjob', - 'handjob', - 'cunnilingus', - 'fellatio', 'sodomy', - 'milf', - 'cocksucker', - 'cocksucking', - 'fap', - 'nofap', - 'no fap', - 'no-fap', - 'fapping', - 'no nut', - 'no-nut', - 'no-nut-november', - 'nutting', - 'nuttin', - 'masturbate', - 'masturbates', - 'masturbating', - 'masturbation', + 'stfu', + 'wtf', - 'heroin', - 'cocaine', ]; export default bannedWords; diff --git a/website/server/libs/slack.js b/website/server/libs/slack.js index 69c072e2a4..7143c33932 100644 --- a/website/server/libs/slack.js +++ b/website/server/libs/slack.js @@ -324,34 +324,21 @@ function sendShadowMutedPostNotification ({ .catch(err => logger.error(err, 'Error while sending flag data to Slack.')); } -function sendSlurNotification ({ +// slack slur notification for Profiles +function sendProfileSlurNotification ({ authorEmail, author, - group, - message, + uuid, + language, + problemContent, }) { if (SKIP_FLAG_METHODS) { return; } - const text = `${author.profile.name} (${author._id}) tried to post a slur`; + const title = 'User Profile Report: Slur'; + const titleLink = `${BASE_URL}/profile/${uuid}`; - let titleLink; - let title = `Slur in ${group.name}`; - - if (group.id === TAVERN_ID) { - titleLink = `${BASE_URL}/groups/tavern`; - } else if (group.privacy === 'public') { - titleLink = `${BASE_URL}/groups/guild/${group.id}`; - } else { - title += ` - (${group.privacy} ${group.type})`; - } - - const authorName = formatUser({ - name: author.auth.local.username, - displayName: author.profile.name, - email: authorEmail, - uuid: author.id, - }); + const text = `@${author} ${authorEmail} (${uuid}, ${language}) tried to post a slur in their Profile.`; flagSlack .send({ @@ -359,10 +346,10 @@ function sendSlurNotification ({ attachments: [{ fallback: 'Slur Message', color: 'danger', - author_name: authorName, + author_email: authorEmail, title, title_link: titleLink, - text: message, + text: problemContent, mrkdwn_in: [ 'text', ], @@ -378,6 +365,6 @@ export { sendProfileFlagNotification, sendSubscriptionNotification, sendShadowMutedPostNotification, - sendSlurNotification, + sendProfileSlurNotification, formatUser, }; diff --git a/website/server/libs/user/index.js b/website/server/libs/user/index.js index 118e0586d9..8abbfd429e 100644 --- a/website/server/libs/user/index.js +++ b/website/server/libs/user/index.js @@ -1,4 +1,6 @@ import _ from 'lodash'; +import * as slack from '../slack'; +import { getUserInfo } from '../email'; import common from '../../../common'; import * as Tasks from '../../models/task'; import { model as Groups } from '../../models/group'; @@ -105,19 +107,6 @@ function checkPreferencePurchase (user, path, item) { return _.get(user.purchased, itemPath); } -async function checkNewInputForProfanity (user, res, newValue) { - const containsSlur = stringContainsProfanity(newValue, 'slur'); - const containsBannedWord = stringContainsProfanity(newValue); - if (containsSlur || containsBannedWord) { - if (containsSlur) { - user.flags.chatRevoked = true; - await user.save(); - throw new BadRequest(res.t('bannedSlurUsedInProfile')); - } - throw new BadRequest(res.t('bannedWordUsedInProfile')); - } -} - export async function update (req, res, { isV3 = false }) { const { user } = res.locals; @@ -134,17 +123,40 @@ export async function update (req, res, { isV3 = false }) { }); } + let slurWasUsed = false; + let problemContent = ''; + if (req.body['profile.name'] !== undefined) { const newName = req.body['profile.name']; if (newName === null) throw new BadRequest(res.t('invalidReqParams')); if (newName.length > 30) throw new BadRequest(res.t('displaynameIssueLength')); if (nameContainsNewline(newName)) throw new BadRequest(res.t('displaynameIssueNewline')); - await checkNewInputForProfanity(user, res, newName); + if (stringContainsProfanity(newName, 'slur')) { + slurWasUsed = true; + problemContent += `Profile Name: ${newName}\n\n`; + } } if (req.body['profile.blurb'] !== undefined) { const newBlurb = req.body['profile.blurb']; - await checkNewInputForProfanity(user, res, newBlurb); + if (stringContainsProfanity(newBlurb, 'slur')) { + slurWasUsed = true; + problemContent += `Profile Blurb: ${newBlurb}`; + } + } + + if (slurWasUsed) { + const authorEmail = getUserInfo(user, ['email']).email; + user.flags.chatRevoked = true; + await user.save(); + slack.sendProfileSlurNotification({ + authorEmail, + author: user.auth.local.username, + uuid: user.id, + language: user.preferences.language, + problemContent, + }); + throw new BadRequest(res.t('bannedSlurUsedInProfile')); } let groupsToMirror; diff --git a/website/server/libs/user/validation.js b/website/server/libs/user/validation.js index 3c3defb088..82f66b6134 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'); @@ -43,7 +43,6 @@ function usernameContainsInvalidCharacters (username) { export function verifyDisplayName (displayName, res) { const issues = []; if (displayName.length < 1 || displayName.length > 30) issues.push(res.t('displaynameIssueLength')); - if (stringContainsProfanity(displayName)) issues.push(res.t('bannedWordUsedInProfile')); if (stringContainsProfanity(displayName, 'slur')) issues.push(res.t('bannedSlurUsedInProfile')); if (nameContainsNewline(displayName)) issues.push(res.t('displaynameIssueNewline'));