diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index 8a18a8e3f..75bd2477d 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -9,6 +9,7 @@ import { } from '../constants/assets'; import coralApi from '../../../coral-framework/helpers/request'; +import t from 'coral-framework/services/i18n'; /** * Action disptacher related to assets @@ -19,12 +20,16 @@ import coralApi from '../../../coral-framework/helpers/request'; export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch) => { dispatch({type: FETCH_ASSETS_REQUEST}); return coralApi(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`) - .then(({result, count}) => - dispatch({type: FETCH_ASSETS_SUCCESS, - assets: result, - count - })) - .catch((error) => dispatch({type: FETCH_ASSETS_FAILURE, error})); + .then(({result, count}) => + dispatch({type: FETCH_ASSETS_SUCCESS, + assets: result, + count + })) + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: FETCH_ASSETS_FAILURE, error: errorMessage}); + }); }; // Update an asset state @@ -32,9 +37,12 @@ export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filte export const updateAssetState = (id, closedAt) => (dispatch) => { dispatch({type: UPDATE_ASSET_STATE_REQUEST}); return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}}) - .then(() => - dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) - .catch((error) => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error})); + .then(() => dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: UPDATE_ASSET_STATE_FAILURE, error: errorMessage}); + }); }; export const updateAssets = (assets) => (dispatch) => { diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 3a9e282ff..0c1264e1f 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -4,6 +4,7 @@ import coralApi from 'coral-framework/helpers/request'; import * as Storage from 'coral-framework/helpers/storage'; import {handleAuthToken} from 'coral-framework/actions/auth'; import {resetWebsocket} from 'coral-framework/services/client'; +import t from 'coral-framework/services/i18n'; //============================================================================== // SIGN IN @@ -41,13 +42,27 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => dispatch(checkLoginSuccess(user)); }) .catch((error) => { - if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + + if (error.translation_key === 'NOT_AUTHORIZED') { + + // invalid credentials + dispatch({ + type: actions.LOGIN_FAILURE, + message: t('error.email_password') + }); + } + else if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { dispatch({ type: actions.LOGIN_MAXIMUM_EXCEEDED, - message: error.translation_key + message: t(`error.${error.translation_key}`), }); } else { - dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); + dispatch({ + type: actions.LOGIN_FAILURE, + message: errorMessage, + }); } }); }; @@ -56,25 +71,30 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => // FORGOT PASSWORD //============================================================================== -const forgotPassowordRequest = () => ({ +const forgotPasswordRequest = () => ({ type: actions.FETCH_FORGOT_PASSWORD_REQUEST }); -const forgotPassowordSuccess = () => ({ +const forgotPasswordSuccess = () => ({ type: actions.FETCH_FORGOT_PASSWORD_SUCCESS }); -const forgotPassowordFailure = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_FAILURE +const forgotPasswordFailure = (error) => ({ + type: actions.FETCH_FORGOT_PASSWORD_FAILURE, + error, }); export const requestPasswordReset = (email) => (dispatch) => { - dispatch(forgotPassowordRequest(email)); + dispatch(forgotPasswordRequest(email)); const redirectUri = location.href; return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}}) - .then(() => dispatch(forgotPassowordSuccess())) - .catch((error) => dispatch(forgotPassowordFailure(error))); + .then(() => dispatch(forgotPasswordSuccess())) + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(forgotPasswordFailure(errorMessage)); + }); }; //============================================================================== @@ -112,6 +132,7 @@ export const checkLogin = () => (dispatch) => { }) .catch((error) => { console.error(error); - dispatch(checkLoginFailure(`${error.translation_key}`)); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(checkLoginFailure(errorMessage)); }); }; diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 36b9ffb0a..ddae062a4 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -15,6 +15,7 @@ import { } from '../constants/community'; import coralApi from '../../../coral-framework/helpers/request'; +import t from 'coral-framework/services/i18n'; export const fetchAccounts = (query = {}) => (dispatch) => { @@ -30,7 +31,11 @@ export const fetchAccounts = (query = {}) => (dispatch) => { totalPages }); }) - .catch((error) => dispatch({type: FETCH_COMMENTERS_FAILURE, error})); + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: FETCH_COMMENTERS_FAILURE, error: errorMessage}); + }); }; const requestFetchAccounts = () => ({ @@ -47,7 +52,6 @@ export const newPage = () => ({ }); export const setRole = (id, role) => (dispatch) => { - return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}}) .then(() => { return dispatch({type: SET_ROLE, id, role}); diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index 15413e220..122ffdc4a 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -2,6 +2,7 @@ import coralApi from 'coral-framework/helpers/request'; import * as actions from '../constants/install'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; +import t from 'coral-framework/services/i18n'; export const nextStep = () => ({type: actions.NEXT_STEP}); export const previousStep = () => ({type: actions.PREVIOUS_STEP}); @@ -17,7 +18,8 @@ const clearErrors = () => ({type: actions.CLEAR_ERRORS}); const validation = (formData, dispatch, next) => { if (!(formData != null)) { - return dispatch(hasError()); + dispatch(hasError()); + return; } const validKeys = Object.keys(formData) @@ -40,7 +42,8 @@ const validation = (formData, dispatch, next) => { }); if (empty.length) { - return dispatch(hasError()); + dispatch(hasError()); + return; } // RegExp Validation @@ -59,7 +62,8 @@ const validation = (formData, dispatch, next) => { }); if (validation.length) { - return dispatch(hasError()); + dispatch(hasError()); + return; } dispatch(clearErrors()); @@ -90,7 +94,8 @@ export const finishInstall = () => (dispatch, getState) => { }) .catch((error) => { console.error(error); - dispatch(installFailure(`${error.translation_key}`)); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(installFailure(errorMessage)); }); }; @@ -113,6 +118,7 @@ export const checkInstall = (next) => (dispatch) => { }) .catch((error) => { console.error(error); - dispatch(checkInstallFailure(`${error.translation_key}`)); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(checkInstallFailure(errorMessage)); }); }; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index 65c200420..ba8b917ea 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -1,4 +1,5 @@ import coralApi from '../../../coral-framework/helpers/request'; +import t from 'coral-framework/services/i18n'; export const SETTINGS_LOADING = 'SETTINGS_LOADING'; export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; @@ -20,7 +21,9 @@ export const fetchSettings = () => (dispatch) => { dispatch({type: SETTINGS_RECEIVED, settings}); }) .catch((error) => { - dispatch({type: SETTINGS_FETCH_ERROR, error}); + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: SETTINGS_FETCH_ERROR, error: errorMessage}); }); }; @@ -49,6 +52,8 @@ export const saveSettingsToServer = () => (dispatch, getState) => { dispatch({type: SAVE_SETTINGS_SUCCESS, settings}); }) .catch((error) => { - dispatch({type: SAVE_SETTINGS_FAILED, error}); + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: SAVE_SETTINGS_FAILED, error: errorMessage}); }); }; diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index e888d5b74..7ba051a90 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -1,5 +1,6 @@ import coralApi from '../../../coral-framework/helpers/request'; import * as userTypes from '../constants/users'; +import t from 'coral-framework/services/i18n'; /** * Action disptacher related to users @@ -10,7 +11,11 @@ export const userStatusUpdate = (status, userId, commentId) => { dispatch({type: userTypes.UPDATE_STATUS_REQUEST}); return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}}) .then((res) => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res})) - .catch((error) => dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error})); + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error: errorMessage}); + }); }; }; @@ -18,7 +23,11 @@ export const userStatusUpdate = (status, userId, commentId) => { export const sendNotificationEmail = (userId, subject, body) => { return (dispatch) => { return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, body}}) - .catch((error) => dispatch({type: userTypes.USER_EMAIL_FAILURE, error})); + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: userTypes.USER_EMAIL_FAILURE, error: errorMessage}); + }); }; }; @@ -26,6 +35,10 @@ export const sendNotificationEmail = (userId, subject, body) => { export const enableUsernameEdit = (userId) => { return (dispatch) => { return coralApi(`/users/${userId}/username-enable`, {method: 'POST'}) - .catch((error) => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error})); + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error: errorMessage}); + }); }; }; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js index 9deee53f7..e75e66d23 100644 --- a/client/coral-admin/src/components/AdminLogin.js +++ b/client/coral-admin/src/components/AdminLogin.js @@ -2,7 +2,6 @@ import React, {PropTypes} from 'react'; import Layout from 'coral-admin/src/components/ui/Layout'; import styles from './NotFound.css'; import {Button, TextField, Alert, Success} from 'coral-ui'; -import t from 'coral-framework/services/i18n'; import Recaptcha from 'react-recaptcha'; class AdminLogin extends React.Component { @@ -35,7 +34,7 @@ class AdminLogin extends React.Component { const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props; const signInForm = (
- {errorMessage && {t(`error.${errorMessage}`)}} + {errorMessage && {errorMessage}} { + if (e.translation_key) { + addNotification('error', t(`error.${e.translation_key}`)); + } else if (error.networkError) { + addNotification('error', t('error.network_error')); + } else { + addNotification('error', t('edit_comment.unexpected_error')); + console.error(e); + } + }); } if (successfullyEdited) { const status = response.data.editComment.comment.status; diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index f6c67fd8d..c912d1a72 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -197,6 +197,7 @@ class Stream extends React.Component { } {loggedIn && !banned && diff --git a/client/coral-embed-stream/src/components/SuspendedAccount.js b/client/coral-embed-stream/src/components/SuspendedAccount.js index 63fab84fa..1a41bac91 100644 --- a/client/coral-embed-stream/src/components/SuspendedAccount.js +++ b/client/coral-embed-stream/src/components/SuspendedAccount.js @@ -9,7 +9,8 @@ class SuspendedAccount extends Component { static propTypes = { canEditName: PropTypes.bool, - editName: PropTypes.func.isRequired + editName: PropTypes.func.isRequired, + currentUsername: PropTypes.string.isRequired, } state = { @@ -21,7 +22,11 @@ class SuspendedAccount extends Component { const {editName} = this.props; const {username} = this.state; e.preventDefault(); - if (validate.username(username)) { + + if (username === this.props.currentUsername) { + this.setState({alert: t('error.SAME_USERNAME_PROVIDED')}); + } + else if (validate.username(username)) { editName(username) .then(() => location.reload()) .catch((error) => { diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index d252bb59f..24739ddde 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -10,7 +10,7 @@ export const fetchAssetFailure = (error) => ({type: actions.FETCH_ASSET_FAILURE, const updateAssetSettingsRequest = () => ({type: actions.UPDATE_ASSET_SETTINGS_REQUEST}); const updateAssetSettingsSuccess = (settings) => ({type: actions.UPDATE_ASSET_SETTINGS_SUCCESS, settings}); -const updateAssetSettingsFailure = () => ({type: actions.UPDATE_ASSET_SETTINGS_FAILURE}); +const updateAssetSettingsFailure = (error) => ({type: actions.UPDATE_ASSET_SETTINGS_FAILURE, error}); export const updateConfiguration = (newConfig) => (dispatch, getState) => { const assetId = getState().asset.toJS().id; @@ -20,7 +20,10 @@ export const updateConfiguration = (newConfig) => (dispatch, getState) => { dispatch(addNotification('success', t('framework.success_update_settings'))); dispatch(updateAssetSettingsSuccess(newConfig)); }) - .catch((error) => dispatch(updateAssetSettingsFailure(error))); + .catch((error) => { + console.error(error); + dispatch(updateAssetSettingsFailure(error)); + }); }; export const updateOpenStream = (closedBody) => (dispatch, getState) => { @@ -31,7 +34,10 @@ export const updateOpenStream = (closedBody) => (dispatch, getState) => { dispatch(addNotification('success', t('framework.success_update_settings'))); dispatch(fetchAssetSuccess(closedBody)); }) - .catch((error) => dispatch(fetchAssetFailure(error))); + .catch((error) => { + console.error(error); + dispatch(fetchAssetFailure(error)); + }); }; const openStream = () => ({type: actions.OPEN_COMMENTS}); diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 614b87632..05e078010 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -75,7 +75,9 @@ export const createUsername = (userId, formData) => (dispatch) => { dispatch(updateUsername(formData)); }) .catch((error) => { - dispatch(createUsernameFailure(t(`error.${error.translation_key}`))); + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(createUsernameFailure(errorMessage)); }); }; @@ -139,16 +141,20 @@ export const fetchSignIn = (formData) => { dispatch(hideSignInDialog()); }) .catch((error) => { + console.error(error); if (error.metadata) { // the user might not have a valid email. prompt the user user re-request the confirmation email dispatch( signInFailure(t('error.email_not_verified', error.metadata)) ); - } else { + } else if (error.translation_key === 'NOT_AUTHORIZED') { // invalid credentials - dispatch(signInFailure(t('error.email_password'))); + dispatch(signInFailure(t('error.email_password'), error.metadata)); + } else { + const str = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(signInFailure(str)); } }); }; @@ -234,12 +240,8 @@ export const fetchSignUp = (formData) => (dispatch, getState) => { dispatch(signUpSuccess(user)); }) .catch((error) => { - let errorMessage = t(`error.${error.message}`); - - // if there is no translation defined, just show the error string - if (errorMessage === `error.${error.message}`) { - errorMessage = error.message; - } + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); dispatch(signUpFailure(errorMessage)); }); }; @@ -256,8 +258,9 @@ const forgotPasswordSuccess = () => ({ type: actions.FETCH_FORGOT_PASSWORD_SUCCESS }); -const forgotPasswordFailure = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_FAILURE +const forgotPasswordFailure = (error) => ({ + type: actions.FETCH_FORGOT_PASSWORD_FAILURE, + error, }); export const fetchForgotPassword = (email) => (dispatch, getState) => { @@ -268,7 +271,11 @@ export const fetchForgotPassword = (email) => (dispatch, getState) => { body: {email, loc: redirectUri} }) .then(() => dispatch(forgotPasswordSuccess())) - .catch((error) => dispatch(forgotPasswordFailure(error))); + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(forgotPasswordFailure(errorMessage)); + }); }; //============================================================================== @@ -326,7 +333,8 @@ export const checkLogin = () => (dispatch) => { }) .catch((error) => { console.error(error); - dispatch(checkLoginFailure(`${error.translation_key}`)); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(checkLoginFailure(errorMessage)); }); }; @@ -360,11 +368,10 @@ export const requestConfirmEmail = (email) => (dispatch, getState) => { .then(() => { dispatch(verifyEmailSuccess()); }) - .catch((err) => { - - // email might have already been verifyed - dispatch(verifyEmailFailure(err)); - throw err; + .catch((error) => { + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(verifyEmailFailure(errorMessage)); }); }; diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index b978f686d..a6ad45d4a 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -14,6 +14,8 @@ export const editName = (username) => (dispatch) => { dispatch(addNotification('success', t('framework.success_name_update'))); }) .catch((error) => { - dispatch(editUsernameFailure(t(`error.${error.translation_key}`))); + console.error(error); + const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); + dispatch(editUsernameFailure(errorMessage)); }); }; diff --git a/client/coral-plugin-commentbox/__tests__/commentBox.spec.js b/client/coral-plugin-commentbox/__tests__/commentBox.spec.js index 92e085c79..6ae09e786 100644 --- a/client/coral-plugin-commentbox/__tests__/commentBox.spec.js +++ b/client/coral-plugin-commentbox/__tests__/commentBox.spec.js @@ -20,7 +20,7 @@ describe('CommentBox', () => { }); it('should render the CommentBox appropriately', () => { - expect(render.contains('
- `${text}`; + `${text}`; marked.setOptions({renderer}); diff --git a/errors.js b/errors.js index 8b11412f0..79731dad6 100644 --- a/errors.js +++ b/errors.js @@ -161,7 +161,13 @@ const ErrInstallLock = new APIError('install lock active', { // ErrPermissionUpdateUsername is returned when the user does not have permission to update their username. const ErrPermissionUpdateUsername = new APIError('You do not have permission to update your username.', { translation_key: 'EDIT_USERNAME_NOT_AUTHORIZED', - status: 500 + status: 403 +}); + +// ErrSameUsernameProvided is returned when attempting to update a username to the same username. +const ErrSameUsernameProvided = new APIError('Same username provided.', { + translation_key: 'SAME_USERNAME_PROVIDED', + status: 400 }); // ErrLoginAttemptMaximumExceeded is returned when the login maximum is exceeded. @@ -209,6 +215,7 @@ module.exports = { ErrAuthentication, ErrNotAuthorized, ErrPermissionUpdateUsername, + ErrSameUsernameProvided, ErrSettingsInit, ErrInstallLock, ErrLoginAttemptMaximumExceeded, diff --git a/locales/en.yml b/locales/en.yml index 2fa1b6bbd..944902e15 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -186,6 +186,7 @@ en: USERNAME_REQUIRED: "Must input a username" EDIT_WINDOW_ENDED: "You can no longer edit this comment. The time window to do so has expired." EDIT_USERNAME_NOT_AUTHORIZED: "You do not have permission to update your username." + SAME_USERNAME_PROVIDED: "You must submit a different username." EMAIL_IN_USE: "Email address already in use" EMAIL_REQUIRED: "An email address is required" LOGIN_MAXIMUM_EXCEEDED: "You have made too many unsuccessful password attempts. Please wait." diff --git a/services/passport.js b/services/passport.js index 92cd914b3..8396cc581 100644 --- a/services/passport.js +++ b/services/passport.js @@ -352,6 +352,9 @@ passport.use(new LocalStrategy({ passReqToCallback: true }, async (req, email, password, done) => { + // Normalize email + email = email.toLowerCase(); + // We need to check if this request has a recaptcha on it at all, if it does, // we must verify it first. If verification fails, we fail the request early. // We can only do this obviously when recaptcha is enabled. diff --git a/services/users.js b/services/users.js index 599ef8b33..78ec57b4c 100644 --- a/services/users.js +++ b/services/users.js @@ -528,7 +528,7 @@ module.exports = class UsersService { } /** - * + * * @param {String} id the id of the current user * @param {Object} token a jwt token used to sign in the user */ @@ -858,36 +858,52 @@ module.exports = class UsersService { /** * Updates the user's username. - * @param {String} id the id of the user to be enabled. - * @param {String} username The new username for the user. + * @param {String} id The id of the user. + * @param {String} username The new username for the user. * @return {Promise} */ - static editName(id, username) { - return UserModel.update({ - id, - canEditName: true - }, { - $set: { - username: username, - lowercaseUsername: username.toLowerCase(), - canEditName: false, - status: 'PENDING', - } - }) - .then((result) => { - if (result.nModified <= 0) { - return Promise.reject(errors.ErrPermissionUpdateUsername); + static async editName(id, username) { + try { + const result = await UserModel.findOneAndUpdate({ + id, + username: {$ne: username}, + canEditName: true + }, { + $set: { + username: username, + lowercaseUsername: username.toLowerCase(), + canEditName: false, + status: 'PENDING', + } + }, { + new: true, + }); + + if (!result) { + const user = await UsersService.findById(id); + if (user === null) { + throw errors.ErrNotFound; + } + + if (!user.canEditName) { + throw errors.ErrPermissionUpdateUsername; + } + + if (user.username === username) { + throw errors.ErrSameUsernameProvided; + } + + throw new Error('edit username failed for an unexpected reason'); } return result; - }) - .catch((err) => { + } + catch(err) { if (err.code === 11000) { throw errors.ErrUsernameTaken; } - throw err; - }); + } } /** @@ -928,7 +944,7 @@ module.exports = class UsersService { } }; -// Extract all the tokenUserNotFound plugins so we can integrate with other +// Extract all the tokenUserNotFound plugins so we can integrate with other // providers. let tokenUserNotFoundHooks = null; diff --git a/test/server/routes/api/account/index.js b/test/server/routes/api/account/index.js index 67744f9bc..08b9c8b1d 100644 --- a/test/server/routes/api/account/index.js +++ b/test/server/routes/api/account/index.js @@ -46,10 +46,27 @@ describe('/api/v1/account/username', () => { .set(passport.inject({id: 'wrongid', roles: []})) .send({username: 'MojoJojo'})) .then(() => { - done(new Error('Exected Error')); + done(new Error('Expected Error')); }) .catch((err) => { - expect(err).to.be.truthy; + expect(err).to.be.ok; + done(); + }); + }); + + it('it should return an error when the user submits the same username', (done) => { + chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/username') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({username: 'Ana'})) + .then(() => { + done(new Error('Expected Error')); + }) + .catch((err) => { + expect(err).to.be.ok; done(); }); }); @@ -60,10 +77,10 @@ describe('/api/v1/account/username', () => { .set(passport.inject({id: mockUser.id, roles: []})) .send({username: 'MojoJojo'}) .then(() => { - done(new Error('Exected Error')); + done(new Error('Expected Error')); }) .catch((err) => { - expect(err).to.be.truthy; + expect(err).to.be.ok; done(); }); }); diff --git a/test/server/services/users.js b/test/server/services/users.js index 9ee90a693..027ac524c 100644 --- a/test/server/services/users.js +++ b/test/server/services/users.js @@ -246,7 +246,7 @@ describe('services.UsersService', () => { done(new Error('Error expected')); }) .catch((err) => { - expect(err).to.be.truthy; + expect(err).to.be.ok; done(); }); }); @@ -260,7 +260,7 @@ describe('services.UsersService', () => { done(new Error('Error expected')); }) .catch((err) => { - expect(err).to.be.truthy; + expect(err).to.be.ok; done(); }); }); @@ -272,7 +272,7 @@ describe('services.UsersService', () => { expect(false).to.be.true; }) .catch((err) => { - expect(err).to.be.truthy; + expect(err).to.be.ok; }); }); });