{{ entry.severity }}: {{ entry.timestamp }} (Caller: {{ entry.caller }})
diff --git a/app/apps/server/bridges/livechat.js b/app/apps/server/bridges/livechat.js
index 5437246a0333..f319dfd7ab89 100644
--- a/app/apps/server/bridges/livechat.js
+++ b/app/apps/server/bridges/livechat.js
@@ -12,8 +12,8 @@ export class AppLivechatBridge {
this.orch = orch;
}
- isOnline() {
- return Livechat.online();
+ isOnline(department) {
+ return Livechat.online(department);
}
async createMessage(message, appId) {
diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js
index 876c028b4db1..1071117d180e 100644
--- a/app/apps/server/bridges/messages.js
+++ b/app/apps/server/bridges/messages.js
@@ -1,9 +1,9 @@
-import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { Messages, Users, Subscriptions } from '../../../models';
import { Notifications } from '../../../notifications';
import { updateMessage } from '../../../lib/server/functions/updateMessage';
+import { executeSendMessage } from '../../../lib/server/methods/sendMessage';
export class AppMessageBridge {
constructor(orch) {
@@ -13,13 +13,11 @@ export class AppMessageBridge {
async create(message, appId) {
this.orch.debugLog(`The App ${ appId } is creating a new message.`);
- let msg = this.orch.getConverters().get('messages').convertAppMessage(message);
+ const convertedMessage = this.orch.getConverters().get('messages').convertAppMessage(message);
- Meteor.runAsUser(msg.u._id, () => {
- msg = Meteor.call('sendMessage', msg);
- });
+ const sentMessage = executeSendMessage(convertedMessage.u._id, convertedMessage);
- return msg._id;
+ return sentMessage._id;
}
async getById(messageId, appId) {
diff --git a/app/apps/server/bridges/uploads.js b/app/apps/server/bridges/uploads.js
index a6a3efc6c1d7..fced605713f9 100644
--- a/app/apps/server/bridges/uploads.js
+++ b/app/apps/server/bridges/uploads.js
@@ -14,8 +14,10 @@ export class AppUploadBridge {
getBuffer(upload, appId) {
this.orch.debugLog(`The App ${ appId } is getting the upload: "${ upload.id }"`);
+ const rocketChatUpload = this.orch.getConverters().get('uploads').convertToRocketChat(upload);
+
return new Promise((resolve, reject) => {
- FileUpload.getBuffer(upload, (error, result) => {
+ FileUpload.getBuffer(rocketChatUpload, (error, result) => {
if (error) {
return reject(error);
}
diff --git a/app/apps/server/bridges/users.js b/app/apps/server/bridges/users.js
index 7329777f7026..a35a9502eb63 100644
--- a/app/apps/server/bridges/users.js
+++ b/app/apps/server/bridges/users.js
@@ -1,4 +1,8 @@
-import { Users } from '../../../models/server';
+import { Random } from 'meteor/random';
+
+import { setUserAvatar, checkUsernameAvailability, deleteUser } from '../../../lib/server/functions';
+import { Users } from '../../../models';
+
export class AppUserBridge {
constructor(orch) {
@@ -17,6 +21,64 @@ export class AppUserBridge {
return this.orch.getConverters().get('users').convertByUsername(username);
}
+ async getAppUser(appId) {
+ this.orch.debugLog(`The App ${ appId } is getting its assigned user`);
+
+ const user = Users.findOne({ appId });
+
+ return this.orch.getConverters().get('users').convertToApp(user);
+ }
+
+ async create(userDescriptor, appId, { avatarUrl }) {
+ this.orch.debugLog(`The App ${ appId } is requesting to create a new user.`);
+ const user = this.orch.getConverters().get('users').convertToRocketChat(userDescriptor);
+
+ if (!user._id) {
+ user._id = Random.id();
+ }
+
+ if (!user.createdAt) {
+ user.createdAt = new Date();
+ }
+
+ switch (user.type) {
+ case 'app':
+ if (!checkUsernameAvailability(user.username)) {
+ throw new Error(`The username "${ user.username }" is already being used. Rename or remove the user using it to install this App`);
+ }
+
+ Users.insert(user);
+
+ if (avatarUrl) {
+ setUserAvatar(user, avatarUrl, '', 'local');
+ }
+
+ break;
+
+ default:
+ throw new Error('Creating normal users is currently not supported');
+ }
+
+ return user._id;
+ }
+
+ async remove(user, appId) {
+ this.orch.debugLog(`The App's user is being removed: ${ appId }`);
+
+ // It's actually not a problem if there is no App user to delete - just means we don't need to do anything more.
+ if (!user) {
+ return true;
+ }
+
+ try {
+ deleteUser(user.id);
+ } catch (err) {
+ throw new Error(`Errors occurred while deleting an app user: ${ err }`);
+ }
+
+ return true;
+ }
+
async getActiveUserCount() {
return Users.getActiveLocalUserCount();
}
diff --git a/app/apps/server/communication/rest.js b/app/apps/server/communication/rest.js
index 2787a965135a..d8793a6d8ca4 100644
--- a/app/apps/server/communication/rest.js
+++ b/app/apps/server/communication/rest.js
@@ -239,6 +239,14 @@ export class AppsRestApi {
return API.v1.failure({ status: 'compiler_error', messages: aff.getCompilerErrors() });
}
+ if (aff.hasAppUserError()) {
+ return API.v1.failure({
+ status: 'app_user_error',
+ messages: [aff.getAppUserError().message],
+ payload: { username: aff.getAppUserError().username },
+ });
+ }
+
info.status = aff.getApp().getStatus();
return API.v1.success({
@@ -449,15 +457,16 @@ export class AppsRestApi {
delete() {
const prl = manager.getOneById(this.urlParams.id);
- if (prl) {
- Promise.await(manager.remove(prl.getID()));
+ if (!prl) {
+ return API.v1.notFound(`No App found by the id of: ${ this.urlParams.id }`);
+ }
- const info = prl.getInfo();
- info.status = prl.getStatus();
+ Promise.await(manager.remove(prl.getID()));
- return API.v1.success({ app: info });
- }
- return API.v1.notFound(`No App found by the id of: ${ this.urlParams.id }`);
+ const info = prl.getInfo();
+ info.status = prl.getStatus();
+
+ return API.v1.success({ app: info });
},
});
diff --git a/app/apps/server/converters/users.js b/app/apps/server/converters/users.js
index bccbfa0fce53..79d4016e7c20 100644
--- a/app/apps/server/converters/users.js
+++ b/app/apps/server/converters/users.js
@@ -41,6 +41,30 @@ export class AppUsersConverter {
createdAt: user.createdAt,
updatedAt: user._updatedAt,
lastLoginAt: user.lastLogin,
+ appId: user.appId,
+ };
+ }
+
+ convertToRocketChat(user) {
+ if (!user) {
+ return undefined;
+ }
+
+ return {
+ _id: user.id,
+ username: user.username,
+ emails: user.emails,
+ type: user.type,
+ active: user.isEnabled,
+ name: user.name,
+ roles: user.roles,
+ status: user.status,
+ statusConnection: user.statusConnection,
+ utcOffset: user.utfOffset,
+ createdAt: user.createdAt,
+ _updatedAt: user.updatedAt,
+ lastLogin: user.lastLoginAt,
+ appId: user.appId,
};
}
@@ -50,6 +74,8 @@ export class AppUsersConverter {
return UserType.USER;
case 'bot':
return UserType.BOT;
+ case 'app':
+ return UserType.APP;
case '':
case undefined:
return UserType.UNKNOWN;
diff --git a/app/apps/server/storage/logs-storage.js b/app/apps/server/storage/logs-storage.js
index 78ef627ba599..005a69e70c8f 100644
--- a/app/apps/server/storage/logs-storage.js
+++ b/app/apps/server/storage/logs-storage.js
@@ -1,5 +1,6 @@
import { AppConsole } from '@rocket.chat/apps-engine/server/logging';
import { AppLogStorage } from '@rocket.chat/apps-engine/server/storage';
+import { InstanceStatus } from 'meteor/konecty:multiple-instances-status';
export class AppRealLogsStorage extends AppLogStorage {
constructor(model) {
@@ -25,6 +26,8 @@ export class AppRealLogsStorage extends AppLogStorage {
return new Promise((resolve, reject) => {
const item = AppConsole.toStorageEntry(appId, logger);
+ item.instanceId = InstanceStatus.id();
+
try {
const id = this.db.insert(item);
diff --git a/app/authorization/client/views/permissionsRole.html b/app/authorization/client/views/permissionsRole.html
index a8a5ac92c312..74579e16fe5d 100644
--- a/app/authorization/client/views/permissionsRole.html
+++ b/app/authorization/client/views/permissionsRole.html
@@ -34,7 +34,9 @@
-
+
+
{{_ "Leave the description field blank if you dont want to show the role"}}.
+
{{#if editable}}
diff --git a/app/authorization/server/index.js b/app/authorization/server/index.js
index 01ac6e9e8078..80394b15fcfc 100644
--- a/app/authorization/server/index.js
+++ b/app/authorization/server/index.js
@@ -20,8 +20,7 @@ import './methods/deleteRole';
import './methods/removeRoleFromPermission';
import './methods/removeUserFromRole';
import './methods/saveRole';
-import './publications/permissions';
-import './publications/roles';
+import './streamer/permissions';
import './startup';
export {
diff --git a/app/authorization/server/publications/roles.js b/app/authorization/server/publications/roles.js
deleted file mode 100644
index c4ca8d926eb5..000000000000
--- a/app/authorization/server/publications/roles.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { Roles } from '../../../models';
-import { clearCache } from '../functions/hasPermission';
-
-Meteor.publish('roles', function() {
- console.warn('The publication "roles" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
-
- return Roles.find();
-});
-
-Roles.on('change', ({ diff }) => {
- if (diff && Object.keys(diff).length === 1 && diff._updatedAt) {
- // avoid useless changes
- return;
- }
- clearCache();
-});
diff --git a/app/authorization/server/startup.js b/app/authorization/server/startup.js
index 3314bd586900..6781accad030 100644
--- a/app/authorization/server/startup.js
+++ b/app/authorization/server/startup.js
@@ -4,6 +4,7 @@ import { Meteor } from 'meteor/meteor';
import { Roles, Permissions, Settings } from '../../models';
import { settings } from '../../settings/server';
import { getSettingPermissionId, CONSTANTS } from '../lib';
+import { clearCache } from './functions/hasPermission';
Meteor.startup(function() {
// Note:
@@ -17,15 +18,15 @@ Meteor.startup(function() {
{ _id: 'add-user-to-joined-room', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'add-user-to-any-c-room', roles: ['admin'] },
{ _id: 'add-user-to-any-p-room', roles: [] },
- { _id: 'api-bypass-rate-limit', roles: ['admin', 'bot'] },
+ { _id: 'api-bypass-rate-limit', roles: ['admin', 'bot', 'app'] },
{ _id: 'archive-room', roles: ['admin', 'owner'] },
{ _id: 'assign-admin-role', roles: ['admin'] },
{ _id: 'assign-roles', roles: ['admin'] },
{ _id: 'ban-user', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'bulk-register-user', roles: ['admin'] },
- { _id: 'create-c', roles: ['admin', 'user', 'bot'] },
- { _id: 'create-d', roles: ['admin', 'user', 'bot'] },
- { _id: 'create-p', roles: ['admin', 'user', 'bot'] },
+ { _id: 'create-c', roles: ['admin', 'user', 'bot', 'app'] },
+ { _id: 'create-d', roles: ['admin', 'user', 'bot', 'app'] },
+ { _id: 'create-p', roles: ['admin', 'user', 'bot', 'app'] },
{ _id: 'create-personal-access-tokens', roles: ['admin', 'user'] },
{ _id: 'create-user', roles: ['admin'] },
{ _id: 'clean-channel-history', roles: ['admin'] },
@@ -44,9 +45,9 @@ Meteor.startup(function() {
{ _id: 'edit-room', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'edit-room-retention-policy', roles: ['admin'] },
{ _id: 'force-delete-message', roles: ['admin', 'owner'] },
- { _id: 'join-without-join-code', roles: ['admin', 'bot'] },
- { _id: 'leave-c', roles: ['admin', 'user', 'bot', 'anonymous'] },
- { _id: 'leave-p', roles: ['admin', 'user', 'bot', 'anonymous'] },
+ { _id: 'join-without-join-code', roles: ['admin', 'bot', 'app'] },
+ { _id: 'leave-c', roles: ['admin', 'user', 'bot', 'anonymous', 'app'] },
+ { _id: 'leave-p', roles: ['admin', 'user', 'bot', 'anonymous', 'app'] },
{ _id: 'manage-assets', roles: ['admin'] },
{ _id: 'manage-emoji', roles: ['admin'] },
{ _id: 'manage-user-status', roles: ['admin'] },
@@ -64,15 +65,15 @@ Meteor.startup(function() {
{ _id: 'run-migration', roles: ['admin'] },
{ _id: 'set-moderator', roles: ['admin', 'owner'] },
{ _id: 'set-owner', roles: ['admin', 'owner'] },
- { _id: 'send-many-messages', roles: ['admin', 'bot'] },
+ { _id: 'send-many-messages', roles: ['admin', 'bot', 'app'] },
{ _id: 'set-leader', roles: ['admin', 'owner'] },
{ _id: 'unarchive-room', roles: ['admin'] },
- { _id: 'view-c-room', roles: ['admin', 'user', 'bot', 'anonymous'] },
+ { _id: 'view-c-room', roles: ['admin', 'user', 'bot', 'app', 'anonymous'] },
{ _id: 'user-generate-access-token', roles: ['admin'] },
- { _id: 'view-d-room', roles: ['admin', 'user', 'bot'] },
+ { _id: 'view-d-room', roles: ['admin', 'user', 'bot', 'app'] },
{ _id: 'view-full-other-user-info', roles: ['admin'] },
{ _id: 'view-history', roles: ['admin', 'user', 'anonymous'] },
- { _id: 'view-joined-room', roles: ['guest', 'bot', 'anonymous'] },
+ { _id: 'view-joined-room', roles: ['guest', 'bot', 'app', 'anonymous'] },
{ _id: 'view-join-code', roles: ['admin'] },
{ _id: 'view-logs', roles: ['admin'] },
{ _id: 'view-other-user-channels', roles: ['admin'] },
@@ -125,6 +126,7 @@ Meteor.startup(function() {
{ name: 'owner', scope: 'Subscriptions', description: 'Owner' },
{ name: 'user', scope: 'Users', description: '' },
{ name: 'bot', scope: 'Users', description: '' },
+ { name: 'app', scope: 'Users', description: '' },
{ name: 'guest', scope: 'Users', description: '' },
{ name: 'anonymous', scope: 'Users', description: '' },
{ name: 'livechat-agent', scope: 'Users', description: 'Livechat Agent' },
@@ -207,4 +209,12 @@ Meteor.startup(function() {
};
settings.onload('*', createPermissionForAddedSetting);
+
+ Roles.on('change', ({ diff }) => {
+ if (diff && Object.keys(diff).length === 1 && diff._updatedAt) {
+ // avoid useless changes
+ return;
+ }
+ clearCache();
+ });
});
diff --git a/app/authorization/server/publications/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js
similarity index 100%
rename from app/authorization/server/publications/permissions/emitter.js
rename to app/authorization/server/streamer/permissions/emitter.js
diff --git a/app/authorization/server/publications/permissions/index.js b/app/authorization/server/streamer/permissions/index.js
similarity index 100%
rename from app/authorization/server/publications/permissions/index.js
rename to app/authorization/server/streamer/permissions/index.js
diff --git a/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js b/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js
index 9b8660572c95..fb8755501854 100644
--- a/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js
+++ b/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js
@@ -93,9 +93,13 @@ Template.mailMessagesInstructions.helpers({
});
Template.mailMessagesInstructions.events({
- 'click .js-cancel, click .mail-messages__instructions--selected'(e, t) {
+ 'click .mail-messages__instructions--selected'(e, t) {
t.reset(true);
},
+ 'click .js-cancel'(e, t) {
+ t.reset(true);
+ t.data.tabBar.close();
+ },
'click .js-send'(e, instance) {
const { selectedUsers, selectedEmails, selectedMessages } = instance;
const $emailsInput = instance.$('[name="emails"]');
diff --git a/app/custom-oauth/server/custom_oauth_server.js b/app/custom-oauth/server/custom_oauth_server.js
index 40745c93fb93..4bebfb030d2f 100644
--- a/app/custom-oauth/server/custom_oauth_server.js
+++ b/app/custom-oauth/server/custom_oauth_server.js
@@ -17,6 +17,107 @@ const logger = new Logger('CustomOAuth');
const Services = {};
const BeforeUpdateOrCreateUserFromExternalService = [];
+const normalizers = {
+ // Set 'id' to '_id' for any sources that provide it
+ _id(identity) {
+ if (identity._id && !identity.id) {
+ identity.id = identity._id;
+ }
+ },
+
+ // Fix for Reddit
+ redit(identity) {
+ if (identity.result) {
+ return identity.result;
+ }
+ },
+
+ // Fix WordPress-like identities having 'ID' instead of 'id'
+ wordpress(identity) {
+ if (identity.ID && !identity.id) {
+ identity.id = identity.ID;
+ }
+ },
+
+ // Fix Auth0-like identities having 'user_id' instead of 'id'
+ user_id(identity) {
+ if (identity.user_id && !identity.id) {
+ identity.id = identity.user_id;
+ }
+ },
+
+ characterid(identity) {
+ if (identity.CharacterID && !identity.id) {
+ identity.id = identity.CharacterID;
+ }
+ },
+
+ // Fix Dataporten having 'user.userid' instead of 'id'
+ dataporten(identity) {
+ if (identity.user && identity.user.userid && !identity.id) {
+ if (identity.user.userid_sec && identity.user.userid_sec[0]) {
+ identity.id = identity.user.userid_sec[0];
+ } else {
+ identity.id = identity.user.userid;
+ }
+ identity.email = identity.user.email;
+ }
+ },
+
+ // Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
+ xenforo(identity) {
+ if (identity.user && identity.user.user_id && !identity.id) {
+ identity.id = identity.user.user_id;
+ identity.email = identity.user.user_email;
+ }
+ },
+
+ // Fix general 'phid' instead of 'id' from phabricator
+ phabricator(identity) {
+ if (identity.phid && !identity.id) {
+ identity.id = identity.phid;
+ }
+ },
+
+ // Fix Keycloak-like identities having 'sub' instead of 'id'
+ kaycloak(identity) {
+ if (identity.sub && !identity.id) {
+ identity.id = identity.sub;
+ }
+ },
+
+ // Fix OpenShift identities where id is in 'metadata' object
+ openshift(identity) {
+ if (!identity.id && identity.metadata && identity.metadata.uid) {
+ identity.id = identity.metadata.uid;
+ identity.name = identity.fullName;
+ }
+ },
+
+ // Fix general 'userid' instead of 'id' from provider
+ userid(identity) {
+ if (identity.userid && !identity.id) {
+ identity.id = identity.userid;
+ }
+ },
+
+ // Fix Nextcloud provider
+ nextcloud(identity) {
+ if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
+ identity.id = identity.ocs.data.id;
+ identity.name = identity.ocs.data.displayname;
+ identity.email = identity.ocs.data.email;
+ }
+ },
+
+ // Fix when authenticating from a meteor app with 'emails' field
+ meteor(identity) {
+ if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
+ identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
+ }
+ },
+};
+
export class CustomOAuth {
constructor(name, options) {
logger.debug('Init CustomOAuth', name, options);
@@ -221,76 +322,11 @@ export class CustomOAuth {
normalizeIdentity(identity) {
if (identity) {
- // Set 'id' to '_id' for any sources that provide it
- if (identity._id && !identity.id) {
- identity.id = identity._id;
- }
-
- // Fix for Reddit
- if (identity.result) {
- identity = identity.result;
- }
-
- // Fix WordPress-like identities having 'ID' instead of 'id'
- if (identity.ID && !identity.id) {
- identity.id = identity.ID;
- }
-
- // Fix Auth0-like identities having 'user_id' instead of 'id'
- if (identity.user_id && !identity.id) {
- identity.id = identity.user_id;
- }
-
- if (identity.CharacterID && !identity.id) {
- identity.id = identity.CharacterID;
- }
-
- // Fix Dataporten having 'user.userid' instead of 'id'
- if (identity.user && identity.user.userid && !identity.id) {
- if (identity.user.userid_sec && identity.user.userid_sec[0]) {
- identity.id = identity.user.userid_sec[0];
- } else {
- identity.id = identity.user.userid;
+ for (const normalizer of Object.values(normalizers)) {
+ const result = normalizer(identity);
+ if (result) {
+ identity = result;
}
- identity.email = identity.user.email;
- }
-
- // Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
- if (identity.user && identity.user.user_id && !identity.id) {
- identity.id = identity.user.user_id;
- identity.email = identity.user.user_email;
- }
- // Fix general 'phid' instead of 'id' from phabricator
- if (identity.phid && !identity.id) {
- identity.id = identity.phid;
- }
-
- // Fix Keycloak-like identities having 'sub' instead of 'id'
- if (identity.sub && !identity.id) {
- identity.id = identity.sub;
- }
-
- // Fix OpenShift identities where id is in 'metadata' object
- if (!identity.id && identity.metadata && identity.metadata.uid) {
- identity.id = identity.metadata.uid;
- identity.name = identity.fullName;
- }
-
- // Fix general 'userid' instead of 'id' from provider
- if (identity.userid && !identity.id) {
- identity.id = identity.userid;
- }
-
- // Fix Nextcloud provider
- if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
- identity.id = identity.ocs.data.id;
- identity.name = identity.ocs.data.displayname;
- identity.email = identity.ocs.data.email;
- }
-
- // Fix when authenticating from a meteor app with 'emails' field
- if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
- identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
}
}
diff --git a/app/custom-sounds/server/index.js b/app/custom-sounds/server/index.js
index 873a373e9191..d6b84fae0269 100644
--- a/app/custom-sounds/server/index.js
+++ b/app/custom-sounds/server/index.js
@@ -5,4 +5,3 @@ import './methods/deleteCustomSound';
import './methods/insertOrUpdateSound';
import './methods/listCustomSounds';
import './methods/uploadCustomSound';
-import './publications/customSounds';
diff --git a/app/custom-sounds/server/publications/customSounds.js b/app/custom-sounds/server/publications/customSounds.js
deleted file mode 100644
index d088cc78efcd..000000000000
--- a/app/custom-sounds/server/publications/customSounds.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import s from 'underscore.string';
-
-import { CustomSounds } from '../../../models';
-
-Meteor.publish('customSounds', function(filter, limit) {
- console.warn('The publication "customSounds" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
-
- const fields = {
- name: 1,
- extension: 1,
- };
-
- filter = s.trim(filter);
-
- const options = {
- fields,
- limit,
- sort: { name: 1 },
- };
-
- if (filter) {
- const filterReg = new RegExp(s.escapeRegExp(filter), 'i');
- return CustomSounds.findByName(filterReg, options);
- }
-
- return CustomSounds.find({}, options);
-});
diff --git a/app/discussion/server/index.js b/app/discussion/server/index.js
index 5e8fadefa943..32b341422bf6 100644
--- a/app/discussion/server/index.js
+++ b/app/discussion/server/index.js
@@ -3,8 +3,6 @@ import './authorization';
import './permissions';
import './hooks/propagateDiscussionMetadata';
-import './publications/discussionParentAutocomplete';
-import './publications/discussionsOfRoom';
// Methods
import './methods/createDiscussion';
diff --git a/app/discussion/server/publications/discussionParentAutocomplete.js b/app/discussion/server/publications/discussionParentAutocomplete.js
deleted file mode 100644
index dbe50fbe4208..000000000000
--- a/app/discussion/server/publications/discussionParentAutocomplete.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { Rooms } from '../../../models/server';
-import { hasPermission } from '../../../authorization/server';
-
-Meteor.publish('discussionParentAutocomplete', function(selector) {
- console.warn('The publication "discussionParentAutocomplete" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
-
- if (hasPermission(this.userId, 'view-c-room') !== true) {
- return this.ready();
- }
-
- const pub = this;
- const options = {
- fields: {
- _id: 1,
- name: 1,
- },
- limit: 10,
- sort: {
- name: 1,
- },
- };
-
- const cursorHandle = Rooms.findDiscussionParentByNameStarting(selector.name, options).observeChanges({
- added(_id, record) {
- return pub.added('autocompleteRecords', _id, record);
- },
- changed(_id, record) {
- return pub.changed('autocompleteRecords', _id, record);
- },
- removed(_id, record) {
- return pub.removed('autocompleteRecords', _id, record);
- },
- });
-
- this.ready();
-
- this.onStop(function() {
- return cursorHandle.stop();
- });
-});
diff --git a/app/discussion/server/publications/discussionsOfRoom.js b/app/discussion/server/publications/discussionsOfRoom.js
deleted file mode 100644
index 23f70605b53a..000000000000
--- a/app/discussion/server/publications/discussionsOfRoom.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { Messages } from '../../../models/server';
-
-Meteor.publish('discussionsOfRoom', function(rid, limit = 50) {
- console.warn('The publication "discussionsOfRoom" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
-
- const publication = this;
-
- if (!Meteor.call('canAccessRoom', rid, this.userId)) {
- return this.ready();
- }
-
- const cursorHandle = Messages.find({ rid, drid: { $exists: true } }, { sort: { ts: -1 }, limit }).observeChanges({
- added(_id, record) {
- return publication.added('rocketchat_discussions_of_room', _id, record);
- },
- changed(_id, record) {
- return publication.changed('rocketchat_discussions_of_room', _id, record);
- },
- removed(_id) {
- return publication.removed('rocketchat_discussions_of_room', _id);
- },
- });
-
- this.ready();
- return this.onStop(function() {
- return cursorHandle.stop();
- });
-});
diff --git a/app/emoji-custom/server/index.js b/app/emoji-custom/server/index.js
index 162ae9a35d14..880398b6c1e2 100644
--- a/app/emoji-custom/server/index.js
+++ b/app/emoji-custom/server/index.js
@@ -1,6 +1,5 @@
import './startup/emoji-custom';
import './startup/settings';
-import './publications/fullEmojiData';
import './methods/listEmojiCustom';
import './methods/deleteEmojiCustom';
import './methods/insertOrUpdateEmoji';
diff --git a/app/emoji-custom/server/publications/fullEmojiData.js b/app/emoji-custom/server/publications/fullEmojiData.js
deleted file mode 100644
index f0e0d91c0ad5..000000000000
--- a/app/emoji-custom/server/publications/fullEmojiData.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import s from 'underscore.string';
-
-import { EmojiCustom } from '../../../models';
-
-Meteor.publish('fullEmojiData', function(filter, limit) {
- console.warn('The publication "fullEmojiData" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
-
- const fields = {
- name: 1,
- aliases: 1,
- extension: 1,
- };
-
- filter = s.trim(filter);
-
- const options = {
- fields,
- limit,
- sort: { name: 1 },
- };
-
- if (filter) {
- const filterReg = new RegExp(s.escapeRegExp(filter), 'i');
- return EmojiCustom.findByNameOrAlias(filterReg, options);
- }
-
- return EmojiCustom.find({}, options);
-});
diff --git a/app/federation/server/endpoints/dispatch.js b/app/federation/server/endpoints/dispatch.js
index 0dae5285f9d2..6d776db21597 100644
--- a/app/federation/server/endpoints/dispatch.js
+++ b/app/federation/server/endpoints/dispatch.js
@@ -22,388 +22,403 @@ import { getUpload, requestEventsFromLatest } from '../handler';
import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage';
import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage';
-API.v1.addRoute('federation.events.dispatch', { authRequired: false }, {
- async post() {
- if (!isFederationEnabled()) {
- return API.v1.failure('Federation not enabled');
+const eventHandlers = {
+ //
+ // PING
+ //
+ async [eventTypes.PING]() {
+ return {
+ success: true,
+ };
+ },
+
+ //
+ // GENESIS
+ //
+ async [eventTypes.GENESIS](event) {
+ switch (event.data.contextType) {
+ case contextDefinitions.ROOM.type:
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
+
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { room } } = event;
+
+ // Check if room exists
+ const persistedRoom = Rooms.findOne({ _id: room._id });
+
+ if (persistedRoom) {
+ // Update the federation
+ Rooms.update({ _id: persistedRoom._id }, { $set: { federation: room.federation } });
+ } else {
+ // Denormalize room
+ const denormalizedRoom = normalizers.denormalizeRoom(room);
+
+ // Create the room
+ Rooms.insert(denormalizedRoom);
+ }
+ }
+ return eventResult;
}
+ },
- //
- // Decrypt the payload if needed
- let payload;
+ //
+ // ROOM_DELETE
+ //
+ async [eventTypes.ROOM_DELETE](event) {
+ const { data: { roomId } } = event;
- try {
- payload = decryptIfNeeded(this.request, this.bodyParams);
- } catch (err) {
- return API.v1.failure('Could not decrypt payload');
+ // Check if room exists
+ const persistedRoom = Rooms.findOne({ _id: roomId });
+
+ if (persistedRoom) {
+ // Delete the room
+ deleteRoom(roomId);
}
- //
- // Convert from EJSON
- const { events } = EJSON.fromJSONValue(payload);
+ // Remove all room events
+ await FederationRoomEvents.removeRoomEvents(roomId);
- logger.server.debug(`federation.events.dispatch => events=${ events.map((e) => JSON.stringify(e, null, 2)) }`);
+ return {
+ success: true,
+ };
+ },
- // Loop over received events
- for (const event of events) {
- /* eslint-disable no-await-in-loop */
+ //
+ // ROOM_ADD_USER
+ //
+ async [eventTypes.ROOM_ADD_USER0](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
- let eventResult;
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { roomId, user, subscription, domainsAfterAdd } } = event;
- switch (event.type) {
- //
- // PING
- //
- case eventTypes.PING:
- eventResult = {
- success: true,
- };
- break;
-
- //
- // GENESIS
- //
- case eventTypes.GENESIS:
- switch (event.data.contextType) {
- case contextDefinitions.ROOM.type:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
-
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { room } } = event;
-
- // Check if room exists
- const persistedRoom = Rooms.findOne({ _id: room._id });
-
- if (persistedRoom) {
- // Update the federation
- Rooms.update({ _id: persistedRoom._id }, { $set: { federation: room.federation } });
- } else {
- // Denormalize room
- const denormalizedRoom = normalizers.denormalizeRoom(room);
-
- // Create the room
- Rooms.insert(denormalizedRoom);
- }
- }
- break;
- }
- break;
+ // Check if user exists
+ const persistedUser = Users.findOne({ _id: user._id });
- //
- // ROOM_DELETE
- //
- case eventTypes.ROOM_DELETE:
- const { data: { roomId } } = event;
+ if (persistedUser) {
+ // Update the federation
+ Users.update({ _id: persistedUser._id }, { $set: { federation: user.federation } });
+ } else {
+ // Denormalize user
+ const denormalizedUser = normalizers.denormalizeUser(user);
- // Check if room exists
- const persistedRoom = Rooms.findOne({ _id: roomId });
+ // Create the user
+ Users.insert(denormalizedUser);
+ }
- if (persistedRoom) {
- // Delete the room
- deleteRoom(roomId);
- }
+ // Check if subscription exists
+ const persistedSubscription = Subscriptions.findOne({ _id: subscription._id });
- // Remove all room events
- await FederationRoomEvents.removeRoomEvents(roomId);
+ if (persistedSubscription) {
+ // Update the federation
+ Subscriptions.update({ _id: persistedSubscription._id }, { $set: { federation: subscription.federation } });
+ } else {
+ // Denormalize subscription
+ const denormalizedSubscription = normalizers.denormalizeSubscription(subscription);
- eventResult = {
- success: true,
- };
+ // Create the subscription
+ Subscriptions.insert(denormalizedSubscription);
+ }
- break;
+ // Refresh the servers list
+ FederationServers.refreshServers();
- //
- // ROOM_ADD_USER
- //
- case eventTypes.ROOM_ADD_USER:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+ // Update the room's federation property
+ Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterAdd } });
+ }
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { roomId, user, subscription, domainsAfterAdd } } = event;
+ return eventResult;
+ },
+
+ //
+ // ROOM_REMOVE_USER
+ //
+ async [eventTypes.ROOM_REMOVE_USER](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
+
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { roomId, user, domainsAfterRemoval } } = event;
- // Check if user exists
- const persistedUser = Users.findOne({ _id: user._id });
+ // Remove the user's subscription
+ Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
- if (persistedUser) {
- // Update the federation
- Users.update({ _id: persistedUser._id }, { $set: { federation: user.federation } });
- } else {
- // Denormalize user
- const denormalizedUser = normalizers.denormalizeUser(user);
+ // Refresh the servers list
+ FederationServers.refreshServers();
- // Create the user
- Users.insert(denormalizedUser);
- }
+ // Update the room's federation property
+ Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterRemoval } });
+ }
- // Check if subscription exists
- const persistedSubscription = Subscriptions.findOne({ _id: subscription._id });
+ return eventResult;
+ },
- if (persistedSubscription) {
- // Update the federation
- Subscriptions.update({ _id: persistedSubscription._id }, { $set: { federation: subscription.federation } });
- } else {
- // Denormalize subscription
- const denormalizedSubscription = normalizers.denormalizeSubscription(subscription);
+ //
+ // ROOM_MESSAGE
+ //
+ async [eventTypes.ROOM_MESSAGE](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
- // Create the subscription
- Subscriptions.insert(denormalizedSubscription);
- }
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { message } } = event;
- // Refresh the servers list
- FederationServers.refreshServers();
+ // Check if message exists
+ const persistedMessage = Messages.findOne({ _id: message._id });
- // Update the room's federation property
- Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterAdd } });
- }
- break;
+ if (persistedMessage) {
+ // Update the federation
+ Messages.update({ _id: persistedMessage._id }, { $set: { federation: message.federation } });
+ } else {
+ // Load the room
+ const room = Rooms.findOneById(message.rid);
- //
- // ROOM_REMOVE_USER
- //
- case eventTypes.ROOM_REMOVE_USER:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+ // Denormalize message
+ const denormalizedMessage = normalizers.denormalizeMessage(message);
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { roomId, user, domainsAfterRemoval } } = event;
+ // Is there a file?
+ if (denormalizedMessage.file) {
+ const fileStore = FileUpload.getStore('Uploads');
- // Remove the user's subscription
- Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+ const { federation: { origin } } = denormalizedMessage;
- // Refresh the servers list
- FederationServers.refreshServers();
+ const { upload, buffer } = getUpload(origin, denormalizedMessage.file._id);
- // Update the room's federation property
- Rooms.update({ _id: roomId }, { $set: { 'federation.domains': domainsAfterRemoval } });
+ const oldUploadId = upload._id;
+
+ // Normalize upload
+ delete upload._id;
+ upload.rid = denormalizedMessage.rid;
+ upload.userId = denormalizedMessage.u._id;
+ upload.federation = {
+ _id: denormalizedMessage.file._id,
+ origin,
+ };
+
+ Meteor.runAsUser(upload.userId, () => Meteor.wrapAsync(fileStore.insert.bind(fileStore))(upload, buffer));
+
+ // Update the message's file
+ denormalizedMessage.file._id = upload._id;
+
+ // Update the message's attachments
+ for (const attachment of denormalizedMessage.attachments) {
+ attachment.title_link = attachment.title_link.replace(oldUploadId, upload._id);
+ attachment.image_url = attachment.image_url.replace(oldUploadId, upload._id);
}
- break;
+ }
+
+ // Create the message
+ Messages.insert(denormalizedMessage);
- //
- // ROOM_MESSAGE
- //
- case eventTypes.ROOM_MESSAGE:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+ // Notify users
+ notifyUsersOnMessage(denormalizedMessage, room);
+ sendAllNotifications(denormalizedMessage, room);
+ }
+ }
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { message } } = event;
+ return eventResult;
+ },
- // Check if message exists
- const persistedMessage = Messages.findOne({ _id: message._id });
+ //
+ // ROOM_EDIT_MESSAGE
+ //
+ async [eventTypes.ROOM_EDIT_MESSAGE](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
+
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { message } } = event;
+
+ // Check if message exists
+ const persistedMessage = Messages.findOne({ _id: message._id });
+
+ if (!persistedMessage) {
+ eventResult.success = false;
+ eventResult.reason = 'missingMessageToEdit';
+ } else {
+ // Update the message
+ Messages.update({ _id: persistedMessage._id }, { $set: { msg: message.msg, federation: message.federation } });
+ }
+ }
- if (persistedMessage) {
- // Update the federation
- Messages.update({ _id: persistedMessage._id }, { $set: { federation: message.federation } });
- } else {
- // Load the room
- const room = Rooms.findOneById(message.rid);
+ return eventResult;
+ },
- // Denormalize message
- const denormalizedMessage = normalizers.denormalizeMessage(message);
+ //
+ // ROOM_DELETE_MESSAGE
+ //
+ async [eventTypes.ROOM_DELETE_MESSAGE](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
- // Is there a file?
- if (denormalizedMessage.file) {
- const fileStore = FileUpload.getStore('Uploads');
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { roomId, messageId } } = event;
- const { federation: { origin } } = denormalizedMessage;
+ // Remove the message
+ Messages.removeById(messageId);
- const { upload, buffer } = getUpload(origin, denormalizedMessage.file._id);
+ // Notify the room
+ Notifications.notifyRoom(roomId, 'deleteMessage', { _id: messageId });
+ }
- const oldUploadId = upload._id;
+ return eventResult;
+ },
- // Normalize upload
- delete upload._id;
- upload.rid = denormalizedMessage.rid;
- upload.userId = denormalizedMessage.u._id;
- upload.federation = {
- _id: denormalizedMessage.file._id,
- origin,
- };
+ //
+ // ROOM_SET_MESSAGE_REACTION
+ //
+ async [eventTypes.ROOM_SET_MESSAGE_REACTION](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
+
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { messageId, username, reaction } } = event;
+
+ // Get persisted message
+ const persistedMessage = Messages.findOne({ _id: messageId });
+
+ // Make sure reactions exist
+ persistedMessage.reactions = persistedMessage.reactions || {};
+
+ let reactionObj = persistedMessage.reactions[reaction];
+
+ // If there are no reactions of that type, add it
+ if (!reactionObj) {
+ reactionObj = {
+ usernames: [username],
+ };
+ } else {
+ // Otherwise, add the username
+ reactionObj.usernames.push(username);
+ reactionObj.usernames = [...new Set(reactionObj.usernames)];
+ }
- Meteor.runAsUser(upload.userId, () => Meteor.wrapAsync(fileStore.insert.bind(fileStore))(upload, buffer));
+ // Update the property
+ Messages.update({ _id: messageId }, { $set: { [`reactions.${ reaction }`]: reactionObj } });
+ }
- // Update the message's file
- denormalizedMessage.file._id = upload._id;
+ return eventResult;
+ },
- // Update the message's attachments
- for (const attachment of denormalizedMessage.attachments) {
- attachment.title_link = attachment.title_link.replace(oldUploadId, upload._id);
- attachment.image_url = attachment.image_url.replace(oldUploadId, upload._id);
- }
- }
+ //
+ // ROOM_UNSET_MESSAGE_REACTION
+ //
+ async [eventTypes.ROOM_UNSET_MESSAGE_REACTION](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
- // Create the message
- Messages.insert(denormalizedMessage);
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { messageId, username, reaction } } = event;
- // Notify users
- notifyUsersOnMessage(denormalizedMessage, room);
- sendAllNotifications(denormalizedMessage, room);
- }
- }
- break;
-
- //
- // ROOM_EDIT_MESSAGE
- //
- case eventTypes.ROOM_EDIT_MESSAGE:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
-
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { message } } = event;
-
- // Check if message exists
- const persistedMessage = Messages.findOne({ _id: message._id });
-
- if (!persistedMessage) {
- eventResult.success = false;
- eventResult.reason = 'missingMessageToEdit';
- } else {
- // Update the message
- Messages.update({ _id: persistedMessage._id }, { $set: { msg: message.msg, federation: message.federation } });
- }
- }
- break;
+ // Get persisted message
+ const persistedMessage = Messages.findOne({ _id: messageId });
- //
- // ROOM_DELETE_MESSAGE
- //
- case eventTypes.ROOM_DELETE_MESSAGE:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+ // Make sure reactions exist
+ persistedMessage.reactions = persistedMessage.reactions || {};
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { roomId, messageId } } = event;
+ // If there are no reactions of that type, ignore
+ if (!persistedMessage.reactions[reaction]) {
+ return eventResult;
+ }
- // Remove the message
- Messages.removeById(messageId);
+ const reactionObj = persistedMessage.reactions[reaction];
- // Notify the room
- Notifications.notifyRoom(roomId, 'deleteMessage', { _id: messageId });
- }
- break;
-
- //
- // ROOM_SET_MESSAGE_REACTION
- //
- case eventTypes.ROOM_SET_MESSAGE_REACTION:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
-
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { messageId, username, reaction } } = event;
-
- // Get persisted message
- const persistedMessage = Messages.findOne({ _id: messageId });
-
- // Make sure reactions exist
- persistedMessage.reactions = persistedMessage.reactions || {};
-
- let reactionObj = persistedMessage.reactions[reaction];
-
- // If there are no reactions of that type, add it
- if (!reactionObj) {
- reactionObj = {
- usernames: [username],
- };
- } else {
- // Otherwise, add the username
- reactionObj.usernames.push(username);
- reactionObj.usernames = [...new Set(reactionObj.usernames)];
- }
-
- // Update the property
- Messages.update({ _id: messageId }, { $set: { [`reactions.${ reaction }`]: reactionObj } });
- }
- break;
+ // Get the username index on the list
+ const usernameIdx = reactionObj.usernames.indexOf(username);
- //
- // ROOM_UNSET_MESSAGE_REACTION
- //
- case eventTypes.ROOM_UNSET_MESSAGE_REACTION:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+ // If the index is not found, ignore
+ if (usernameIdx === -1) {
+ return eventResult;
+ }
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { messageId, username, reaction } } = event;
+ // Remove the username from the given reaction
+ reactionObj.usernames.splice(usernameIdx, 1);
- // Get persisted message
- const persistedMessage = Messages.findOne({ _id: messageId });
+ // If there are no more users for that reaction, remove the property
+ if (reactionObj.usernames.length === 0) {
+ Messages.update({ _id: messageId }, { $unset: { [`reactions.${ reaction }`]: 1 } });
+ } else {
+ // Otherwise, update the property
+ Messages.update({ _id: messageId }, { $set: { [`reactions.${ reaction }`]: reactionObj } });
+ }
+ }
- // Make sure reactions exist
- persistedMessage.reactions = persistedMessage.reactions || {};
+ return eventResult;
+ },
- // If there are no reactions of that type, ignore
- if (!persistedMessage.reactions[reaction]) {
- continue;
- }
+ //
+ // ROOM_MUTE_USER
+ //
+ async [eventTypes.ROOM_MUTE_USER](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
- const reactionObj = persistedMessage.reactions[reaction];
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { roomId, user } } = event;
- // Get the username index on the list
- const usernameIdx = reactionObj.usernames.indexOf(username);
+ // Denormalize user
+ const denormalizedUser = normalizers.denormalizeUser(user);
- // If the index is not found, ignore
- if (usernameIdx === -1) {
- continue;
- }
+ // Mute user
+ Rooms.muteUsernameByRoomId(roomId, denormalizedUser.username);
+ }
- // Remove the username from the given reaction
- reactionObj.usernames.splice(usernameIdx, 1);
+ return eventResult;
+ },
- // If there are no more users for that reaction, remove the property
- if (reactionObj.usernames.length === 0) {
- Messages.update({ _id: messageId }, { $unset: { [`reactions.${ reaction }`]: 1 } });
- } else {
- // Otherwise, update the property
- Messages.update({ _id: messageId }, { $set: { [`reactions.${ reaction }`]: reactionObj } });
- }
- }
- break;
+ //
+ // ROOM_UNMUTE_USER
+ //
+ async [eventTypes.ROOM_UNMUTE_USER](event) {
+ const eventResult = await FederationRoomEvents.addEvent(event.context, event);
- //
- // ROOM_MUTE_USER
- //
- case eventTypes.ROOM_MUTE_USER:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+ // If the event was successfully added, handle the event locally
+ if (eventResult.success) {
+ const { data: { roomId, user } } = event;
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { roomId, user } } = event;
+ // Denormalize user
+ const denormalizedUser = normalizers.denormalizeUser(user);
- // Denormalize user
- const denormalizedUser = normalizers.denormalizeUser(user);
+ // Mute user
+ Rooms.unmuteUsernameByRoomId(roomId, denormalizedUser.username);
+ }
- // Mute user
- Rooms.muteUsernameByRoomId(roomId, denormalizedUser.username);
- }
- break;
+ return eventResult;
+ },
+};
- //
- // ROOM_UNMUTE_USER
- //
- case eventTypes.ROOM_UNMUTE_USER:
- eventResult = await FederationRoomEvents.addEvent(event.context, event);
+API.v1.addRoute('federation.events.dispatch', { authRequired: false }, {
+ async post() {
+ if (!isFederationEnabled()) {
+ return API.v1.failure('Federation not enabled');
+ }
- // If the event was successfully added, handle the event locally
- if (eventResult.success) {
- const { data: { roomId, user } } = event;
+ //
+ // Decrypt the payload if needed
+ let payload;
- // Denormalize user
- const denormalizedUser = normalizers.denormalizeUser(user);
+ try {
+ payload = decryptIfNeeded(this.request, this.bodyParams);
+ } catch (err) {
+ return API.v1.failure('Could not decrypt payload');
+ }
- // Mute user
- Rooms.unmuteUsernameByRoomId(roomId, denormalizedUser.username);
- }
- break;
+ //
+ // Convert from EJSON
+ const { events } = EJSON.fromJSONValue(payload);
+
+ logger.server.debug(`federation.events.dispatch => events=${ events.map((e) => JSON.stringify(e, null, 2)) }`);
+
+ // Loop over received events
+ for (const event of events) {
+ /* eslint-disable no-await-in-loop */
+
+ let eventResult;
- //
- // Could not find event
- //
- default:
- continue;
+ if (eventHandlers[event.type]) {
+ eventResult = await eventHandlers[event.type](event);
}
// If there was an error handling the event, take action
diff --git a/app/importer-slack-users/server/importer.js b/app/importer-slack-users/server/importer.js
index 9895fd07990d..6371a568f4a9 100644
--- a/app/importer-slack-users/server/importer.js
+++ b/app/importer-slack-users/server/importer.js
@@ -102,7 +102,7 @@ export class SlackUsersImporter extends Base {
// since we have an existing user, let's try a few things
userId = existantUser._id;
u.rocketId = existantUser._id;
- Users.update({ _id: u.rocketId }, { $addToSet: { importIds: u.id } });
+ Users.update({ _id: u.rocketId }, { $addToSet: { importIds: u.user_id } });
Users.setEmail(existantUser._id, u.email);
Users.setEmailVerified(existantUser._id, u.email);
@@ -117,7 +117,7 @@ export class SlackUsersImporter extends Base {
Meteor.runAsUser(userId, () => {
Meteor.call('setUsername', u.username, { joinDefaultChannelsSilenced: true });
Users.setName(userId, u.name);
- Users.update({ _id: userId }, { $addToSet: { importIds: u.id } });
+ Users.update({ _id: userId }, { $addToSet: { importIds: u.user_id } });
Users.setEmail(userId, u.email);
Users.setEmailVerified(userId, u.email);
u.rocketId = userId;
diff --git a/app/integrations/client/route.js b/app/integrations/client/route.js
index 7ee526094c70..18cd9743bfe6 100644
--- a/app/integrations/client/route.js
+++ b/app/integrations/client/route.js
@@ -3,9 +3,7 @@ import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { t } from '../../utils';
-const dynamic = () => {
- import('./views');
-};
+const dynamic = () => import('./views');
FlowRouter.route('/admin/integrations', {
name: 'admin-integrations',
diff --git a/app/integrations/client/views/integrations.js b/app/integrations/client/views/integrations.js
index 0bace131be02..c568fa9293a5 100644
--- a/app/integrations/client/views/integrations.js
+++ b/app/integrations/client/views/integrations.js
@@ -3,12 +3,15 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { Tracker } from 'meteor/tracker';
import moment from 'moment';
+import _ from 'underscore';
import { hasAtLeastOnePermission } from '../../../authorization';
import { integrations } from '../../lib/rocketchat';
import { SideNav } from '../../../ui-utils/client';
import { APIClient } from '../../../utils/client';
+const ITEMS_COUNT = 50;
+
Template.integrations.helpers({
hasPermission() {
return hasAtLeastOnePermission([
@@ -38,7 +41,25 @@ Template.integrations.onRendered(() => {
Template.integrations.onCreated(async function() {
this.integrations = new ReactiveVar([]);
+ this.offset = new ReactiveVar(0);
+ this.total = new ReactiveVar(0);
+
+ this.autorun(async () => {
+ const offset = this.offset.get();
+ const { integrations, total } = await APIClient.v1.get(`integrations.list?count=${ ITEMS_COUNT }&offset=${ offset }`);
+ this.total.set(total);
+ this.integrations.set(this.integrations.get().concat(integrations));
+ });
+});
- const { integrations } = await APIClient.v1.get('integrations.list');
- this.integrations.set(integrations);
+Template.integrations.events({
+ 'scroll .content': _.throttle(function(e, instance) {
+ if (e.target.scrollTop >= (e.target.scrollHeight - e.target.clientHeight)) {
+ const integrations = instance.integrations.get();
+ if (instance.total.get() <= integrations.length) {
+ return;
+ }
+ return instance.offset.set(instance.offset.get() + ITEMS_COUNT);
+ }
+ }, 200),
});
diff --git a/app/integrations/client/views/integrationsOutgoing.js b/app/integrations/client/views/integrationsOutgoing.js
index 040313001063..dba874aca11b 100644
--- a/app/integrations/client/views/integrationsOutgoing.js
+++ b/app/integrations/client/views/integrationsOutgoing.js
@@ -51,7 +51,7 @@ Template.integrationsOutgoing.onCreated(async function _integrationsOutgoingOnCr
};
const integration = await getIntegration(params.id, Meteor.userId());
- if (!integration) {
+ if (params.id && !integration) {
toastr.error(TAPi18n.__('No_integration_found'));
FlowRouter.go('admin-integrations');
return;
diff --git a/app/integrations/server/index.js b/app/integrations/server/index.js
index 65e7e01cb60a..b022e3034fea 100644
--- a/app/integrations/server/index.js
+++ b/app/integrations/server/index.js
@@ -1,8 +1,6 @@
import '../lib/rocketchat';
import './logger';
import './lib/validation';
-import './publications/integrations';
-import './publications/integrationHistory';
import './methods/incoming/addIncomingIntegration';
import './methods/incoming/updateIncomingIntegration';
import './methods/incoming/deleteIncomingIntegration';
diff --git a/app/integrations/server/lib/triggerHandler.js b/app/integrations/server/lib/triggerHandler.js
index 866a5a10166b..04c4227fa023 100644
--- a/app/integrations/server/lib/triggerHandler.js
+++ b/app/integrations/server/lib/triggerHandler.js
@@ -491,22 +491,8 @@ integrations.triggerHandler = new class RocketChatIntegrationHandler {
}
}
- executeTriggers(...args) {
- logger.outgoing.debug('Execute Trigger:', args[0]);
-
- const argObject = this.eventNameArgumentsToObject(...args);
- const { event, message, room } = argObject;
-
- // Each type of event should have an event and a room attached, otherwise we
- // wouldn't know how to handle the trigger nor would we have anywhere to send the
- // result of the integration
- if (!event) {
- return;
- }
-
+ getTriggersToExecute(room, message) {
const triggersToExecute = [];
-
- logger.outgoing.debug('Starting search for triggers for the room:', room ? room._id : '__any');
if (room) {
switch (room.t) {
case 'd':
@@ -573,6 +559,25 @@ integrations.triggerHandler = new class RocketChatIntegrationHandler {
break;
}
}
+ return triggersToExecute;
+ }
+
+ executeTriggers(...args) {
+ logger.outgoing.debug('Execute Trigger:', args[0]);
+
+ const argObject = this.eventNameArgumentsToObject(...args);
+ const { event, message, room } = argObject;
+
+ // Each type of event should have an event and a room attached, otherwise we
+ // wouldn't know how to handle the trigger nor would we have anywhere to send the
+ // result of the integration
+ if (!event) {
+ return;
+ }
+
+ logger.outgoing.debug('Starting search for triggers for the room:', room ? room._id : '__any');
+
+ const triggersToExecute = this.getTriggersToExecute(room, message);
if (this.triggers.__any) {
// For outgoing integration which don't rely on rooms.
diff --git a/app/integrations/server/publications/integrationHistory.js b/app/integrations/server/publications/integrationHistory.js
deleted file mode 100644
index cc2ea926a235..000000000000
--- a/app/integrations/server/publications/integrationHistory.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { hasAtLeastOnePermission } from '../../../authorization/server';
-import { IntegrationHistory } from '../../../models/server';
-import { mountIntegrationHistoryQueryBasedOnPermissions } from '../lib/mountQueriesBasedOnPermission';
-
-Meteor.publish('integrationHistory', function _integrationHistoryPublication(integrationId, limit = 25) {
- console.warn('The publication "integrationHistory" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
- if (!hasAtLeastOnePermission(this.userId, [
- 'manage-outgoing-integrations',
- 'manage-own-outgoing-integrations',
- ])) {
- throw new Meteor.Error('not-authorized');
- }
-
- return IntegrationHistory.find(Object.assign(mountIntegrationHistoryQueryBasedOnPermissions(this.userId, integrationId)), { sort: { _updatedAt: -1 }, limit });
-});
diff --git a/app/integrations/server/publications/integrations.js b/app/integrations/server/publications/integrations.js
deleted file mode 100644
index 48ebe040b380..000000000000
--- a/app/integrations/server/publications/integrations.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-
-import { hasAtLeastOnePermission } from '../../../authorization/server';
-import { Integrations } from '../../../models/server';
-import { mountIntegrationQueryBasedOnPermissions } from '../lib/mountQueriesBasedOnPermission';
-
-Meteor.publish('integrations', function _integrationPublication() {
- console.warn('The publication "integrations" is deprecated and will be removed after version v3.0.0');
- if (!this.userId) {
- return this.ready();
- }
-
- if (!hasAtLeastOnePermission(this.userId, [
- 'manage-outgoing-integrations',
- 'manage-own-outgoing-integrations',
- 'manage-incoming-integrations',
- 'manage-own-incoming-integrations',
- ])) {
- throw new Meteor.Error('not-authorized');
- }
-
- return Integrations.find(mountIntegrationQueryBasedOnPermissions(this.userId));
-});
diff --git a/app/invites/server/functions/findOrCreateInvite.js b/app/invites/server/functions/findOrCreateInvite.js
index 6ba834a74818..cd01c0cc06e9 100644
--- a/app/invites/server/functions/findOrCreateInvite.js
+++ b/app/invites/server/functions/findOrCreateInvite.js
@@ -15,6 +15,7 @@ function getInviteUrl(invite) {
return getURL(`invite/${ _id }`, {
full: useDirectLink,
cloud: !useDirectLink,
+ cloud_route: 'invite',
});
}
@@ -26,14 +27,14 @@ export const findOrCreateInvite = (userId, invite) => {
return false;
}
- if (!hasPermission(userId, 'create-invite-links')) {
- throw new Meteor.Error('not_authorized');
- }
-
if (!invite.rid) {
throw new Meteor.Error('error-the-field-is-required', 'The field rid is required', { method: 'findOrCreateInvite', field: 'rid' });
}
+ if (!hasPermission(userId, 'create-invite-links', invite.rid)) {
+ throw new Meteor.Error('not_authorized');
+ }
+
const subscription = Subscriptions.findOneByRoomIdAndUserId(invite.rid, userId, { fields: { _id: 1 } });
if (!subscription) {
throw new Meteor.Error('error-invalid-room', 'The rid field is invalid', { method: 'findOrCreateInvite', field: 'rid' });
diff --git a/app/invites/server/functions/useInviteToken.js b/app/invites/server/functions/useInviteToken.js
index 51563956a05a..90a7b3ec32df 100644
--- a/app/invites/server/functions/useInviteToken.js
+++ b/app/invites/server/functions/useInviteToken.js
@@ -1,6 +1,6 @@
import { Meteor } from 'meteor/meteor';
-import { Invites, Users } from '../../../models';
+import { Invites, Users } from '../../../models/server';
import { validateInviteToken } from './validateInviteToken';
import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom';
@@ -16,18 +16,20 @@ export const useInviteToken = (userId, token) => {
const { inviteData, room } = validateInviteToken(token);
const user = Users.findOneById(userId);
+ Users.updateInviteToken(user._id, token);
- if (addUserToRoom(room._id, user)) {
- Invites.update(inviteData._id, {
- $inc: {
- uses: 1,
- },
- });
+ Invites.increaseUsageById(inviteData._id);
+
+ // If the user already has an username, then join the invite room,
+ // If no username is set yet, then the the join will happen on the setUsername method
+ if (user.username) {
+ addUserToRoom(room._id, user);
}
return {
room: {
rid: inviteData.rid,
+ prid: room.prid,
fname: room.fname,
name: room.name,
t: room.t,
diff --git a/app/invites/server/functions/validateInviteToken.js b/app/invites/server/functions/validateInviteToken.js
index 528b268d51a4..88d35fd5ccab 100644
--- a/app/invites/server/functions/validateInviteToken.js
+++ b/app/invites/server/functions/validateInviteToken.js
@@ -12,7 +12,7 @@ export const validateInviteToken = (token) => {
throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' });
}
- const room = Rooms.findOneById(inviteData.rid, { fields: { _id: 1, name: 1, fname: 1, t: 1 } });
+ const room = Rooms.findOneById(inviteData.rid, { fields: { _id: 1, name: 1, fname: 1, t: 1, prid: 1 } });
if (!room) {
throw new Meteor.Error('error-invalid-room', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'rid' });
}
diff --git a/app/katex/katex.min.css b/app/katex/katex.min.css
index 631f6c1d6746..c0cd1451ae6d 100644
--- a/app/katex/katex.min.css
+++ b/app/katex/katex.min.css
@@ -1 +1 @@
-@font-face{font-family:KaTeX_AMS;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype");font-weight:700;font-style:italic}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:KaTeX_SansSerif;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_SansSerif;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:KaTeX_SansSerif;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Script;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size1;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size2;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size3;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size4;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Typewriter;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype");font-weight:400;font-style:normal}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:inline-block;text-align:initial}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;white-space:nowrap;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important}.katex .katex-html{display:inline-block}.katex .katex-mathml{position:absolute;clip:rect(1px,1px,1px,1px);padding:0;border:0;height:1px;width:1px;overflow:hidden}.katex .base{position:relative}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathit{font-family:KaTeX_Math;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-weight:700;font-style:italic}.katex .amsrm,.katex .mathbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr{font-family:KaTeX_Script}.katex .mathsf{font-family:KaTeX_SansSerif}.katex .mainit{font-family:KaTeX_Main;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;vertical-align:bottom;position:relative}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;vertical-align:bottom;font-size:1px;width:2px}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{display:inline-block;width:100%}.katex .mspace{display:inline-block}.katex .mspace.negativethinspace{margin-left:-.16667em}.katex .mspace.muspace{width:.055556em}.katex .mspace.thinspace{width:.16667em}.katex .mspace.negativemediumspace{margin-left:-.22222em}.katex .mspace.mediumspace{width:.22222em}.katex .mspace.thickspace{width:.27778em}.katex .mspace.sixmuspace{width:.333333em}.katex .mspace.eightmuspace{width:.444444em}.katex .mspace.enspace{width:.5em}.katex .mspace.twelvemuspace{width:.666667em}.katex .mspace.quad{width:1em}.katex .mspace.qquad{width:2em}.katex .clap,.katex .llap,.katex .rlap{width:0;position:relative}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{display:inline-block;border:0 solid;position:relative}.katex .overline .overline-line,.katex .underline .underline-line{display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer,.katex .sizing{display:inline-block}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{width:0;position:relative}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;margin:0 -.125em;width:.25em;overflow:hidden;position:relative}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{display:block;position:absolute;width:100%;fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex svg path{stroke:none}.katex .vertical-separator svg{width:.25em}.katex .stretchy{width:100%;display:block;position:relative;overflow:hidden}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{width:100%;position:relative;overflow:hidden}.katex .halfarrow-left{position:absolute;left:0;width:50.2%;overflow:hidden}.katex .halfarrow-right{position:absolute;right:0;width:50.2%;overflow:hidden}.katex .brace-left{position:absolute;left:0;width:25.1%;overflow:hidden}.katex .brace-center{position:absolute;left:25%;width:50%;overflow:hidden}.katex .brace-right{position:absolute;right:0;width:25.1%;overflow:hidden}.katex .x-arrow-pad{padding:0 .5em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox{box-sizing:border-box;border:.04em solid #000}.katex .fcolorbox{box-sizing:border-box;border:.04em solid}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap+.mbin,.katex .cancel-lap+.mord,.katex .cancel-lap+.msupsub,.katex .mbin+.cancel-lap,.katex .mord+.cancel-lap{margin-left:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}
\ No newline at end of file
+@font-face{font-family:KaTeX_AMS;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_Caligraphic;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_Fraktur;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype");font-weight:700;font-style:italic}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:KaTeX_Main;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype");font-weight:700;font-style:italic}@font-face{font-family:KaTeX_Math;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype");font-weight:700;font-style:normal}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype");font-weight:400;font-style:italic}@font-face{font-family:"KaTeX_SansSerif";src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Script;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size1;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size2;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size3;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Size4;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype");font-weight:400;font-style:normal}@font-face{font-family:KaTeX_Typewriter;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype");font-weight:400;font-style:normal}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important}.katex .katex-version:after{content:"0.11.1"}.katex .katex-mathml{position:absolute;clip:rect(1px,1px,1px,1px);padding:0;border:0;height:1px;width:1px;overflow:hidden}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathdefault{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-weight:700;font-style:italic}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;vertical-align:bottom;position:relative}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;vertical-align:bottom;font-size:1px;width:2px;min-width:2px}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{display:inline-block;width:100%;border-bottom-style:solid}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{width:0;position:relative}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{display:inline-block;border:0 solid;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{display:inline-block;width:100%;border-bottom-style:solid}.katex .hdashline{display:inline-block;width:100%;border-bottom-style:dashed}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .op-limits>.vlist-t{text-align:center}.katex .accent>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{display:block;position:absolute;width:100%;height:inherit;fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex svg path{stroke:none}.katex img{border-style:none;min-width:0;min-height:0;max-width:none;max-height:none}.katex .stretchy{width:100%;display:block;position:relative;overflow:hidden}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{width:100%;position:relative;overflow:hidden}.katex .halfarrow-left{position:absolute;left:0;width:50.2%;overflow:hidden}.katex .halfarrow-right{position:absolute;right:0;width:50.2%;overflow:hidden}.katex .brace-left{position:absolute;left:0;width:25.1%;overflow:hidden}.katex .brace-center{position:absolute;left:25%;width:50%;overflow:hidden}.katex .brace-right{position:absolute;right:0;width:25.1%;overflow:hidden}.katex .x-arrow-pad{padding:0 .5em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{box-sizing:border-box;border:.04em solid}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{text-align:left}
diff --git a/app/lib/server/functions/deleteUser.js b/app/lib/server/functions/deleteUser.js
index 031ce3c612eb..4962c5522f06 100644
--- a/app/lib/server/functions/deleteUser.js
+++ b/app/lib/server/functions/deleteUser.js
@@ -12,6 +12,10 @@ export const deleteUser = function(userId) {
fields: { username: 1, avatarOrigin: 1, federation: 1 },
});
+ if (!user) {
+ return;
+ }
+
if (user.federation) {
const existingSubscriptions = Subscriptions.find({ 'u._id': user._id }).count();
diff --git a/app/lib/server/functions/getAvatarSuggestionForUser.js b/app/lib/server/functions/getAvatarSuggestionForUser.js
index 2960145bfb9d..f2bb8cd47306 100644
--- a/app/lib/server/functions/getAvatarSuggestionForUser.js
+++ b/app/lib/server/functions/getAvatarSuggestionForUser.js
@@ -5,97 +5,132 @@ import { ServiceConfiguration } from 'meteor/service-configuration';
import { settings } from '../../../settings';
-export function getAvatarSuggestionForUser(user) {
- check(user, Object);
-
- const avatars = [];
-
- if (user.services && user.services.facebook && user.services.facebook.id && settings.get('Accounts_OAuth_Facebook')) {
- avatars.push({
- service: 'facebook',
- url: `https://graph.facebook.com/${ user.services.facebook.id }/picture?type=large`,
- });
- }
-
- if (user.services && user.services.google && user.services.google.picture && user.services.google.picture !== 'https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg' && settings.get('Accounts_OAuth_Google')) {
- avatars.push({
- service: 'google',
- url: user.services.google.picture,
- });
- }
-
- if (user.services && user.services.github && user.services.github.username && settings.get('Accounts_OAuth_Github')) {
- avatars.push({
- service: 'github',
- url: `https://avatars.githubusercontent.com/${ user.services.github.username }?s=200`,
- });
- }
-
- if (user.services && user.services.linkedin && user.services.linkedin.pictureUrl && settings.get('Accounts_OAuth_Linkedin')) {
- avatars.push({
- service: 'linkedin',
- url: user.services.linkedin.pictureUrl,
- });
- }
-
- if (user.services && user.services.twitter && user.services.twitter.profile_image_url_https && settings.get('Accounts_OAuth_Twitter')) {
- avatars.push({
- service: 'twitter',
- url: user.services.twitter.profile_image_url_https.replace(/_normal|_bigger/, ''),
- });
- }
-
- if (user.services && user.services.gitlab && user.services.gitlab.avatar_url && settings.get('Accounts_OAuth_Gitlab')) {
- avatars.push({
- service: 'gitlab',
- url: user.services.gitlab.avatar_url,
- });
- }
-
- if (user.services && user.services.blockstack && user.services.blockstack.image && settings.get('Blockstack_Enable')) {
- avatars.push({
- service: 'blockstack',
- url: user.services.blockstack.image,
- });
- }
-
- for (const service in user.services) {
- if (user.services[service]._OAuthCustom) {
- const services = ServiceConfiguration.configurations.find({ service }, { fields: { secret: 0 } }).fetch();
+const avatarProviders = {
+ facebook(user) {
+ if (user.services && user.services.facebook && user.services.facebook.id && settings.get('Accounts_OAuth_Facebook')) {
+ return {
+ service: 'facebook',
+ url: `https://graph.facebook.com/${ user.services.facebook.id }/picture?type=large`,
+ };
+ }
+ },
+
+ google(user) {
+ if (user.services && user.services.google && user.services.google.picture && user.services.google.picture !== 'https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg' && settings.get('Accounts_OAuth_Google')) {
+ return {
+ service: 'google',
+ url: user.services.google.picture,
+ };
+ }
+ },
+
+ github(user) {
+ if (user.services && user.services.github && user.services.github.username && settings.get('Accounts_OAuth_Github')) {
+ return {
+ service: 'github',
+ url: `https://avatars.githubusercontent.com/${ user.services.github.username }?s=200`,
+ };
+ }
+ },
+
+ linkedin(user) {
+ if (user.services && user.services.linkedin && user.services.linkedin.pictureUrl && settings.get('Accounts_OAuth_Linkedin')) {
+ return {
+ service: 'linkedin',
+ url: user.services.linkedin.pictureUrl,
+ };
+ }
+ },
+
+ twitter(user) {
+ if (user.services && user.services.twitter && user.services.twitter.profile_image_url_https && settings.get('Accounts_OAuth_Twitter')) {
+ return {
+ service: 'twitter',
+ url: user.services.twitter.profile_image_url_https.replace(/_normal|_bigger/, ''),
+ };
+ }
+ },
+
+ gitlab(user) {
+ if (user.services && user.services.gitlab && user.services.gitlab.avatar_url && settings.get('Accounts_OAuth_Gitlab')) {
+ return {
+ service: 'gitlab',
+ url: user.services.gitlab.avatar_url,
+ };
+ }
+ },
+
+ blockstack(user) {
+ if (user.services && user.services.blockstack && user.services.blockstack.image && settings.get('Blockstack_Enable')) {
+ return {
+ service: 'blockstack',
+ url: user.services.blockstack.image,
+ };
+ }
+ },
+
+ customOAuth(user) {
+ const avatars = [];
+ for (const service in user.services) {
+ if (user.services[service]._OAuthCustom) {
+ const services = ServiceConfiguration.configurations.find({ service }, { fields: { secret: 0 } }).fetch();
+
+ if (services.length > 0) {
+ if (user.services[service].avatarUrl) {
+ avatars.push({
+ service,
+ url: user.services[service].avatarUrl,
+ });
+ }
+ }
+ }
+ }
+ return avatars;
+ },
+
+ emails(user) {
+ const avatars = [];
+ if (user.emails && user.emails.length > 0) {
+ for (const email of user.emails) {
+ if (email.verified === true) {
+ avatars.push({
+ service: 'gravatar',
+ url: Gravatar.imageUrl(email.address, {
+ default: '404',
+ size: 200,
+ secure: true,
+ }),
+ });
+ }
- if (services.length > 0) {
- if (user.services[service].avatarUrl) {
+ if (email.verified !== true) {
avatars.push({
- service,
- url: user.services[service].avatarUrl,
+ service: 'gravatar',
+ url: Gravatar.imageUrl(email.address, {
+ default: '404',
+ size: 200,
+ secure: true,
+ }),
});
}
}
}
- }
+ return avatars;
+ },
+};
- if (user.emails && user.emails.length > 0) {
- for (const email of user.emails) {
- if (email.verified === true) {
- avatars.push({
- service: 'gravatar',
- url: Gravatar.imageUrl(email.address, {
- default: '404',
- size: 200,
- secure: true,
- }),
- });
- }
+export function getAvatarSuggestionForUser(user) {
+ check(user, Object);
+
+ const avatars = [];
- if (email.verified !== true) {
- avatars.push({
- service: 'gravatar',
- url: Gravatar.imageUrl(email.address, {
- default: '404',
- size: 200,
- secure: true,
- }),
- });
+ for (const avatarProvider of Object.values(avatarProviders)) {
+ const avatar = avatarProvider(user);
+ if (avatar) {
+ if (Array.isArray(avatar)) {
+ avatars.push(...avatar);
+ } else {
+ avatars.push(avatar);
}
}
}
diff --git a/app/lib/server/functions/loadMessageHistory.js b/app/lib/server/functions/loadMessageHistory.js
index 662512844000..eb214fcddcb7 100644
--- a/app/lib/server/functions/loadMessageHistory.js
+++ b/app/lib/server/functions/loadMessageHistory.js
@@ -2,23 +2,12 @@ import { settings } from '../../../settings';
import { Messages } from '../../../models';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
-const hideMessagesOfType = [];
+const hideMessagesOfType = new Set();
-settings.get(/Message_HideType_.+/, function(key, value) {
- const type = key.replace('Message_HideType_', '');
- const types = type === 'mute_unmute' ? ['user-muted', 'user-unmuted'] : [type];
-
- return types.forEach((type) => {
- const index = hideMessagesOfType.indexOf(type);
-
- if (value === true && index === -1) {
- return hideMessagesOfType.push(type);
- }
-
- if (index > -1) {
- return hideMessagesOfType.splice(index, 1);
- }
- });
+settings.get('Hide_System_Messages', function(key, values) {
+ const hiddenTypes = values.reduce((array, value) => [...array, ...value === 'mute_unmute' ? ['user-muted', 'user-unmuted'] : [value]], []);
+ hideMessagesOfType.clear();
+ hiddenTypes.forEach((item) => hideMessagesOfType.add(item));
});
export const loadMessageHistory = function loadMessageHistory({ userId, rid, end, limit = 20, ls }) {
@@ -35,12 +24,7 @@ export const loadMessageHistory = function loadMessageHistory({ userId, rid, end
};
}
- let records;
- if (end != null) {
- records = Messages.findVisibleByRoomIdBeforeTimestampNotContainingTypes(rid, end, hideMessagesOfType, options).fetch();
- } else {
- records = Messages.findVisibleByRoomIdNotContainingTypes(rid, hideMessagesOfType, options).fetch();
- }
+ const records = end != null ? Messages.findVisibleByRoomIdBeforeTimestampNotContainingTypes(rid, end, Array.from(hideMessagesOfType.values()), options).fetch() : Messages.findVisibleByRoomIdNotContainingTypes(rid, Array.from(hideMessagesOfType.values()), options).fetch();
const messages = normalizeMessagesForUser(records, userId);
let unreadNotLoaded = 0;
let firstUnread;
@@ -51,7 +35,7 @@ export const loadMessageHistory = function loadMessageHistory({ userId, rid, end
if ((firstMessage != null ? firstMessage.ts : undefined) > ls) {
delete options.limit;
- const unreadMessages = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(rid, ls, firstMessage.ts, hideMessagesOfType, {
+ const unreadMessages = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(rid, ls, firstMessage.ts, Array.from(hideMessagesOfType.values()), {
limit: 1,
sort: {
ts: 1,
diff --git a/app/lib/server/functions/sendMessage.js b/app/lib/server/functions/sendMessage.js
index 9d2d5c7cfa94..7e0caef79a9d 100644
--- a/app/lib/server/functions/sendMessage.js
+++ b/app/lib/server/functions/sendMessage.js
@@ -8,7 +8,6 @@ import { Apps } from '../../../apps/server';
import { Markdown } from '../../../markdown/server';
import { isURL, isRelativeURL } from '../../../utils/lib/isURL';
import { FileUpload } from '../../../file-upload/server';
-import { Users } from '../../../models/server';
/**
* IMPORTANT
@@ -144,39 +143,17 @@ const validateMessage = (message) => {
}
};
-const validateUserIdentity = (message, _id) => {
- if (!message.alias && !message.avatar) {
- return;
- }
- const forbiddenPropsToChangeWhenUserIsNotABot = ['alias', 'avatar'];
- const user = Users.findOneById(_id, { fields: { roles: 1, name: 1 } });
- /**
- * If the query returns no user, the message has likely
- * been sent by a Livechat Visitor, so we don't need to
- * validate whether the sender is a bot.
- */
- if (!user) {
- return;
- }
- const userIsNotABot = !user.roles.includes('bot');
- const messageContainsAnyForbiddenProp = Object.keys(message).some((key) => forbiddenPropsToChangeWhenUserIsNotABot.includes(key));
- if ((userIsNotABot && messageContainsAnyForbiddenProp) || (settings.get('Message_SetNameToAliasEnabled') && message.alias !== user.name)) {
- throw new Error('You are not authorized to change message properties');
- }
-};
-
export const sendMessage = function(user, message, room, upsert = false) {
if (!user || !message || !room._id) {
return false;
}
- const { _id, username, name } = user;
- validateUserIdentity(message, _id);
validateMessage(message);
if (!message.ts) {
message.ts = new Date();
}
+ const { _id, username, name } = user;
message.u = {
_id,
username,
diff --git a/app/lib/server/functions/setUsername.js b/app/lib/server/functions/setUsername.js
index baed79351cf8..d705d436c084 100644
--- a/app/lib/server/functions/setUsername.js
+++ b/app/lib/server/functions/setUsername.js
@@ -4,10 +4,11 @@ import { Accounts } from 'meteor/accounts-base';
import { FileUpload } from '../../../file-upload';
import { settings } from '../../../settings';
-import { Users, Messages, Subscriptions, Rooms, LivechatDepartmentAgents } from '../../../models';
+import { Users, Messages, Subscriptions, Rooms, LivechatDepartmentAgents, Invites } from '../../../models';
import { hasPermission } from '../../../authorization';
import { RateLimiter } from '../lib';
import { Notifications } from '../../../notifications/server';
+import { addUserToRoom } from './addUserToRoom';
import { checkUsernameAvailability, setUserAvatar, getAvatarSuggestionForUser } from '.';
@@ -87,6 +88,14 @@ export const _setUsername = function(userId, u) {
}
}
+ // If it's the first username and the user has an invite Token, then join the invite room
+ if (!previousUsername && user.inviteToken) {
+ const inviteData = Invites.findOneById(user.inviteToken);
+ if (inviteData && inviteData.rid) {
+ addUserToRoom(inviteData.rid, user);
+ }
+ }
+
Notifications.notifyLogged('Users:NameChanged', {
_id: user._id,
name: user.name,
diff --git a/app/lib/server/methods/saveSettings.js b/app/lib/server/methods/saveSettings.js
index d7cf7bd02ad6..97c12bd85cac 100644
--- a/app/lib/server/methods/saveSettings.js
+++ b/app/lib/server/methods/saveSettings.js
@@ -37,6 +37,9 @@ Meteor.methods({
case 'int':
check(value, Number);
break;
+ case 'multiSelect':
+ check(value, Array);
+ break;
default:
check(value, String);
break;
diff --git a/app/lib/server/methods/sendMessage.js b/app/lib/server/methods/sendMessage.js
index 126aee112969..827ad31ba39c 100644
--- a/app/lib/server/methods/sendMessage.js
+++ b/app/lib/server/methods/sendMessage.js
@@ -15,89 +15,93 @@ import { RateLimiter } from '../lib';
import { canSendMessage } from '../../../authorization/server';
import { SystemLogger } from '../../../logger/server';
-Meteor.methods({
- sendMessage(message) {
- check(message, Object);
+export function executeSendMessage(uid, message) {
+ if (message.tmid && !settings.get('Threads_enabled')) {
+ throw new Meteor.Error('error-not-allowed', 'not-allowed', {
+ method: 'sendMessage',
+ });
+ }
- const uid = Meteor.userId();
- if (!uid) {
- throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+ if (message.ts) {
+ const tsDiff = Math.abs(moment(message.ts).diff());
+ if (tsDiff > 60000) {
+ throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', {
method: 'sendMessage',
+ message_ts: message.ts,
+ server_ts: new Date().getTime(),
});
+ } else if (tsDiff > 10000) {
+ message.ts = new Date();
}
+ } else {
+ message.ts = new Date();
+ }
+
+ if (message.msg) {
+ const adjustedMessage = messageProperties.messageWithoutEmojiShortnames(message.msg);
- if (message.tmid && !settings.get('Threads_enabled')) {
- throw new Meteor.Error('error-not-allowed', 'not-allowed', {
+ if (messageProperties.length(adjustedMessage) > settings.get('Message_MaxAllowedSize')) {
+ throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', {
method: 'sendMessage',
});
}
+ }
- if (message.ts) {
- const tsDiff = Math.abs(moment(message.ts).diff());
- if (tsDiff > 60000) {
- throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', {
- method: 'sendMessage',
- message_ts: message.ts,
- server_ts: new Date().getTime(),
- });
- } else if (tsDiff > 10000) {
- message.ts = new Date();
- }
- } else {
- message.ts = new Date();
- }
+ const user = Users.findOneById(uid, {
+ fields: {
+ username: 1,
+ ...!!settings.get('Message_SetNameToAliasEnabled') && { name: 1 },
+ },
+ });
+ let { rid } = message;
- if (message.msg) {
- const adjustedMessage = messageProperties.messageWithoutEmojiShortnames(message.msg);
+ // do not allow nested threads
+ if (message.tmid) {
+ const parentMessage = Messages.findOneById(message.tmid);
+ message.tmid = parentMessage.tmid || message.tmid;
+ rid = parentMessage.rid;
+ }
- if (messageProperties.length(adjustedMessage) > settings.get('Message_MaxAllowedSize')) {
- throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', {
- method: 'sendMessage',
- });
- }
- }
+ if (!rid) {
+ throw new Error('The \'rid\' property on the message object is missing.');
+ }
- const user = Users.findOneById(uid, {
- fields: {
- username: 1,
- ...!!settings.get('Message_SetNameToAliasEnabled') && { name: 1 },
- },
- });
- let { rid } = message;
-
- // do not allow nested threads
- if (message.tmid) {
- const parentMessage = Messages.findOneById(message.tmid);
- message.tmid = parentMessage.tmid || message.tmid;
- rid = parentMessage.rid;
+ try {
+ const room = canSendMessage(rid, { uid, username: user.username });
+ if (message.alias == null && settings.get('Message_SetNameToAliasEnabled')) {
+ message.alias = user.name;
}
- if (!rid) {
- throw new Error('The \'rid\' property on the message object is missing.');
+ metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736
+ return sendMessage(user, message, room, false);
+ } catch (error) {
+ if (error === 'error-not-allowed') {
+ throw new Meteor.Error('error-not-allowed');
}
- try {
- const room = canSendMessage(rid, { uid, username: user.username });
- if (message.alias == null && settings.get('Message_SetNameToAliasEnabled')) {
- message.alias = user.name;
- }
+ SystemLogger.error('Error sending message:', error);
- metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736
- return sendMessage(user, message, room);
- } catch (error) {
- if (error === 'error-not-allowed') {
- throw new Meteor.Error('error-not-allowed');
- }
+ Notifications.notifyUser(uid, 'message', {
+ _id: Random.id(),
+ rid: message.rid,
+ ts: new Date(),
+ msg: TAPi18n.__(error, {}, user.language),
+ });
+ }
+}
- SystemLogger.error('Error sending message:', error);
+Meteor.methods({
+ sendMessage(message) {
+ check(message, Object);
- Notifications.notifyUser(uid, 'message', {
- _id: Random.id(),
- rid: message.rid,
- ts: new Date(),
- msg: TAPi18n.__(error, {}, user.language),
+ const uid = Meteor.userId();
+ if (!uid) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+ method: 'sendMessage',
});
}
+
+ return executeSendMessage(uid, message);
},
});
// Limit a user, who does not have the "bot" role, to sending 5 msgs/second
diff --git a/app/lib/server/startup/rateLimiter.js b/app/lib/server/startup/rateLimiter.js
index 1286ade7e995..eeda6db04411 100644
--- a/app/lib/server/startup/rateLimiter.js
+++ b/app/lib/server/startup/rateLimiter.js
@@ -23,8 +23,8 @@ DDPRateLimiter.addRule = (matcher, calls, time, callback) => {
const { _increment } = DDPRateLimiter;
DDPRateLimiter._increment = function(input) {
- const session = Meteor.server.sessions[input.connectionId];
- input.broadcastAuth = session && session.connectionHandle && session.connectionHandle.broadcastAuth === true;
+ const session = Meteor.server.sessions.get(input.connectionId);
+ input.broadcastAuth = (session && session.connectionHandle && session.connectionHandle.broadcastAuth) === true;
return _increment.call(DDPRateLimiter, input);
};
@@ -33,8 +33,8 @@ DDPRateLimiter._increment = function(input) {
// being shared among all matchs
RateLimiter.prototype.check = function(input) {
// ==== BEGIN OVERRIDE ====
- const session = Meteor.server.sessions[input.connectionId];
- input.broadcastAuth = session && session.connectionHandle && session.connectionHandle.broadcastAuth === true;
+ const session = Meteor.server.sessions.get(input.connectionId);
+ input.broadcastAuth = (session && session.connectionHandle && session.connectionHandle.broadcastAuth) === true;
// ==== END OVERRIDE ====
const self = this;
diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.js
index a269ec0edadf..0014786f813c 100644
--- a/app/lib/server/startup/settings.js
+++ b/app/lib/server/startup/settings.js
@@ -1093,28 +1093,56 @@ settings.addGroup('Message', function() {
type: 'int',
public: true,
});
- this.add('Message_HideType_uj', false, {
- type: 'boolean',
- public: true,
- });
- this.add('Message_HideType_ul', false, {
- type: 'boolean',
- public: true,
- });
- this.add('Message_HideType_ru', false, {
- type: 'boolean',
- public: true,
- });
- this.add('Message_HideType_au', false, {
- type: 'boolean',
- public: true,
- });
- this.add('Message_HideType_mute_unmute', false, {
- type: 'boolean',
+
+ this.add('Hide_System_Messages', [], {
+ type: 'multiSelect',
public: true,
+ values: [
+ {
+ key: 'uj',
+ i18nLabel: 'Message_HideType_uj',
+ }, {
+ key: 'ul',
+ i18nLabel: 'Message_HideType_ul',
+ }, {
+ key: 'ru',
+ i18nLabel: 'Message_HideType_ru',
+ }, {
+ key: 'au',
+ i18nLabel: 'Message_HideType_au',
+ }, {
+ key: 'mute_unmute',
+ i18nLabel: 'Message_HideType_mute_unmute',
+ }, {
+ key: 'r',
+ i18nLabel: 'Message_HideType_r',
+ }, {
+ key: 'ut',
+ i18nLabel: 'Message_HideType_ut',
+ }, {
+ key: 'wm',
+ i18nLabel: 'Message_HideType_wm',
+ }, {
+ key: 'rm',
+ i18nLabel: 'Message_HideType_rm',
+ }, {
+ key: 'subscription_role_added',
+ i18nLabel: 'Message_HideType_subscription_role_added',
+ }, {
+ key: 'subscription_role_removed',
+ i18nLabel: 'Message_HideType_subscription_role_removed',
+ }, {
+ key: 'room_archived',
+ i18nLabel: 'Message_HideType_room_archived',
+ }, {
+ key: 'room_unarchived',
+ i18nLabel: 'Message_HideType_room_unarchived',
+ },
+ ],
});
+
this.add('Message_ErasureType', 'Delete', {
type: 'select',
public: true,
diff --git a/app/livechat/client/views/app/livechatCustomFieldForm.html b/app/livechat/client/views/app/livechatCustomFieldForm.html
index e90f9c4ab973..d879ce74218c 100644
--- a/app/livechat/client/views/app/livechatCustomFieldForm.html
+++ b/app/livechat/client/views/app/livechatCustomFieldForm.html
@@ -34,6 +34,12 @@