How to use authenticated proxies with undici - javascript

Hello so am trying to use undici with a proxy but it doesn't work i tired this
const client = new Client({
url: 'www.google.com',
proxy: 'http://user:pass#host:port'
})
as well as this
const { HttpsProxyAgent } = require("https-proxy-agent");
const proxy = new HttpsProxyAgent("http://user:pass#host:port");
time = new Date()
client.request({
path: '/',
method: 'GET',
httpsAgent: proxy
},
but nothing seems to work

See this link here:
https://github.com/nodejs/undici/blob/01302e6d2b2629cca4ad9327abe0f7a317f8399f/docs/best-practices/proxy.md#connect-with-authentication
import { Client } from 'undici'
import { createServer } from 'http'
import proxy from 'proxy'
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
proxy.authenticate = function (req, fn) {
fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`)
}
server.on('request', (req, res) => {
console.log(req.url) // '/hello?foo=bar'
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const client = new Client(proxyUrl)
const response = await client.request({
method: 'GET',
path: serverUrl + '/hello?foo=bar',
headers: {
'proxy-authorization': `Basic ${Buffer.from('user:pass').toString('base64')}`
}
})
response.body.setEncoding('utf8')
let data = ''
for await (const chunk of response.body) {
data += chunk
}
console.log(response.statusCode) // 200
console.log(JSON.parse(data)) // { hello: 'world' }
server.close()
proxy.close()
client.close()
function buildServer () {
return new Promise((resolve, reject) => {
const server = createServer()
server.listen(0, () => resolve(server))
})
}
function buildProxy () {
return new Promise((resolve, reject) => {
const server = proxy(createServer())
server.listen(0, () => resolve(server))
})
}

Related

Capture request body using Opentelemetry in node.js

I am trying to capture response body using otel-instrumentation-http and responseHook. But it doesn't capture the response body sent by the server.
tracing.js
// OTel
const { Resource } = require('#opentelemetry/resources');
const {
SemanticResourceAttributes,
} = require('#opentelemetry/semantic-conventions');
const {http_1} = require('http');
// SDKs
const { NodeTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } = require('#opentelemetry/sdk-trace-node');
// Instrumentation
const { HttpInstrumentation } = require('#opentelemetry/instrumentation-http');
const { registerInstrumentations } = require('#opentelemetry/instrumentation');
const { ExpressInstrumentation } = require('#opentelemetry/instrumentation-express');
// Exporters
const { OTLPTraceExporter } = require('#opentelemetry/exporter-trace-otlp-http');
const exporter = new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces'
});
const initialize = function(config) {
const provider = new NodeTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]:
config.serviceName,
}),
});
// export spans to console (useful for debugging)
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
// export spans to opentelemetry collector
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();
registerInstrumentations({
instrumentations: [
new HttpInstrumentation({
responseHook: (span, response) => {
response.on('data', (chunk) => {
if(!response['myBody']){
response['myBody'] = [];
response['myBody'].push(chunk);
}
else{
response['myBody'].push(chunk);
}
});
response.on('end', () => {
const responseBody = Buffer.concat(response['myBody']).toString();
console.log('respnse body is', responseBody);
console.log('response headers are', response.headers);
span.setAttribute('http.response.body',responseBody);
span.setAttributes('http.response.headers', response.headers);
});
}
}),
new ExpressInstrumentation({
ignoreLayersType: ['router', 'middleware'],
requestHook: (span, requestInfo) => {
span.setAttribute(
'http.request.request_body',
JSON.stringify(requestInfo.request.body)
);
span.setAttribute(
'http.request.query_params',
JSON.stringify(requestInfo.request.query)
);
}
})
]
});
};
module.exports = {initialize};
app.js
const { initialize } = require('./tracing');
initialize({serviceName: 'api-observability-orchestrator'});
const express = require('express');
const port = process.env.PORT || '8080';
const app = express();
app.use(express.json());
app.get('/', (req, resp) => {
resp.send({ message: 'Hello World !!' });
});
app.get('/path/:name', (req, resp) => {
resp.send({ message: `Hello ${req.params.name} !!` });
});
app.get('/query', (req, resp) => {
resp.send({ message: `Hello ${req.query.name} !!` });
});
app.post('/payload', (req, res) => {
res.json({ requestBody: req.body });
});
app.listen(parseInt(port, 10), () => {
console.log('Listening for requests on localhost:', port);
});
I have looked at the docs, and similary github issue: github-3104tried instrumenting by adding listeners inside the library function _traceClientRequest itself.
Output:
respnse body is {"partialSuccess":{}}
response headers are {
'content-type': 'application/json',
date: 'Fri, 27 Jan 2023 16:02:17 GMT',
'content-length': '21'
}
Expectation
<actual response body>

Can't get req.body with client fetch() and Node.js

I have simple app.js for Node.js under localhost:3000
app.js:
let http = require('http');
http.createServer((req, res) => {
res.writeHead(200);
let response;
if(~req.url.indexOf('post')) {
response = req.body.content;
} else {
response = '<script src="http://localhost/fetch.js"></script>';
}
res.end(response);
}).listen(3000);
The file fetch.js is placed on my another local server and is successfully enqueued to the page
fetch.js:
read('http://localhost:3000/?post').then((response) => {
console.log(response);
});
async function read(url) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
content: 'Text'
})
});
return response.text();
}
So I render HTML with fetch.js which then send POST request to the same page, but with a query ?post
However, when I run node app.js I get the error
Can not read property 'content' of undefined
So I don't get req.body
Why and how to resolve?
i think you are missing parser for your http server, there is no body because you actually didn't parse the body.
assemble the chunks like below then parse it as the header sais.
this is my work for myself
private parseBody(req: IncomingMessage) {
return new Promise((resolve, reject) => {
const chunks: any[] = []
req.on("data", (chunk) => {
chunks.push(chunk)
})
req.on("end", () => {
const data = Buffer.concat(chunks)
switch (req.headers["content-type"]) {
case "application/json":
resolve(this.parseJson(data.toString()))
break
case "application/x-www-form-urlencoded":
resolve(this.parseUrlEncoded(data.toString()))
break
default:
resolve({})
}
})
})
http server is very abstract and doesn't support anything basicly, i suggest using express or fastify.
working example: https://frontendguruji.com/blog/how-to-parse-post-request-in-node-js-without-expressjs-body-parser/
update
this is the class im using
http.resolver.ts
private parseJson(data: string) {
return JSON.parse(data)
}
private parseUrlEncoded(data: string) {
const parsedData = new URLSearchParams(data)
const dataObj: any = {}
for (var pair of parsedData.entries()) {
dataObj[pair[0]] = pair[1]
}
return dataObj
}
private parseBody(req: IncomingMessage) {
return new Promise((resolve, reject) => {
const chunks: any[] = []
req.on("data", (chunk) => {
chunks.push(chunk)
})
req.on("end", () => {
const data = Buffer.concat(chunks)
switch (req.headers["content-type"]) {
case "application/json":
resolve(this.parseJson(data.toString()))
break
case "application/x-www-form-urlencoded":
resolve(this.parseUrlEncoded(data.toString()))
break
default:
resolve(parse(req.url ?? "/", true).query)
}
})
})
}
you may use await behind the parseBody function after
Ok, thanks to #Fide. The link he posted has the answer:
let http = require('http');
http.createServer((req, res) => {
if(~req.url.indexOf('post')) {
let body;
req.on('data', function(data) {
body = data;
})
req.on('end', function() {
res.writeHead(200);
res.end(body);
})
} else {
res.writeHead(200);
res.end('<script src="http://localhost/fetch.js"></script>');
}
}).listen(3000);

Stop multiple request sent on refresh token using axios-auth-refresh

I am using axios-auth-refresh axios interceptor 3rd party library to handle my refresh token, the issue is that i have multiple request on my dashboard around 10 request and every time when the token expires, it will execute the my refresh token function multiple times and based on how many requests that are made. I previously refer to this stackoverflow question but it didn't somehow work on me and some requests are not being made then I opt at using axios-auth-refresh library to handle my refresh token function, because I thought that the option pauseInstanceWhileRefreshing: true will help me prevent it but it didn't somehow work.
What I want to achieve is to execute a single refresh token request to the server while other requests are on queue (I am using cookie to store my token), once it successfully replace the token then it will retry or resend those request back to the server. Below is my code:
import axios from 'axios';
import axiosRetry from 'axios-retry';
import config from 'config';
import {isNull, isUndefined} from 'underscore';
import Cookies from 'universal-cookie';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
const api_client = () => {
const client = axios.create({baseURL: config.server});
const cookies = new Cookies();
client.interceptors.request.use(request => {
const cookies = new Cookies();
const cookieToken = cookies.get('token');
const cookieUser = cookies.get('user');
if(!isNull(cookieToken) && !isNull(cookieUser)) {
if(!isUndefined(cookieToken.token)) {
request.headers['Authorization'] = `Bearer ${cookieToken.token}`;
}
request.withCredentials = true;
request.headers['Content-Type'] = 'application/json'
request.headers['X-Fname'] = cookieUser.username;
}
return request;
}, error => {
console.log(error);
});
const handleRefreshAuth = failedRequest => {
return getRefreshAuthToken().then(response => {
if(response.data) {
cookies.remove("token", { domain: window.location.hostname, path: "/grt" });
cookies.remove("token", { domain: window.location.hostname, path: "/" });
cookies.set("token", { token: response.data.token, refreshToken: response.data.refreshToken, }, { domain: window.location.hostname, path: "/grt", expires: new Date(Date.now() + 259200) });
failedRequest.response.config.headers['Authorization'] = `Bearer ${response.data.token}`;
return Promise.resolve();
}
})
}
const options = {
pauseInstanceWhileRefreshing: true
}
createAuthRefreshInterceptor(client, handleRefreshAuth, options);
const getRefreshAuthToken = () => {
return new Promise((resolve, reject) => {
const cookieToken = cookies.get('token');
const cookieUser = cookies.get('user');
if((!isNull(cookieToken) && !isNull(cookieUser)) || (!isUndefined(cookieToken) && !isUndefined(cookieUser))) {
client.post(`/user/refresh?refreshToken=${cookieToken.refreshToken}`).then(response => {
if(response) {
resolve(response)
}
})
}
})
}
return client;
}
export const retrieve = async (url, data = undefined) => {
return new Promise(async (resolve, reject) => {
await api_client().get(url, data).then(response => {
if(response) {
resolve(response.data);
}
})
})
}
export const send = async (url, data = undefined) => {
return new Promise(async (resolve, reject) => {
await api_client().post(url, data, { skipAuthRefresh: true }).then(response => {
if(response) {
resolve(response.data);
}
})
})
}
export default {retrieve, send};
Network
Old code
I added this one as well, it might be that I missed something else on this one, using raw axios interceptor method, same issue as the new one.
import axios from 'axios';
import axiosRetry from 'axios-retry';
import config from 'config';
import {isNull, isUndefined} from 'underscore';
import Cookies from 'universal-cookie';
const api_client = () => {
const client = axios.create({baseURL: config.server});
let isRefreshing = false;
let failedQueue = [];
const retryCodes = [
401
]
axiosRetry(client, {
retries: 3,
retryDelay: (retryCount) => {
return retryCount * 1000;
},
retryCondition: (error) => {
return retryCodes.includes(error.response.status);
}
});
client.interceptors.request.use(request => {
const cookies = new Cookies();
const cookieToken = cookies.get('token');
const cookieUser = cookies.get('user');
if(!isNull(cookieToken) && !isNull(cookieUser)) {
if(!isUndefined(cookieToken.token)) {
request.headers['Authorization'] = `Bearer ${cookieToken.token}`;
}
request.withCredentials = true;
request.headers['Content-Type'] = 'application/json'
request.headers['X-Fname'] = cookieUser.username;
}
return request;
}, error => {
console.log(error);
});
const processQueue = (error, token = null) => {
const cookies = new Cookies();
console.log('processQueue', token)
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
const cookieToken = cookies.get('token');
prom.resolve(cookieToken.token);
}
})
failedQueue = [];
}
const getRefreshAuthToken = async () => {
return new Promise(async (resolve, reject) => {
const cookies = new Cookies();
const cookieToken = cookies.get('token');
const cookieUser = cookies.get('user');
if((!isNull(cookieToken) && !isNull(cookieUser)) || (!isUndefined(cookieToken) && !isUndefined(cookieUser))) {
await client.post(`/user/refresh?refreshToken=${cookieToken.refreshToken}`).then(response => {
if(response) {
resolve(response)
}
})
}
})
}
client.interceptors.response.use(response => {
return response;
}, error => {
const originalRequest = error.config;
const cookies = new Cookies();
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function(resolve, reject) {
failedQueue.push({ resolve, reject });
}).then(response => {
if(response) {
console.log('from test response', response)
cookies.remove("token", { domain: window.location.hostname, path: "/grt" });
cookies.remove("token", { domain: window.location.hostname, path: "/" });
cookies.set("token", { token: response.data.token, refreshToken: response.data.refreshToken, }, { domain: window.location.hostname, path: "/grt", expires: new Date(Date.now() + 259200) });
originalRequest.headers['Authorization'] = `Bearer ${cookies.get('token').token}`;
return client(originalRequest);
}
}).catch(error => {
return Promise.reject(error);
});
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise((resolve, reject) => {
getRefreshAuthToken()
.then(response => {
if(!isUndefined(response)) {
cookies.remove("token", { domain: window.location.hostname, path: "/grt" });
cookies.remove("token", { domain: window.location.hostname, path: "/" });
cookies.set("token", { token: response.data.token, refreshToken: response.data.refreshToken, }, { domain: window.location.hostname, path: "/grt", expires: new Date(Date.now() + 259200) });
}
processQueue(null, cookies.get('token').token);
resolve(client(originalRequest));
}).catch(error => {
// add to queue for failed requests
processQueue(error, null);
reject(error);
}).then(() => {
isRefreshing = false
});
});
}
return Promise.reject(error);
})
return client;
}
export const retrieve = async (url, data = undefined) => {
return new Promise(async (resolve, reject) => {
await api_client().get(url, data).then(response => {
if(response) {
resolve(response.data);
}
})
})
}
export const send = async (url, data = undefined) => {
return new Promise(async (resolve, reject) => {
await api_client().post(url, data, { skipAuthRefresh: true }).then(response => {
if(response) {
resolve(response.data);
}
})
})
}
export default {retrieve, send};
Does anyone knows how to fix this? If so, please let me know, thanks!

How to mock nested function in Jest?

I am getting this error
Cannot find module 'httpsGet' from 'functions/getSecureString.test.js'
httpsGet() is my own function, and is at the button of getSecureString.js, and called by getSecureString(). httpsGet() uses the https module to get content from a website that requires client side certificates.
Question
I am trying to mock httpsGet() and I am guessing the problem I have is because it isn't included with require() and hence jest.mock('httpsGet') fails.
Can anyone figure out if that is the case, and how I should fix it?
Live example at: https://repl.it/#SandraSchlichti/jest-playground-4
getSecureString.test.js
const getStatusCode = require('./getSectureString');
jest.mock('httpsGet');
describe("getSecureString ", () => {
describe('when httpsGet returns expected statusCode and body includes expected string', () => {
let result;
beforeAll(async () => {
httpsGet.mockResolvedValue({
statusCode: 200,
body: 'xyz secret_string xyz'
})
result = await getSecureString({
hostname: 'encrypted.google.com',
path: '/',
string: 'secret_string',
statusCode: 200,
aftaleId: 1234,
certFile: 1234,
keyFile: 1234,
timeout: 1000,
})
});
it('should return 1', () => {
expect(result).toEqual(1)
})
});
describe('when httpsGet returns expected statusCode and body includes expected string', () => {
let result;
beforeAll(async () => {
httpsGet.mockResolvedValue({
statusCode: 200,
body: 'xyz secret_string xyz'
})
result = await getSecureString({
hostname: 'encrypted.google.com',
path: '/',
string: 'not_secret_string',
statusCode: 201,
aftaleId: 1234,
certFile: 1234,
keyFile: 1234,
timeout: 1000,
})
});
it('should return 0', () => {
expect(result).toEqual(0)
})
});
describe("when an exception is thrown", () => {
let result;
beforeAll(async () => {
// mockRejected value returns rejected promise
// which will be handled by the try/catch
httpsGet.mockRejectedValue({
statusCode: 200,
body: 'xyz secret_string xyz'
})
result = await getSecureString();
})
it('should return -1', () => {
expect(result).toEqual(-1)
})
});
});
getSecureString.js
const fs = require('fs');
const https = require('https');
var uuid = require('uuid');
const {v4: uuidv4} = require('uuid');
module.exports = async function getSecureString(options) {
options = options || {};
options.hostname = options.hostname || {};
options.path = options.path || '/';
options.string = options.string || {};
options.statusCode = options.statusCode || {};
options.aftaleId = options.aftaleId || {};
options.certFile = options.certFile || {};
options.keyFile = options.keyFile || {};
options.timeout = options.timeout || 0;
const opt = {
hostname: options.hostname,
port: 443,
path: options.path,
method: 'GET',
cert: fs.readFileSync(options.certFile),
key: fs.readFileSync(options.keyFile),
headers: {'AID': options.aftaleId
},
};
opt.agent = new https.Agent(opt);
try {
const r = await httpsGet(opt, options.timeout);
return (r.statusCode === options.statusCode && r.body.includes(options.string)) ? 1 : 0;
} catch (error) {
console.error(error);
}
};
function httpsGet(opts, timeout) {
return new Promise((resolve, reject) => {
const req = https.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data.toString();
});
res.on('end', () => {
resolve({body, statusCode: res.statusCode});
});
});
req.setTimeout(timeout, function() {
req.destroy('error');
});
req.on('error', (e) => {
console.error(e);
reject(e);
});
});
};
A function that is used in the same module it was declared cannot be spied mocked, unless it's consistently as a method of some object, which is cumbersome and incompatible with ES modules:
module.exports.httpsGet = ...
...
module.exports.httpsGet(...);
Otherwise a function should be moved to another module that can mocked, or should be tested as is. In this case underlying API (https.get) can be mocked instead.

In azure functions (js) POST request after async/await call does not work

I am currently working with azure functions in javascript. In my function, I am first getting a specific element from my CosmoDB (this is the async/await part). I get a result and then I want to do an https POST request. However, my problem is, that it never finished the HTTPs request and I don't really know why. What am I doing wrong?
(As you can see I tried 2 different ways of doing the request, once with the standard https function and the commented out the part with npm request package. However, both ways won't work).
Here is my code:
const CosmosClient = require('#azure/cosmos').CosmosClient;
var https = require('https');
var http = require('http');
var request = require('request');
const endpoint = "someEndpoint";
const masterKey = "anymasterkey";
const database = {
"id": "Database"
};
const container = {
"id": "Container1"
};
const databaseId = database.id;
const containerId = container.id;
const client = new CosmosClient({
endpoint: endpoint,
auth: {
masterKey: masterKey
}
});
module.exports = function (context, req) {
const country = "de";
const bban = 12345678;
const querySpec = {
query: "SELECT * FROM Container1 f WHERE f.country = #country AND f.bban = #bban",
parameters: [{
name: "#country",
value: country
},
{
name: "#bban",
value: bban
}
]
};
getContainers(querySpec).then((results) => {
const result = results[0];
context.log('here before request');
var options = {
host: 'example.com',
port: '80',
path: '/test',
method: 'POST'
};
// Set up the request
var req = http.request(options, (res) => {
var body = "";
context.log('request');
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
context.res = body;
context.done();
});
}).on("error", (error) => {
context.log('error');
context.res = {
status: 500,
body: error
};
context.done();
});
req.end();
// request({
// baseUrl: 'someURL',
// port: 443,
// uri: 'someuri',
// method: 'POST',
// headers: {
// 'Content-Type': 'text/xml;charset=UTF-8',
// 'SOAPAction': 'someaction'
// },
// function (error, response, body) {
// context.log('inside request')
// if (error) {
// context.log('error', error);
// } else {
// context.log('response');
// }
// }
// })
})
};
async function getContainers(querySpec) {
const {container, database} = await init();
return new Promise(async (resolve, reject) => {
const {
result: results
} = await container.items.query(querySpec).toArray();
resolve(results);
})
}
async function init() {
const {
database
} = await client.databases.createIfNotExists({
id: databaseId
});
const {
container
} = await database.containers.createIfNotExists({
id: containerId
});
return {
database,
container
};
}
The last thing that happens is the print of "here before request". After that the function just does nothing until it timesout. So what am I doing wrong? Can't I just this combination of await/async and requests?
As commented you are not sending any data to the POST call. You need to have a req.write before the req.end
req.write(data);
req.end();
That is why the POST call is failing for you. After this fix, it should work

Categories