diff --git a/processes.config.js b/processes.config.js index 5e3a336..d38bbda 100644 --- a/processes.config.js +++ b/processes.config.js @@ -1,14 +1,16 @@ -module.exports = [{ - script: './dist/index.js', - name: 'host_my_shit', - time: true, - source_map_support: true, - watch: false, - autorestart: true, - error_file: './logs/err.log', - out_file: './logs/out.log', - log_file: './logs/combined.log', - env: { - NODE_ENV: "production" - } -}]; +module.exports = [ + { + script: "./dist/index.js", + name: "waifu_valt", + time: true, + source_map_support: true, + watch: false, + autorestart: true, + error_file: "./logs/err.log", + out_file: "./logs/out.log", + log_file: "./logs/combined.log", + env: { + NODE_ENV: "production", + }, + }, +]; diff --git a/src/engine/IAvEngine.ts b/src/engine/IAvEngine.ts index d0cbdeb..d4ff7de 100644 --- a/src/engine/IAvEngine.ts +++ b/src/engine/IAvEngine.ts @@ -1,7 +1,22 @@ -import { AvScanResult } from "../utils/typeings.js"; +import { AvScanResult, Awaitable } from "../utils/typeings.js"; export interface IAvEngine { - scan(resource: string): Promise | AvScanResult; + /** + * Scan a file using the av service defined + * @param {string} resource + * @returns {Awaitable} + */ + scan(resource: string): Awaitable; - get enabled(): boolean; + /** + * Return true if this engine is enabled + * @returns {Awaitable} + */ + get enabled(): Awaitable; + + /** + * Get the name of this engine. normally returns the name of the AV service being used + * @returns {Awaitable} + */ + get name(): Awaitable; } diff --git a/src/engine/impl/av/ClamAvEngine.ts b/src/engine/impl/av/ClamAvEngine.ts index bae81bf..8b90aa6 100644 --- a/src/engine/impl/av/ClamAvEngine.ts +++ b/src/engine/impl/av/ClamAvEngine.ts @@ -29,11 +29,17 @@ export class ClamAvEngine implements IAvEngine { return { errorCode: e.code, passed: false, - additionalMessage: e.messag, + additionalMessage: e.message, + engineName: this.name, }; } return { passed: true, + engineName: this.name, }; } + + public get name(): string { + return "ClamAv"; + } } diff --git a/src/engine/impl/av/MsDefenderEngine.ts b/src/engine/impl/av/MsDefenderEngine.ts index 5c699e8..ac88062 100644 --- a/src/engine/impl/av/MsDefenderEngine.ts +++ b/src/engine/impl/av/MsDefenderEngine.ts @@ -31,11 +31,17 @@ export class MsDefenderEngine implements IAvEngine { return { errorCode: e.code, passed: false, - additionalMessage: e.messag, + additionalMessage: e.message, + engineName: this.name, }; } return { passed: true, + engineName: this.name, }; } + + public get name(): string { + return "MsDefender"; + } } diff --git a/src/factory/AvFactory.ts b/src/factory/AvFactory.ts index d32edba..6a77e7d 100644 --- a/src/factory/AvFactory.ts +++ b/src/factory/AvFactory.ts @@ -7,10 +7,14 @@ export class AvFactory { public constructor(@Inject(AV_ENGINE) private readonly engines: IAvEngine[]) {} /** - * Get the first enabled AV engine - * @returns {IAvEngine | null} + * Get all enabled av engines + * @returns {IAvEngine[]} */ - public getFirstAvailableAvEngine(): IAvEngine | null { - return this.engines.find(e => e.enabled) ?? null; + public async getAvEngines(): Promise { + const enabledEngines = await Promise.all( + this.engines.map(async engine => ((await engine.enabled) ? engine : null)), + ); + + return enabledEngines.filter(engine => engine !== null) as IAvEngine[]; } } diff --git a/src/manager/AvManager.ts b/src/manager/AvManager.ts index 586bb54..24e1c72 100644 --- a/src/manager/AvManager.ts +++ b/src/manager/AvManager.ts @@ -1,28 +1,47 @@ -import { Inject, Injectable } from "@tsed/di"; +import { Inject, Injectable, OnInit } from "@tsed/di"; import { AvFactory } from "../factory/AvFactory.js"; import type { PlatformMulterFile } from "@tsed/common"; import { Logger } from "@tsed/logger"; import { FileEngine } from "../engine/impl/index.js"; import { BadRequest } from "@tsed/exceptions"; import path from "node:path"; +import { IAvEngine } from "../engine/IAvEngine.js"; +import { AvScanResult } from "../utils/typeings.js"; @Injectable() -export class AvManager { +export class AvManager implements OnInit { + private avEngines: IAvEngine[]; + public constructor( - @Inject() private readonly avFactory: AvFactory, + @Inject() private avFactory: AvFactory, @Inject() private logger: Logger, @Inject() private fileEngine: FileEngine, ) {} + public async $onInit(): Promise { + this.avEngines = await this.avFactory.getAvEngines(); + if (this.avEngines.length === 0) { + this.logger.warn("No AV is enabled! AV scanning is disabled"); + } else if (this.avEngines.length === 1) { + this.logger.info(`Using av engine ${this.avEngines[0].name} to scan files`); + } else { + this.logger.info( + `Multiple AV engines are enabled, the engines will be used to scan files in the following order: ${this.avEngines.map(e => e.name).join(", ")}`, + ); + } + } + public async scanFile(file: string | PlatformMulterFile): Promise { - const avEngineToUse = this.avFactory.getFirstAvailableAvEngine(); - if (!avEngineToUse) { - this.logger.warn("No AV is enabled!"); + if (this.avEngines.length === 0) { return; } const resource = typeof file === "string" ? path.basename(file) : file.filename; - const scanResult = await avEngineToUse.scan(resource); - if (!scanResult.passed) { + const scanResults = await this.doScan(resource); + + for (const scanResult of scanResults) { + if (scanResult.passed) { + continue; + } const fileExists = await this.fileEngine.fileExists(resource); if (fileExists) { try { @@ -33,19 +52,23 @@ export class AvManager { throw new BadRequest(e.message); } } - let errStr = ""; + let errStr = `AV engine ${scanResult.engineName} found issues `; if (scanResult.additionalMessage) { - errStr += `AV scan of resource ${resource} failed.`; + errStr += `AV scan of resource ${resource} for issues terminated with message "${scanResult.additionalMessage}"`; } if (scanResult.errorCode) { - errStr += ` scan filed with error code ${scanResult.errorCode}`; + errStr += ` scan exited with code ${scanResult.errorCode}`; } if (errStr === "") { - // the scan failed but nothing was reported - errStr = "AV Scan failed with no reported reason"; + // the scan found issues but nothing was reported + errStr = "AV Scan ended with no reported reason"; } this.logger.warn(errStr); throw new BadRequest("Failed to store file"); } } + + private doScan(resource: string): Promise { + return Promise.all(this.avEngines.map(engine => engine.scan(resource))); + } } diff --git a/src/utils/typeings.ts b/src/utils/typeings.ts index 94ea9cc..326d364 100644 --- a/src/utils/typeings.ts +++ b/src/utils/typeings.ts @@ -18,6 +18,7 @@ export type EntrySettings = { export type AvScanResult = { passed: boolean; + engineName: string; errorCode?: number; additionalMessage?: string; }; @@ -54,3 +55,5 @@ export type IpBlockedAwareFileEntry = { }; export type ProtectionLevel = "Encrypted" | "Password" | "None"; + +export type Awaitable = Promise | T;