Skip to content
Snippets Groups Projects
Commit 8b9c1342 authored by Gabriel Engel's avatar Gabriel Engel
Browse files

Merge pull request #1974 from RocketChat/admin-add-user

Added option for admins to manually add new users
parents e9e89644 5bb440d7
No related merge requests found
Showing
with 261 additions and 99 deletions
## NEXT
- Added option for admins to manually add new users
- Added option for admin to require user to change password
-
## 0.15.0, 2016-Jan-25
......
......@@ -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'}
......
......@@ -68,6 +68,7 @@
"Activate" : "Activate",
"Add_custom_oauth" : "Add custom oauth",
"Add_Members" : "Add Members",
"Add_User" : "Add User",
"Add_users" : "Add users",
"Administration" : "Administration",
"After_OAuth2_authentication_users_will_be_redirected_to_this_URL" : "After OAuth2 authentication, users will be redirected to this URL",
......@@ -355,6 +356,7 @@
"Notify_all_in_this_room" : "Notify all in this room",
"OAuth_Application" : "OAuth Application",
"OAuth_Applications" : "OAuth Applications",
"Old_and_new_password_must_be_different" : "The new password must be different from the old password.",
"Old_and_new_password_required" : "You need to provide both old and new password for changing your password.",
"Old_Password" : "Old Password",
"Online" : "Online",
......@@ -417,6 +419,7 @@
"Remove_custom_oauth" : "Remove custom oauth",
"Remove_from_room" : "Remove from room",
"Removed" : "Removed",
"Require_password_change" : "Require password change",
"Reset" : "Reset",
"Reset_password" : "Reset password",
"Restart" : "Restart",
......@@ -553,6 +556,7 @@
"User__username__was_removed_as_a_moderator_by__user_by_" : "User <em>__username__</em> was removed as a moderator by <em>__user_by__</em>",
"User__username__was_removed_as_a_owner_by__user_by_" : "User <em>__username__</em> was removed as a owner by <em>__user_by__</em>",
"User_added_by" : "User <em>__user_added__</em> added by <em>__user_by__</em>.",
"User_added_successfully" : "User added successfully",
"User_Channels" : "User Channels",
"User_has_been_activated" : "User has been activated",
"User_has_been_deactivated" : "User has been deactivated",
......@@ -611,6 +615,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.",
......
......@@ -24,6 +24,9 @@ Meteor.startup ->
{ _id: 'view-full-other-user-info',
roles : ['admin']}
{ _id: 'add-user',
roles : ['admin']}
{ _id: 'edit-other-user-info',
roles : ['admin']}
......
......@@ -65,6 +65,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/clearRequirePasswordChange.js', 'server');
api.addFiles('server/methods/joinDefaultChannels.coffee', 'server');
api.addFiles('server/methods/removeOAuthService.coffee', 'server');
api.addFiles('server/methods/robotMethods.coffee', 'server');
......@@ -75,8 +76,8 @@ 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/insertOrUpdateUser.coffee', 'server');
api.addFiles('server/methods/setEmail.js', 'server');
api.addFiles('server/methods/updateUser.coffee', 'server');
api.addFiles('server/methods/restartServer.coffee', 'server');
// SERVER STARTUP
......
Meteor.methods({
clearRequirePasswordChange: function() {
if (!Meteor.userId()) {
throw new Meteor.Error('invalid-user', "[methods] clearRequirePasswordChange -> Invalid user");
}
return RocketChat.models.Users.unsetRequirePasswordChange(Meteor.userId());
}
})
\ No newline at end of file
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 s.trim(userData.name)
throw new Meteor.Error 'name-is-required', 'Name field is required'
unless s.trim(userData.username)
throw new Meteor.Error 'user-name-is-required', 'Username field is required'
try
nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$'
catch
nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$'
if not nameValidation.test userData.username
throw new Meteor.Error 'username-invalid', "#{username} is not a valid username"
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', "#{userData.username} is already in use :("
if userData.email and not RocketChat.checkEmailAvailability userData.email
throw new Meteor.Error 'email-unavailable', "#{userData.email} 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 } }
return _id
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
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
if userData.email
Meteor.runAsUser userData._id, ->
Meteor.call 'setEmail', userData.email
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
......@@ -204,6 +204,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 {
......@@ -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
Template.adminUserEdit.helpers
email: ->
return @emails?[0]?.address
canEditOrAdd: ->
return (Session.get('adminSelectedUser') and RocketChat.authz.hasAtLeastOnePermission('edit-other-user-info')) or (not Session.get('adminSelectedUser') and RocketChat.authz.hasAtLeastOnePermission('add-user'))
user: ->
return Meteor.users.findOne(Session.get('adminSelectedUser'))
Template.adminUserEdit.events
'click .cancel': (e, t) ->
......@@ -14,24 +17,47 @@ 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.email = $("#email", ".edit-form").val()
userData.password = $("#password", ".edit-form").val()
unless userData._id and userData.name
toastr.error TAPi18n.__('The_field_is_required'), TAPi18n.__('Name')
else
Meteor.call 'updateUser', userData, (error, result) ->
@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
@save = =>
if this.validate()
userData = this.getUserData()
Meteor.call 'insertOrUpdateUser', userData, (error, result) =>
if result
toastr.success t('User_updated_successfully')
instance.cancel()
if userData._id
toastr.success t('User_updated_successfully')
else
toastr.success t('User_added_successfully')
Session.set('adminSelectedUser', result);
Session.set('showUserInfo', result);
Meteor.subscribe 'fullUserData', userData.username, 1
this.cancel()
if error
toastr.error error.reason
<template name="adminUserEdit">
{{#unless hasPermission 'edit-other-user-info'}}
{{#unless canEditOrAdd}}
<p>You are not authorized to view this page.</p>
{{else}}
<div class="about clearfix">
<form class="edit-form">
<h3>{{name}}</h3>
{{#if user}}
<h3>{{user.name}}</h3>
{{else}}
<h3>{{_ "Add_User"}}</h3>
{{/if}}
<div class="input-line">
<label for="name">{{_ "Name"}}</label>
<input type="text" id="name" autocomplete="off" value="{{name}}">
<input type="text" id="name" autocomplete="off" value="{{user.name}}">
</div>
<div class="input-line">
<label for="username">{{_ "Username"}}</label>
<input type="text" id="username" autocomplete="off" value="{{username}}">
<input type="text" id="username" autocomplete="off" value="{{user.username}}">
</div>
<div class="input-line">
<label for="email">{{_ "E-mail"}}</label>
<input type="email" id="email" autocomplete="off" value="{{email}}">
<input type="email" id="email" autocomplete="off" value="{{user.emails.[0].address}}">
</div>
{{#if hasPermission 'edit-other-user-password'}}
<div class="input-line">
<label for="password">{{_ "Password"}}</label>
<input type="password" id="password" autocomplete="off" value="">
</div>
<div class="input-line">
<label for="changePassword">
<input type="checkbox" id="changePassword" value="1" checked="{{user.requirePasswordChange}}">
{{_ "Require_password_change"}}
</label>
</div>
{{/if}}
</form>
</div>
......
......@@ -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()
......
......@@ -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: ->
......@@ -41,18 +39,31 @@ Template.adminUsers.onCreated ->
groups: ['adminusers', 'adminusers-selected'],
id: 'invite-user',
i18nTitle: 'Invite_Users',
icon: 'icon-plus',
icon: 'icon-paper-plane',
template: 'adminInviteUser',
order: 1
})
RocketChat.TabBar.addButton({
groups: ['adminusers', 'adminusers-selected'],
id: 'add-user',
i18nTitle: 'Add_User',
icon: 'icon-plus',
template: 'adminUserEdit',
openClick: (e, t) ->
Session.set('adminSelectedUser')
Session.set('showUserInfo')
return true
order: 2
})
RocketChat.TabBar.addButton({
groups: ['adminusers-selected']
id: 'admin-user-info',
i18nTitle: 'User_Info',
icon: 'icon-user',
template: 'adminUserInfo',
order: 2
order: 3
})
@autorun ->
......@@ -63,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'
......@@ -105,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()
......
......@@ -17,14 +17,15 @@ Template.flexTabBar.events
RocketChat.TabBar.closeFlex()
$('.flex-tab').css('max-width', '')
else
if @width?
$('.flex-tab').css('max-width', "#{@width}px")
else
$('.flex-tab').css('max-width', '')
if not @openClick? or @openClick(e,t)
if @width?
$('.flex-tab').css('max-width', "#{@width}px")
else
$('.flex-tab').css('max-width', '')
RocketChat.TabBar.setTemplate @template, ->
$('.flex-tab')?.find("input[type='text']:first")?.focus()
$('.flex-tab .content')?.scrollTop(0)
RocketChat.TabBar.setTemplate @template, ->
$('.flex-tab')?.find("input[type='text']:first")?.focus()
$('.flex-tab .content')?.scrollTop(0)
Template.flexTabBar.onCreated ->
# close flex if the visible group changed and the opened template is not in the new visible group
......
......@@ -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
......
......@@ -51,27 +51,31 @@
{{#unless hasUsername}}
{{> username}}
{{else}}
{{> spotlight}}
{{> mobileMessageMenu}}
{{> videoCall overlay=true}}
<div id="user-card-popover"></div>
<div id="rocket-chat" class="menu-nav menu-closed">
<div class="connection-status">
{{> status}}
</div>
{{#unless modal}}
<div class="flex-tab-bar" role="toolbar">
{{> flexTabBar}}
{{#if requirePasswordChange}}
{{> logoLayout render="requestPasswordChange"}}
{{else}}
{{> spotlight}}
{{> mobileMessageMenu}}
{{> videoCall overlay=true}}
<div id="user-card-popover"></div>
<div id="rocket-chat" class="menu-nav menu-closed">
<div class="connection-status">
{{> status}}
</div>
{{#unless modal}}
<div class="flex-tab-bar" role="toolbar">
{{> flexTabBar}}
</div>
{{/unless}}
<div class="main-content {{flexOpened}} {{flexOpenedRTC1}} {{flexOpenedRTC2}} {{#if modal}}main-modal{{/if}}">
{{> Template.dynamic template=center}}
</div>
{{/unless}}
<div class="main-content {{flexOpened}} {{flexOpenedRTC1}} {{flexOpenedRTC2}} {{#if modal}}main-modal{{/if}}">
{{> Template.dynamic template=center}}
{{#unless modal}}
{{> sideNav }}
{{/unless}}
</div>
{{#unless modal}}
{{> sideNav }}
{{/unless}}
</div>
{{> audioNotification }}
{{> 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');
......
<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% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment