diff --git a/client/routes/adminRouter.coffee b/client/routes/adminRouter.coffee index c226715e635..63b03b9d917 100644 --- a/client/routes/adminRouter.coffee +++ b/client/routes/adminRouter.coffee @@ -2,9 +2,11 @@ FlowRouter.route '/admin/users', name: 'admin-users' triggersExit: [ -> Session.set 'adminSelectedUser' + Session.set 'showUserInfo' ] action: -> Session.set 'adminSelectedUser' + Session.set 'showUserInfo' RocketChat.TabBar.showGroup 'adminusers' BlazeLayout.render 'main', {center: 'adminUsers'} diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index bacfd3fded4..59d343dde70 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -610,6 +610,7 @@ "You_have_been_muted" : "You have been muted and cannot speak in this room", "You_need_confirm_email" : "You need to confirm your email to login!", "You_need_install_an_extension_to_allow_screen_sharing" : "You need install an extension to allow screen sharing", + "You_need_to_change_your_password" : "You need to change your password", "You_should_name_it_to_easily_manage_your_integrations" : "You should name it to easily manage your integrations.", "You_will_not_be_able_to_recover" : "You will not be able to recover this message!", "Your_entry_has_been_deleted" : "Your entry has been deleted.", diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 5f2639f86fd..fb17e59f92f 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -55,6 +55,7 @@ Package.onUse(function(api) { // SERVER FUNCTIONS api.addFiles('server/functions/checkUsernameAvailability.coffee', 'server'); + api.addFiles('server/functions/checkEmailAvailability.js', 'server'); api.addFiles('server/functions/sendMessage.coffee', 'server'); api.addFiles('server/functions/settings.coffee', 'server'); api.addFiles('server/functions/setUsername.coffee', 'server'); @@ -63,6 +64,7 @@ Package.onUse(function(api) { // SERVER METHODS api.addFiles('server/methods/addOAuthService.coffee', 'server'); api.addFiles('server/methods/checkRegistrationSecretURL.coffee', 'server'); + api.addFiles('server/methods/clearRequestPasswordChange.js', 'server'); api.addFiles('server/methods/joinDefaultChannels.coffee', 'server'); api.addFiles('server/methods/removeOAuthService.coffee', 'server'); api.addFiles('server/methods/robotMethods.coffee', 'server'); @@ -73,7 +75,7 @@ Package.onUse(function(api) { api.addFiles('server/methods/setAdminStatus.coffee', 'server'); api.addFiles('server/methods/setRealName.coffee', 'server'); api.addFiles('server/methods/setUsername.coffee', 'server'); - api.addFiles('server/methods/updateUser.coffee', 'server'); + api.addFiles('server/methods/insertOrUpdateUser.coffee', 'server'); api.addFiles('server/methods/restartServer.coffee', 'server'); // SERVER STARTUP diff --git a/packages/rocketchat-lib/server/functions/checkEmailAvailability.js b/packages/rocketchat-lib/server/functions/checkEmailAvailability.js new file mode 100644 index 00000000000..1176500aaf0 --- /dev/null +++ b/packages/rocketchat-lib/server/functions/checkEmailAvailability.js @@ -0,0 +1,3 @@ +RocketChat.checkEmailAvailability = function(email) { + return !Meteor.users.findOne({ "emails.address": { $regex : new RegExp("^" + s.trim(s.escapeRegExp(email)) + "$", "i") } }) +} diff --git a/packages/rocketchat-lib/server/methods/clearRequestPasswordChange.js b/packages/rocketchat-lib/server/methods/clearRequestPasswordChange.js new file mode 100644 index 00000000000..93c0d4dd770 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/clearRequestPasswordChange.js @@ -0,0 +1,9 @@ +Meteor.methods({ + clearRequestPasswordChange: function() { + if (!Meteor.userId()) { + throw new Meteor.Error('invalid-user', "[methods] clearRequestPasswordChange -> Invalid user"); + } + + return RocketChat.models.Users.unsetRequirePasswordChange(Meteor.userId()); + } +}) \ No newline at end of file diff --git a/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee b/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee new file mode 100644 index 00000000000..5efe00eb1ef --- /dev/null +++ b/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee @@ -0,0 +1,53 @@ +Meteor.methods + insertOrUpdateUser: (userData) -> + if not Meteor.userId() + throw new Meteor.Error('invalid-user', "[methods] updateUser -> Invalid user") + + user = Meteor.user() + + canEditUser = RocketChat.authz.hasPermission( user._id, 'edit-other-user-info') + canAddUser = RocketChat.authz.hasPermission( user._id, 'add-user') + + if userData._id and user._id isnt userData._id and canEditUser isnt true + throw new Meteor.Error 'not-authorized', '[methods] updateUser -> Not authorized' + + if not userData._id and canAddUser isnt true + throw new Meteor.Error 'not-authorized', '[methods] updateUser -> Not authorized' + + unless userData.name + throw new Meteor.Error 'name-is-required', 'Name field is required' + + unless userData.username + throw new Meteor.Error 'user-name-is-required', 'Username field is required' + + if not userData._id and not userData.password + throw new Meteor.Error 'password-is-required', 'Password is required when adding a user' + + if not userData._id + if not RocketChat.checkUsernameAvailability userData.username + throw new Meteor.Error 'username-unavailable', "#{username} is already in use :(" + + if userData.email and not RocketChat.checkEmailAvailability userData.email + throw new Meteor.Error 'username-unavailable', "#{username} is already in use :(" + + # insert user + createUser = { username: userData.username, password: userData.password } + if userData.email + createUser.email = userData.email + + _id = Accounts.createUser(createUser) + if userData.requirePasswordChange + Meteor.users.update { _id: _id }, { $set: { name: userData.name, requirePasswordChange: userData.requirePasswordChange } } + + else + #update user + Meteor.users.update { _id: userData._id }, { $set: { name: userData.name, requirePasswordChange: userData.requirePasswordChange } } + + Meteor.runAsUser userData._id, -> + Meteor.call 'setUsername', userData.username + + canEditUserPassword = RocketChat.authz.hasPermission( user._id, 'edit-other-user-password') + if canEditUserPassword and userData.password.trim() + Accounts.setPassword userData._id, userData.password.trim() + + return true diff --git a/packages/rocketchat-lib/server/methods/updateUser.coffee b/packages/rocketchat-lib/server/methods/updateUser.coffee deleted file mode 100644 index 2416d3d1184..00000000000 --- a/packages/rocketchat-lib/server/methods/updateUser.coffee +++ /dev/null @@ -1,30 +0,0 @@ -Meteor.methods - updateUser: (userData) -> - if not Meteor.userId() - throw new Meteor.Error('invalid-user', "[methods] updateUser -> Invalid user") - - user = Meteor.user() - - canEditUserPermission = RocketChat.authz.hasPermission( user._id, 'edit-other-user-info') - if user._id isnt userData._id and canEditUserPermission isnt true - throw new Meteor.Error 'not-authorized', '[methods] updateUser -> Not authorized' - - unless userData._id - throw new Meteor.Error 'id-is-required', '[methods] updateUser -> User id is required' - - unless userData.name - throw new Meteor.Error 'name-is-required', 'Name field is required' - - unless userData.username - throw new Meteor.Error 'user-name-is-required', 'Username field is required' - - Meteor.users.update { _id: userData._id }, { $set: { name: userData.name } } - - Meteor.runAsUser userData._id, -> - Meteor.call 'setUsername', userData.username - - canEditUserPassword = RocketChat.authz.hasPermission( user._id, 'edit-other-user-password') - if canEditUserPassword and userData.password.trim() - Accounts.setPassword userData._id, userData.password.trim() - - return true diff --git a/packages/rocketchat-lib/server/models/Users.coffee b/packages/rocketchat-lib/server/models/Users.coffee index 9e9aaf502b3..2bba9aef91d 100644 --- a/packages/rocketchat-lib/server/models/Users.coffee +++ b/packages/rocketchat-lib/server/models/Users.coffee @@ -196,6 +196,13 @@ RocketChat.models.Users = new class extends RocketChat.models._Base return @update _id, update + unsetRequirePasswordChange: (_id) -> + update = + $unset: + "requirePasswordChange" : true + + return @update _id, update + setLanguage: (_id, language) -> update = $set: diff --git a/packages/rocketchat-theme/assets/stylesheets/base.less b/packages/rocketchat-theme/assets/stylesheets/base.less index cd454102936..00ac3f096aa 100644 --- a/packages/rocketchat-theme/assets/stylesheets/base.less +++ b/packages/rocketchat-theme/assets/stylesheets/base.less @@ -261,6 +261,23 @@ blockquote { margin-top: 20px; text-align: right; } + + &.request-password { + margin: 0 auto; + + fieldset { + margin-top: 20px; + + label { + display: block; + margin-top: 20px; + } + } + + .submit { + text-align: center; + } + } } .input-line { @@ -4299,7 +4316,6 @@ a.github-fork { } .attention-message { - color: white; padding-top: 50px; font-size: 24px; @@ -4308,6 +4324,10 @@ a.github-fork { margin-bottom: 20px; font-size: 40px; } + + span { + display: block; + } } .search-messages-list { diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less b/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less index 13141a7836e..aed17bcef85 100755 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less +++ b/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less @@ -100,6 +100,9 @@ blockquote { background-color: #DFDFDF; } } + &.request-password { + color: white; + } } .input-line { @@ -383,7 +386,7 @@ a.github-fork { border-color: #9f0030; background-color: #D30230; } - } + } } span.soon { color: #aaa; @@ -1208,3 +1211,7 @@ a.github-fork { } } } + +.attention-message { + color: white; +} \ No newline at end of file diff --git a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee index 4706fbe11b1..7c6748ebc02 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee +++ b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee @@ -17,23 +17,39 @@ Template.adminUserEdit.events t.save() Template.adminUserEdit.onCreated -> - instance = @ - - @cancel = -> + @cancel = => RocketChat.TabBar.setTemplate 'adminUserInfo' - @save = -> - userData = { _id: Template.currentData()._id } - userData.name = $("#name", ".edit-form").val() - userData.username = $("#username", ".edit-form").val() - userData.password = $("#password", ".edit-form").val() + @getUserData = => + userData = { _id: Session.get('adminSelectedUser') } + userData.name = s.trim(this.$("#name").val()) + userData.username = s.trim(this.$("#username").val()) + userData.email = s.trim(this.$("#email").val()) + userData.password = s.trim(this.$("#password").val()) + userData.requirePasswordChange = this.$("#changePassword:checked").length > 0 + return userData + + @validate = => + userData = this.getUserData() + + errors = [] + unless userData.name + errors.push 'Name' + unless userData.username + errors.push 'Username' + unless userData.email + errors.push 'E-mail' + + for error in errors + toastr.error(TAPi18n.__('The_field_is_required', TAPi18n.__(error))) + + return errors.length is 0 - unless userData._id and userData.name - toastr.error TAPi18n.__('The_field_is_required'), TAPi18n.__('Name') - else - Meteor.call 'updateUser', userData, (error, result) -> + @save = => + if this.validate() + Meteor.call 'insertOrUpdateUser', this.getUserData(), (error, result) => if result toastr.success t('User_updated_successfully') - instance.cancel() + this.cancel() if error toastr.error error.reason diff --git a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html index 34b3d5b5d4a..002135df0ed 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html +++ b/packages/rocketchat-ui-admin/admin/users/adminUserEdit.html @@ -19,7 +19,7 @@
- +
{{#if hasPermission 'edit-other-user-password'}}
diff --git a/packages/rocketchat-ui-admin/admin/users/adminUserInfo.coffee b/packages/rocketchat-ui-admin/admin/users/adminUserInfo.coffee index bf27f68b74e..31bfe5626ec 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUserInfo.coffee +++ b/packages/rocketchat-ui-admin/admin/users/adminUserInfo.coffee @@ -19,7 +19,6 @@ Template.adminUserInfo.helpers return "UTC #{@utcOffset}" hasAdminRole: -> - console.log 'hasAdmin: ', RocketChat.authz.hasRole(@_id, 'admin') return RocketChat.authz.hasRole(@_id, 'admin') Template.adminUserInfo.events @@ -31,7 +30,7 @@ Template.adminUserInfo.events toastr.success t('User_has_been_deactivated') if error toastr.error error.reason - + 'click .activate': (e) -> e.stopPropagation() e.preventDefault() @@ -40,7 +39,7 @@ Template.adminUserInfo.events toastr.success t('User_has_been_activated') if error toastr.error error.reason - + 'click .make-admin': (e) -> e.stopPropagation() e.preventDefault() @@ -49,7 +48,7 @@ Template.adminUserInfo.events toastr.success t('User_is_now_an_admin') if error toastr.error error.reason - + 'click .remove-admin': (e) -> e.stopPropagation() e.preventDefault() @@ -74,18 +73,19 @@ Template.adminUserInfo.events closeOnConfirm: false html: false }, -> - swal + swal title: t('Deleted') text: t('User_has_been_deleted') type: 'success' timer: 2000 - showConfirmButton: false + showConfirmButton: false Meteor.call 'deleteUser', _id, (error, result) -> if error toastr.error error.reason Session.set 'adminSelectedUser' - + Session.set 'showUserInfo' + 'click .edit-user': (e) -> e.stopPropagation() e.preventDefault() diff --git a/packages/rocketchat-ui-admin/admin/users/adminUsers.coffee b/packages/rocketchat-ui-admin/admin/users/adminUsers.coffee index 077be9bc825..d9634577365 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUsers.coffee +++ b/packages/rocketchat-ui-admin/admin/users/adminUsers.coffee @@ -9,8 +9,6 @@ Template.adminUsers.helpers return 'left' unless RocketChat.TabBar.isFlexOpen() userData: -> return Meteor.users.findOne Session.get 'adminSelectedUser' - userChannels: -> - return ChatSubscription.find({ "u._id": Session.get 'adminSelectedUser' }, { fields: { rid: 1, name: 1, t: 1 }, sort: { t: 1, name: 1 } }).fetch() isLoading: -> return 'btn-loading' unless Template.instance().ready?.get() hasMore: -> @@ -54,6 +52,7 @@ Template.adminUsers.onCreated -> template: 'adminUserEdit', openClick: (e, t) -> Session.set('adminSelectedUser') + Session.set('showUserInfo') return true order: 2 }) @@ -75,7 +74,9 @@ Template.adminUsers.onCreated -> @autorun -> if Session.get 'adminSelectedUser' - channelSubscription = instance.subscribe 'userChannels', Session.get 'adminSelectedUser' + instance.subscribe 'fullUserData', Session.get('adminSelectedUser'), 1 + Session.set 'showUserInfo', Session.get('adminSelectedUser') + RocketChat.TabBar.setData Meteor.users.findOne Session.get 'adminSelectedUser' RocketChat.TabBar.showGroup 'adminusers-selected' @@ -117,7 +118,7 @@ Template.adminUsers.events 'click .user-info': (e) -> e.preventDefault() Session.set 'adminSelectedUser', @_id - Session.set 'showUserInfo', Meteor.users.findOne(@_id)?.username or true + Session.set 'showUserInfo', Meteor.users.findOne(@_id)?.username RocketChat.TabBar.setTemplate 'adminUserInfo' RocketChat.TabBar.openFlex() diff --git a/packages/rocketchat-ui-master/master/main.coffee b/packages/rocketchat-ui-master/master/main.coffee index a5896523f8a..758a12a3399 100644 --- a/packages/rocketchat-ui-master/master/main.coffee +++ b/packages/rocketchat-ui-master/master/main.coffee @@ -143,6 +143,9 @@ Template.main.helpers console.log 'layout.helpers flexOpenedRTC2' if window.rocketDebug return 'layout2' if (Session.get('rtcLayoutmode') > 1) + requirePasswordChange: -> + return Meteor.user()?.requirePasswordChange is true + Template.main.events diff --git a/packages/rocketchat-ui-master/master/main.html b/packages/rocketchat-ui-master/master/main.html index 68d5ba17aef..8d8b9d369c0 100644 --- a/packages/rocketchat-ui-master/master/main.html +++ b/packages/rocketchat-ui-master/master/main.html @@ -51,27 +51,31 @@ {{#unless hasUsername}} {{> username}} {{else}} - {{> spotlight}} - {{> mobileMessageMenu}} - {{> videoCall overlay=true}} -
-