SFTP through HTTP proxy using ssh2-sftp-client library in Node.js - node.js

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.

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;
}
}

Related

Wrong between const and string

react native passing const and expect string erorr
I'm trying to pass apple key but im getting an error saying that const apikey : google string ... any idea ?
import { useEffect, useState } from "react";
import { Platform } from "react-native";
import Purchases,{
CustomerInfo,
PurchasesOffering,
} from "react-native-purchases";
const APIKeys = {
apple: "appl_KDUGooX",
google: "your_revenuecat_api_key",
};
const typeOfMembership = {
monthly: "proMonthly",
yearly: "proYearly",
};
function useRevenueCat(){
const [currentOffering,setCurrentOffering] =
useState<PurchasesOffering | null>(null);
const [customerInfo,setCustomerInfo] =
useState<CustomerInfo | null>(null);
const isProMember =
customerInfo?.activeSubscriptions.includes(typeOfMembership.monthly) ||
customerInfo?.activeSubscriptions.includes(typeOfMembership.yearly);
useEffect(() => {
const fetchData = async () => {
Purchases.setDebugLogsEnabled(true);
if (Platform.OS == "android") {
await Purchases.configure({ apikey: APIKeys.google });
} else {
await Purchases.configure({ apikey: APIKeys.apple });
}
const offering = await Purchases.getOfferings();
const customerInfo = await Purchases.getCustomerInfo();
}
},[])
}
export default useRevenueCat;
Where can i change it to make it correctly ??

node.js gatsby developer -> createPageError

//gatsby-node.js code
const { getDataSource } = require('./src/data-loader');
exports.createPages = async ({ actions }) => {
const { createPage } = actions;
const dataSource = await getDataSource();
createPage({
path: '/',
component: require.resolve('./src/templates/single-page.js'),
context: { dataSource },
});
};
--api-client.js code
const axios = require('axios');
class ApiClient {
constructor() {
const client = axios.create({
baseURL: process.env.CB_API_BASE_URL || 'http://127.0.0.1:8080',
});
client.interceptors.response.use((resp) => {
return resp.data;
});
this.client = client;
}
async getAllGlobalStats() {
const response = await this.client.get('global-stats');
return response.result;
}
async getByAgeAndBySex() {
const response = await this.client.get(`key-value/byAgeAndSex`);
return JSON.parse(response.result.value);
}
}
module.exports = ApiClient;
I get ERROR #11321 when running gatsby develop.
A message like the one below will be output.
'"gatsby-node.js" threw an error while running the createPages lifecycle:
Request failed with status code 404'
i tried
localhost:port -> 127.0.0.1:port ->(X)
port change -> (x)
cache remove -> (x)
help!
node.js -> 16.13.2
gatsby cli -> 3.10.0

Redis redis.createClient() in Typescript

