diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 46c69fa5645b..cefbcf224ac4 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -48,10 +48,19 @@
"Accounts_denyUnverifiedEmail": "Deny unverified email",
"Accounts_EmailVerification": "Email Verification",
"Accounts_EmailVerification_Description": "Make sure you have correct SMTP settings to use this feature",
+ "Accounts_Email_Approved": "[name]
Your account was approved.
",
+ "Accounts_Email_Activated": "[name]
Your account was activated.
",
+ "Accounts_Email_Deactivated": "[name]
Your account was deactivated.
",
+ "Accounts_Email_Approved_Subject": "Account approved",
+ "Accounts_Email_Activated_Subject": "Account activated",
+ "Accounts_Email_Deactivated_Subject": "Account deactivated",
"Accounts_Enrollment_Email": "Enrollment Email",
"Accounts_Enrollment_Email_Default": "Welcome to [Site_Name]
Go to [Site_URL] and try the best open source chat solution available today!
",
"Accounts_Enrollment_Email_Description": "You may use the following placeholders:
- [name], [fname], [lname] for the user's full name, first name or last name, respectively.
- [email] for the user's email.
- [Site_Name] and [Site_URL] for the Application Name and URL respectively.
",
"Accounts_Enrollment_Email_Subject_Default": "Welcome to [Site_Name]",
+ "Accounts_Admin_Email_Approval_Needed_Default": "The user [name] ([email]) has been registered.
Please check \"Administration -> Users\" to activate or delete it.
",
+ "Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "The user [name] ([email]) has been registered.
Reason: [reason]
Please check \"Administration -> Users\" to activate or delete it.
",
+ "Accounts_Admin_Email_Approval_Needed_Subject_Default": "A new user registered and needs approval",
"Accounts_ForgetUserSessionOnWindowClose": "Forget User Session on Window Close",
"Accounts_Iframe_api_method": "Api Method",
"Accounts_Iframe_api_url": "API URL",
@@ -938,6 +947,7 @@
"Invalid_name": "The name must not be empty",
"Invalid_notification_setting_s": "Invalid notification setting: %s",
"Invalid_pass": "The password must not be empty",
+ "Invalid_reason": "The reason to join must not be empty",
"Invalid_room_name": "%s is not a valid room name",
"Invalid_secret_URL_message": "The URL provided is invalid.",
"Invalid_setting_s": "Invalid setting: %s",
@@ -1528,6 +1538,7 @@
"Read_only_changed_successfully": "Read only changed successfully",
"Read_only_channel": "Read Only Channel",
"Read_only_group": "Read Only Group",
+ "Reason_To_Join": "Reason to Join",
"RealName_Change_Disabled": "Your Rocket.Chat administrator has disabled the changing of names",
"Receive_alerts": "Receive alerts",
"Record": "Record",
diff --git a/packages/rocketchat-lib/lib/placeholders.js b/packages/rocketchat-lib/lib/placeholders.js
index 8c3907b567fa..45f996e86173 100644
--- a/packages/rocketchat-lib/lib/placeholders.js
+++ b/packages/rocketchat-lib/lib/placeholders.js
@@ -16,6 +16,7 @@ RocketChat.placeholders.replace = function(str, data) {
str = str.replace(/\[lname\]/g, s.strRightBack(data.name, ' ') || '');
str = str.replace(/\[email\]/g, data.email || '');
str = str.replace(/\[password\]/g, data.password || '');
+ str = str.replace(/\[reason\]/g, data.reason || '');
str = str.replace(/\[User\]/g, data.user || '');
str = str.replace(/\[Room\]/g, data.room || '');
diff --git a/packages/rocketchat-lib/server/functions/getFullUserData.js b/packages/rocketchat-lib/server/functions/getFullUserData.js
index 7bc9c9893fbf..056c4643d90f 100644
--- a/packages/rocketchat-lib/server/functions/getFullUserData.js
+++ b/packages/rocketchat-lib/server/functions/getFullUserData.js
@@ -9,7 +9,8 @@ RocketChat.getFullUserData = function({userId, filter, limit}) {
status: 1,
utcOffset: 1,
type: 1,
- active: 1
+ active: 1,
+ reason: 1
};
if (RocketChat.authz.hasPermission(userId, 'view-full-other-user-info')) {
diff --git a/packages/rocketchat-lib/server/models/Users.js b/packages/rocketchat-lib/server/models/Users.js
index 5381c8f7ce4d..e81af4599ff6 100644
--- a/packages/rocketchat-lib/server/models/Users.js
+++ b/packages/rocketchat-lib/server/models/Users.js
@@ -507,6 +507,26 @@ class ModelUsers extends RocketChat.models._Base {
return this.update({ _id }, update);
}
+ setReason(_id, reason) {
+ const update = {
+ $set: {
+ reason
+ }
+ };
+
+ return this.update(_id, update);
+ }
+
+ unsetReason(_id) {
+ const update = {
+ $unset: {
+ reason: true
+ }
+ };
+
+ return this.update(_id, update);
+ }
+
// INSERT
create(data) {
const user = {
diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js
index 8e21ccd78594..dbda0e31d271 100644
--- a/packages/rocketchat-lib/server/startup/settings.js
+++ b/packages/rocketchat-lib/server/startup/settings.js
@@ -107,6 +107,7 @@ RocketChat.settings.addGroup('Accounts', function() {
}
});
this.add('Accounts_ManuallyApproveNewUsers', false, {
+ 'public': true,
type: 'boolean'
});
this.add('Accounts_AllowedDomainsList', '', {
diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
index 7a2a4378e3a0..f7237d1d399e 100644
--- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
+++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html
@@ -65,7 +65,7 @@ {{phoneNumber}} {{/each}}
{{/if}}
- {{#if lastLogin}}
+ {{#if createdAt}}
{{createdAt}}
@@ -75,6 +75,12 @@
{{lastLogin}}
{{/if}}
+ {{#if shouldDisplayReason}}
+
+
+
{{user.reason}}
+
+ {{/if}}
{{/if}}
{{#if utc}}
diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.js b/packages/rocketchat-ui-flextab/client/tabs/userInfo.js
index 92f1bbcd023b..e53b75943255 100644
--- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.js
+++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.js
@@ -155,6 +155,11 @@ Template.userInfo.helpers({
const roomRoles = RoomRoles.findOne({'u._id': user._id, rid: Session.get('openedRoom') }) || {};
const roles = _.union(userRoles.roles || [], roomRoles.roles || []);
return roles.length && RocketChat.models.Roles.find({ _id: { $in: roles }, description: { $exists: 1 } }, { fields: { description: 1 } });
+ },
+
+ shouldDisplayReason() {
+ const user = Template.instance().user.get();
+ return RocketChat.settings.get('Accounts_ManuallyApproveNewUsers') && user.active === false && user.reason;
}
});
/* globals isRtl popover */
diff --git a/packages/rocketchat-ui-login/client/login/form.html b/packages/rocketchat-ui-login/client/login/form.html
index 6ac86a292ea4..ef5277bf675c 100644
--- a/packages/rocketchat-ui-login/client/login/form.html
+++ b/packages/rocketchat-ui-login/client/login/form.html
@@ -28,11 +28,11 @@
{{{_ "Registration_Succeeded"}}}
autocapitalize="off" autocorrect="off"
placeholder="{{emailOrUsernamePlaceholder}}" autofocus>
-
-
- {{#if hasOnePassword}}
-
- {{/if}}
+
+
+ {{#if hasOnePassword}}
+
+ {{/if}}
-
+
+
+
{{/if}}
{{#if state 'register'}}
@@ -58,10 +58,10 @@ {{{_ "Registration_Succeeded"}}}
+
{{#if requirePasswordConfirmation}}
+
+
+ {{/if}}
+ {{#if manuallyApproveNewUsers}}
+
+
{{/if}}
{{/if}}
{{#if state 'forgot-password' 'email-verification'}}
diff --git a/packages/rocketchat-ui-login/client/login/form.js b/packages/rocketchat-ui-login/client/login/form.js
index 5a97a2a8dd6d..96eb5b70fdc7 100644
--- a/packages/rocketchat-ui-login/client/login/form.js
+++ b/packages/rocketchat-ui-login/client/login/form.js
@@ -60,6 +60,9 @@ Template.loginForm.helpers({
},
hasOnePassword() {
return typeof OnePassword !== 'undefined' && OnePassword.findLoginForUrl && typeof device !== 'undefined' && device.platform && device.platform.toLocaleLowerCase() === 'ios';
+ },
+ manuallyApproveNewUsers() {
+ return RocketChat.settings.get('Accounts_ManuallyApproveNewUsers');
}
});
@@ -252,6 +255,9 @@ Template.loginForm.onCreated(function() {
if (RocketChat.settings.get('Accounts_RequirePasswordConfirmation') && formObj['confirm-pass'] !== formObj['pass']) {
validationObj['confirm-pass'] = t('Invalid_confirm_pass');
}
+ if (RocketChat.settings.get('Accounts_ManuallyApproveNewUsers') && !formObj['reason']) {
+ validationObj['reason'] = t('Invalid_reason');
+ }
validateCustomFields(formObj, validationObj);
}
$('#login-card h2').removeClass('error');
diff --git a/server/lib/accounts.js b/server/lib/accounts.js
index 40d433672c1c..946b0b0f67c1 100644
--- a/server/lib/accounts.js
+++ b/server/lib/accounts.js
@@ -12,6 +12,54 @@ Accounts.emailTemplates.siteName = RocketChat.settings.get('Site_Name');
Accounts.emailTemplates.from = `${ RocketChat.settings.get('Site_Name') } <${ RocketChat.settings.get('From_Email') }>`;
+Accounts.emailTemplates.userToActivate = {
+ subject() {
+ const subject = TAPi18n.__('Accounts_Admin_Email_Approval_Needed_Subject_Default');
+ const siteName = RocketChat.settings.get('Site_Name');
+
+ return `[${ siteName }] ${ subject }`;
+ },
+
+ html(options = {}) {
+ const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
+ const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
+
+ const email = options.reason ? 'Accounts_Admin_Email_Approval_Needed_With_Reason_Default' : 'Accounts_Admin_Email_Approval_Needed_Default';
+
+ const html = RocketChat.placeholders.replace(TAPi18n.__(email), {
+ name: options.name,
+ email: options.email,
+ reason: options.reason
+ });
+
+ return header + html + footer;
+ }
+};
+
+Accounts.emailTemplates.userActivated = {
+ subject({active, username}) {
+ const action = active ? (username ? 'Activated' : 'Approved') : 'Deactivated';
+ const subject = `Accounts_Email_${ action }_Subject`;
+ const siteName = RocketChat.settings.get('Site_Name');
+
+ return `[${ siteName }] ${ TAPi18n.__(subject) }`;
+ },
+
+ html({active, name, username}) {
+ const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || '');
+ const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || '');
+
+ const action = active ? (username ? 'Activated' : 'Approved') : 'Deactivated';
+
+ const html = RocketChat.placeholders.replace(TAPi18n.__(`Accounts_Email_${ action }`), {
+ name
+ });
+
+ return header + html + footer;
+ }
+};
+
+
const verifyEmailHtml = Accounts.emailTemplates.verifyEmail.text;
Accounts.emailTemplates.verifyEmail.html = function(user, url) {
@@ -94,6 +142,27 @@ Accounts.onCreateUser(function(options, user = {}) {
}
}
+ if (!user.active) {
+ const destinations = [];
+
+ RocketChat.models.Roles.findUsersInRole('admin').forEach(adminUser => {
+ if (Array.isArray(adminUser.emails)) {
+ adminUser.emails.forEach(email => {
+ destinations.push(`${ adminUser.name }<${ email.address }>`);
+ });
+ }
+ });
+
+ const email = {
+ to: destinations,
+ from: RocketChat.settings.get('From_Email'),
+ subject: Accounts.emailTemplates.userToActivate.subject(),
+ html: Accounts.emailTemplates.userToActivate.html(options)
+ };
+
+ Meteor.defer(() => Email.send(email));
+ }
+
return user;
});
diff --git a/server/methods/registerUser.js b/server/methods/registerUser.js
index e2371beaeed9..392a2f794a5f 100644
--- a/server/methods/registerUser.js
+++ b/server/methods/registerUser.js
@@ -4,6 +4,7 @@ Meteor.methods({
registerUser(formData) {
const AllowAnonymousRead = RocketChat.settings.get('Accounts_AllowAnonymousRead');
const AllowAnonymousWrite = RocketChat.settings.get('Accounts_AllowAnonymousWrite');
+ const manuallyApproveNewUsers = RocketChat.settings.get('Accounts_ManuallyApproveNewUsers');
if (AllowAnonymousRead === true && AllowAnonymousWrite === true && formData.email == null) {
const userId = Accounts.insertUserDoc({}, {
globalRoles: [
@@ -19,7 +20,8 @@ Meteor.methods({
email: String,
pass: String,
name: String,
- secretURL: Match.Optional(String)
+ secretURL: Match.Optional(String),
+ reason: Match.Optional(String)
}));
}
@@ -33,7 +35,9 @@ Meteor.methods({
const userData = {
email: s.trim(formData.email.toLowerCase()),
- password: formData.pass
+ password: formData.pass,
+ name: formData.name,
+ reason: formData.reason
};
// Check if user has already been imported and never logged in. If so, set password and let it through
@@ -48,6 +52,11 @@ Meteor.methods({
RocketChat.models.Users.setName(userId, s.trim(formData.name));
+ const reason = s.trim(formData.reason);
+ if (manuallyApproveNewUsers && reason) {
+ RocketChat.models.Users.setReason(userId, reason);
+ }
+
RocketChat.saveCustomFields(userId, formData);
try {
diff --git a/server/methods/setUserActiveStatus.js b/server/methods/setUserActiveStatus.js
index f4a2eea9d1ea..58607a653468 100644
--- a/server/methods/setUserActiveStatus.js
+++ b/server/methods/setUserActiveStatus.js
@@ -26,6 +26,21 @@ Meteor.methods({
if (active === false) {
RocketChat.models.Users.unsetLoginTokens(userId);
+ } else {
+ RocketChat.models.Users.unsetReason(userId);
+ }
+
+ const destinations = Array.isArray(user.emails) && user.emails.map(email => `${ user.name || user.username }<${ email.address }>`);
+
+ if (destinations) {
+ const email = {
+ to: destinations,
+ from: RocketChat.settings.get('From_Email'),
+ subject: Accounts.emailTemplates.userActivated.subject({active}),
+ html: Accounts.emailTemplates.userActivated.html({active, name: user.name, username: user.username})
+ };
+
+ Meteor.defer(() => Email.send(email));
}
return true;
diff --git a/tests/data/user.js b/tests/data/user.js
index 92294f99dfcf..f9d51e8ba46b 100644
--- a/tests/data/user.js
+++ b/tests/data/user.js
@@ -1,6 +1,7 @@
export const username = `user.test.${ Date.now() }`;
export const email = `${ username }@rocket.chat`;
export const password = 'rocket.chat';
+export const reason = 'rocket.chat.reason';
export const adminUsername = 'rocketchat.internal.admin.test';
export const adminEmail = `${ adminUsername }@rocket.chat`;
diff --git a/tests/end-to-end/ui/12-settings.js b/tests/end-to-end/ui/12-settings.js
index d54756e6b8b6..759cd13019b7 100644
--- a/tests/end-to-end/ui/12-settings.js
+++ b/tests/end-to-end/ui/12-settings.js
@@ -16,7 +16,7 @@ import admin from '../../pageobjects/administration.page';
import {checkIfUserIsValid, checkIfUserIsAdmin} from '../../data/checks';
import {targetUser, imgURL} from '../../data/interactions.js';
-import {adminUsername, adminEmail, adminPassword, username, email, password} from '../../data/user.js';
+import {adminUsername, adminEmail, adminPassword, username, email, password, reason} from '../../data/user.js';
function api(path) {
return prefix + path;
@@ -454,6 +454,7 @@ describe('[Api Settings Change]', () => {
});
it('register the user', () => {
+ browser.refresh();
loginPage.registerButton.waitForVisible(5000);
loginPage.registerButton.click();
loginPage.nameField.waitForVisible(5000);
@@ -461,6 +462,8 @@ describe('[Api Settings Change]', () => {
loginPage.emailField.setValue(`setting${ email }`);
loginPage.passwordField.setValue(password);
loginPage.confirmPasswordField.setValue(password);
+ loginPage.reasonField.waitForVisible(5000);
+ loginPage.reasonField.setValue(reason);
loginPage.submit();
diff --git a/tests/pageobjects/login.page.js b/tests/pageobjects/login.page.js
index 318970150b14..1084a0cbc109 100644
--- a/tests/pageobjects/login.page.js
+++ b/tests/pageobjects/login.page.js
@@ -12,6 +12,7 @@ class LoginPage extends Page {
get emailField() { return browser.element('[name=email]'); }
get passwordField() { return browser.element('[name=pass]'); }
get confirmPasswordField() { return browser.element('[name=confirm-pass]'); }
+ get reasonField() { return browser.element('[name=reason]'); }
get inputUsername() { return browser.element('form#login-card input#username'); }
get emailOrUsernameInvalidText() { return browser.element('[name=emailOrUsername]~.input-error'); }