Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-coats-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes an issue with encrypted room's message previews on the sidebar not always being properly decrypted
6 changes: 6 additions & 0 deletions .changeset/grumpy-suns-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/http-router': patch
'@rocket.chat/meteor': patch
---

Fixes incoming webhook integrations not receiving parsed JSON from x-www-form-urlencoded payload field.
5 changes: 1 addition & 4 deletions apps/meteor/app/api/server/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ export class RocketChatAPIRouter<
return async (c: HonoContext): Promise<ResponseSchema<TypedOptions>> => {
const { req, res } = c;
const queryParams = this.parseQueryParams(req);
const bodyParams = await this.parseBodyParams<{ bodyParamsOverride: Record<string, any> }>({
request: req,
extra: { bodyParamsOverride: c.var['bodyParams-override'] || {} },
});
const bodyParams = c.get('bodyParams-override') ?? (await this.parseBodyParams({ request: req }));

const request = req.raw.clone();

Expand Down
24 changes: 9 additions & 15 deletions apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Cloud, Serialized } from '@rocket.chat/core-typings';
import { Cloud } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';
Expand All @@ -11,15 +11,7 @@ import { SystemLogger } from '../../../../server/lib/logger/system';
import { settings } from '../../../settings/server';
import { LICENSE_VERSION } from '../license';

const workspaceLicensePayloadSchema = z.object({
version: z.number(),
address: z.string(),
license: z.string(),
updatedAt: z.string().datetime(),
expireAt: z.string().datetime(),
});