I was trying to connect Redis (v4.0.1) to my express server with typescript but having a bit issue. Am learning typescript. It's showing redlines on host inside redis.createClient() Can anyone help me out?
const host = process.env.REDIS_HOST;
const port = process.env.REDIS_PORT;
const redisClient = redis.createClient({
host,
port,
});
Argument of type '{ host: string | undefined; port: string | undefined; }' is not assignable to parameter of type 'Omit<RedisClientOptions<never, RedisScripts>, "modules">'.
Object literal may only specify known properties, and 'host' does not exist in type 'Omit<RedisClientOptions<never, RedisScripts>, "modules">'.ts(2345)
Options have changed when redis updated to 4.0.1. This should help you.
This works as expected (redis v4.1.0)
const url = process.env.REDIS_URL || 'redis://localhost:6379';
const redisClient = redis.createClient({
url
});
what I did in my project was this
file: services/internal/cache.ts
/* eslint-disable no-inline-comments */
import type { RedisClientType } from 'redis'
import { createClient } from 'redis'
import { config } from '#app/config'
import { logger } from '#app/utils/logger'
let redisClient: RedisClientType
let isReady: boolean
const cacheOptions = {
url: config.redis.tlsFlag ? config.redis.urlTls : config.redis.url,
}
if (config.redis.tlsFlag) {
Object.assign(cacheOptions, {
socket: {
// keepAlive: 300, // 5 minutes DEFAULT
tls: false,
},
})
}
async function getCache(): Promise<RedisClientType> {
if (!isReady) {
redisClient = createClient({
...cacheOptions,
})
redisClient.on('error', err => logger.error(`Redis Error: ${err}`))
redisClient.on('connect', () => logger.info('Redis connected'))
redisClient.on('reconnecting', () => logger.info('Redis reconnecting'))
redisClient.on('ready', () => {
isReady = true
logger.info('Redis ready!')
})
await redisClient.connect()
}
return redisClient
}
getCache().then(connection => {
redisClient = connection
}).catch(err => {
// eslint-disable-next-line #typescript-eslint/no-unsafe-assignment
logger.error({ err }, 'Failed to connect to Redis')
})
export {
getCache,
}
then you just import where you need:
import { getCache } from '#services/internal/cache'
const cache = await getCache()
cache.setEx(accountId, 60, JSON.stringify(account))
The option to add a host, port in redis.createClient is no longer supported by redis. So it is not inside type createClient. use URL instead.
import { createClient } from 'redis';
const client = createClient({
socket: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT)
},
password: process.env.REDIS_PW
});
client.on('error', (err) => console.error(err));
client.connect();
export { client };

Mongodb Typescript UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'conn' of undefined at Object.connectToDatabase [as default]

typescript connection to mongo database throws error , it can read conn of undefined after conn has been declared globally
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'conn' of undefined
at Object.connectToDatabase [as default]
import { MongoClient, Db } from "mongodb";
import config from "../config/config";
const { dbName, mongoDBUri } = config;
type MongoConnection = {
client: MongoClient;
db: Db;
};
declare global {
namespace NodeJS {
interface Global {
mongodb: {
conn: MongoConnection | null;
promise: Promise<MongoConnection> | null;
};
}
}
}
let cached = global.mongodb;
async function connectToDatabase() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
cached.promise = MongoClient.connect(mongoDBUri as string, opts).then(
(client) => {
return {
client,
db: client.db(dbName),
};
}
);
}
cached.conn = await cached.promise;
return cached.conn;
}
export default connectToDatabase;
You can use the below setup
//interfaces/db.interface
export interface dbConfig {
host: string;
port: number;
database: string;
username: string;
password: string;
}
//database.ts
import { dbConfig } from "#interfaces/db.interface";
const { host, port, database, username, password }: dbConfig = config.get("dbConfig");
export const dbConnection = {
url: `mongodb://${username}:${password}#${host}:${port}/${database}?authSource=admin`,
options: {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
},
};
//app.ts (express app)
import { dbConnection } from "#databases";
constructor(routes: Routes[]) {
this.app = express();
this.port = process.env.PORT || 5000;
this.env = process.env.NODE_ENV || "development";
this.connectToDatabase();
}
private connectToDatabase() {
if (this.env !== "production") {
set("debug", true);
}
connect(dbConnection.url, dbConnection.options)
.catch((error) =>
console.log(`${error}`)
);
}
Here I am assuming you have the setup of paths in the tsconfig.json file so that # will work in imports.
After several times of trying, I had to use the NextJs MongoDB connection pattern and convert it to typescript and it worked perfectly fine
import config from "./../config/config";
import { MongoClient, Db } from "mongodb";
const { dbName, mongoDBUri } = config;
if (!mongoDBUri) {
throw new Error(
"Define the mongoDBUri environment variable inside .env"
);
}
if (!dbName) {
throw new Error(
"Define the dbName environment variable inside .env"
);
}
type MongoConnection = {
client: MongoClient;
db: Db;
};
declare global {
namespace NodeJS {
interface Global {
mongodb: {
conn: MongoConnection | null;
promise: Promise<MongoConnection> | null;
};
}
}
}
let cached = global.mongodb;
if (!cached) {
cached = global.mongodb = { conn: null, promise: null };
}
export default async function connectToDatabase() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
console.log("Establishing new database connection");
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
cached.promise = MongoClient.connect(mongoDBUri as string, opts).then(
(client) => {
return {
client,
db: client.db(dbName),
};
}
);
}
cached.conn = await cached.promise;
return cached.conn;
}

