diff --git a/config.js b/config.js index d01ad8817..47ff1dcad 100644 --- a/config.js +++ b/config.js @@ -43,6 +43,32 @@ const CONFIG = { process.env.TALK_WHITELISTED_LANGUAGES && process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()), + // USERNAME_CAST_REGEXP defiles the regex expression that will be used to + // strip characters from a username during a username cast operation. + USERNAME_CAST_REGEXP: new RegExp( + process.env.USERNAME_CAST_REGEXP || '[^a-zA-Z_]', + 'g' + ), + + // USERNAME_REPLACEMENT_CAST_REGEXP defiles the regex expression that will be + // used to replace characters with the replacement character during a username + // cast operation. First duplicates will be replaced, then + USERNAME_REPLACEMENT_CAST_REGEXP: new RegExp( + process.env.USERNAME_REPLACEMENT_CAST_REGEXP || ' +', + 'g' + ), + + // USERNAME_REPLACEMENT_CHARACTER is the character used to replace other + // characters matching the USERNAME_REPLACEMENT_CAST_REGEXP. + USERNAME_REPLACEMENT_CHARACTER: + process.env.USERNAME_REPLACEMENT_CHARACTER || '_', + + // USERNAME_VALIDATION_REGEX defines the allowed characters for a username in + // Talk. + USERNAME_VALIDATION_REGEX: new RegExp( + process.env.USERNAME_VALIDATION_REGEX || '^[A-Za-z0-9_]+$' + ), + // When TRUE, it ensures that database indexes created in core will not add // indexes. CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE', diff --git a/package.json b/package.json index c6b98acba..05dc39cae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "4.8.5", + "version": "4.8.6", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "private": true, diff --git a/services/users.js b/services/users.js index c7040304a..6a257aeda 100644 --- a/services/users.js +++ b/services/users.js @@ -22,6 +22,10 @@ const { ROOT_URL, RECAPTCHA_WINDOW, RECAPTCHA_INCORRECT_TRIGGER, + USERNAME_CAST_REGEXP, + USERNAME_REPLACEMENT_CAST_REGEXP, + USERNAME_REPLACEMENT_CHARACTER, + USERNAME_VALIDATION_REGEX, } = require('../config'); const { jwt: JWT_SECRET } = require('../secrets'); const debug = require('debug')('talk:services:users'); @@ -525,7 +529,10 @@ class Users { } static castUsername(username) { - return username.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, ''); + return username + .trim() + .replace(USERNAME_REPLACEMENT_CAST_REGEXP, USERNAME_REPLACEMENT_CHARACTER) + .replace(USERNAME_CAST_REGEXP, ''); } /** @@ -554,7 +561,11 @@ class Users { for (let i = 0; i < MAX_ATTEMPTS; i++) { // Generate `GROUP_ATTEMPTS` guesses for the username. const usernameGuesses = Array.from(Array(GROUP_ATTEMPTS)).map( - () => `${castedName}_${random(0, END_NUMBER_MAX)}` + () => + `${castedName}${USERNAME_REPLACEMENT_CHARACTER}${random( + 0, + END_NUMBER_MAX + )}` ); // Map them all to lowercase. @@ -684,13 +695,11 @@ class Users { * @return {Promise} */ static async isValidUsername(username, checkAgainstWordlist = true) { - const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; - if (!username) { throw new ErrMissingUsername(); } - if (!onlyLettersNumbersUnderscore.test(username)) { + if (!USERNAME_VALIDATION_REGEX.test(username)) { throw new ErrSpecialChars(); }