Add user and option to force user to change password

pull/1974/head
Marcelo Schmidt 9 years ago
parent d1b62bd17b
commit 5443507df8
  1. 2
      client/routes/adminRouter.coffee
  2. 1
      i18n/en.i18n.json
  3. 4
      packages/rocketchat-lib/package.js
  4. 3
      packages/rocketchat-lib/server/functions/checkEmailAvailability.js
  5. 9
      packages/rocketchat-lib/server/methods/clearRequestPasswordChange.js
  6. 53
      packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee
  7. 30
      packages/rocketchat-lib/server/methods/updateUser.coffee
  8. 7
      packages/rocketchat-lib/server/models/Users.coffee
  9. 22
      packages/rocketchat-theme/assets/stylesheets/base.less
  10. 7
      packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less
  11. 42
      packages/rocketchat-ui-admin/admin/users/adminUserEdit.coffee
  12. 2
      packages/rocketchat-ui-admin/admin/users/adminUserEdit.html
  13. 2
      packages/rocketchat-ui-admin/admin/users/adminUserInfo.coffee
  14. 9
      packages/rocketchat-ui-admin/admin/users/adminUsers.coffee
  15. 3
      packages/rocketchat-ui-master/master/main.coffee
  16. 4
      packages/rocketchat-ui-master/master/main.html
  17. 2
      packages/rocketchat-ui/package.js
  18. 19
      packages/rocketchat-ui/views/app/requestPasswordChange.html
  19. 31
      packages/rocketchat-ui/views/app/requestPasswordChange.js
  20. 1
      server/publications/fullUserData.coffee
  21. 1
      server/publications/userData.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'}

@ -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.",

@ -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

@ -0,0 +1,3 @@
RocketChat.checkEmailAvailability = function(email) {
return !Meteor.users.findOne({ "emails.address": { $regex : new RegExp("^" + s.trim(s.escapeRegExp(email)) + "$", "i") } })
}

@ -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());
}
})

@ -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

@ -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

@ -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:

@ -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 {

@ -100,6 +100,9 @@ blockquote {
background-color: #DFDFDF;
}
}
&.request-password {
color: white;
}
}
.input-line {
@ -1208,3 +1211,7 @@ a.github-fork {
}
}
}
.attention-message {
color: white;
}

@ -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

@ -19,7 +19,7 @@
</div>
<div class="input-line">
<label for="email">{{_ "E-mail"}}</label>
<input type="text" id="email" autocomplete="off" value="{{user.emails.[0].address}}">
<input type="email" id="email" autocomplete="off" value="{{user.emails.[0].address}}">
</div>
{{#if hasPermission 'edit-other-user-password'}}
<div class="input-line">

@ -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
@ -85,6 +84,7 @@ Template.adminUserInfo.events
if error
toastr.error error.reason
Session.set 'adminSelectedUser'
Session.set 'showUserInfo'
'click .edit-user': (e) ->
e.stopPropagation()

@ -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()

@ -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

@ -50,6 +50,9 @@
{{else}}
{{#unless hasUsername}}
{{> username}}
{{else}}
{{#if requirePasswordChange}}
{{> logoLayout render="requestPasswordChange"}}
{{else}}
{{> spotlight}}
{{> mobileMessageMenu}}
@ -72,6 +75,7 @@
{{/unless}}
</div>
{{> audioNotification }}
{{/if}}
{{/unless}}
{{/unless}}
{{/if}}

@ -85,6 +85,7 @@ Package.onUse(function(api) {
api.addFiles('views/app/pageContainer.html', 'client');
api.addFiles('views/app/pageSettingsContainer.html', 'client');
api.addFiles('views/app/privateHistory.html', 'client');
api.addFiles('views/app/requestPasswordChange.html', 'client');
api.addFiles('views/app/room.html', 'client');
api.addFiles('views/app/roomSearch.html', 'client');
api.addFiles('views/app/secretURL.html', 'client');
@ -101,6 +102,7 @@ Package.onUse(function(api) {
api.addFiles('views/app/burguer.coffee', 'client');
api.addFiles('views/app/home.coffee', 'client');
api.addFiles('views/app/privateHistory.coffee', 'client');
api.addFiles('views/app/requestPasswordChange.js', 'client');
api.addFiles('views/app/room.coffee', 'client');
api.addFiles('views/app/roomSearch.coffee', 'client');
api.addFiles('views/app/secretURL.coffee', 'client');

@ -0,0 +1,19 @@
<template name="requestPasswordChange">
<div class="content">
<div class="attention-message">
<i class="icon-attention"></i>
<span>{{_ 'You_need_to_change_your_password'}}</span>
</div>
<div class="rocket-form request-password">
<form>
<fieldset>
<label for="oldPassword">{{_ "Old_Password"}}</label><input type="password" name="oldPassword" id="oldPassword" />
<label for="newPassword">{{_ "Password"}}</label><input type="password" name="newPassword" id="newPassword" />
<div class="submit">
<button type="submit" class="button save"><i class="icon-send"></i><span>{{_ "Save"}}</span></button>
</div>
</fieldset>
</form>
</div>
</div>
</template>

@ -0,0 +1,31 @@
Template.requestPasswordChange.events({
'submit'(e, instance) {
e.preventDefault();
oldPassword = s.trim(instance.$('#oldPassword').val());
newPassword = s.trim(instance.$('#newPassword').val());
instance.changePassword(oldPassword, newPassword);
}
})
Template.requestPasswordChange.onCreated(function() {
this.changePassword = function(oldPassword, newPassword) {
if (!oldPassword || !newPassword) {
toastr.warning(t('Old_and_new_password_required'));
} else {
Accounts.changePassword(oldPassword, newPassword, function(error) {
if(error) {
toastr.error(t('Incorrect_Password'));
} else {
Meteor.call('clearRequestPasswordChange', function() {
toastr.success(t('Password_changed_successfully'))
return true;
});
}
});
}
}
})
Template.requestPasswordChange.onRendered(function() {
this.$('#oldPassword').focus();
})

@ -18,6 +18,7 @@ Meteor.publish 'fullUserData', (filter, limit) ->
active: 1
services: 1
roles : 1
requirePasswordChange : 1
else
limit = 1

@ -16,3 +16,4 @@ Meteor.publish 'userData', ->
defaultRoom: 1
'services.github.id': 1
'services.gitlab.id': 1
requirePasswordChange: 1

Loading…
Cancel
Save