snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import fs from 'node:fs';
import type { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { Import } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { Meteor } from 'meteor/meteor';
import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { Importers } from '../../lib/import';
import { RocketChatImportFileInstance } from '../../lib/import/startup/store';
import { SystemLogger } from '../../lib/logger/system';
import { settings } from '../../settings';
async function getHttpFileStream(fileUrl: string): Promise<Readable> {
const response = await fetch(fileUrl, {
ignoreSsrfValidation: false,
allowList: settings.get<string>('SSRF_Allowlist'),
});
const body = response.body as Readable;
if (!response.ok) {
body.resume();
throw new Error(`Unexpected response status ${response.status}`);
}
return body;
}
function copyLocalFile(filePath: fs.PathLike, writeStream: fs.WriteStream): void {
const readStream = fs.createReadStream(filePath);
readStream.pipe(writeStream);
}
export const executeDownloadPublicImportFile = async (userId: IUser['_id'], fileUrl: string, importerKey: string): Promise<void> => {
const importer = Importers.get(importerKey);
const isUrl = fileUrl.startsWith('http');
if (!importer) {
throw new Meteor.Error(
'error-importer-not-defined',
`The importer (${importerKey}) has no import class defined.`,
'downloadImportFile',
);
}
// Check if it's a valid url or path before creating a new import record
if (!isUrl && !fs.existsSync(fileUrl)) {
throw new Meteor.Error('error-import-file-missing', fileUrl, 'downloadPublicImportFile');
}
const operation = await Import.newOperation(userId, importer.name, importer.key);
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
const oldFileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1).split('?')[0];
const date = new Date();
const dateStr = `${date.getUTCFullYear()}${date.getUTCMonth()}${date.getUTCDate()}${date.getUTCHours()}${date.getUTCMinutes()}${date.getUTCSeconds()}`;
const newFileName = `${dateStr}_${userId}_${oldFileName}`;
// Store the file name on the imports collection
await instance.startFileUpload(newFileName);
await instance.updateProgress(ProgressStep.DOWNLOADING_FILE);
const writeStream = RocketChatImportFileInstance.createWriteStream(newFileName);
let errorProgressUpdate: Promise<unknown> | undefined;
const markImportAsFailed = (): Promise<unknown> => {
errorProgressUpdate ??= instance.updateProgress(ProgressStep.ERROR).catch((error) => {
SystemLogger.error({ msg: 'Failed to update import progress to ERROR', err: error });
});
return errorProgressUpdate;
};
writeStream.on('error', () => {
void markImportAsFailed();
});
let readStream: Readable | undefined;
if (isUrl) {
try {
readStream = await getHttpFileStream(fileUrl);
} catch (error) {
writeStream.destroy();
await markImportAsFailed();
throw error;
}
}
writeStream.on('finish', () => {
void instance.updateProgress(ProgressStep.FILE_LOADED);
});
if (readStream) {
void pipeline(readStream, writeStream).catch(() => markImportAsFailed());
return;
}
// If the url is actually a folder path on the current machine, skip moving it to the file store
if (fs.statSync(fileUrl).isDirectory()) {
await instance.updateRecord({ file: fileUrl });
await instance.updateProgress(ProgressStep.FILE_LOADED);
return;
}
copyLocalFile(fileUrl, writeStream);
};
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
downloadPublicImportFile(fileUrl: string, importerKey: string): void;
}
}
Meteor.methods<ServerMethods>({
async downloadPublicImportFile(fileUrl: string, importerKey: string) {
methodDeprecationLogger.method('downloadPublicImportFile', '9.0.0', '/v1/downloadPublicImportFile');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'downloadPublicImportFile');
}
if (!(await hasPermissionAsync(userId, 'run-import'))) {
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'downloadPublicImportFile');
}
await executeDownloadPublicImportFile(userId, fileUrl, importerKey);
},
});

View File

@@ -0,0 +1,88 @@
import fs from 'node:fs';
import path from 'node:path';
import type { IImportProgress, IImporterSelection } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Imports } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { Importers } from '../../lib/import';
import { RocketChatImportFileInstance } from '../../lib/import/startup/store';
export const executeGetImportFileData = async (): Promise<IImporterSelection | { waiting: true }> => {
const operation = await Imports.findLastImport();
if (!operation) {
throw new Meteor.Error('error-operation-not-found', 'Import Operation Not Found', 'getImportFileData');
}
const { importerKey } = operation;
const importer = Importers.get(importerKey);
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'getImportFileData');
}
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
const waitingSteps: IImportProgress['step'][] = [
ProgressStep.DOWNLOADING_FILE,
ProgressStep.PREPARING_CHANNELS,
ProgressStep.PREPARING_MESSAGES,
ProgressStep.PREPARING_USERS,
ProgressStep.PREPARING_CONTACTS,
ProgressStep.PREPARING_STARTED,
];
if (waitingSteps.indexOf(instance.progress.step) >= 0) {
if (instance.importRecord?.valid) {
return { waiting: true };
}
throw new Meteor.Error('error-import-operation-invalid', 'Invalid Import Operation', 'getImportFileData');
}
const readySteps: IImportProgress['step'][] = [
ProgressStep.USER_SELECTION,
ProgressStep.DONE,
ProgressStep.CANCELLED,
ProgressStep.ERROR,
];
if (readySteps.indexOf(instance.progress.step) >= 0) {
return instance.buildSelection();
}
const fileName = instance.importRecord.file;
if (fileName) {
const fullFilePath = fs.existsSync(fileName) ? fileName : path.join(RocketChatImportFileInstance.absolutePath, fileName);
await instance.prepareUsingLocalFile(fullFilePath);
}
return instance.buildSelection();
};
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
getImportFileData(): IImporterSelection | { waiting: true };
}
}
Meteor.methods<ServerMethods>({
async getImportFileData() {
methodDeprecationLogger.method('getImportFileData', '9.0.0', '/v1/getImportFileData');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'getImportFileData');
}
if (!(await hasPermissionAsync(userId, 'run-import'))) {
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'getImportFileData');
}
return executeGetImportFileData();
},
});