const fetchCloudWorkspaceLicensePayload = async ({ token }: { token: string }): Promise<Serialized<Cloud.WorkspaceLicensePayload>> => {
const fetchCloudWorkspaceLicensePayload = async ({ token }: { token: string }): Promise<Cloud.WorkspaceLicensePayload> => {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/license`, {
headers: {
Expand All @@ -41,13 +33,15 @@ const fetchCloudWorkspaceLicensePayload = async ({ token }: { token: string }):

const payload = await response.json();

const assertWorkspaceLicensePayload = workspaceLicensePayloadSchema.safeParse(payload);
const result = Cloud.WorkspaceLicensePayloadSchema.safeParse(payload);

if (!assertWorkspaceLicensePayload.success) {
SystemLogger.error({ msg: 'workspaceLicensePayloadSchema failed type validation', errors: assertWorkspaceLicensePayload.error.issues });
if (!result.success) {
throw new CloudWorkspaceLicenseError('failed type validation', {
cause: z.prettifyError(result.error),
});
}

return payload;
return result.data;
};

export async function getWorkspaceLicense() {
Expand All @@ -66,7 +60,7 @@ export async function getWorkspaceLicense() {

const payload = await fetchCloudWorkspaceLicensePayload({ token });

if (currentLicense.value && Date.parse(payload.updatedAt) <= currentLicense._updatedAt.getTime()) {
if (currentLicense.value && payload.updatedAt.getTime() <= currentLicense._updatedAt.getTime()) {
return;
}
await callbacks.run('workspaceLicenseChanged', payload.license);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Cloud, type Serialized } from '@rocket.chat/core-typings';
import { Cloud } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';

Expand All @@ -11,45 +11,13 @@ import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '..
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';
import { handleAnnouncementsOnWorkspaceSync, handleNpsOnWorkspaceSync } from './handleCommsSync';

const workspaceCommPayloadSchema = z.object({
workspaceId: z.string().optional(),
publicKey: z.string().optional(),
nps: z
.object({
id: z.string(),
startAt: z.string().datetime(),
expireAt: z.string().datetime(),
})
.optional(),
announcements: z.object({
create: z.array(
z.object({
_id: z.string(),
_updatedAt: z.string().datetime().optional(),
selector: z.object({
roles: z.array(z.string()),
}),
platform: z.array(z.enum(['web', 'mobile'])),
expireAt: z.string().datetime(),
startAt: z.string().datetime(),
createdBy: z.enum(['cloud', 'system']),
createdAt: z.string().datetime(),
dictionary: z.record(z.string(), z.record(z.string(), z.string())).optional(),
view: z.unknown(),
surface: z.enum(['banner', 'modal']),
}),
),
delete: z.array(z.string()).optional(),
}),
});

const fetchCloudAnnouncementsSync = async ({
token,
data,
}: {
token: string;
data: Cloud.WorkspaceSyncRequestPayload;
}): Promise<Serialized<Cloud.WorkspaceCommsResponsePayload>> => {
}): Promise<Cloud.WorkspaceCommsResponsePayload> => {
const cloudUrl = settings.get<string>('Cloud_Url');
const response = await fetch(`${cloudUrl}/api/v3/comms/workspace`, {
method: 'POST',
Expand All @@ -70,13 +38,15 @@ const fetchCloudAnnouncementsSync = async ({

const payload = await response.json();

const assertWorkspaceCommPayload = workspaceCommPayloadSchema.safeParse(payload);
const result = Cloud.WorkspaceCommsResponsePayloadSchema.safeParse(payload);

if (!assertWorkspaceCommPayload.success) {
SystemLogger.error({ msg: 'workspaceCommPayloadSchema failed type validation', errors: assertWorkspaceCommPayload.error.issues });
if (!result.success) {
throw new CloudWorkspaceConnectionError('failed type validation', {
cause: z.prettifyError(result.error),
});
}

return payload;
return result.data;
};

export async function announcementSync() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import type { Cloud, Serialized } from '@rocket.chat/core-typings';
import { Cloud } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';

import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError';
import { SystemLogger } from '../../../../../server/lib/logger/system';
import { settings } from '../../../../settings/server';

const workspaceSyncPayloadSchema = z.object({
workspaceId: z.string(),
publicKey: z.string().optional(),
license: z.string(),
});

export async function fetchWorkspaceSyncPayload({
token,
data,
}: {
token: string;
data: Cloud.WorkspaceSyncRequestPayload;
}): Promise<Serialized<Cloud.WorkspaceSyncResponse>> {
}): Promise<Cloud.WorkspaceSyncResponse> {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/sync`, {
method: 'POST',
Expand All @@ -35,11 +28,13 @@ export async function fetchWorkspaceSyncPayload({

const payload = await response.json();

const assertWorkspaceSyncPayload = workspaceSyncPayloadSchema.safeParse(payload);
const result = Cloud.WorkspaceSyncResponseSchema.safeParse(payload);

if (!assertWorkspaceSyncPayload.success) {
SystemLogger.error({ msg: 'workspaceCommPayloadSchema failed type validation', errors: assertWorkspaceSyncPayload.error.issues });
if (!result.success) {
throw new CloudWorkspaceConnectionError('failed type validation', {
cause: z.prettifyError(result.error),
});
}

return payload;
return result.data;
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { NPS, Banner } from '@rocket.chat/core-services';
import type { Cloud, Serialized } from '@rocket.chat/core-typings';
import type { Cloud, IBanner } from '@rocket.chat/core-typings';

import { getAndCreateNpsSurvey } from '../../../../../server/services/nps/getAndCreateNpsSurvey';

export const handleNpsOnWorkspaceSync = async (nps: Exclude<Serialized<Cloud.WorkspaceSyncPayload>['nps'], undefined>) => {
const { id: npsId, expireAt } = nps;

const startAt = new Date(nps.startAt);
export const handleNpsOnWorkspaceSync = async (nps: Cloud.NpsSurveyAnnouncement) => {
const { id: npsId, startAt, expireAt } = nps;

await NPS.create({
npsId,
startAt,
expireAt: new Date(expireAt),
expireAt,
createdBy: {
_id: 'rocket.cat',
username: 'rocket.cat',
Expand All @@ -25,44 +23,24 @@ export const handleNpsOnWorkspaceSync = async (nps: Exclude<Serialized<Cloud.Wor
}
};

export const handleBannerOnWorkspaceSync = async (banners: Exclude<Serialized<Cloud.WorkspaceSyncPayload>['banners'], undefined>) => {
export const handleBannerOnWorkspaceSync = async (banners: IBanner[]) => {
for await (const banner of banners) {
const { createdAt, expireAt, startAt, inactivedAt, _updatedAt, ...rest } = banner;

await Banner.create({
...rest,
createdAt: new Date(createdAt),
expireAt: new Date(expireAt),
startAt: new Date(startAt),
...(inactivedAt && { inactivedAt: new Date(inactivedAt) }),
});
await Banner.create(banner);
}
};

const deserializeAnnouncement = (announcement: Serialized<Cloud.IAnnouncement>): Cloud.IAnnouncement => {
const { inactivedAt, _updatedAt, expireAt, startAt, createdAt } = announcement;

return {
...announcement,
_updatedAt: new Date(_updatedAt),
expireAt: new Date(expireAt),
startAt: new Date(startAt),
createdAt: new Date(createdAt),
inactivedAt: inactivedAt ? new Date(inactivedAt) : undefined,
};
};

export const handleAnnouncementsOnWorkspaceSync = async (
announcements: Exclude<Serialized<Cloud.WorkspaceCommsResponsePayload>['announcements'], undefined>,
) => {
export const handleAnnouncementsOnWorkspaceSync = async (announcements: {
create: Cloud.Announcement[];
delete?: Cloud.Announcement['_id'][];
}) => {
const { create, delete: deleteIds } = announcements;

if (deleteIds) {
await Promise.all(deleteIds.map((bannerId) => Banner.disable(bannerId)));
await Promise.all(deleteIds.map((announcementId) => Banner.disable(announcementId)));
}

await Promise.all(
create.map(deserializeAnnouncement).map((announcement) => {
create.map((announcement) => {
const { view, selector } = announcement;

return Banner.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Cloud, type Serialized } from '@rocket.chat/core-typings';
import { Cloud } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';
Expand All @@ -14,80 +14,14 @@ import { getWorkspaceLicense } from '../getWorkspaceLicense';
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';
import { handleBannerOnWorkspaceSync, handleNpsOnWorkspaceSync } from './handleCommsSync';

const workspaceClientPayloadSchema = z.object({
workspaceId: z.string(),
publicKey: z.string().optional(),
trial: z
.object({
trialing: z.boolean(),
trialID: z.string(),
endDate: z.string().datetime(),
marketing: z.object({
utmContent: z.string(),
utmMedium: z.string(),
utmSource: z.string(),
utmCampaign: z.string(),
}),
DowngradesToPlan: z.object({
id: z.string(),
}),
trialRequested: z.boolean(),
})
.optional(),
nps: z.object({
id: z.string(),
startAt: z.string().datetime(),
expireAt: z.string().datetime(),
}),
banners: z.array(
z.object({
_id: z.string(),
_updatedAt: z.string().datetime(),
platform: z.array(z.string()),
expireAt: z.string().datetime(),
startAt: z.string().datetime(),
roles: z.array(z.string()).optional(),
createdBy: z.object({
_id: z.string(),
username: z.string().optional(),
}),
createdAt: z.string().datetime(),
view: z.any(),
active: z.boolean().optional(),
inactivedAt: z.string().datetime().optional(),
snapshot: z.string().optional(),
}),
),
announcements: z.object({
create: z.array(
z.object({
_id: z.string(),
_updatedAt: z.string().datetime(),
selector: z.object({
roles: z.array(z.string()),
}),
platform: z.array(z.enum(['web', 'mobile'])),
expireAt: z.string().datetime(),
startAt: z.string().datetime(),
createdBy: z.enum(['cloud', 'system']),
createdAt: z.string().datetime(),
dictionary: z.record(z.string(), z.record(z.string(), z.string())),
view: z.any(),
surface: z.enum(['banner', 'modal']),
}),
),
delete: z.array(z.string()),
}),
});

/** @deprecated */
const fetchWorkspaceClientPayload = async ({
token,
workspaceRegistrationData,
}: {
token: string;
workspaceRegistrationData: WorkspaceRegistrationData<undefined>;
}): Promise<Serialized<Cloud.WorkspaceSyncPayload> | undefined> => {
}): Promise<Cloud.WorkspaceSyncPayload | undefined> => {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/client`, {
method: 'POST',
Expand All @@ -113,17 +47,19 @@ const fetchWorkspaceClientPayload = async ({
return undefined;
}

const assertWorkspaceClientPayload = workspaceClientPayloadSchema.safeParse(payload);
const result = Cloud.WorkspaceSyncPayloadSchema.safeParse(payload);

if (!assertWorkspaceClientPayload.success) {
throw new CloudWorkspaceConnectionError('Invalid response from Rocket.Chat Cloud');
if (!result.success) {
throw new CloudWorkspaceConnectionError('Invalid response from Rocket.Chat Cloud', {
cause: z.prettifyError(result.error),
});
}

return payload;
return result.data;
};

/** @deprecated */
const consumeWorkspaceSyncPayload = async (result: Serialized<Cloud.WorkspaceSyncPayload>) => {
const consumeWorkspaceSyncPayload = async (result: Cloud.WorkspaceSyncPayload) => {
if (result.publicKey) {
(await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey)).modifiedCount &&
void notifyOnSettingChangedById('Cloud_Workspace_PublicKey');
Expand Down
Loading
Loading