Skip to content

Commit

Permalink
Merge branch 'develop' into feat/e2e-key-reset-modal
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored Oct 18, 2024
2 parents 45d9b8e + 4aa731d commit 502616b
Show file tree
Hide file tree
Showing 248 changed files with 2,886 additions and 2,699 deletions.
8 changes: 8 additions & 0 deletions .changeset/fifty-mails-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@rocket.chat/web-ui-registration': patch
"@rocket.chat/meteor": major
---

Login services button was not respecting the button color and text color settings. Implemented a fix to respect these settings and change the button colors accordingly.

Added a warning on all settings which allow admins to change OAuth button colors, so that they can be alerted about WCAG (Web Content Accessibility Guidelines) compliance.
9 changes: 9 additions & 0 deletions .changeset/fuzzy-pans-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/apps": minor
"@rocket.chat/core-typings": minor
"@rocket.chat/model-typings": minor
---

Adds a `source` field to livechat visitors, which stores the channel (eg API, widget, SMS, email-inbox, app) that's been used by the visitor to send messages.
Uses the new `source` field to assure each visitor is linked to a single source, so that each connection through a distinct channel creates a new visitor.
9 changes: 9 additions & 0 deletions .changeset/plenty-hairs-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@rocket.chat/meteor": major
"@rocket.chat/core-typings": major
"@rocket.chat/model-typings": major
"@rocket.chat/models": major
---

Adds a new collection to store all the workspace cloud tokens to defer the race condition management to MongoDB instead of having to handle it within the settings cache.
Removes the Cloud_Workspace_Access_Token & Cloud_Workspace_Access_Token_Expires_At settings since they are not going to be used anymore.
5 changes: 5 additions & 0 deletions .changeset/real-plants-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes E2EE not rendering new navigation feature preview
7 changes: 7 additions & 0 deletions .changeset/shiny-falcons-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/core-typings': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds a new setting to allow mapping LDAP attributes to the user's extension
6 changes: 6 additions & 0 deletions .changeset/tender-turkeys-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": major
"@rocket.chat/i18n": patch
---

Updates End-to-end settings translations and removes beta wording
7 changes: 7 additions & 0 deletions .changeset/two-emus-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/core-typings': major
'@rocket.chat/rest-typings': major
'@rocket.chat/meteor': major
---