Trouble connecting to Graphql subscriptions?

I have followed Apollo docs but seem to still be having issues connecting to subscriptions. Here is my code: On the frontend, I can see it trying to connect but is logging:
WebSocket connection to 'ws://localhost:4000/subscriptions' failed:
I have been following this: https://www.apollographql.com/docs/apollo-server/data/subscriptions/ but it seems like the documentation is slightly behind so I may be missing something.
Client:
import ReactDOM from 'react-dom';
import './index.css';
import Routes from './routes';
import 'semantic-ui-css/semantic.min.css';
import { setContext } from '#apollo/client/link/context';
import { WebSocketLink } from '#apollo/client/link/ws';
import { getMainDefinition } from '#apollo/client/utilities';
import {
ApolloProvider,
ApolloClient,
HttpLink,
InMemoryCache,
split,
} from '#apollo/client';
// Http link
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
// Websocket link
const wsLink = new WebSocketLink({
uri: 'ws://localhost:4000/subscriptions',
options: {
reconnect: true
}
});
// Attach auth headers to requests
const middlewareLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
x_token: token ? `${token}` : "",
x_refresh_token: refreshToken ? `${refreshToken}`: ""
}
}
});
// Combine
const httpLinkWithMiddleware = middlewareLink.concat(httpLink);
// Split link - either http or ws depending on graphql
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLinkWithMiddleware,
);
// Create client with link
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
// Provide client
const App = (
<ApolloProvider client={client}>
<Routes />
</ApolloProvider>
)
// Render
ReactDOM.render(
App,
document.getElementById('root')
);
Server:
import express from 'express';
import path from 'path';
import { fileLoader, mergeTypes, mergeResolvers } from 'merge-graphql-schemas';
import { ApolloServer } from 'apollo-server-express';
import { refreshTokens } from './auth';
import models from './models';
import cors from 'cors';
import jwt from 'jsonwebtoken';
const SECRET = "";
const SECRET2 = "";
const typeDefs = mergeTypes(fileLoader(path.join(__dirname, './schema')));
const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './resolvers')));
const PORT = 4000;
const app = express();
// Cors
app.use(cors('*'));
// Add tokens
const addUser = async (req, res, next) => {
const token = req.headers['x_token'];
if (token) {
try {
const { user } = jwt.verify(token, SECRET);
req.user = user;
} catch (err) {
const refreshToken = req.headers['x_refresh_token'];
const newTokens = await refreshTokens(token, refreshToken, models, SECRET, SECRET2);
if (newTokens.token && newTokens.refreshToken) {
res.set('Access-Control-Expose-Headers', 'x_token', 'x_refresh_token');
res.set('x_token', newTokens.token);
res.set('x_refresh_token', newTokens.refreshToken);
}
req.user = newTokens.user;
}
}
next();
};
app.use(addUser);
// Create server
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
path: '/subscriptions'
},
context: ({req, res, connection}) => {
const user = req.user;
return { models, SECRET, SECRET2, user };
},
});
// Apply middleware
server.applyMiddleware({ app });
// Sync and listen
models.sequelize.sync({force: true}).then(x => {
app.listen({ port: PORT }, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
}
);
});
Any help would be appreciated...
I can't see in the server declaring a websocket creation. Something like:
const WebSocket = require('ws');
const graphQLWebSocket = new WebSocket.Server({noServer: true});
Then also put in:
server.installSubscriptionHandlers(graphQLWebSocket);
After the row with server.applyMiddleware({ app });
More info here.

Resources