The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/app/ui-account/client/accountProfile.js

560 lines
17 KiB

import { SHA256 } from 'meteor/sha';
import { ReactiveVar } from 'meteor/reactive-var';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import _ from 'underscore';
import s from 'underscore.string';
import toastr from 'toastr';
import { modal, SideNav } from '../../ui-utils';
import { t, handleError } from '../../utils';
import { settings } from '../../settings';
import { Notifications } from '../../notifications';
import { callbacks } from '../../callbacks';
const validateEmail = (email) => /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(email);
const validateUsername = (username) => {
const reg = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`);
return reg.test(username);
};
const validateName = (name) => name && name.length;
const validateStatusMessage = (statusMessage) => {
if (!statusMessage || statusMessage.length <= 120 || statusMessage.length === 0) {
return true;
}
};
const validatePassword = (password, confirmationPassword) => {
if (!confirmationPassword) {
return true;
}
return password === confirmationPassword;
};
const filterNames = (old) => {
const reg = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`);
return [...old.replace(' ', '')].filter((f) => reg.test(f)).join('');
};
const filterEmail = (old) => old.replace(' ', '');
const setAvatar = function(event, template) {
const { blob, contentType, service } = this.suggestion;
template.avatar.set({
service,
contentType,
blob,
});
};
const loginWith = function(event, template) {
const loginWithService = `loginWith${ s.capitalize(this.name) }`;
const serviceConfig = {};
Meteor[loginWithService](serviceConfig, function(error) {
if (error && error.error) {
if (error.error === 'github-no-public-email') {
return alert(t('github_no_public_email'));
}
return toastr.error(error.message);
}
template.getSuggestions();
});
};
const isUserEmailVerified = (user) => user.emails && user.emails[0] && user.emails[0].verified;
const getUserEmailAddress = (user) => user.emails && user.emails[0] && user.emails[0].address;
Template.accountProfile.helpers({
emailInvalid() {
return !validateEmail(Template.instance().email.get());
},
usernameInvalid() {
return !validateUsername(Template.instance().username.get());
},
usernameAvaliable() {
return Template.instance().usernameAvaliable.get() !== false;
},
nameInvalid() {
return !validateName(Template.instance().realname.get());
},
statusMessageInvalid() {
return !validateStatusMessage(Template.instance().statusText.get());
},
confirmationPasswordInvalid() {
const { password, confirmationPassword } = Template.instance();
return !validatePassword(password.get(), confirmationPassword.get());
},
selectUrl() {
return Template.instance().url.get().trim() ? '' : 'disabled';
},
services() {
const suggestions = Template.instance().suggestions.get();
if (suggestions.avatars) {
return Object.keys(suggestions.avatars).map((service) => ({
name: service,
// TODO: improve this fix
service: !suggestions.avatars[service.toLowerCase()] ? settings.get(`Accounts_OAuth_${ s.capitalize(service.toLowerCase()) }`) : false,
suggestion: suggestions.avatars[service.toLowerCase()],
}))
.filter(({ service, suggestion }) => service || suggestion);
}
},
initialsUsername() {
const user = Meteor.user();
return `@${ user && user.username }`;
},
avatarPreview() {
return Template.instance().avatar.get();
},
suggestions() {
return Template.instance().suggestions.get();
},
ifThenElse(condition, val, not = '') {
return condition ? val : not;
},
canSave(ret) {
const instance = Template.instance();
instance.dep.depend();
const realname = instance.realname.get();
const statusText = instance.statusText.get();
const username = instance.username.get();
const password = instance.password.get();
const confirmationPassword = instance.confirmationPassword.get();
const email = instance.email.get();
const usernameAvaliable = instance.usernameAvaliable.get();
const avatar = instance.avatar.get();
const user = Meteor.user();
const { customFields = {} } = user;
if (instance.view.isRendered) {
if (instance.findAll('[data-customfield="true"]').some((el) => {
const key = el.getAttribute('name');
const value = customFields[key] || '';
return el.value !== value;
})) {
return;
}
}
if (!avatar && user.name === realname && user.username === username && getUserEmailAddress(user) === email === email && (!password || password !== confirmationPassword)) {
return ret;
}
if (!validateEmail(email) || (!validateUsername(username) || usernameAvaliable !== true) || !validateName(realname) || !validateStatusMessage(statusText)) {
return ret;
}
},
allowDeleteOwnAccount() {
return settings.get('Accounts_AllowDeleteOwnAccount');
},
realname() {
return Meteor.user().name;
},
username() {
return Meteor.user().username;
},
statusText() {
return Meteor.user().statusText;
},
email() {
const user = Meteor.user();
return getUserEmailAddress(user);
},
emailVerified() {
const user = Meteor.user();
return isUserEmailVerified(user);
},
allowRealNameChange() {
return settings.get('Accounts_AllowRealNameChange');
},
allowStatusMessageChange() {
return settings.get('Accounts_AllowUserStatusMessageChange');
},
allowUsernameChange() {
return settings.get('Accounts_AllowUsernameChange') && settings.get('LDAP_Enable') !== true;
},
allowEmailChange() {
return settings.get('Accounts_AllowEmailChange');
},
allowPasswordChange() {
return settings.get('Accounts_AllowPasswordChange');
},
canConfirmNewPassword() {
const password = Template.instance().password.get();
return settings.get('Accounts_AllowPasswordChange') && password && password !== '';
},
allowAvatarChange() {
return settings.get('Accounts_AllowUserAvatarChange');
},
customFields() {
return Meteor.user().customFields;
},
});
Template.accountProfile.onCreated(function() {
const self = this;
const user = Meteor.user();
self.dep = new Tracker.Dependency();
self.realname = new ReactiveVar(user.name);
self.email = new ReactiveVar(getUserEmailAddress(user));
self.username = new ReactiveVar(user.username);
self.password = new ReactiveVar();
self.confirmationPassword = new ReactiveVar();
self.suggestions = new ReactiveVar();
self.avatar = new ReactiveVar();
self.url = new ReactiveVar('');
self.usernameAvaliable = new ReactiveVar(true);
self.statusText = new ReactiveVar(user.statusText);
Notifications.onLogged('updateAvatar', () => self.avatar.set());
self.getSuggestions = function() {
self.suggestions.set(undefined);
Meteor.call('getAvatarSuggestion', function(error, avatars) {
self.suggestions.set({ ready: true, avatars });
});
};
self.getSuggestions();
const settingsTemplate = this.parentTemplate(3);
if (settingsTemplate.child == null) {
settingsTemplate.child = [];
}
settingsTemplate.child.push(this);
this.clearForm = function() {
this.find('[name=password]').value = '';
};
this.changePassword = function(newPassword, callback) {
const instance = this;
if (!newPassword) {
return callback();
} if (!settings.get('Accounts_AllowPasswordChange')) {
toastr.remove();
toastr.error(t('Password_Change_Disabled'));
instance.clearForm();
}
};
this.save = function(typedPassword, cb) {
const avatar = self.avatar.get();
if (avatar) {
const params = [avatar.blob];
params.push(avatar.contentType);
params.push(avatar.service);
Meteor.call('setAvatarFromService', ...params, function(err) {
if (err && err.details && err.details.timeToReset) {
toastr.error(t('error-too-many-requests', {
seconds: parseInt(err.details.timeToReset / 1000),
}));
} else {
toastr.success(t('Avatar_changed_successfully'));
callbacks.run('userAvatarSet', avatar.service);
}
});
}
const instance = this;
const data = {};
const user = Meteor.user();
if (typedPassword) {
data.typedPassword = typedPassword;
}
if (s.trim(self.password.get()) && settings.get('Accounts_AllowPasswordChange')) {
data.newPassword = self.password.get();
}
if (s.trim(self.realname.get()) !== user.name) {
if (!settings.get('Accounts_AllowRealNameChange')) {
toastr.remove();
toastr.error(t('RealName_Change_Disabled'));
instance.clearForm();
return cb && cb();
}
data.realname = s.trim(self.realname.get());
}
if (s.trim(self.statusText.get()) !== user.statusText) {
if (!settings.get('Accounts_AllowUserStatusMessageChange')) {
toastr.remove();
toastr.error(t('StatusMessage_Change_Disabled'));
instance.clearForm();
return cb && cb();
}
data.statusText = s.trim(self.statusText.get());
}
if (s.trim(self.username.get()) !== user.username) {
if (!settings.get('Accounts_AllowUsernameChange')) {
toastr.remove();
toastr.error(t('Username_Change_Disabled'));
instance.clearForm();
return cb && cb();
}
data.username = s.trim(self.username.get());
}
if (s.trim(self.email.get()) !== getUserEmailAddress(user)) {
if (!settings.get('Accounts_AllowEmailChange')) {
toastr.remove();
toastr.error(t('Email_Change_Disabled'));
instance.clearForm();
return cb && cb();
}
data.email = s.trim(self.email.get());
}
const customFields = {};
$('[data-customfield=true]').each(function() {
customFields[this.name] = $(this).val() || '';
});
if (Object.keys(data).length + Object.keys(customFields).length === 0) {
return cb && cb();
}
Meteor.call('saveUserProfile', data, customFields, function(error, results) {
cb && cb();
if (results) {
toastr.remove();
toastr.success(t('Profile_saved_successfully'));
modal.close();
instance.clearForm();
self.password.set();
}
if (error) {
toastr.remove();
handleError(error);
}
});
};
});
Template.accountProfile.onRendered(function() {
Tracker.afterFlush(() => {
if (!settings.get('Accounts_AllowUserProfileChange')) {
FlowRouter.go('home');
}
this.clearForm();
SideNav.setFlex('accountFlex');
SideNav.openFlex();
});
});
const checkAvailability = _.debounce((username, { usernameAvaliable }) => {
Meteor.call('checkUsernameAvailability', username, function(error, data) {
usernameAvaliable.set(data);
});
}, 300);
Template.accountProfile.events({
'change [data-customfield="true"], input [data-customfield="true"]': _.debounce((e, i) => {
i.dep.changed();
}, 300),
'click .js-select-avatar-initials'() {
Meteor.call('resetAvatar', function(err) {
if (err && err.details && err.details.timeToReset) {
toastr.error(t('error-too-many-requests', {
seconds: parseInt(err.details.timeToReset / 1000),
}));
} else {
toastr.success(t('Avatar_changed_successfully'));
callbacks.run('userAvatarSet', 'initials');
}
});
},
'click .js-select-avatar-url'(e, instance, ...args) {
const url = instance.url.get().trim();
if (!url) {
return;
}
setAvatar.apply({
suggestion: {
service: 'url',
blob: url,
contentType: '',
},
}, [e, instance, ...args]);
},
'input .js-avatar-url-input'(e, instance) {
const text = e.target.value;
instance.url.set(text);
},
'click .js-select-avatar'(...args) {
this.suggestion ? setAvatar.apply(this, args) : loginWith.apply(this, args);
},
'input [name=email]'(e, instance) {
const input = e.target;
const position = input.selectionEnd || input.selectionStart;
const { length } = input.value;
const modified = filterEmail(input.value);
input.value = modified;
document.activeElement === input && e && /input/i.test(e.type) && (input.selectionEnd = position + input.value.length - length);
instance.email.set(modified);
},
'input [name=username]'(e, instance) {
const input = e.target;
const position = input.selectionEnd || input.selectionStart;
const { length } = input.value;
const modified = filterNames(input.value);
input.value = modified;
document.activeElement === input && e && /input/i.test(e.type) && (input.selectionEnd = position + input.value.length - length);
instance.username.set(modified);
instance.usernameAvaliable.set();
checkAvailability(modified, instance);
},
'input [name=realname]'(e, instance) {
instance.realname.set(e.target.value);
},
'input [name=statusText]'(e, instance) {
instance.statusText.set(e.target.value);
},
'input [name=password]'(e, instance) {
instance.password.set(e.target.value);
if (e.target.value.length === 0) {
instance.confirmationPassword.set('');
}
},
'input [name=confirmation-password]'(e, instance) {
instance.confirmationPassword.set(e.target.value);
},
'submit form'(e, instance) {
e.preventDefault();
const user = Meteor.user();
const email = instance.email.get();
const password = instance.password.get();
const send = $(e.target.send);
send.addClass('loading');
const reqPass = ((email !== getUserEmailAddress(user))
|| s.trim(password)) && (user && user.services && user.services.password && s.trim(user.services.password.bcrypt));
if (!reqPass) {
return instance.save(undefined, () => setTimeout(() => send.removeClass('loading'), 1000));
}
modal.open({
title: t('Please_enter_your_password'),
text: t('For_your_security_you_must_enter_your_current_password_to_continue'),
type: 'input',
inputType: 'password',
showCancelButton: true,
closeOnConfirm: false,
confirmButtonText: t('Save'),
cancelButtonText: t('Cancel'),
}, (typedPassword) => {
if (typedPassword) {
toastr.remove();
toastr.warning(t('Please_wait_while_your_profile_is_being_saved'));
instance.save(SHA256(typedPassword), () => send.removeClass('loading'));
} else {
modal.showInputError(t('You_need_to_type_in_your_password_in_order_to_do_this'));
return false;
}
});
},
'click .js-logout'(e) {
e.preventDefault();
$(e.target).addClass('loading');
Meteor.logoutOtherClients(function(error) {
setTimeout(function functionName() {
if (error) {
toastr.remove();
handleError(error);
} else {
toastr.remove();
toastr.success(t('Logged_out_of_other_clients_successfully'));
}
$(e.target).removeClass('loading');
}, 1000);
});
},
'click .js-delete-account'(e) {
e.preventDefault();
const user = Meteor.user();
if (s.trim(user && user.services && user.services.password && user.services.password.bcrypt)) {
modal.open({
title: t('Are_you_sure_you_want_to_delete_your_account'),
text: t('If_you_are_sure_type_in_your_password'),
type: 'input',
inputType: 'password',
showCancelButton: true,
closeOnConfirm: false,
confirmButtonText: t('Delete'),
cancelButtonText: t('Cancel'),
}, (typedPassword) => {
if (typedPassword) {
toastr.remove();
toastr.warning(t('Please_wait_while_your_account_is_being_deleted'));
Meteor.call('deleteUserOwnAccount', SHA256(typedPassword), function(error) {
if (error) {
toastr.remove();
modal.showInputError(t('Your_password_is_wrong'));
} else {
modal.close();
}
});
} else {
modal.showInputError(t('You_need_to_type_in_your_password_in_order_to_do_this'));
return false;
}
});
} else {
modal.open({
title: t('Are_you_sure_you_want_to_delete_your_account'),
text: t('If_you_are_sure_type_in_your_username'),
type: 'input',
showCancelButton: true,
closeOnConfirm: false,
confirmButtonText: t('Delete'),
cancelButtonText: t('Cancel'),
}, (deleteConfirmation) => {
const user = Meteor.user();
if (deleteConfirmation === (user && user.username)) {
toastr.remove();
toastr.warning(t('Please_wait_while_your_account_is_being_deleted'));
Meteor.call('deleteUserOwnAccount', deleteConfirmation, function(error) {
if (error) {
toastr.remove();
modal.showInputError(t('Your_password_is_wrong'));
} else {
modal.close();
}
});
} else {
modal.showInputError(t('You_need_to_type_in_your_username_in_order_to_do_this'));
return false;
}
});
}
},
'click #resend-verification-email'(e) {
const user = Meteor.user();
e.preventDefault();
e.currentTarget.innerHTML = `${ e.currentTarget.innerHTML } ...`;
e.currentTarget.disabled = true;
Meteor.call('sendConfirmationEmail', getUserEmailAddress(user), (error, results) => {
if (results) {
toastr.success(t('Verification_email_sent'));
} else if (error) {
handleError(error);
}
e.currentTarget.innerHTML = e.currentTarget.innerHTML.replace(' ...', '');
e.currentTarget.disabled = false;
});
},
'change .js-select-avatar-upload [type=file]'(event, template) {
const e = event.originalEvent || event;
let { files } = e.target;
if (!files || files.length === 0) {
files = (e.dataTransfer && e.dataTransfer.files) || [];
}
Object.keys(files).forEach((key) => {
const blob = files[key];
if (!/image\/.+/.test(blob.type)) {
return;
}
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
template.avatar.set({
service: 'upload',
contentType: blob.type,
blob: reader.result,
});
callbacks.run('userAvatarSet', 'upload');
};
});
},
});