All files / src/platform/storage/providers local-vps.provider.ts

0% Statements 0/49
0% Branches 0/24
0% Functions 0/4
0% Lines 0/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121                                                                                                                                                                                                                                                 
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
 
@Injectable()
export class LocalVpsStorageProvider {
  private readonly logger = new Logger(LocalVpsStorageProvider.name);
 
  // Fallback to local project folder if /var/lib/bevision does not exist or is not writable
  private getResolvedRootPath(customRoot?: string): string {
    const defaultRoot = customRoot || '/var/lib/bevision';
    try {
      if (!fs.existsSync(defaultRoot)) {
        fs.mkdirSync(defaultRoot, { recursive: true });
      }
      return defaultRoot;
    } catch {
      // Fallback
      const fallback = path.join(process.cwd(), 'uploads');
      if (!fs.existsSync(fallback)) {
        fs.mkdirSync(fallback, { recursive: true });
      }
      return fallback;
    }
  }
 
  async upload(
    tenantId: string,
    relativePath: string,
    buffer: Buffer,
    volumeRoot?: string,
  ): Promise<{ absolutePath: string; checksum: string; size: number }> {
    const rootPath = this.getResolvedRootPath(volumeRoot);
    const tenantDir = path.join(rootPath, 'tenants', tenantId);
    const destinationPath = path.join(tenantDir, relativePath);
 
    // Path traversal validation check
    const relativeToTenant = path.relative(tenantDir, destinationPath);
    if (
      relativeToTenant.startsWith('..') ||
      path.isAbsolute(relativeToTenant)
    ) {
      throw new BadRequestException(
        'Security: Path traversal attempt detected',
      );
    }
 
    const dir = path.dirname(destinationPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
 
    fs.writeFileSync(destinationPath, buffer);
    const checksum = crypto.createHash('sha256').update(buffer).digest('hex');
 
    this.logger.log(
      `File uploaded successfully to VPS path: ${destinationPath}`,
    );
    return {
      absolutePath: destinationPath,
      checksum,
      size: buffer.length,
    };
  }
 
  async read(
    tenantId: string,
    relativePath: string,
    volumeRoot?: string,
  ): Promise<Buffer> {
    const rootPath = this.getResolvedRootPath(volumeRoot);
    const tenantDir = path.join(rootPath, 'tenants', tenantId);
    const sourcePath = path.join(tenantDir, relativePath);
 
    // Path traversal check
    const relativeToTenant = path.relative(tenantDir, sourcePath);
    if (
      relativeToTenant.startsWith('..') ||
      path.isAbsolute(relativeToTenant)
    ) {
      throw new BadRequestException(
        'Security: Path traversal attempt detected',
      );
    }
 
    if (!fs.existsSync(sourcePath)) {
      throw new BadRequestException('File not found on storage volume');
    }
 
    return fs.readFileSync(sourcePath);
  }
 
  async delete(
    tenantId: string,
    relativePath: string,
    volumeRoot?: string,
  ): Promise<boolean> {
    const rootPath = this.getResolvedRootPath(volumeRoot);
    const tenantDir = path.join(rootPath, 'tenants', tenantId);
    const targetPath = path.join(tenantDir, relativePath);
 
    // Path traversal check
    const relativeToTenant = path.relative(tenantDir, targetPath);
    if (
      relativeToTenant.startsWith('..') ||
      path.isAbsolute(relativeToTenant)
    ) {
      throw new BadRequestException(
        'Security: Path traversal attempt detected',
      );
    }
 
    if (fs.existsSync(targetPath)) {
      fs.unlinkSync(targetPath);
      return true;
    }
    return false;
  }
}