From d4cade892b01f9e7476c93139df4630991c3304a Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 6 Sep 2023 08:40:39 -0600 Subject: [PATCH 1/9] callback being called --- apps/meteor/app/authentication/server/startup/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index e3b97c1aae88..624368f1629b 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -324,7 +324,7 @@ const insertUserDocAsync = async function (options, user) { await addUserRolesAsync(_id, roles); // Make user's roles to be present on callback - user = await Users.findOneById(_id, { projection: { username: 1, type: 1 } }); + user = await Users.findOneById(_id, { projection: { username: 1, type: 1, roles: 1 } }); if (user.username) { if (options.joinDefaultChannels !== false) { From d118801142bf3ccda8cf1427acaea252984f9fe0 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 6 Sep 2023 10:12:02 -0600 Subject: [PATCH 2/9] roles --- apps/meteor/app/lib/server/functions/saveUser.js | 2 +- apps/meteor/lib/callbacks.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.js index f912626c833e..9530ee3b365e 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -279,6 +279,7 @@ const saveNewUser = async function (userData, sendPassword) { password: userData.password, joinDefaultChannels: userData.joinDefaultChannels, isGuest, + globalRoles: roles, }; if (userData.email) { createUser.email = userData.email; @@ -288,7 +289,6 @@ const saveNewUser = async function (userData, sendPassword) { const updateUser = { $set: { - roles, ...(typeof userData.name !== 'undefined' && { name: userData.name }), settings: userData.settings || {}, }, diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 46a27357f546..49ae28c5535d 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -20,6 +20,7 @@ import type { InquiryWithAgentInfo, ILivechatTagRecord, TransferData, + AtLeast, } from '@rocket.chat/core-typings'; import type { FilterOperators } from 'mongodb'; @@ -124,7 +125,7 @@ type ChainedCallbackSignatures = { 'livechat.onLoadConfigApi': (config: { room: IOmnichannelRoom }) => Record; 'beforeSaveMessage': (message: IMessage, room?: IRoom) => IMessage; - 'afterCreateUser': (user: IUser) => IUser; + 'afterCreateUser': (user: AtLeast) => IUser; 'afterDeleteRoom': (rid: IRoom['_id']) => IRoom['_id']; 'livechat:afterOnHold': (room: Pick) => Pick; 'livechat:afterOnHoldChatResumed': (room: Pick) => Pick; From 32e0068d4264e0febb57c992e7ebbd3c32d608cc Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 6 Sep 2023 10:58:09 -0600 Subject: [PATCH 3/9] roles --- .../app/authentication/server/startup/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index 624368f1629b..cc541554e814 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -263,10 +263,14 @@ Accounts.onCreateUser(function (...args) { const { insertUserDoc } = Accounts; const insertUserDocAsync = async function (options, user) { - const globalRoles = []; + let globalRoles = new Set(); + + if (Match.test(options.globalRoles, [String]) && options.globalRoles.length > 0) { + options.globalRoles.map((role) => globalRoles.add(role)); + } if (Match.test(user.globalRoles, [String]) && user.globalRoles.length > 0) { - globalRoles.push(...user.globalRoles); + user.globalRoles.map((role) => globalRoles.add(role)); } delete user.globalRoles; @@ -275,10 +279,12 @@ const insertUserDocAsync = async function (options, user) { const defaultAuthServiceRoles = parseCSV(settings.get('Accounts_Registration_AuthenticationServices_Default_Roles') || ''); if (defaultAuthServiceRoles.length > 0) { - globalRoles.push(...defaultAuthServiceRoles); + defaultAuthServiceRoles.map((role) => globalRoles.add(role)); } } + globalRoles = [...globalRoles]; + const roles = getNewUserRoles(globalRoles); if (!user.type) { From 8f6c79866225e7a3c6d031dba6588b339e1384a5 Mon Sep 17 00:00:00 2001 From: murtaza98 Date: Thu, 7 Sep 2023 13:49:17 +0530 Subject: [PATCH 4/9] skip new user role setting for updates to role via admin panel --- apps/meteor/app/authentication/server/startup/index.js | 2 +- apps/meteor/app/lib/server/functions/saveUser.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index cc541554e814..240fb1e35189 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -285,7 +285,7 @@ const insertUserDocAsync = async function (options, user) { globalRoles = [...globalRoles]; - const roles = getNewUserRoles(globalRoles); + const roles = options.skipNewUserRolesSetting ? globalRoles : getNewUserRoles(globalRoles); if (!user.type) { user.type = 'user'; diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.js index 9530ee3b365e..c743b21e72df 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -280,6 +280,7 @@ const saveNewUser = async function (userData, sendPassword) { joinDefaultChannels: userData.joinDefaultChannels, isGuest, globalRoles: roles, + skipNewUserRolesSetting: true, }; if (userData.email) { createUser.email = userData.email; From c6c8d8ba0e52ac87a44875872195051e37b0c7d0 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 8 Apr 2024 10:43:20 -0600 Subject: [PATCH 5/9] test --- .../authentication/server/startup/index.js | 33 +++++++++---------- .../api/livechat/19-business-hours.ts | 13 ++++++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index 9996b3c74053..1975ea5e7082 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -92,28 +92,28 @@ Meteor.startup(() => { }); }); -Accounts.emailTemplates.verifyEmail.html = function (userModel, url) { +Accounts.emailTemplates.verifyEmail.html = function(userModel, url) { const name = safeHtmlDots(userModel.name); return Mailer.replace(verifyEmailTemplate, { Verification_Url: url, name }); }; -Accounts.emailTemplates.verifyEmail.subject = function () { +Accounts.emailTemplates.verifyEmail.subject = function() { const subject = settings.get('Verification_Email_Subject'); return Mailer.replace(subject || ''); }; -Accounts.urls.resetPassword = function (token) { +Accounts.urls.resetPassword = function(token) { return Meteor.absoluteUrl(`reset-password/${token}`); }; -Accounts.emailTemplates.resetPassword.subject = function (userModel) { +Accounts.emailTemplates.resetPassword.subject = function(userModel) { return Mailer.replace(settings.get('Forgot_Password_Email_Subject') || '', { name: userModel.name, }); }; -Accounts.emailTemplates.resetPassword.html = function (userModel, url) { +Accounts.emailTemplates.resetPassword.html = function(userModel, url) { return Mailer.replacekey( Mailer.replace(resetPasswordTemplate, { name: userModel.name, @@ -123,12 +123,12 @@ Accounts.emailTemplates.resetPassword.html = function (userModel, url) { ); }; -Accounts.emailTemplates.enrollAccount.subject = function (user) { +Accounts.emailTemplates.enrollAccount.subject = function(user) { const subject = settings.get('Accounts_Enrollment_Email_Subject'); return Mailer.replace(subject, user); }; -Accounts.emailTemplates.enrollAccount.html = function (user = {} /* , url*/) { +Accounts.emailTemplates.enrollAccount.html = function(user = {} /* , url*/) { return Mailer.replace(enrollAccountTemplate, { name: escapeHTML(user.name), email: user.emails && user.emails[0] && escapeHTML(user.emails[0].address), @@ -182,7 +182,7 @@ const validateEmailDomain = (user) => { return true; }; -const onCreateUserAsync = async function (options, user = {}) { +const onCreateUserAsync = async function(options, user = {}) { if (!options.skipBeforeCreateUserCallback) { await beforeCreateUserCallback.run(options, user); } @@ -256,14 +256,14 @@ const onCreateUserAsync = async function (options, user = {}) { return user; }; -Accounts.onCreateUser(function (...args) { +Accounts.onCreateUser(function(...args) { // Depends on meteor support for Async return Promise.await(onCreateUserAsync.call(this, ...args)); }); const { insertUserDoc } = Accounts; -const insertUserDocAsync = async function (options, user) { - let globalRoles = new Set(); +const insertUserDocAsync = async function(options, user) { + const globalRoles = new Set(); if (Match.test(options.globalRoles, [String]) && options.globalRoles.length > 0) { options.globalRoles.map((role) => globalRoles.add(role)); @@ -283,9 +283,8 @@ const insertUserDocAsync = async function (options, user) { } } - globalRoles = [...globalRoles]; - - const roles = options.skipNewUserRolesSetting ? globalRoles : getNewUserRoles(globalRoles); + const arrayGlobalRoles = [...globalRoles]; + const roles = options.skipNewUserRolesSetting ? arrayGlobalRoles : getNewUserRoles(arrayGlobalRoles); if (!user.type) { user.type = 'user'; @@ -364,12 +363,12 @@ const insertUserDocAsync = async function (options, user) { return _id; }; -Accounts.insertUserDoc = function (...args) { +Accounts.insertUserDoc = function(...args) { // Depends on meteor support for Async return Promise.await(insertUserDocAsync.call(this, ...args)); }; -const validateLoginAttemptAsync = async function (login) { +const validateLoginAttemptAsync = async function(login) { login = await callbacks.run('beforeValidateLogin', login); if (!(await isValidLoginAttemptByIp(getClientAddress(login.connection)))) { @@ -436,7 +435,7 @@ const validateLoginAttemptAsync = async function (login) { return true; }; -Accounts.validateLoginAttempt(function (...args) { +Accounts.validateLoginAttempt(function(...args) { // Depends on meteor support for Async return Promise.await(validateLoginAttemptAsync.call(this, ...args)); }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 8b6dc224377f..53aaa7021a71 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -838,6 +838,19 @@ describe('LIVECHAT - business hours', function () { expect(latestAgent.statusLivechat).to.be.undefined; }); + it('should verify a newly created agent to be assigned to the default business hour', async () => { + const agent = await createUser({ roles: ['user', 'livechat-agent'] }); + const agentCredentials = await login(agent.username, password); + + const latestAgent: ILivechatAgent = await getMe(agentCredentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id); + + // cleanup + await deleteUser(agent); + }); + after(async () => { await deleteUser(agent._id); }); From 41e0130c843356ddb04d0dfd4fd5fe40e36ce7aa Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 8 Apr 2024 10:59:25 -0600 Subject: [PATCH 6/9] lint --- .../authentication/server/startup/index.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index 1975ea5e7082..d2d4415177d5 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -92,28 +92,28 @@ Meteor.startup(() => { }); }); -Accounts.emailTemplates.verifyEmail.html = function(userModel, url) { +Accounts.emailTemplates.verifyEmail.html = function (userModel, url) { const name = safeHtmlDots(userModel.name); return Mailer.replace(verifyEmailTemplate, { Verification_Url: url, name }); }; -Accounts.emailTemplates.verifyEmail.subject = function() { +Accounts.emailTemplates.verifyEmail.subject = function () { const subject = settings.get('Verification_Email_Subject'); return Mailer.replace(subject || ''); }; -Accounts.urls.resetPassword = function(token) { +Accounts.urls.resetPassword = function (token) { return Meteor.absoluteUrl(`reset-password/${token}`); }; -Accounts.emailTemplates.resetPassword.subject = function(userModel) { +Accounts.emailTemplates.resetPassword.subject = function (userModel) { return Mailer.replace(settings.get('Forgot_Password_Email_Subject') || '', { name: userModel.name, }); }; -Accounts.emailTemplates.resetPassword.html = function(userModel, url) { +Accounts.emailTemplates.resetPassword.html = function (userModel, url) { return Mailer.replacekey( Mailer.replace(resetPasswordTemplate, { name: userModel.name, @@ -123,12 +123,12 @@ Accounts.emailTemplates.resetPassword.html = function(userModel, url) { ); }; -Accounts.emailTemplates.enrollAccount.subject = function(user) { +Accounts.emailTemplates.enrollAccount.subject = function (user) { const subject = settings.get('Accounts_Enrollment_Email_Subject'); return Mailer.replace(subject, user); }; -Accounts.emailTemplates.enrollAccount.html = function(user = {} /* , url*/) { +Accounts.emailTemplates.enrollAccount.html = function (user = {} /* , url*/) { return Mailer.replace(enrollAccountTemplate, { name: escapeHTML(user.name), email: user.emails && user.emails[0] && escapeHTML(user.emails[0].address), @@ -182,7 +182,7 @@ const validateEmailDomain = (user) => { return true; }; -const onCreateUserAsync = async function(options, user = {}) { +const onCreateUserAsync = async function (options, user = {}) { if (!options.skipBeforeCreateUserCallback) { await beforeCreateUserCallback.run(options, user); } @@ -256,13 +256,13 @@ const onCreateUserAsync = async function(options, user = {}) { return user; }; -Accounts.onCreateUser(function(...args) { +Accounts.onCreateUser(function (...args) { // Depends on meteor support for Async return Promise.await(onCreateUserAsync.call(this, ...args)); }); const { insertUserDoc } = Accounts; -const insertUserDocAsync = async function(options, user) { +const insertUserDocAsync = async function (options, user) { const globalRoles = new Set(); if (Match.test(options.globalRoles, [String]) && options.globalRoles.length > 0) { @@ -363,12 +363,12 @@ const insertUserDocAsync = async function(options, user) { return _id; }; -Accounts.insertUserDoc = function(...args) { +Accounts.insertUserDoc = function (...args) { // Depends on meteor support for Async return Promise.await(insertUserDocAsync.call(this, ...args)); }; -const validateLoginAttemptAsync = async function(login) { +const validateLoginAttemptAsync = async function (login) { login = await callbacks.run('beforeValidateLogin', login); if (!(await isValidLoginAttemptByIp(getClientAddress(login.connection)))) { @@ -435,7 +435,7 @@ const validateLoginAttemptAsync = async function(login) { return true; }; -Accounts.validateLoginAttempt(function(...args) { +Accounts.validateLoginAttempt(function (...args) { // Depends on meteor support for Async return Promise.await(validateLoginAttemptAsync.call(this, ...args)); }); From 134d910c6172eda2fcea40a7cb46e829f8e36b14 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 8 Apr 2024 11:37:54 -0600 Subject: [PATCH 7/9] Create tame-ducks-turn.md --- .changeset/tame-ducks-turn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tame-ducks-turn.md diff --git a/.changeset/tame-ducks-turn.md b/.changeset/tame-ducks-turn.md new file mode 100644 index 000000000000..0ad730b9b310 --- /dev/null +++ b/.changeset/tame-ducks-turn.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed a problem that caused `afterCreateUser` callback to be called without new user's roles inside. This caused Omnichannel Business Hour manager to ignore these users from assigning open business hours until the manager restarted or the business hour restarted. From a4ebb85be5bf521f338ab0a7022fdd1cb69d9bf1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 8 Apr 2024 13:02:16 -0600 Subject: [PATCH 8/9] Update 19-business-hours.ts --- .../api/livechat/19-business-hours.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 53aaa7021a71..c0e32f839cb9 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -838,17 +838,22 @@ describe('LIVECHAT - business hours', function () { expect(latestAgent.statusLivechat).to.be.undefined; }); - it('should verify a newly created agent to be assigned to the default business hour', async () => { - const agent = await createUser({ roles: ['user', 'livechat-agent'] }); - const agentCredentials = await login(agent.username, password); - - const latestAgent: ILivechatAgent = await getMe(agentCredentials as any); - expect(latestAgent).to.be.an('object'); - expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); - expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id); - - // cleanup - await deleteUser(agent); + describe('Special Case - Agent created, BH already enabled', () => { + let agent: ILivechatAgent; + let agentCredentials: IUserCredentialsHeader; + before(async () => { + agent = await createUser({ roles: ['user', 'livechat-agent'] }); + agentCredentials = await login(agent.username, password); + }); + after(async () => { + await deleteUser(agent); + }); + it('should verify a newly created agent to be assigned to the default business hour', async () => { + const latestAgent: ILivechatAgent = await getMe(agentCredentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id); + }); }); after(async () => { From 1698a52c91922edc7ad96ebaffcbabe423d76757 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 8 Apr 2024 13:33:12 -0600 Subject: [PATCH 9/9] Update 19-business-hours.ts --- .../end-to-end/api/livechat/19-business-hours.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index c0e32f839cb9..869f36fd9695 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -839,17 +839,17 @@ describe('LIVECHAT - business hours', function () { }); describe('Special Case - Agent created, BH already enabled', () => { - let agent: ILivechatAgent; - let agentCredentials: IUserCredentialsHeader; + let newAgent: ILivechatAgent; + let newAgentCredentials: IUserCredentialsHeader; before(async () => { - agent = await createUser({ roles: ['user', 'livechat-agent'] }); - agentCredentials = await login(agent.username, password); + newAgent = await createUser({ roles: ['user', 'livechat-agent'] }); + newAgentCredentials = await login(newAgent.username, password); }); after(async () => { - await deleteUser(agent); + await deleteUser(newAgent); }); it('should verify a newly created agent to be assigned to the default business hour', async () => { - const latestAgent: ILivechatAgent = await getMe(agentCredentials as any); + const latestAgent: ILivechatAgent = await getMe(newAgentCredentials as any); expect(latestAgent).to.be.an('object'); expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id);