import { Injectable } from '@nestjs/common';
import { GetObjectCommand,S3Client } from '@aws-sdk/client-s3';
import { createHash } from 'node:crypto';
import { DatabaseService } from '../../../infrastructure/database/database.service';
import { assertFileVerifyInput } from './file-input-validation';
@Injectable()
export class FileVerificationService {
private readonly bucket=process.env.OBJECT_STORAGE_BUCKET ?? 'gastro-os';
private readonly s3=new S3Client({
region:process.env.OBJECT_STORAGE_REGION ?? 'local',
endpoint:process.env.OBJECT_STORAGE_ENDPOINT,
forcePathStyle:true,
credentials:{
accessKeyId:process.env.OBJECT_STORAGE_ACCESS_KEY ?? 'minio',
secretAccessKey:process.env.OBJECT_STORAGE_SECRET_KEY ?? 'change-me',
},
});
constructor(private readonly database:DatabaseService) {}
async verify(input:unknown) {
assertFileVerifyInput(input);
const rows=await this.database.query<any>(
`select * from file_assets
where id=$1 and organization_id=$2 and location_id=$3
and status in ('available','processing')`,
[input.fileAssetId,input.organizationId,input.locationId],
);
const asset=rows[0];
if(!asset) return {processed:false};
const object=await this.s3.send(new GetObjectCommand({Bucket:this.bucket,Key:asset.storage_key}));
const bytes=Buffer.from(await object.Body!.transformToByteArray());
const checksum=createHash('sha256').update(bytes).digest('hex');
const detectedMime=object.ContentType ?? asset.mime_type;
const checksumMismatch=asset.checksum_sha256 && checksum!==asset.checksum_sha256;
const obviousDangerousMime=[
'text/html','image/svg+xml','application/javascript','text/javascript',
'application/x-sh','application/x-msdownload'
].includes(detectedMime);
const scanStatus=checksumMismatch || obviousDangerousMime ? 'infected' : 'clean';
const status=scanStatus==='clean'?'available':'rejected';
await this.database.query(
`update file_assets
set scan_status=$1,detected_mime_type=$2,verified_checksum_sha256=$3,
scan_completed_at=now(),status=$4,
rejection_reason=case when $4='rejected' then 'verification_failed' else rejection_reason end,
updated_at=now()
where id=$5 and organization_id=$6 and location_id=$7`,
[scanStatus,detectedMime,checksum,status,input.fileAssetId,input.organizationId,input.locationId],
);
return {processed:true,scanStatus,checksum,detectedMime};
}
}
