Merge branch 'gdpr-username' of github.com:coralproject/talk into gdpr-email

* 'gdpr-username' of github.com:coralproject/talk:
  Adding translations
  Catching if username cant be updated
  fixes for tests
  fixed tests
  Fixed bug in mutation
This commit is contained in:
okbel
2018-04-30 20:14:15 -03:00
9 changed files with 129 additions and 76 deletions
@@ -54,6 +54,15 @@ const withProfileQuery = withQuery(
me {
id
username
state {
status {
username {
history {
created_at
}
}
}
}
}
...${getDefinitionName(TabPanel.fragments.root)}
${getSlotFragmentSpreads(slots, 'root')}
+16
View File
@@ -1,4 +1,5 @@
import get from 'lodash/get';
import moment from 'moment';
/**
* getReliability
@@ -33,3 +34,18 @@ export const isSuspended = user => {
export const isBanned = user => {
return get(user, 'state.status.banned.status');
};
/**
* canUsernameBeUpdated
* retrieves boolean whether a username can be updated or not
*/
export const canUsernameBeUpdated = status => {
const oldestEditTime = moment()
.subtract(14, 'days')
.toDate();
return !status.username.history.some(({ created_at }) =>
moment(created_at).isAfter(oldestEditTime)
);
};
+9 -13
View File
@@ -14,25 +14,21 @@ module.exports = (user, perm) => {
user.password.length > 0
);
case types.CHANGE_USERNAME: {
case types.CHANGE_USERNAME:
return user.status.username.status === 'REJECTED';
case types.SET_USERNAME: {
// Only users who have their usernames rejected or those users who
// not changed their usernames within 14 days can change their usernames.
const now = moment();
const deadline = moment().subtract(14, 'days');
return (
user.status.username.status === 'REJECTED' ||
get(user, 'status.username.history', [])
.filter(({ status }) => status === 'CHANGED')
.every(({ created_at }) =>
moment(created_at)
.add(14, 'days')
.isAfter(now)
)
user.status.username.status === 'UNSET' ||
get(user, 'status.username.history', []).every(({ created_at }) =>
moment(created_at).isBefore(deadline)
)
);
}
case types.SET_USERNAME:
return user.status.username.status === 'UNSET';
case types.CREATE_COMMENT:
case types.CREATE_ACTION:
case types.DELETE_ACTION:
@@ -19,10 +19,20 @@ class ChangeUsernameContentDialog extends React.Component {
confirmChanges = async () => {
if (this.formHasError()) {
this.showError();
} else {
await this.props.save();
this.props.next();
return;
}
if (!this.props.canUsernameBeUpdated) {
this.props.notify(
'error',
t('talk-plugin-auth.change_username.change_username_attempt')
);
return;
}
await this.props.save();
this.props.next();
this.props.closeDialog();
};
formHasError = () =>
@@ -97,6 +107,8 @@ ChangeUsernameContentDialog.propTypes = {
onChange: PropTypes.func,
formData: PropTypes.object,
username: PropTypes.string,
canUsernameBeUpdated: PropTypes.bool.isRequired,
notify: PropTypes.func.isRequired,
};
export default ChangeUsernameContentDialog;
@@ -11,6 +11,7 @@ import errorMsj from 'coral-framework/helpers/error';
import ConfirmChangesDialog from './ConfirmChangesDialog';
import ChangeUsernameContentDialog from './ChangeUsernameContentDialog';
import ChangeEmailContentDialog from './ChangeEmailContentDialog';
import { canUsernameBeUpdated } from 'coral-framework/utils/user';
const initialState = {
editing: false,
@@ -120,13 +121,10 @@ class Profile extends React.Component {
saveUsername = async () => {
const { newUsername } = this.state.formData;
const { id } = this.props;
const { setUsername } = this.props;
try {
await this.props.changeUsername({
id,
username: newUsername,
});
await setUsername(newUsername);
this.props.notify(
'success',
t('talk-plugin-auth.change_username.changed_username_success_msg')
@@ -159,8 +157,13 @@ class Profile extends React.Component {
};
render() {
const { username, emailAddress } = this.props;
const { editing, formData } = this.state;
const {
username,
emailAddress,
root: { me: { state: { status } } },
notify,
} = this.props;
const { editing, formData, showDialog } = this.state;
return (
<section
@@ -169,12 +172,14 @@ class Profile extends React.Component {
})}
>
<ConfirmChangesDialog
showDialog={this.state.showDialog}
showDialog={showDialog}
closeDialog={this.closeDialog}
finish={this.finish}
>
{username !== formData.newUsername && (
<ChangeUsernameContentDialog
notify={notify}
canUsernameBeUpdated={canUsernameBeUpdated(status)}
save={this.saveUsername}
onChange={this.onChange}
formData={this.state.formData}
@@ -260,6 +265,8 @@ class Profile extends React.Component {
Profile.propTypes = {
updateEmailAddress: PropTypes.func.isRequired,
changeUsername: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
setUsername: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
username: PropTypes.string,
emailAddress: PropTypes.string,
@@ -142,7 +142,7 @@ en:
cancel: "Cancel"
edit: "Edit"
changed_password_msg: "Changed Password - Your password has been successfully changed"
change_username:
change_username:
change_username_note: "Usernames can be changed every 14 days"
save: "Save"
edit_profile: "Edit Profile"
@@ -165,6 +165,8 @@ en:
confirm_change: "Confirm Change"
cancel: "Cancel"
change_email_msg: "Email Address Changed - Your email address has been successfully changed. This email address will now be used for signing in and email notifications"
changed_username_success_msg: "Username Changed - Your username has been successfully changed. You will not be able to change your user name for 14 days."
change_username_attempt: "Username can't be updated. Usernames can be changed every 14 days"
de:
talk-plugin-auth:
login:
@@ -266,7 +268,7 @@ es:
cancel: "Cancelar"
edit: "Editar"
changed_password_msg: "Contraseña Actualizada - Tu contraseña ha sido exitosamente actualizada"
change_username:
change_username:
change_username_note: "El usuario puede ser cambiado cada 14 días"
save: "Guardar"
edit_profile: "Editar Perfil"
@@ -278,7 +280,8 @@ es:
bottom_note: "Nota: No podrás cambiar tu usuario por 14 días"
confirm_changes: "Confirmar Cambios"
username_does_not_match: "El usuario no coincide"
changed_username_success_msg: "Username Changed - Your username has been successfully changed. You will not be able to change your user name for 14 days."
changed_username_success_msg: "Usuario Actualizado - Tu usuario ha sido exitosamente actualizado. No podrás cambiar el usuario por 14 días."
change_username_attempt: "El usuario no puede ser actualizado. Los usuarios pueden ser cambiados cada 14 días."
fr:
talk-plugin-auth:
login:
+15 -5
View File
@@ -253,9 +253,19 @@ class Users {
},
{
'status.username.status': { $in: ['APPROVED', 'SET'] },
'status.username.history.created_at': {
$lte: oldestEditTime,
},
$or: [
{
'status.username.history.created_at': {
$lte: oldestEditTime,
},
},
{
'status.username.history': [],
},
{
'status.username.history': { $exists: false },
},
],
},
],
};
@@ -286,8 +296,8 @@ class Users {
if (
!['UNSET', 'APPROVED', 'SET'].includes(user.status.username.status) ||
!user.status.username.history.every(({ created_at }) =>
oldestEditTime.isAfter(created_at)
user.status.username.history.some(({ created_at }) =>
moment(created_at).isAfter(oldestEditTime)
)
) {
throw new ErrPermissionUpdateUsername();
@@ -89,7 +89,7 @@ describe('graph.mutations.changeUsername', () => {
expect(res.data.changeUsername.errors).to.have.length(1);
expect(res.data.changeUsername.errors[0]).to.have.property(
'translation_key',
'EDIT_USERNAME_NOT_AUTHORIZED'
'NOT_AUTHORIZED'
);
// Set the user to the desired status.
+43 -43
View File
@@ -304,63 +304,63 @@ describe('services.UsersService', () => {
await UsersService[func](user.id, user.username);
}
});
});
if (func === 'setUsername') {
it('should let a user set their username from UNSET', async () => {
const user = mockUsers[0];
if (func === 'setUsername') {
it('should let a user set their username from UNSET', async () => {
const user = mockUsers[0];
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, 'UNSET');
await UsersService.setUsername(user.id, 'new_username', null);
});
describe('time based', () => {
afterEach(() => {
timekeeper.reset();
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, 'UNSET');
await UsersService.setUsername(user.id, 'new_username', null);
});
['SET', 'APPROVED'].forEach(status => {
it(`should not allow users to change their username if it was changed within 14 of today from ${status}`, async () => {
const user = mockUsers[0];
describe('time based', () => {
afterEach(() => {
timekeeper.reset();
});
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, status);
['SET', 'APPROVED'].forEach(status => {
it(`should not allow users to change their username if it was changed within 14 of today from ${status}`, async () => {
const user = mockUsers[0];
timekeeper.travel(
moment()
.add(5, 'days')
.toDate()
);
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, status);
try {
await UsersService.setUsername(user.id, 'new_username', null);
throw new Error('edit was processed successfully');
} catch (err) {
expect(err).have.property(
'translation_key',
'EDIT_USERNAME_NOT_AUTHORIZED'
timekeeper.travel(
moment()
.add(5, 'days')
.toDate()
);
}
});
it(`allows users to change their username if it was changed 14 days before today from ${status}`, async () => {
const user = mockUsers[0];
try {
await UsersService.setUsername(user.id, 'new_username', null);
throw new Error('edit was processed successfully');
} catch (err) {
expect(err).have.property(
'translation_key',
'EDIT_USERNAME_NOT_AUTHORIZED'
);
}
});
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, status);
it(`allows users to change their username if it was changed 14 days before today from ${status}`, async () => {
const user = mockUsers[0];
timekeeper.travel(
moment()
.add(15, 'days')
.toDate()
);
// Set the user to the desired status.
await UsersService.setUsernameStatus(user.id, status);
await UsersService.setUsername(user.id, 'new_username', null);
timekeeper.travel(
moment()
.add(15, 'days')
.toDate()
);
await UsersService.setUsername(user.id, 'new_username', null);
});
});
});
});
}
}
});
});
describe('#isValidUsername', () => {