Files
workavia-drive/tdrive/backend/node/src/services/av/service/index.ts
T
Montassar Ghanmy f12f0892f1 🐛 Fix config retrieval and type handling (#739)
🐛 Fix config retrieval and type handling
2024-11-22 10:58:51 +01:00

99 lines
4.1 KiB
TypeScript

import { Initializable, TdriveServiceProvider } from "../../../core/platform/framework";
import { getLogger, logger, TdriveLogger } from "../../../core/platform/framework";
import NodeClam from "clamscan";
import { AVStatus, DriveFile } from "src/services/documents/entities/drive-file";
import { FileVersion } from "src/services/documents/entities/file-version";
import { DriveExecutionContext } from "src/services/documents/types";
import globalResolver from "../../../services/global-resolver";
import { getFilePath } from "../../files/services";
import { getConfigOrDefault } from "../../../utils/get-config";
import { AVException } from "./av-exception";
export class AVServiceImpl implements TdriveServiceProvider, Initializable {
version: "1";
av: NodeClam = null;
logger: TdriveLogger = getLogger("Antivirus Service");
avEnabled: boolean = getConfigOrDefault<boolean>("drive.featureAntivirus", false);
private MAX_FILE_SIZE: number = getConfigOrDefault<number>("av.maxFileSize", 26214400); // 25 MB
async init(): Promise<this> {
try {
if (this.avEnabled) {
this.av = await new NodeClam().init({
removeInfected: false, // Do not remove infected files
quarantineInfected: false, // Do not quarantine, just alert
scanLog: null, // No log file for this test
debugMode: getConfigOrDefault<boolean>("av.debugMode", false), // Enable debug messages
clamdscan: {
host: getConfigOrDefault<string>("av.host", "localhost"), // IP of the server
port: getConfigOrDefault<number>("av.port", 3310) as number, // ClamAV server port
timeout: getConfigOrDefault<number>("av.timeout", 2000), // Timeout for scans
localFallback: true, // Use local clamscan if needed
},
});
}
} catch (error) {
logger.error({ error: `${error}` }, "Error while initializing Antivirus Service");
throw AVException.initializationFailed("Failed to initialize Antivirus service");
}
return this;
}
async scanDocument(
item: Partial<DriveFile>,
version: Partial<FileVersion>,
onScanComplete: (status: AVStatus) => Promise<void>,
context: DriveExecutionContext,
): Promise<AVStatus> {
try {
// get the file from the storage
const file = await globalResolver.services.files.get(
version.file_metadata.external_id,
context,
);
if (!file) {
this.logger.error(`File ${version.file_metadata.external_id} not found`);
throw AVException.fileNotFound(`File ${version.file_metadata.external_id} not found`);
}
// check if the file is too large
if (file.upload_data.size > this.MAX_FILE_SIZE) {
this.logger.info(
`File ${file.id} is too large (${file.upload_data.size} bytes) to be scanned. Skipping...`,
);
return "skipped";
}
// read the file from the storage
const readableStream = await globalResolver.platformServices.storage.read(getFilePath(file), {
totalChunks: file.upload_data.chunks,
encryptionAlgo: globalResolver.services.files.getEncryptionAlgorithm(),
encryptionKey: file.encryption_key,
});
// scan the file
this.av.scanStream(readableStream, async (err, { isInfected, viruses }) => {
if (err) {
await onScanComplete("scan_failed");
this.logger.error(`Scan failed for item ${item.id} due to error: ${err.message}`);
} else if (isInfected) {
await onScanComplete("malicious");
this.logger.info(`Item ${item.id} is malicious. Viruses found: ${viruses.join(", ")}`);
} else {
await onScanComplete("safe");
this.logger.info(`Item ${item.id} is safe with no viruses detected.`);
}
});
return "scanning";
} catch (error) {
// mark the file as failed to scan
await onScanComplete("scan_failed");
// log the error
this.logger.error(`Error scanning file ${item.last_version_cache.file_metadata.external_id}`);
throw AVException.scanFailed("Document scanning encountered an error");
}
}
}