3

I need to transfer files using the Node.js library ssh2-sftp-client. My problem is I need to connect through an HTTP Proxy. The documentation has a step for SOCKS proxy. I am wondering how this can be achieved with HTTP proxy.

Jiji
  • 1,092
  • 3
  • 15
  • 36

1 Answers1

3

My version is based on the solution from your link. All the credits go to https://github.com/lmdc45

import {Injectable, Logger} from '@nestjs/common';
import * as Client from 'ssh2-sftp-client'
import * as fs from "fs";
import {ConfigService} from "@nestjs/config";
import * as http from "http";
import {SocksClient} from "socks";
import {Socket} from "net";
import {SocksClientOptions} from "socks/typings/common/constants";

export type SftpProxyConfig = { targetHost: string, targetPort: number, proxyType: 'http' | 'socks4' | 'socks5', proxyHost: string, proxyPort: number }

@Injectable()
export class SftpService {

  private readonly logger = new Logger(SftpService.name);

  constructor(
    private readonly configService: ConfigService
  ) {}


  async doSftpAction(
    connectionOptions?: Client.ConnectOptions
  ) {
    const sftp = new Client();
    const connectionInfo = connectionOptions ? connectionOptions : await this.getSFTPConnectionInfo();

    let sftpWrapper;
    try {
      sftpWrapper = await sftp.connect(connectionInfo);
      
      // Here comes your code
    } catch (error) {
      this.logger.error(error.message, error);
      throw new Error(error.message);
    } finally {
      if (sftpWrapper) {
        await sftp.end();
      }
    }
  }

  private async getSFTPConnectionInfo(): Promise<Client.ConnectOptions> {
    const host = this.configService.get<string>('SFTP_HOST');
    const port = this.configService.get<number>('SFTP_PORT');
    const connectionOptions: Client.ConnectOptions = {host, port};

    const username = this.configService.get<string>('SFTP_USER');
    const password = this.configService.get<string>('SFTP_PASSWORD');
    const privateKey = this.configService.get<string>('SFTP_SSH_KEY');
    const passphrase = this.configService.get<string>('SFTP_SSH_PASSPHRASE');
    const proxyHost = this.configService.get<string>('SFTP_PROXY_HOST');
    const proxyPort = this.configService.get<number>('SFTP_PROXY_PORT');
    const proxyType = this.configService.get<'http' | 'socks4' | 'socks5'>('SFTP_PROXY_TYPE')
    const debug = this.configService.get('SFTP_DEBUG_LOG') || false;

    if (username)
      connectionOptions.username = username;
    if (password)
      connectionOptions.password = password;
    else if (privateKey) {
      connectionOptions.privateKey = fs.readFileSync(this.configService.get<string>('SFTP_SSH_KEY'));
      if (passphrase)
        connectionOptions.passphrase = passphrase;
    }
    if (proxyHost && proxyPort && proxyType)
      connectionOptions.sock = await this.getProxy({
        targetHost: host,
        targetPort: port,
        proxyHost,
        proxyPort,
        proxyType
      });
    connectionOptions.debug = JSON.parse(debug) ? information => this.logger.log(information) : undefined;
    return connectionOptions;
  }

  public async getProxy(config: SftpProxyConfig): Promise<NodeJS.ReadableStream> {
    return (config.proxyType === 'http' ?
      await this.getHttpProxy(config) :
      await this.getSocksProxy(config)) as NodeJS.ReadableStream;
  }

  private async getHttpProxy(config: SftpProxyConfig):Promise<Socket>{
    // Make a request to a tunneling proxy
    const res = new Promise<Socket>((resolve, reject) => {

      const options = {
        port: config.proxyPort,
        host: config.proxyHost,
        method: 'CONNECT',
        path: `${config.targetHost}:${config.targetPort}`
      };

      const req = http.request(options);
      req.end();

      req.on('connect', (res, socket, head) => {
        this.logger.log(`Status Code: ${res.statusCode}`);
        resolve(socket);
      });
    });
    return res;
  }

  private async getSocksProxy(config: SftpProxyConfig): Promise<Socket> { 
    const options = {
      proxy: {
        host: config.proxyHost, // ipv4 or ipv6 or hostname
        port: config.proxyPort,
        type: config.proxyType === 'socks4' ? 4 : 5 // Proxy version (4 or 5)
      },

      command: 'connect', // SOCKS command (createConnection factory function only supports the connect command)

      destination: {
        host: config.targetHost,
        port: config.targetPort
      }
    };

    const info = await SocksClient.createConnection(options as SocksClientOptions);
    return info.socket;
  }
}
Klaus Ertl
  • 1,234
  • 1
  • 12
  • 21