Changes the payload of the startImport endpoint to decrease the amount of data it requires
13 changes: 13 additions & 0 deletions .yarn/patches/mongodb-npm-4.17.2-40d1286d70.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/mongodb.d.ts b/mongodb.d.ts
index 9696a0d0104095e8e4dfa4a4fe05fe81fd8b50c7..f5aa6e76fe6f2e6a55e6e2a720be15b7a6f98107 100644
--- a/mongodb.d.ts
+++ b/mongodb.d.ts
@@ -5535,7 +5535,7 @@ export declare interface MonitorOptions extends Omit<ConnectionOptions, 'id' | '
* depth any helpers that make use of NestedPaths should devolve to not asserting any
* type safety on the input.
*/
-export declare type NestedPaths<Type, Depth extends number[]> = Depth['length'] extends 8 ? [] : Type extends string | number | boolean | Date | RegExp | Buffer | Uint8Array | ((...args: any[]) => any) | {
+export declare type NestedPaths<Type, Depth extends number[]> = Depth['length'] extends 3 ? [] : Type extends string | number | boolean | Date | RegExp | Buffer | Uint8Array | ((...args: any[]) => any) | {
_bsontype: string;
} ? [] : Type extends ReadonlyArray<infer ArrayType> ? [] | [number, ...NestedPaths<ArrayType, [...Depth, 1]>] : Type extends Map<string, any> ? [string] : Type extends object ? {
[Key in Extract<keyof Type, string>]: Type[Key] extends Type ? [Key] : Type extends Type[Key] ? [Key] : Type[Key] extends ReadonlyArray<infer ArrayType> ? Type extends ArrayType ? [Key] : ArrayType extends Type ? [Key] : [
2 changes: 2 additions & 0 deletions apps/meteor/app/api/server/helpers/parseJsonQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export async function parseJsonQuery(api: PartialThis): Promise<{
'/api/v1/integrations.list',
'/api/v1/custom-user-status.list',
'/api/v1/custom-sounds.list',
'/api/v1/channels.list',
'/api/v1/channels.online',
].includes(route);

const isUnsafeQueryParamsAllowed = process.env.ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS?.toUpperCase() === 'TRUE';
Expand Down
25 changes: 20 additions & 5 deletions apps/meteor/app/api/server/v1/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import {
isChannelsConvertToTeamProps,
isChannelsSetReadOnlyProps,
isChannelsDeleteProps,
isChannelsListProps,
isChannelsFilesListProps,
isChannelsOnlineProps,
} from '@rocket.chat/rest-typings';
import { Meteor } from 'meteor/meteor';

Expand Down Expand Up @@ -929,14 +931,21 @@ API.v1.addRoute(
permissionsRequired: {
GET: { permissions: ['view-c-room', 'view-joined-room'], operation: 'hasAny' },
},
validateParams: isChannelsListProps,
},
{
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields, query } = await this.parseJsonQuery();
const hasPermissionToSeeAllPublicChannels = await hasPermissionAsync(this.userId, 'view-c-room');

const ourQuery: Record<string, any> = { ...query, t: 'c' };
const { _id } = this.queryParams;

const ourQuery = {
...query,
...(_id ? { _id } : {}),
t: 'c',
};

if (!hasPermissionToSeeAllPublicChannels) {
const roomIds = (
Expand Down Expand Up @@ -1075,17 +1084,23 @@ API.v1.addRoute(

API.v1.addRoute(
'channels.online',
{ authRequired: true },
{ authRequired: true, validateParams: isChannelsOnlineProps },
{
async get() {
const { query } = await this.parseJsonQuery();
if (!query || Object.keys(query).length === 0) {
const { _id } = this.queryParams;

if ((!query || Object.keys(query).length === 0) && !_id) {
return API.v1.failure('Invalid query');
}

const ourQuery = Object.assign({}, query, { t: 'c' });
const filter = {
...query,
...(_id ? { _id } : {}),
t: 'c',
};

const room = await Rooms.findOne(ourQuery as Record<string, any>);
const room = await Rooms.findOne(filter as Record<string, any>);
if (!room) {
return API.v1.failure('Channel does not exists');
}
Expand Down
5 changes: 2 additions & 3 deletions apps/meteor/app/api/server/v1/misc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import crypto from 'crypto';

import { isOAuthUser, type IUser } from '@rocket.chat/core-typings';
import { Settings, Users } from '@rocket.chat/models';
import { Settings, Users, WorkspaceCredentials } from '@rocket.chat/models';
import {
isShieldSvgProps,
isSpotlightProps,
Expand Down Expand Up @@ -664,6 +664,7 @@ API.v1.addRoute(
const settingsIds: string[] = [];

if (this.bodyParams.setDeploymentAs === 'new-workspace') {
await WorkspaceCredentials.unsetCredentialByScope();
settingsIds.push(
'Cloud_Service_Agree_PrivacyTerms',
'Cloud_Workspace_Id',
Expand All @@ -675,9 +676,7 @@ API.v1.addRoute(
'Cloud_Workspace_PublicKey',
'Cloud_Workspace_License',
'Cloud_Workspace_Had_Trial',
'Cloud_Workspace_Access_Token',
'uniqueID',
'Cloud_Workspace_Access_Token_Expires_At',
);
}

Expand Down
37 changes: 32 additions & 5 deletions apps/meteor/app/apps/server/bridges/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ export class AppLivechatBridge extends LivechatBridge {
const appMessage = (await this.orch.getConverters().get('messages').convertAppMessage(message)) as IMessage | undefined;
const livechatMessage = appMessage as ILivechatMessage | undefined;

if (guest) {
const visitorSource = {
type: OmnichannelSourceType.APP,
id: appId,
alias: this.orch.getManager()?.getOneById(appId)?.getNameSlug(),
};
const fullVisitor = await LivechatVisitors.findOneEnabledByIdAndSource({
_id: guest._id,
sourceFilter: { 'source.type': visitorSource.type, 'source.id': visitorSource.id, 'source.alias': visitorSource.alias },
});
if (!fullVisitor?.source) {
await LivechatVisitors.setSourceById(guest._id, visitorSource);
}
}

const msg = await LivechatTyped.sendMessage({
guest: guest as ILivechatVisitor,
message: livechatMessage as ILivechatMessage,
Expand Down Expand Up @@ -286,7 +301,7 @@ export class AppLivechatBridge extends LivechatBridge {
}

return Promise.all(
(await LivechatVisitors.findEnabled(query).toArray()).map(
(await LivechatVisitors.findEnabledBySource({ 'source.type': OmnichannelSourceType.APP, 'source.id': appId }, query).toArray()).map(
async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor),
),
);
Expand All @@ -295,7 +310,7 @@ export class AppLivechatBridge extends LivechatBridge {
protected async findVisitorById(id: string, appId: string): Promise<IVisitor | undefined> {
this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`);

return this.orch.getConverters()?.get('visitors').convertById(id);
return this.orch.getConverters()?.get('visitors').convertByIdAndSource(id, appId);
}

protected async findVisitorByEmail(email: string, appId: string): Promise<IVisitor | undefined> {
Expand All @@ -304,7 +319,9 @@ export class AppLivechatBridge extends LivechatBridge {
return this.orch
.getConverters()
?.get('visitors')
.convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email));
.convertVisitor(
await LivechatVisitors.findOneGuestByEmailAddressAndSource(email, { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }),
);
}

protected async findVisitorByToken(token: string, appId: string): Promise<IVisitor | undefined> {
Expand All @@ -313,7 +330,12 @@ export class AppLivechatBridge extends LivechatBridge {
return this.orch
.getConverters()
?.get('visitors')
.convertVisitor(await LivechatVisitors.getVisitorByToken(token, {}));
.convertVisitor(
await LivechatVisitors.getVisitorByTokenAndSource({
token,
sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId },
}),
);
}

protected async findVisitorByPhoneNumber(phoneNumber: string, appId: string): Promise<IVisitor | undefined> {
Expand All @@ -322,7 +344,12 @@ export class AppLivechatBridge extends LivechatBridge {
return this.orch
.getConverters()
?.get('visitors')
.convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber));
.convertVisitor(
await LivechatVisitors.findOneVisitorByPhoneAndSource(phoneNumber, {
'source.type': OmnichannelSourceType.APP,
'source.id': appId,
}),
);
}

protected async findDepartmentByIdOrName(value: string, appId: string): Promise<IDepartment | undefined> {
Expand Down
21 changes: 21 additions & 0 deletions apps/meteor/app/apps/server/converters/visitors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OmnichannelSourceType } from '@rocket.chat/core-typings';
import { LivechatVisitors } from '@rocket.chat/models';

import { transformMappedData } from './transformMappedData';
Expand All @@ -14,12 +15,30 @@ export class AppVisitorsConverter {
return this.convertVisitor(visitor);
}

async convertByIdAndSource(id, appId) {
const visitor = await LivechatVisitors.findOneEnabledByIdAndSource({
_id: id,
sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId },
});

return this.convertVisitor(visitor);
}

async convertByToken(token) {
const visitor = await LivechatVisitors.getVisitorByToken(token);

return this.convertVisitor(visitor);
}

async convertByTokenAndSource(token, appId) {
const visitor = await LivechatVisitors.getVisitorByTokenAndSource({
token,
sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId },
});

return this.convertVisitor(visitor);
}

async convertVisitor(visitor) {
if (!visitor) {
return undefined;
Expand All @@ -37,6 +56,7 @@ export class AppVisitorsConverter {
livechatData: 'livechatData',
status: 'status',
contactId: 'contactId',
source: 'source',
};

return transformMappedData(visitor, map);
Expand All @@ -56,6 +76,7 @@ export class AppVisitorsConverter {
livechatData: visitor.livechatData,
status: visitor.status || 'online',
contactId: visitor.contactId,
source: visitor.source,
...(visitor.visitorEmails && { visitorEmails: visitor.visitorEmails }),
...(visitor.department && { department: visitor.department }),
};
Expand Down
43 changes: 23 additions & 20 deletions apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { Settings } from '@rocket.chat/models';
import type { IWorkspaceCredentials } from '@rocket.chat/core-typings';
import { WorkspaceCredentials } from '@rocket.chat/models';

import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server';
import { getWorkspaceAccessTokenWithScope } from './getWorkspaceAccessTokenWithScope';
import { retrieveRegistrationStatus } from './retrieveRegistrationStatus';

const hasWorkspaceAccessTokenExpired = (credentials: IWorkspaceCredentials): boolean => new Date() >= credentials.expirationDate;

/**
* @param {boolean} forceNew
* @param {string} scope
* @param {boolean} save
* @returns string
* Returns the access token for the workspace, if it is expired or forceNew is true, it will get a new one
* and save it to the database, therefore if this function does not throw an error, it will always return a valid token.
*
* @param {boolean} forceNew - If true, it will get a new token even if the current one is not expired
* @param {string} scope - The scope of the token to get
* @param {boolean} save - If true, it will save the new token to the database
* @throws {CloudWorkspaceAccessTokenError} If the workspace is not registered (no credentials in the database)
*
* @returns string - A valid access token for the workspace
*/
export async function getWorkspaceAccessToken(forceNew = false, scope = '', save = true, throwOnError = false): Promise<string> {
const { workspaceRegistered } = await retrieveRegistrationStatus();
Expand All @@ -18,26 +24,23 @@ export async function getWorkspaceAccessToken(forceNew = false, scope = '', save
return '';
}

const expires = await Settings.findOneById('Cloud_Workspace_Access_Token_Expires_At');

if (expires === null) {
throw new Error('Cloud_Workspace_Access_Token_Expires_At is not set');
const workspaceCredentials = await WorkspaceCredentials.getCredentialByScope(scope);
if (!workspaceCredentials) {
throw new CloudWorkspaceAccessTokenError();
}

const now = new Date();

if (expires.value && now < expires.value && !forceNew) {
return settings.get<string>('Cloud_Workspace_Access_Token');
if (!hasWorkspaceAccessTokenExpired(workspaceCredentials) && !forceNew) {
return workspaceCredentials.accessToken;
}

const accessToken = await getWorkspaceAccessTokenWithScope(scope, throwOnError);

if (save) {
(await Settings.updateValueById('Cloud_Workspace_Access_Token', accessToken.token)).modifiedCount &&
void notifyOnSettingChangedById('Cloud_Workspace_Access_Token');

(await Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', accessToken.expiresAt)).modifiedCount &&
void notifyOnSettingChangedById('Cloud_Workspace_Access_Token_Expires_At');
await WorkspaceCredentials.updateCredentialByScope({
scope,
accessToken: accessToken.token,
expirationDate: accessToken.expiresAt,
});
}

return accessToken.token;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Settings } from '@rocket.chat/models';
import { Settings, WorkspaceCredentials } from '@rocket.chat/models';

import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener';
import { retrieveRegistrationStatus } from './retrieveRegistrationStatus';
Expand All @@ -9,6 +9,8 @@ export async function removeWorkspaceRegistrationInfo() {
return true;
}

await WorkspaceCredentials.removeAllCredentials();

const settingsIds = [
'Cloud_Workspace_Id',
'Cloud_Workspace_Name',
Expand Down
Loading

0 comments on commit 502616b

Please sign in to comment.