diff --git a/INSTALL.md b/INSTALL.md index 008c4cba3..33b634de8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,7 +15,7 @@ There are some runtime requirements for running Talk from source: - [Node](https://nodejs.org/) v7 or later -- [MongoDB](https://www.mongodb.com/) v3.2 or later +- [MongoDB](https://www.mongodb.com/) v3.4 or later - [Redis](https://redis.io/) v3.2 or later - [Yarn](https://yarnpkg.com/) v0.19.1 or later diff --git a/bin/cli-setup b/bin/cli-setup index 185b04f01..7985bf220 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -116,11 +116,11 @@ const performSetup = () => { return inquirer.prompt([ { type: 'input', - name: 'displayName', - message: 'Display Name', - filter: (displayName) => { + name: 'username', + message: 'Username', + filter: (username) => { return UsersService - .isValidDisplayName(displayName, false) + .isValidDisplayName(username, false) .catch((err) => { throw err.message; }); @@ -174,7 +174,7 @@ const performSetup = () => { settings: settings.toObject(), user: { email: user.email, - displayName: user.displayName, + username: user.username, password: user.password } }); diff --git a/bin/cli-users b/bin/cli-users index 01f46ad2e..473c07d9c 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -32,7 +32,7 @@ function getUserCreateAnswers(options) { email: options.email, password: options.password, confirmPassword: options.password, - displayName: options.name, + username: options.name, roles: [] }; @@ -75,11 +75,11 @@ function getUserCreateAnswers(options) { } }, { - name: 'displayName', - message: 'Display Name', - filter: (displayName) => { + name: 'username', + message: 'Username', + filter: (username) => { return UsersService - .isValidDisplayName(displayName) + .isValidDisplayName(username) .catch((err) => { throw err.message; }); @@ -108,7 +108,7 @@ function createUser(options) { }) .then((answers) => { return UsersService - .createLocalUser(answers.email.trim(), answers.password.trim(), answers.displayName.trim()) + .createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim()) .then((user) => { console.log(`Created user ${user.id}.`); @@ -209,7 +209,7 @@ function updateUser(userID, options) { 'id': userID }, { $set: { - displayName: options.name + username: options.name } }); @@ -238,7 +238,7 @@ function listUsers() { let table = new Table({ head: [ 'ID', - 'Display Name', + 'Username', 'Profiles', 'Roles', 'Status', @@ -249,7 +249,7 @@ function listUsers() { users.forEach((user) => { table.push([ user.id, - user.displayName, + user.username, user.profiles.map((p) => p.provider).join(', '), user.roles.join(', '), user.status, diff --git a/circle.yml b/circle.yml index bae929df7..77e61f102 100644 --- a/circle.yml +++ b/circle.yml @@ -10,11 +10,21 @@ machine: dependencies: override: + # TODO: use the following to add in support for MongoDB 3.4. + # # Upgrade the database version to 3.4. + # - sudo apt-get purge mongodb-org* + # - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 + # - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list + # - sudo apt-get update + # - sudo apt-get install -y mongodb-org + # - sudo service mongod restart + + # Install node dependencies. - yarn cache_directories: - ~/.cache/yarn post: - # Build the static assets + # Build the static assets. - yarn build # Lint the project here, before tests are ran. - yarn lint @@ -30,7 +40,7 @@ test: override: # Run the tests using the junit reporter. - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test - # Run the e2e test suite + # Run the e2e test suite. - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e deployment: diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 61de1c669..14b7fe95d 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -18,7 +18,7 @@ const ActionButton = ({option, type, comment = {}, user, menuOptionsMap, onClick className={`ban ${styles.banButton}`} cStyle='darkGrey' disabled={banned ? 'disabled' : ''} - onClick={() => onClickShowBanDialog(user.id, user.displayName, comment.id) + onClick={() => onClickShowBanDialog(user.id, user.username, comment.id) } raised > diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 74b0361a2..db5cfe5bf 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -23,7 +23,7 @@ const Comment = props => {
  • - {author.displayName || lang.t('comment.anon')} + {author.username || lang.t('comment.anon')} {timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} {comment.flagged ?

    {lang.t('comment.flagged')}

    : null}
    diff --git a/client/coral-admin/src/components/User.js b/client/coral-admin/src/components/User.js index 54de0375e..6b08ed76a 100644 --- a/client/coral-admin/src/components/User.js +++ b/client/coral-admin/src/components/User.js @@ -18,7 +18,7 @@ const User = props => {
  • - {user.displayName} + {user.username}
    diff --git a/client/coral-admin/src/containers/Community/Community.js b/client/coral-admin/src/containers/Community/Community.js index d63dd1e96..bad5654cf 100644 --- a/client/coral-admin/src/containers/Community/Community.js +++ b/client/coral-admin/src/containers/Community/Community.js @@ -13,7 +13,7 @@ const lang = new I18n(translations); const tableHeaders = [ { title: lang.t('community.username_and_email'), - field: 'displayName' + field: 'username' }, { title: lang.t('community.account_creation_date'), diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js index 254550e74..480ce72bf 100644 --- a/client/coral-admin/src/containers/Community/Table.js +++ b/client/coral-admin/src/containers/Community/Table.js @@ -44,7 +44,7 @@ class Table extends Component { {commenters.map((row, i)=> ( - {row.displayName} + {row.username} {row.profiles.map(({id}) => id)} diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js index 91c664c37..90deff640 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js +++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js @@ -21,12 +21,12 @@ const InitialStep = props => { ({ }); }, clearNotification: () => dispatch(clearNotification()), - editName: (displayName) => dispatch(editName(displayName)), + editName: (username) => dispatch(editName(username)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), logout: () => dispatch(logout()), dispatch: d => dispatch(d) diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 53b54d4ba..4d64e07c9 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -10,7 +10,7 @@ class Stream extends React.Component { asset: PropTypes.object.isRequired, comments: PropTypes.array.isRequired, currentUser: PropTypes.shape({ - displayName: PropTypes.string, + username: PropTypes.string, id: PropTypes.string }) } diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index bda46ae42..a2206fc09 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -15,11 +15,11 @@ export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISP const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); -export const updateDisplayName = ({displayName}) => ({type: actions.UPDATE_DISPLAYNAME, displayName}); +export const updateDisplayName = ({username}) => ({type: actions.UPDATE_DISPLAYNAME, username}); export const createDisplayName = (userId, formData) => dispatch => { dispatch(createDisplayNameRequest()); - coralApi('/account/displayname', {method: 'PUT', body: formData}) + coralApi('/account/username', {method: 'PUT', body: formData}) .then(() => { dispatch(createDisplayNameSuccess()); dispatch(hideCreateDisplayNameDialog()); diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 42d2e0398..bf2a77402 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -5,8 +5,8 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; const lang = new I18n(translations); -export const editName = (displayName) => (dispatch) => { - return coralApi('/account/displayname', {method: 'PUT', body: {displayName}}) +export const editName = (username) => (dispatch) => { + return coralApi('/account/username', {method: 'PUT', body: {username}}) .then(() => { dispatch(addNotification('success', lang.t('successNameUpdate'))); }); diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js index 4c221aa81..00adc9952 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -14,16 +14,16 @@ class SuspendedAccount extends Component { } state = { - displayName: '', + username: '', alert: '' } onSubmitClick = (e) => { const {editName} = this.props; - const {displayName} = this.state; + const {username} = this.state; e.preventDefault(); - if (validate.displayName(displayName)) { - editName(displayName) + if (validate.username(username)) { + editName(username) .then(() => location.reload()) .catch((error) => { this.setState({alert: lang.t(`error.${error.message}`)}); @@ -36,7 +36,7 @@ class SuspendedAccount extends Component { render () { const {canEditName} = this.props; - const {displayName, alert} = this.state; + const {username, alert} = this.state; return
    { @@ -51,7 +51,7 @@ class SuspendedAccount extends Component { {alert}
    diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 03c083070..3a3404174 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -21,13 +21,13 @@ class SignUpContent extends React.Component { showErrors: PropTypes.bool, errors: PropTypes.shape({ email: PropTypes.string, - displayName: PropTypes.string, + username: PropTypes.string, password: PropTypes.string, confirmPassword: PropTypes.string, }), formData: PropTypes.shape({ email: PropTypes.string, - displayName: PropTypes.string, + username: PropTypes.string, password: PropTypes.string, confirmPassword: PropTypes.string }) @@ -89,12 +89,12 @@ class SignUpContent extends React.Component { onChange={handleChange} /> (
    {lang.t('signIn.loggedInAs')} - changeTab(1)}>{user.displayName}. {lang.t('signIn.notYou')} + changeTab(1)}>{user.username}. {lang.t('signIn.notYou')} {lang.t('signIn.logout')}
    ); diff --git a/client/coral-sign-in/containers/ChangeDisplayNameContainer.js b/client/coral-sign-in/containers/ChangeDisplayNameContainer.js index bc7fe8267..625985b63 100644 --- a/client/coral-sign-in/containers/ChangeDisplayNameContainer.js +++ b/client/coral-sign-in/containers/ChangeDisplayNameContainer.js @@ -21,7 +21,7 @@ import { class ChangeDisplayNameContainer extends Component { initialState = { formData: { - displayName: '', + username: '', }, errors: {}, showErrors: false diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index c92fe024d..fd9762ee3 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -29,7 +29,7 @@ class SignInContainer extends Component { initialState = { formData: { email: '', - displayName: '', + username: '', password: '', confirmPassword: '' }, diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index e8014014c..36ce3fce4 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -19,7 +19,7 @@ export default { register: 'Register', signUp: 'Sign Up', confirmPassword: 'Confirm Password', - displayName: 'Display Name', + username: 'Username', alreadyHaveAnAccount: 'Already have an account?', recoverPassword: 'Recover password', emailInUse: 'Email address already in use', @@ -32,10 +32,10 @@ export default { 'createdisplay': { writeyourusername: 'Write your username', yourusername: 'Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.', - displayName: 'Display Name', + username: 'Username', save: 'Save', requiredField: 'Required field', - errorCreate: 'Error when changing display name', + errorCreate: 'Error when changing username', checkTheForm: 'Invalid Form. Please, check the fields', specialCharacters: 'Display names can contain letters, numbers and _ only' }, @@ -60,7 +60,7 @@ export default { register: 'Regístrate', signUp: 'Registro', confirmPassword: 'Confirmar Contraseña', - displayName: 'Nombre', + username: 'Nombre', alreadyHaveAnAccount: 'Ya tienes una cuenta?', recoverPassword: 'Recuperar contraseña', emailInUse: 'Este email se encuentra en uso', @@ -73,7 +73,7 @@ export default { 'createdisplay': { writeyourusername: 'Escribe tu nombre', yourusername: 'Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.', - displayName: 'Nombre a mostrar', + username: 'Nombre a mostrar', save: 'Guardar', requiredField: 'Campo necesario', errorCreate: 'Hubo un error al cambiar el nombre de usuario', diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6358f8f06..10e8f34f6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -515,7 +515,7 @@ paths: - name: value in: query type: string - description: A term to search users' displayNames and email addresses. + description: A term to search users' usernames and email addresses. - name: sort in: query type: string @@ -576,7 +576,7 @@ paths: format: email password: type: string - displayName: + username: type: string responses: 201: @@ -899,7 +899,7 @@ definitions: id: type: string description: The uuid.v4 id of the user. - displayName: + username: type: string description: The name appearing next to the user's comments. disabled: diff --git a/errors.js b/errors.js index 7debe6a43..98fc6a52c 100644 --- a/errors.js +++ b/errors.js @@ -59,12 +59,12 @@ const ErrDisplayTaken = new APIError('Display name already in use', { status: 400 }); -const ErrSpecialChars = new APIError('No special characters are allowed in a display name', { +const ErrSpecialChars = new APIError('No special characters are allowed in a username', { translation_key: 'NO_SPECIAL_CHARACTERS', status: 400 }); -const ErrMissingDisplay = new APIError('A display name is required to create a user', { +const ErrMissingDisplay = new APIError('A username is required to create a user', { translation_key: 'DISPLAY_NAME_REQUIRED', status: 400 }); diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index ddffcd708..5a3aee1fd 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -27,8 +27,8 @@ type User { # The ID of the User. id: ID! - # display name of a user. - displayName: String! + # username of a user. + username: String! # Action summaries against the user. action_summaries: [ActionSummary] diff --git a/models/user.js b/models/user.js index 06dc3c7d1..43a34a565 100644 --- a/models/user.js +++ b/models/user.js @@ -12,7 +12,7 @@ const USER_STATUS = [ 'ACTIVE', 'BANNED', 'PENDING', - 'APPROVED' // Indicates that the users' displayname has been approved + 'APPROVED' // Indicates that the users' username has been approved ]; // ProfileSchema is the mongoose schema defined as the representation of a @@ -61,13 +61,20 @@ const UserSchema = new mongoose.Schema({ // This is sourced from the social provider or set manually during user setup // and simply provides a name to display for the given user. - displayName: { + username: { type: String, - unique: true, - lowercase: true, required: true }, + // TODO: find a way that we can instead utilize MongoDB 3.4's collation + // options to build the index in a case insenstive manner: + // https://docs.mongodb.com/manual/reference/collation/ + lowercaseUsername: { + type: String, + required: true, + unique: true + }, + // This is true when the user account is disabled, no action should be // acknowledged when they are disabled. Logins are also prevented. disabled: Boolean, diff --git a/routes/api/account/index.js b/routes/api/account/index.js index d383e9f36..3de9d41c3 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -115,9 +115,9 @@ router.put('/password/reset', (req, res, next) => { }); }); -router.put('/displayname', authorization.needed(), (req, res, next) => { +router.put('/username', authorization.needed(), (req, res, next) => { UsersService - .editName(req.user.id, req.body.displayName) + .editName(req.user.id, req.body.username) .then(() => { res.status(204).end(); }) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index e3acf2c77..dcce15933 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -70,7 +70,7 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); } - // Authorize the user to edit their displayName. + // Authorize the user to edit their username. UsersService.toggleNameEdit(user.id, true) .then(() => { diff --git a/routes/api/setup/index.js b/routes/api/setup/index.js index 60f7d7fd2..e325769cd 100644 --- a/routes/api/setup/index.js +++ b/routes/api/setup/index.js @@ -32,11 +32,11 @@ router.post('/', (req, res, next) => { const { settings, - user: {email, password, displayName} + user: {email, password, username} } = req.body; SetupService - .setup({settings, user: {email, password, displayName}}) + .setup({settings, user: {email, password, username}}) .then(() => { // We're setup! diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 1ce429cb4..4182beb49 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -120,11 +120,11 @@ const SendEmailConfirmation = (app, userID, email, referer) => UsersService // create a local user. router.post('/', (req, res, next) => { - const {email, password, displayName} = req.body; + const {email, password, username} = req.body; const redirectUri = req.header('X-Pym-Url') || req.header('Referer'); UsersService - .createLocalUser(email, password, displayName) + .createLocalUser(email, password, username) .then((user) => { // Send an email confirmation. The Front end will know about the diff --git a/scripts/pree2e.sh b/scripts/pree2e.sh index 9ed5b7b89..985ec4cc8 100755 --- a/scripts/pree2e.sh +++ b/scripts/pree2e.sh @@ -1,5 +1,8 @@ #!/bin/bash +# fail the e2e if any of these fail +set -e + # install selenium selenium-standalone install --config=./selenium.config.js diff --git a/services/passport.js b/services/passport.js index dcb6904ec..d265d9812 100644 --- a/services/passport.js +++ b/services/passport.js @@ -102,6 +102,9 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientID: process.env.TALK_FACEBOOK_APP_ID, clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, + + // TODO: remove displayName reference when we have steps in the FE to handle + // the username create flow. profileFields: ['id', 'displayName', 'picture.type(large)'] }, (accessToken, refreshToken, profile, done) => { UsersService diff --git a/services/setup.js b/services/setup.js index 74ef1d431..2bfbc66a0 100644 --- a/services/setup.js +++ b/services/setup.js @@ -45,7 +45,7 @@ module.exports = class SetupService { /** * This verifies that the current input for the setup is valid. */ - static validate({settings, user: {email, displayName, password}}) { + static validate({settings, user: {email, username, password}}) { // Verify the email address of the user. if (!email) { @@ -57,7 +57,7 @@ module.exports = class SetupService { // Verify other properties of the user. return Promise.all([ - UsersService.isValidDisplayName(displayName, false), + UsersService.isValidDisplayName(username, false), UsersService.isValidPassword(password), settingsModel.validate() ]); @@ -66,11 +66,11 @@ module.exports = class SetupService { /** * This will perform the setup. */ - static setup({settings, user: {email, password, displayName}}) { + static setup({settings, user: {email, password, username}}) { // Validate the settings first. return SetupService - .validate({settings, user: {email, password, displayName}}) + .validate({settings, user: {email, password, username}}) .then(() => { return SettingsService.update(settings); }) @@ -80,7 +80,7 @@ module.exports = class SetupService { // Create the user. return UsersService - .createLocalUser(email, password, displayName) + .createLocalUser(email, password, username) // Grant them administrative privileges and confirm the email account. .then((user) => { diff --git a/services/users.js b/services/users.js index 408d5c618..1b8045c84 100644 --- a/services/users.js +++ b/services/users.js @@ -99,19 +99,23 @@ module.exports = class UsersService { .then(() => dstUser.save()); } + static castDisplayName(displayName) { + return displayName.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, ''); + } + /** * Finds a user given a social profile and if the user does not exist, creates * them. * @param {Object} profile - User social/external profile * @param {Function} done [description] */ - static findOrCreateExternalUser(profile) { + static findOrCreateExternalUser({id, provider, displayName}) { return UserModel .findOne({ profiles: { $elemMatch: { - id: profile.id, - provider: profile.provider + id, + provider } } }) @@ -120,16 +124,16 @@ module.exports = class UsersService { return user; } + // TODO: remove displayName reference when we have steps in the FE to handle + // the username create flow. + let username = UsersService.castDisplayName(displayName); + // The user was not found, lets create them! user = new UserModel({ - displayName: profile.displayName, + username, + lowercaseUsername: username.toLowerCase(), roles: [], - profiles: [ - { - id: profile.id, - provider: profile.provider - } - ] + profiles: [{id, provider}] }); return user.save(); @@ -164,24 +168,24 @@ module.exports = class UsersService { static createLocalUsers(users) { return Promise.all(users.map((user) => { return UsersService - .createLocalUser(user.email, user.password, user.displayName); + .createLocalUser(user.email, user.password, user.username); })); } /** - * Check the requested displayname for naughty words (currently in English) and special chars - * @param {String} displayName word to be checked for profanity + * Check the requested username for blocked words and special chars + * @param {String} username word to be checked for profanity * @param {Boolean} checkAgainstWordlist enables cheching against the wordlist - * @return {Promise} rejected if the machine's sensibilites are offended + * @return {Promise} */ - static isValidDisplayName(displayName, checkAgainstWordlist = true) { + static isValidDisplayName(username, checkAgainstWordlist = true) { const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/; - if (!displayName) { + if (!username) { return Promise.reject(errors.ErrMissingDisplay); } - if (!onlyLettersNumbersUnderscore.test(displayName)) { + if (!onlyLettersNumbersUnderscore.test(username)) { return Promise.reject(errors.ErrSpecialChars); } @@ -189,11 +193,11 @@ module.exports = class UsersService { if (checkAgainstWordlist) { // check for profanity - return Wordlist.displayNameCheck(displayName); + return Wordlist.usernameCheck(username); } // No errors found! - return Promise.resolve(displayName); + return Promise.resolve(username); } /** @@ -215,23 +219,23 @@ module.exports = class UsersService { * Creates the local user with a given email, password, and name. * @param {String} email email of the new user * @param {String} password plaintext password of the new user - * @param {String} displayName name of the display user + * @param {String} username name of the display user * @param {Function} done callback */ - static createLocalUser(email, password, displayName) { + static createLocalUser(email, password, username) { if (!email) { return Promise.reject(errors.ErrMissingEmail); } email = email.toLowerCase().trim(); - displayName = displayName.toLowerCase().trim(); + username = username.trim(); return Promise.all([ - UsersService.isValidDisplayName(displayName), + UsersService.isValidDisplayName(username), UsersService.isValidPassword(password) ]) - .then(() => { // displayName is valid + .then(() => { // username is valid return new Promise((resolve, reject) => { bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => { if (err) { @@ -239,7 +243,8 @@ module.exports = class UsersService { } let user = new UserModel({ - displayName: displayName, + username, + lowercaseUsername: username.toLowerCase(), password: hashedPassword, roles: [], profiles: [ @@ -253,7 +258,7 @@ module.exports = class UsersService { user.save((err) => { if (err) { if (err.code === 11000) { - if (err.message.match('displayName')) { + if (err.message.match('username')) { return reject(errors.ErrDisplayTaken); } return reject(errors.ErrEmailTaken); @@ -397,7 +402,7 @@ module.exports = class UsersService { static findPublicByIdArray(ids) { return UserModel.find({ id: {$in: ids} - }, 'id displayName'); + }, 'id username'); } /** @@ -473,7 +478,7 @@ module.exports = class UsersService { /** * Finds a user using a value which gets compared using a prefix match against - * the user's email address and/or their display name. + * the user's email address and/or their username. * @param {String} value value to search by * @return {Promise} */ @@ -481,9 +486,9 @@ module.exports = class UsersService { return UserModel.find({ $or: [ - // Search by a prefix match on the displayName. + // Search by a prefix match on the username. { - 'displayName': { + 'username': { $regex: new RegExp(`^${value}`), $options: 'i' } @@ -667,18 +672,19 @@ module.exports = class UsersService { } /** - * Updates the user's displayName. + * Updates the user's username. * @param {String} id the id of the user to be enabled. - * @param {String} displayName The new displayname for the user. + * @param {String} username The new username for the user. * @return {Promise} */ - static editName(id, displayName) { + static editName(id, username) { return UserModel.update({ id, canEditName: true }, { $set: { - displayName: displayName.toLowerCase(), + username: username, + lowercaseUsername: username.toLowerCase(), canEditName: false, status: 'PENDING' } diff --git a/services/wordlist.js b/services/wordlist.js index dde032f97..8dc16fba2 100644 --- a/services/wordlist.js +++ b/services/wordlist.js @@ -201,22 +201,22 @@ class Wordlist { /** * check potential username for banned words, special characters */ - static displayNameCheck(displayName) { + static usernameCheck(username) { const wl = new Wordlist(); return wl.load() .then(() => { - displayName = displayName.replace(/_/g, ''); + username = username.replace(/_/g, ''); // test each word, and fail if we find a match const hasBadWords = wl.lists.banned.some(phrase => { - return displayName.indexOf(phrase.join('')) !== -1; + return username.indexOf(phrase.join('')) !== -1; }); if (hasBadWords) { throw Errors.ErrContainsProfanity; } else { - return Promise.resolve(displayName); + return Promise.resolve(username); } }); } diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js index 1dd467b10..28a9929ea 100644 --- a/test/e2e/pages/embedStreamPage.js +++ b/test/e2e/pages/embedStreamPage.js @@ -19,7 +19,7 @@ const embedStreamCommands = { .setValue('@signInDialogEmail', user.email) .setValue('@signInDialogPassword', user.pass) .setValue('@signUpDialogConfirmPassword', user.pass) - .setValue('@signUpDialogDisplayName', user.displayName) + .setValue('@signUpDialogDisplayName', user.username) .waitForElementVisible('@signUpButton') .click('@signUpButton') .waitForElementVisible('@signInViewTrigger') @@ -96,7 +96,7 @@ module.exports = { selector: '#signInDialog #confirmPassword' }, signUpDialogDisplayName: { - selector: '#signInDialog #displayName' + selector: '#signInDialog #username' }, logInButton: { selector: '#coralLogInButton' diff --git a/test/e2e/tests/EmbedStreamTests.js b/test/e2e/tests/EmbedStreamTests.js index 5591d6b8d..78d13ccfa 100644 --- a/test/e2e/tests/EmbedStreamTests.js +++ b/test/e2e/tests/EmbedStreamTests.js @@ -37,7 +37,7 @@ module.exports = { .click('#coralRegister') .waitForElementVisible('#email', 1000) .setValue('#email', mockUser.email) - .setValue('#displayName', mockUser.name) + .setValue('#username', mockUser.name) .setValue('#password', mockUser.pw) .setValue('#confirmPassword', mockUser.pw) .click('#coralSignUpButton') @@ -125,7 +125,7 @@ module.exports = { // Add a mock user .then(() => mocks.users([{ - displayName: 'Baby Blue', + username: 'Baby Blue', email: 'whale@tale.sea', password: 'krill' }])) diff --git a/test/e2e/tests/Visitor/SignUpTest.js b/test/e2e/tests/Visitor/SignUpTest.js index d19ca1d8c..253b41659 100644 --- a/test/e2e/tests/Visitor/SignUpTest.js +++ b/test/e2e/tests/Visitor/SignUpTest.js @@ -13,7 +13,7 @@ module.exports = { embedStreamPage .signUp({ email: `visitor_${Date.now()}@test.com`, - displayName: `visitor${Date.now()}`, + username: `visitor${Date.now()}`, pass: 'testtest' }); }, diff --git a/test/routes/api/account/index.js b/test/routes/api/account/index.js index 5b9acfa9d..3a4493279 100644 --- a/test/routes/api/account/index.js +++ b/test/routes/api/account/index.js @@ -13,7 +13,7 @@ chai.use(require('chai-http')); const UsersService = require('../../../../services/users'); -describe('/api/v1/account/displayname', () => { +describe('/api/v1/account/username', () => { let mockUser; beforeEach(() => SettingsService.init(settings).then(() => { @@ -29,9 +29,9 @@ describe('/api/v1/account/displayname', () => { .post(`/api/v1/users/${mockUser.id}/username-enable`) .set(passport.inject({id: '456', roles: ['ADMIN']})) .then(() => chai.request(app) - .put('/api/v1/account/displayname') + .put('/api/v1/account/username') .set(passport.inject({id: mockUser.id, roles: []})) - .send({displayName: 'MojoJojo'})) + .send({username: 'MojoJojo'})) .then((res) => { expect(res).to.have.status(204); }); @@ -42,9 +42,9 @@ describe('/api/v1/account/displayname', () => { .post(`/api/v1/users/${mockUser.id}/username-enable`) .set(passport.inject({id: '456', roles: ['ADMIN']})) .then(() => chai.request(app) - .put('/api/v1/account/displayname') + .put('/api/v1/account/username') .set(passport.inject({id: 'wrongid', roles: []})) - .send({displayName: 'MojoJojo'})) + .send({username: 'MojoJojo'})) .then(() => { done(new Error('Exected Error')); }) @@ -56,7 +56,7 @@ describe('/api/v1/account/displayname', () => { it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { chai.request(app) - .put('/api/v1/account/displayname') + .put('/api/v1/account/username') .set(passport.inject({id: mockUser.id, roles: []})) .send({username: 'MojoJojo'}) .then(() => { diff --git a/test/routes/api/auth/index.js b/test/routes/api/auth/index.js index 9f3937a91..38b867b7d 100644 --- a/test/routes/api/auth/index.js +++ b/test/routes/api/auth/index.js @@ -45,7 +45,7 @@ describe('/api/v1/auth/local', () => { expect(res2).to.have.status(200); expect(res2).to.be.json; expect(res2.body).to.have.property('user'); - expect(res2.body.user).to.have.property('displayName', 'maria'); + expect(res2.body.user).to.have.property('username', 'Maria'); }); }); @@ -91,7 +91,7 @@ describe('/api/v1/auth/local', () => { expect(res).to.have.status(200); expect(res).to.be.json; expect(res.body).to.have.property('user'); - expect(res.body.user).to.have.property('displayName', 'maria'); + expect(res.body.user).to.have.property('username', 'Maria'); }); }); }); diff --git a/test/routes/api/comments/index.js b/test/routes/api/comments/index.js index 0ee19fc5e..50431159b 100644 --- a/test/routes/api/comments/index.js +++ b/test/routes/api/comments/index.js @@ -49,11 +49,11 @@ describe('/api/v1/comments', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; @@ -183,11 +183,11 @@ describe('/api/v1/comments/:comment_id', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; diff --git a/test/routes/api/queue/index.js b/test/routes/api/queue/index.js index 9f89a961e..bca68f355 100644 --- a/test/routes/api/queue/index.js +++ b/test/routes/api/queue/index.js @@ -45,11 +45,11 @@ describe('/api/v1/queue', () => { }]; const users = [{ - displayName: 'Ana', + username: 'Ana', email: 'ana@gmail.com', password: '123456789' }, { - displayName: 'Maria', + username: 'Maria', email: 'maria@gmail.com', password: '123456789' }]; @@ -103,7 +103,7 @@ describe('/api/v1/queue', () => { expect(res).to.have.status(200); expect(res.body.comments).to.have.length(1); expect(res.body.comments[0]).to.have.property('body'); - expect(res.body.users[0]).to.have.property('displayName'); + expect(res.body.users[0]).to.have.property('username'); expect(res.body.actions[0]).to.have.property('action_type'); }); }); @@ -115,7 +115,7 @@ describe('/api/v1/queue', () => { .end(function(err, res){ expect(err).to.be.null; expect(res).to.have.status(200); - expect(res.body.users[0]).to.have.property('displayName'); + expect(res.body.users[0]).to.have.property('username'); expect(res.body.actions[0]).to.have.property('action_type'); done(); }); diff --git a/test/services/comments.js b/test/services/comments.js index c27050ae7..4c3e0eab9 100644 --- a/test/services/comments.js +++ b/test/services/comments.js @@ -69,15 +69,12 @@ describe('services.CommentsService', () => { const users = [{ email: 'stampi@gmail.com', - displayName: 'Stampi', - password: '1Coral!!', - roles: ['ADMIN'], - _id: '1' + username: 'Stampi', + password: '1Coral!!' }, { email: 'sockmonster@gmail.com', - displayName: 'Sockmonster', - password: '2Coral!!', - _id : '2' + username: 'Sockmonster', + password: '2Coral!!' }]; const actions = [{ @@ -257,42 +254,5 @@ describe('services.CommentsService', () => { expect(c.status_history[1]).to.have.property('assigned_by', '123'); }); }); - - }); - - describe('#tagByStaff()', () => { - - it('creates a new comment by admin', () => { - return UsersService.findLocalUser('stampi@gmail.com', '1Coral!!').then((user) => { - return UsersService.addRoleToUser(user.id, 'ADMIN').then(() => { - UsersService.findById(user.id).then((u) => { - return CommentsService - .publicCreate({ - body: 'This is a comment!', - status: 'ACCEPTED', - author_id: u.id - }).then((c) => { - expect(c).to.not.be.null; - expect(c.tags).to.not.have.length(0); - expect(c.tags[0].name).to.be.equal('STAFF'); - }); - }); - }); - }); - }); - - it('creates a new comment by non admin', () => { - return UsersService.findLocalUser('sockmonster@gmail.com', '2Coral!!').then((user) => { - return CommentsService - .publicCreate({ - body: 'This is a comment!', - status: 'ACCEPTED', - author_id: user.id - }).then((c) => { - expect(c).to.not.be.null; - expect(c.tags).to.have.length(0); - }); - }); - }); }); }); diff --git a/test/services/users.js b/test/services/users.js index 5774d45ae..51ba01403 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -12,15 +12,15 @@ describe('services.UsersService', () => { return SettingsService.init(settings).then(() => { return UsersService.createLocalUsers([{ email: 'stampi@gmail.com', - displayName: 'Stampi', + username: 'Stampi', password: '1Coral!-' }, { email: 'sockmonster@gmail.com', - displayName: 'Sockmonster', + username: 'Sockmonster', password: '2Coral!2' }, { email: 'marvel@gmail.com', - displayName: 'Marvel', + username: 'Marvel', password: '3Coral!3' }]).then((users) => { mockUsers = users; @@ -33,7 +33,7 @@ describe('services.UsersService', () => { return UsersService .findById(mockUsers[0].id) .then((user) => { - expect(user).to.have.property('displayName', 'stampi'); + expect(user).to.have.property('username', 'Stampi'); }); }); }); @@ -53,11 +53,11 @@ describe('services.UsersService', () => { return UsersService.findPublicByIdArray(ids).then((result) => { expect(result).to.have.length(3); const sorted = result.sort((a, b) => { - if(a.displayName < b.displayName) {return -1;} - if(a.displayName > b.displayName) {return 1;} + if(a.username < b.username) {return -1;} + if(a.username > b.username) {return 1;} return 0; }); - expect(sorted[0]).to.have.property('displayName', 'marvel'); + expect(sorted[0]).to.have.property('username', 'Marvel'); }); }); }); @@ -68,8 +68,7 @@ describe('services.UsersService', () => { return UsersService .findLocalUser(mockUsers[0].profiles[0].id, '1Coral!-') .then((user) => { - expect(user).to.have.property('displayName') - .and.to.equal(mockUsers[0].displayName.toLowerCase()); + expect(user).to.have.property('username', mockUsers[0].username); }); }); @@ -84,10 +83,10 @@ describe('services.UsersService', () => { }); describe('#createLocalUser', () => { - it('should not create a user with duplicate display name', () => { + it('should not create a user with duplicate username', () => { return UsersService.createLocalUsers([{ email: 'otrostampi@gmail.com', - displayName: 'StampiTheSecond', + username: 'StampiTheSecond', password: '1Coralito!' }]) .then((user) => { @@ -227,7 +226,7 @@ describe('services.UsersService', () => { .then(() => UsersService.editName(mockUsers[0].id, 'Jojo')) .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { - expect(user).to.have.property('displayName', 'jojo'); + expect(user).to.have.property('username', 'Jojo'); expect(user).to.have.property('canEditName', false); }); }); diff --git a/views/auth-callback.ejs b/views/auth-callback.ejs index d38d759ee..66b9cfede 100644 --- a/views/auth-callback.ejs +++ b/views/auth-callback.ejs @@ -3,7 +3,7 @@