View File

@@ -0,0 +1,48 @@
import type { IImportProgress } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Imports } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { Importers } from '../../lib/import';
export const executeGetImportProgress = async (): Promise<IImportProgress> => {
const operation = await Imports.findLastImport();
if (!operation) {
throw new Meteor.Error('error-operation-not-found', 'Import Operation Not Found', 'getImportProgress');
}
const { importerKey } = operation;
const importer = Importers.get(importerKey);
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'getImportProgress');
}
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
return instance.getProgress();
};
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
getImportProgress(): IImportProgress;
}
}
Meteor.methods<ServerMethods>({
async getImportProgress() {
methodDeprecationLogger.method('getImportProgress', '9.0.0', '/v1/getImportProgress');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'getImportProgress');
}
if (!(await hasPermissionAsync(userId, 'run-import'))) {
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'setupImporter');
}
return executeGetImportProgress();
},
});

View File

@@ -0,0 +1,43 @@
import type { IImport } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Imports } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
export const executeGetLatestImportOperations = async () => {
const data = Imports.find(
{},
{
sort: { _updatedAt: -1 },
limit: 20,
},
);
return data.toArray();
};
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
getLatestImportOperations(): IImport[];
}
}
Meteor.methods<ServerMethods>({
async getLatestImportOperations() {
methodDeprecationLogger.method('getLatestImportOperations', '9.0.0', '/v1/getLatestImportOperations');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'getLatestImportOperations');
}
if (!(await hasPermissionAsync(userId, 'view-import-operations'))) {
throw new Meteor.Error('not_authorized', 'User not authorized', 'getLatestImportOperations');
}
return executeGetLatestImportOperations();
},
});

View File

@@ -0,0 +1,54 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Imports } from '@rocket.chat/models';
import { isStartImportParamsPOST, type StartImportParamsPOST } from '@rocket.chat/rest-typings';
import { Meteor } from 'meteor/meteor';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { Importers } from '../../lib/import';
export const executeStartImport = async ({ input }: StartImportParamsPOST, startedByUserId: IUser['_id']) => {
const operation = await Imports.findLastImport();
if (!operation) {
throw new Meteor.Error('error-operation-not-found', 'Import Operation Not Found', 'startImport');
}
const { importerKey } = operation;
const importer = Importers.get(importerKey);
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'startImport');
}
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
await instance.startImport(input, startedByUserId);
};
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
startImport(params: StartImportParamsPOST): void;
}
}
Meteor.methods<ServerMethods>({
async startImport({ input }: StartImportParamsPOST) {
methodDeprecationLogger.method('startImport', '9.0.0', '/v1/startImport');
if (!input || typeof input !== 'object' || !isStartImportParamsPOST({ input })) {
throw new Meteor.Error(`Invalid Selection data provided to the importer.`);
}
const userId = Meteor.userId();
// Takes name and object with users / channels selected to import
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'startImport');
}
if (!(await hasPermissionAsync(userId, 'run-import'))) {
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'startImport');
}
return executeStartImport({ input }, userId);
},
});

View File

@@ -0,0 +1,81 @@
import { Import } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Meteor } from 'meteor/meteor';
import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { Importers } from '../../lib/import';
import { RocketChatImportFileInstance } from '../../lib/import/startup/store';
import { RocketChatFile } from '../../lib/media/file';
export const executeUploadImportFile = async (
userId: IUser['_id'],
binaryContent: string,
contentType: string,
fileName: string,
importerKey: string,
): Promise<void> => {
const importer = Importers.get(importerKey);
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${importerKey}) has no import class defined.`, 'uploadImportFile');
}
const operation = await Import.newOperation(userId, importer.name, importer.key);
const instance = new importer.importer(importer, operation); // eslint-disable-line new-cap
const date = new Date();
const dateStr = `${date.getUTCFullYear()}${date.getUTCMonth()}${date.getUTCDate()}${date.getUTCHours()}${date.getUTCMinutes()}${date.getUTCSeconds()}`;
const newFileName = `${dateStr}_${userId}_${fileName}`;
// Store the file name and content type on the imports collection
await instance.startFileUpload(newFileName, contentType);
// Save the file on the File Store
const file = Buffer.from(binaryContent, 'base64');
const readStream = RocketChatFile.bufferToStream(file);
const writeStream = RocketChatImportFileInstance.createWriteStream(newFileName, contentType);
await new Promise<void>((resolve, reject) => {
try {
writeStream.on('end', () => {
resolve();
});
writeStream.on('error', (e: Error) => {
reject(e);
});
readStream.pipe(writeStream);
} catch (error) {
reject(error);
}
});
await instance.updateProgress(ProgressStep.FILE_LOADED);
};
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
uploadImportFile(binaryContent: string, contentType: string, fileName: string, importerKey: string): void;
}
}
Meteor.methods<ServerMethods>({
async uploadImportFile(binaryContent, contentType, fileName, importerKey) {
methodDeprecationLogger.method('uploadImportFile', '9.0.0', '/v1/uploadImportFile');
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', 'uploadImportFile');
}
if (!(await hasPermissionAsync(userId, 'run-import'))) {
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', 'uploadImportFile');
}
await executeUploadImportFile(userId, binaryContent, contentType, fileName, importerKey);
},
});