Merge pull request #299 from coralproject/username-displayname

Inital rename of displayName -> username
This commit is contained in:
David Erwin
2017-02-13 17:07:56 -05:00
committed by GitHub
55 changed files with 203 additions and 215 deletions
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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
}
});
+9 -9
View File
@@ -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,
+12 -2
View File
@@ -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:
@@ -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
>
+1 -1
View File
@@ -23,7 +23,7 @@ const Comment = props => {
<li tabIndex={props.index} className={`mdl-card mdl-shadow--2dp ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{author.displayName || lang.t('comment.anon')}</span>
<span>{author.username || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{comment.flagged ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
</div>
+1 -1
View File
@@ -18,7 +18,7 @@ const User = props => {
<li tabIndex={props.index} className={`mdl-card mdl-shadow--2dp ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{user.displayName}</span>
<span>{user.username}</span>
</div>
<div className={styles.sideActions}>
<div className={`actions ${styles.actions}`}>
@@ -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'),
@@ -44,7 +44,7 @@ class Table extends Component {
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
@@ -21,12 +21,12 @@ const InitialStep = props => {
<FormField
className={styles.formField}
id="displayName"
id="username"
type="text"
label='Username'
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.displayName}
errorMsg={install.errors.username}
/>
<FormField
+2 -2
View File
@@ -9,7 +9,7 @@ const initialState = Map({
organizationName: ''
}),
user: Map({
displayName: '',
username: '',
email: '',
password: '',
confirmPassword: ''
@@ -17,7 +17,7 @@ const initialState = Map({
}),
errors: Map({
organizationName: '',
displayName: '',
username: '',
email: '',
password: '',
confirmPassword: ''
+1 -1
View File
@@ -198,7 +198,7 @@
},
"bandialog": {
"ban_user": "Quieres suspender el Usuario?",
"are_you_sure": "Estas segura que quieres suspender a {props.author.displayName}?",
"are_you_sure": "Estas segura que quieres suspender a {props.author.username}?",
"note": "Nota: Suspender este usuario también va a colocar este comentario en la cola de Rechazados.",
"cancel": "Cancelar",
"yes_ban_user": "Si, Suspendan el usuario"
+1 -1
View File
@@ -201,7 +201,7 @@ const mapDispatchToProps = dispatch => ({
});
},
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)
+1 -1
View File
@@ -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
})
}
+2 -2
View File
@@ -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());
+2 -2
View File
@@ -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')));
});
@@ -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 <div className={styles.message}>
<span>{
@@ -51,7 +51,7 @@ class SuspendedAccount extends Component {
{alert}
</div>
<label
htmlFor='displayName'
htmlFor='username'
className="screen-reader-text"
aria-hidden={true}>
{lang.t('editName.label')}
@@ -59,10 +59,10 @@ class SuspendedAccount extends Component {
<input
type='text'
className={styles.editNameInput}
value={displayName}
value={username}
placeholder={lang.t('editName.label')}
id='displayName'
onChange={(e) => this.setState({displayName: e.target.value})}
id='username'
onChange={(e) => this.setState({username: e.target.value})}
rows={3}/><br/>
<Button
onClick={this.onSubmitClick}>
@@ -10,7 +10,7 @@ fragment commentView on Comment {
}
user {
id
name: displayName
name: username
}
action_summaries {
...actionSummaryView
+1 -1
View File
@@ -5,7 +5,7 @@ const lang = new I18n(translations);
export default {
email: lang.t('error.email'),
password: lang.t('error.password'),
displayName: lang.t('error.displayName'),
username: lang.t('error.username'),
confirmPassword: lang.t('error.confirmPassword'),
organizationName: lang.t('error.organizationName'),
};
+1 -1
View File
@@ -2,6 +2,6 @@ export default {
email: email => (/^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)),
password: pass => (/^(?=.{8,}).*$/.test(pass)),
confirmPassword: () => true,
displayName: displayName => (/^[a-zA-Z0-9_]+$/.test(displayName)),
username: username => (/^[a-zA-Z0-9_]+$/.test(username)),
organizationName: org => (/^[a-zA-Z0-9_ ]+$/).test(org)
};
+1 -1
View File
@@ -133,7 +133,7 @@ export default function auth (state = initialState, action) {
case actions.UPDATE_DISPLAYNAME:
console.log('Action', action);
return state
.setIn(['user', 'displayName'], action.displayName);
.setIn(['user', 'username'], action.username);
case actions.VERIFY_EMAIL_FAILURE:
return state
.set('emailVerificationFailure', true)
+1 -1
View File
@@ -4,7 +4,7 @@ import * as actions from '../constants/user';
import * as assetActions from '../constants/assets';
const initialState = Map({
displayName: '',
username: '',
profiles: [],
settings: {},
myComments: [],
+7 -7
View File
@@ -1,12 +1,12 @@
{
"en": {
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
"successNameUpdate": "Your display name has been updated",
"successNameUpdate": "Your username has been updated",
"contentNotAvailable": "This content is not available",
"bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
"editName": {
"msg": "Your account is currently suspended because your display name has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.",
"label": "New Display Name",
"msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.",
"label": "New Username",
"button": "Submit",
"error": "Display names can contain letters, numbers and _ only"
},
@@ -14,7 +14,7 @@
"emailNotVerified": "Email address {0} not verified.",
"email": "Not a valid E-Mail",
"password": "Password must be at least 8 characters",
"displayName": "Display names can contain letters, numbers and _ only",
"username": "Display names can contain letters, numbers and _ only",
"confirmPassword": "Passwords don't match. Please, check again",
"organizationName": "Organization name must only contain letters or numbers.",
"emailPasswordError": "Email and/or password combination incorrect.",
@@ -22,9 +22,9 @@
"PASSWORD_REQUIRED": "Must input a password",
"PASSWORD_LENGTH": "Password is too short",
"EMAIL_IN_USE": "Email address already in use",
"EMAIL_DISPLAY_NAME_IN_USE": "Email address or display name already in use",
"EMAIL_DISPLAY_NAME_IN_USE": "Email address or username already in use",
"DISPLAYNAME_IN_USE": "Display name already in use",
"DISPLAY_NAME_REQUIRED": "Must input a display name",
"DISPLAY_NAME_REQUIRED": "Must input a username",
"NO_SPECIAL_CHARACTERS": "Display names can contain letters, numbers and _ only",
"PROFANITY_ERROR": "Display names must not contain profanity. Please contact the administrator if you believe this to be in error."
}
@@ -39,7 +39,7 @@
"emailNotVerified": "Dirección de correo electrónico {0} no verificada.",
"email": "No es un email válido",
"password": "La contraseña debe tener por lo menos 8 caracteres",
"displayName": "Los nombres pueden contener letras, números y _",
"username": "Los nombres pueden contener letras, números y _",
"organizationName": "El nombre de la organización debe contener letras y/o números.",
"confirmPassword": "Las contraseñas no coinciden",
"emailPasswordError": "Email y/o contraseña incorrecta.",
@@ -32,7 +32,7 @@ const postComment = gql`
id
body
user {
name: displayName
name: username
}
actions {
type: action_type
+1 -1
View File
@@ -56,7 +56,7 @@ const StreamQuery = gql`fragment commentView on Comment {
id
body
user {
name: displayName
name: username
}
tags {
name
@@ -3,7 +3,7 @@ import styles from './SettingsHeader.css';
export default ({userData}) => (
<div className={styles.header}>
<h1>{userData.displayName}</h1>
<h1>{userData.username}</h1>
{
@@ -25,17 +25,17 @@ const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSub
</h1>
</div>
<div>
<label htmlFor="displayName">{lang.t('createdisplay.yourusername')}</label>
<label htmlFor="username">{lang.t('createdisplay.yourusername')}</label>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
<form id="saveDisplayName" onSubmit={handleSubmitDisplayName}>
<FormField
id="displayName"
id="username"
type="string"
label={lang.t('createdisplay.displayName')}
value={formData.displayName}
label={lang.t('createdisplay.username')}
value={formData.username}
onChange={handleChange}
/>
{ props.errors.displayName && <span className={styles.hint}> {lang.t('createdisplay.specialCharacters')} </span> }
{ props.errors.username && <span className={styles.hint}> {lang.t('createdisplay.specialCharacters')} </span> }
<div className={styles.action}>
<Button id="save" type="submit" className={styles.saveButton}>{lang.t('createdisplay.save')}</Button>
</div>
@@ -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}
/>
<FormField
id="displayName"
id="username"
type="text"
label={lang.t('signIn.displayName')}
value={formData.displayName}
label={lang.t('signIn.username')}
value={formData.username}
showErrors={showErrors}
errorMsg={errors.displayName}
errorMsg={errors.username}
onChange={handleChange}
/>
<FormField
+1 -1
View File
@@ -7,7 +7,7 @@ const lang = new I18n(translations);
const UserBox = ({className, user, logout, changeTab}) => (
<div className={`${styles.userBox} ${className ? className : ''}`}>
{lang.t('signIn.loggedInAs')}
<a onClick={() => changeTab(1)}>{user.displayName}</a>. {lang.t('signIn.notYou')}
<a onClick={() => changeTab(1)}>{user.username}</a>. {lang.t('signIn.notYou')}
<a className={styles.logout} onClick={logout} id='logout'>{lang.t('signIn.logout')}</a>
</div>
);
@@ -21,7 +21,7 @@ import {
class ChangeDisplayNameContainer extends Component {
initialState = {
formData: {
displayName: '',
username: '',
},
errors: {},
showErrors: false
@@ -29,7 +29,7 @@ class SignInContainer extends Component {
initialState = {
formData: {
email: '',
displayName: '',
username: '',
password: '',
confirmPassword: ''
},
+5 -5
View File
@@ -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',
+3 -3
View File
@@ -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:
+2 -2
View File
@@ -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
});
+2 -2
View File
@@ -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]
+11 -4
View File
@@ -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,
+2 -2
View File
@@ -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();
})
+1 -1
View File
@@ -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(() => {
+2 -2
View File
@@ -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!
+2 -2
View File
@@ -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
+3
View File
@@ -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
+3
View File
@@ -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
+5 -5
View File
@@ -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) => {
+40 -34
View File
@@ -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'
}
+4 -4
View File
@@ -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);
}
});
}
+2 -2
View File
@@ -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'
+2 -2
View File
@@ -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'
}]))
+1 -1
View File
@@ -13,7 +13,7 @@ module.exports = {
embedStreamPage
.signUp({
email: `visitor_${Date.now()}@test.com`,
displayName: `visitor${Date.now()}`,
username: `visitor${Date.now()}`,
pass: 'testtest'
});
},
+6 -6
View File
@@ -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(() => {
+2 -2
View File
@@ -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');
});
});
});
+4 -4
View File
@@ -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'
}];
+4 -4
View File
@@ -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();
});
+4 -44
View File
@@ -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);
});
});
});
});
});
+11 -12
View File
@@ -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);
});
});
+1 -1
View File
@@ -3,7 +3,7 @@
<body>
<script type="text/javascript">
window.opener.authCallback(<% if (err) { %>'<%- err %>'<% } else { %>null<% } %>, '<%- data %>');
setTimeout(function() { window.close(); }, 50);
// setTimeout(function() { window.close(); }, 50);
</script>
</body>
</html>