Can't use proxy to do JWT authentication with googleapis - node.js

I have a NodeJS API working behind a corporate proxy and I have been trying to do authentication with Google with their NodeJS client:
const google = require('googleapis');
function getServiceAccountInfo() {
return {
type: 'service_account',
project_id: 'XXXXX',
private_key_id: 'XXXXXX',
private_key: 'XXXX',
client_email: 'XXXX',
client_id: 'XXXX',
auth_uri: 'XXXX',
token_uri: 'XXXX',
auth_provider_x509_cert_url: 'XXXX',
client_x509_cert_url: 'XXXXX'
};
}
const SCOPES = 'https://www.googleapis.com/auth/firebase.messaging';
let proxy2 = {
host: 'proxy.hkcsl.net',
port: 8080
};
const proxy3 = {
proxy: 'http://172.25.2.6:8080'
}
const proxy4 = {
proxy: {
host: '172.25.2.6',
port: 8080,
auth: {
username: '',
password: '',
},
}
}
process.env.HTTPS_PROXY = 'https://172.25.2.6:8080';
process.env.https_proxy = 'https://172.25.2.6:8080';
process.env.HTTP_PROXY = 'http://172.25.2.6:8080';
process.env.http_proxy = 'http://172.25.2.6:8080';
google.options({
proxy: proxy4.proxy
});
const key = getServiceAccountInfo();
const jwtClient = new google.auth.JWT(
key.client_email,
undefined, // null,
key.private_key,
SCOPES,
undefined, // null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
console.error(err);
return;
}
console.log(tokens.access_token);
});
However, no matter how I configure the proxy option, I still either get a timeout error or something like
ERROR Error: write EPROTO 101057795:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:openssl\ssl\s23_clnt.c:827:
Some people managed to fix it by changing the port to 443 but it doesn't apply to my case since that port is not available to me inside the server. And I've already referenced on the discussions and solutions proposed here:
StackOverflow: node-request - Getting error “SSL23_GET_SERVER_HELLO:unknown protocol”
googleapis: There is no facility for passing a proxy to the oauth2client
Axios: Request to HTTPS with HTTP proxy fails
Using jwtClient.authenticate() behind a proxy results in ETIMEDOUT error
It also seems like Vue is also encountering a similar problem yesterday, they changed from using axios to using request instead. Is there a similar workaround for googleapis too?

After referencing on the workaround regarding the proxy issues with axios, which google-api-nodejs-client (i.e. googleapis) is using, and crawling through the source code of google-auth-library-nodejs (jwtclient.ts) and node-gtoken (index.ts), I wrote the following method to request token from Google manually as a temporary workaround:
const axios = require('axios-https-proxy-fix');
const querystring = require('querystring');
const jws = require('jws');
const GOOGLE_TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
const GOOGLE_REVOKE_TOKEN_URL =
'https://accounts.google.com/o/oauth2/revoke?token=';
const SCOPES = 'https://www.googleapis.com/auth/firebase.messaging';
const PROXY = {
host: '172.25.2.6',
port: 8080,
auth: {
username: '',
password: '',
}
};
function getServiceAccountInfo() {
return {
type: 'service_account',
project_id: 'XXXX',
private_key_id: 'XXXX',
private_key: 'XXXX',
client_email: 'XXXX',
client_id: 'XXXX',
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
token_uri: 'https://accounts.google.com/o/oauth2/token',
auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
client_x509_cert_url: 'XXXX'
};
}
function requestToken(client_email, private_key) {
const iat = Math.floor(new Date().getTime() / 1000);
const additionalClaims = {};
const payload = Object.assign(
{
iss: client_email,
scope: SCOPES,
aud: GOOGLE_TOKEN_URL,
exp: iat + 3600,
iat,
sub: undefined
},
additionalClaims);
const signedJWT =
jws.sign({header: {alg: 'RS256'}, payload, secret: private_key});
return axios({
method: 'post',
url: GOOGLE_TOKEN_URL,
data: querystring.stringify({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: signedJWT
}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
proxy: PROXY
})
.then(r => {
const body = r.data;
console.log(body);
return body.access_token;
})
.catch(e => {
const body = (e.response && e.response.data) ? e.response.data : {};
let err = e;
if (body.error) {
const desc =
body.error_description ? `: ${body.error_description}` : '';
err = new Error(`${body.error}${desc}`);
}
throw err;
});
}
const key = getServiceAccountInfo();
requestToken(key.client_email, key.private_key);
Reference: GitHub

The best solution I stumbled upon (on macOS) is to use proxychains4 with the following config
strict_chain
tcp_read_time_out 15000
tcp_connect_time_out 8000
localnet 127.0.0.0/255.0.0.0
[ProxyList]
http {your proxy ip address} {your proxy port}

Related

Nest JS retryAttempts on undefined error driving me nuts

I keep getting this error upon starting my nestjs application:
TypeError: Cannot read property 'retryAttempts' of undefined
The error is originating from my common config file, which was loaded into my configurations in app.module. I had tried call an await function in it and it gave me the retryattempts undefined error.
However, after commenting out that line, the error is resolved.
Context: I am trying to load variables from a pcf config server in place of env variables.
getEnvfrompcfconfig
import { getPcfServerClientCredentials } from '#common/configurations/pcf-credentials.config';
import axios, { AxiosResponse } from 'axios';
import * as client from 'cloud-config-client';
import { getAppEnv } from 'cfenv';
export const getEnvFromPcfConfigServer = async () => {
const appEnv = getAppEnv();
const pcfCredentials = getPcfServerClientCredentials();
const { client_secret, client_id, access_token_uri, uri } = pcfCredentials;
const accessToken: string = await axios
.post(
access_token_uri,
new URLSearchParams({
grant_type: 'client_credentials', //gave the values directly for testing
client_id,
client_secret,
}),
)
.then((res: AxiosResponse) => {
return res.data.access_token;
})
.catch((e) => console.log('cannot get access token', e));
const options: client.Options = {
name: 'application',
rejectUnauthorized: false,
endpoint: uri,
profiles: [appEnv.app.space_name || 'dev'],
headers: { authorization: `bearer ${accessToken}` },
};
const config = await client.load(options);
const url = config.get('baseurl');
return url;
};
config file
import { ScheduleLog as ScheduleLogEntity } from '#scheduleLog/entities/schedule-log.entity';
import { CustomNamingStrategy } from '#common/helpers/database/database.helper';
import { Schedule as ScheduleEntity } from 'src/schedule/entities/schedule.entity';
import { ICommonConfiguration } from '#common/interfaces/configurations/common-configuration';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { getCreds } from '#common/configurations/credhub.config';
export const configuration = async () => {
const {
APP_PORT,
ENVIRONMENT,
MARIADB_HOST,
MARIADB_PORT,
MARIADB_USERNAME,
MARIADB_PASSWORD,
MARIADB_DATABASE,
AUTH_PASSWORD,
EUREKA_AWS_URL,
EUREKA_UAA,
EUREKA_PCF,
EUREKA_DATALOADER,
EUREKA_COMMS,
EUREKA_PROXY_HOST,
EUREKA_PROXY_PORT,
EUREKA_FULFILMENT,
} = process.env;
const creds = getCreds();
const thing = await getEnvFromPcfConfigServer(); //<-- line that is causing problem
console.log('thing', thing);
return {
APP: {
PORT: Number(APP_PORT),
ENVIRONMENT,
},
UAAPASSWORD: creds.uaaPassword || AUTH_PASSWORD,
DATABASE: {
type: 'mysql',
host: MARIADB_HOST,
port: Number(MARIADB_PORT),
username: MARIADB_USERNAME,
password: MARIADB_PASSWORD || creds.mariaDbPassword,
database: MARIADB_DATABASE,
entities: [ScheduleEntity, ScheduleLogEntity],
synchronize: false,
namingStrategy: new CustomNamingStrategy(),
// autoLoadEntities: true,
},
HTTPMODULE: {
BASEURL: {
AWS: EUREKA_AWS_URL,
UAA: EUREKA_UAA,
PCF: EUREKA_PCF,
DATALOADER: 'hi',
COMMS: EUREKA_COMMS,
FULFILMENT: EUREKA_FULFILMENT,
},
PROXYAGENT: new HttpsProxyAgent({
host: EUREKA_PROXY_HOST,
port: EUREKA_PROXY_PORT,
}),
},
};
};
App.module
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
envFilePath: `.env.stage.${appEnv.app.space_name || 'local'}`,
}),
I find this very weird. Anyone able to help?

Axios POST request to Twillio returns with an Authentication Error?

in Node.js, I am trying to send a POST request with Axios to Twilio and send an SMS message to my phone. But I am getting an 'error: Authentication Error - No credentials provided ? Here is the code:
const body = {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Body: 'hi from vsc',
To: toNumber,
From: fromNumber,
};
const headers = {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Basic ${accountSID}:${authToken}`,
};
exports.axios = () => axios.post(`https://api.twilio.com/2010-04-01/Accounts/${accountSID}/Messages.json`, body, headers).then((res) => {
console.log(res, 'res');
}).catch((err) => {
console.log(err);
});
I also tried to use the same parameters with POSTMAN and the POST request is successful. I also tried to encode my authorization username and password to Base 64, but with no success.
I wrote to Twilio customer help but haven`t received any replies yet.
Axios makes an auth option available that takes an object with username and password options. You can use this with the username set to your account SID and password set to your auth token.
The headers object should be sent as the headers parameter of a config object in the third parameter to axios.post. Like so:
const params = new URLSearchParams();
params.append('Body','Hello from vcs');
params.append('To',toNumber);
params.append('From',fromNumber);
const headers = {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
};
exports.axios = () => axios.post(
`https://api.twilio.com/2010-04-01/Accounts/${accountSID}/Messages.json`,
params,
{
headers,
auth: {
username: accountSID,
password: authToken
}
}
}).then((res) => {
console.log(res, 'res');
}).catch((err) => {
console.log(err);
});
Headers is actually a field of config, try something like this:
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
Authorization: `Basic ${accountSID}:${authToken}`,
}
}
axios.post(URL, data, config).then(...)
Or this (general example calling a Twilio endpoint)
const axios = require('axios');
const roomSID = 'RM1...';
const participantSID = 'PA8...';
const ACCOUNT_SID = process.env.ACCOUNT_SID;
const AUTH_TOKEN = process.env.AUTH_TOKEN;
const URL = "https://insights.twilio.com/v1/Video/Rooms/"+roomSID+"/Participants/"+participantSID;
axios({
method: 'get',
url: URL,
auth: {
username: ACCOUNT_SID,
password: AUTH_TOKEN
}
})
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error);
});
Working code:
const params = new URLSearchParams();
params.append('Body','Hello from vcs');
params.append('To',toNumber);
params.append('From',fromNumber);
exports.axios = () => axios.post(
`https://api.twilio.com/2010-04-01/Accounts/${accountSID}/Messages.json`,
params,
{
auth: {
username: accountSID,
password: authToken,
},
},
).then((res) => {
console.log(res, 'res');
}).catch((err) => {
console.log(err);
});
The previous solutions did not work for me. I encountered either the Can't find variable: btoa error or A 'To' phone number is required..
Using qs worked for me:
import qs from 'qs';
import axios from 'axios';
const TWILIO_ACCOUNT_SID = ""
const TWILIO_AUTH_TOKEN = ""
const FROM = ""
const TO = ""
const sendText = async (message: string) => {
try {
const result = await axios.post(
`https://api.twilio.com/2010-04-01/Accounts/${TWILIO_ACCOUNT_SID}/Messages.json`,
qs.stringify({
Body: message,
To: TO,
From: FROM,
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
auth: {
username: TWILIO_ACCOUNT_SID,
password: TWILIO_AUTH_TOKEN,
},
},
);
console.log({result});
} catch (e) {
console.log({e});
console.log({e: e.response?.data});
}
};

How to access and store the token and use it in other requests with Node.js?

I'm using the code below to authenticate myself and get a token.
It works perfectly.
const config = {
headers: { Authorization: 'Bearer ${token}' }
};
const bodyParameters = {
username: "teste",
senha: "123456"
};
axios.post(
'url ...',
bodyParameters,
config
).then(console.log).catch(console.log);
And the token below:
My question is: how to access this token and use it in another request?
For example, to get data from this API.
const config2 = {
headers: { Authorization: `Bearer ${token}` }
};
const bodyParameters2 = {
cpf: "12345",
hash: "912409kskllj1u2ou1p4124821"
};
axios.get(
'url..',
bodyParameters,
config
).then(console.log).catch(console.log);
In this case, I get error: 401 (unauthorized).
--UPDATE--
Update based on interceptors.
var LocalStorage = require('node-localstorage').LocalStorage,
localStorage = new LocalStorage('./scratch');
const api = axios.create({
baseURL: 'http://www.externalapi.com/', // this way you setup the static part of it and just call the instance with the rest of the specific route
});
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
exports.novaSolicitacao = (req, res) => {
const bodyParameters = {
username: "teste",
senha: "123456"
};
api.post(
'/login',
bodyParameters,
).then(console.log).catch(console.log);
const bodyParameters2 = {
cpf: "123456",
aceite: true,
};
app.get(
'/search',
bodyParameters2,
config
).then(console.log).catch(error);
I made this update to the code, but I get this error:
data: {
statusCode: 404,
error: 'SESSIONTOKENEXPIRED',
message: 'O token expirou.'
}
},
isAxiosError: true,
toJSON: [Function: toJSON]

Twitter search API getting errors

I am trying to collect twitter search results.
I am using twitter search API with Nodejs.
Started with "quick start" given in twitter api site https://developer.twitter.com/en/docs/labs/recent-search/quick-start, but I am keeping get errors from my request.
this is my code:
const https = require('https');
const request = require('request');
const util = require('util');
const get = util.promisify(request.get);
const post = util.promisify(request.post);
const consumer_key = 'xxxxxxx'; // Add your API key here
const consumer_secret = 'xxxxxx'; // Add your API secret key here
const bearerTokenURL = new URL('https://api.twitter.com/oauth2/token');
const searchURL = new URL('https://api.twitter.com/labs/2/tweets/search');
async function bearerToken (auth) {
const requestConfig = {
url: bearerTokenURL,
auth: {
user: consumer_key,
pass: consumer_secret,
},
form: {
grant_type: 'client_credentials',
},
};
const response = await post(requestConfig);
return JSON.parse(response.body).access_token;
}
(async () => {
let token;
const query = 'obama';
const maxResults = 10;
try {
// Exchange your credentials for a Bearer token
token = await bearerToken({consumer_key, consumer_secret});
} catch (e) {
console.error(`Could not generate a Bearer token. Please check that your credentials are correct and that the Filtered Stream preview is enabled in your Labs dashboard. (${e})`);
process.exit(-1);
}
const requestConfig = {
url: searchURL,
qs: {
query: query,
max_results: maxResults,
format: 'compact',
},
auth: {
bearer: token,
},
headers: {
'User-Agent': 'LabsRecentSearchQuickStartJS',
},
json: true,
};
try {
const res = await get(requestConfig);
console.log(res.statusCode);
console.log(res);
if (res.statusCode !== 200) {
throw new Error(res.json);
return;
}
console.log(res.json);
} catch (e) {
console.error(`Could not get search results. An error occurred: ${e}`);
process.exit(-1);
}
})();
my error:
body: {
errors: [ [Object] ],
title: 'Invalid Request',
detail: 'One or more parameters to your request was invalid.',
type: 'https://api.twitter.com/labs/2/problems/invalid-request' }, [Symbol(kCapture)]: false } Could not get search results. An
error occurred: Error
There's a bug in this script which we need to fix. If you remove the line format: 'compact', then this will work - that parameter is no longer valid in Labs v2.

Hawk authentication error with #hapi/hawk

I'm trying a basic sample to authenticate a request using Hawk scheme and Hapi but the hawk plugin fails because it is trying to access a payload property that does not exist:
Error:
Server started listening on http://localhost:3000
Debug: internal, implementation, error
TypeError: Cannot read property 'payload' of undefined
at Object.authenticate (D:\TEST\node\sample3\node_modules\#hapi\hawk\lib\plugin.js:45:45)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:189:7)
The relevant Hawk plugin code where the error is generated:
...
if (request.route.settings.auth.payload) {
request.events.once('peek', (chunk) => {
...
Server code:
const Hapi = require('#hapi/hapi');
const Hawk = require('#hapi/hawk');
const credentials = {
John: {
key: 'secret',
algorithm: 'sha256'
}
};
const getCredentialsFunc = function (id) {
return credentials[id];
};
const start = async () => {
const server = Hapi.server({ port: 3000, host: 'localhost' });
await server.register(Hawk);
server.auth.strategy('default', 'hawk', { getCredentialsFunc });
server.auth.default('default');
server.route({
method: 'GET',
path: '/',
handler: function (request, h) {
return 'Welcome';
}
});
await server.start();
console.log('Server started listening on %s', server.info.uri);
};
start();
Client code:
const Request = require('request');
const Hawk = require('#hapi/hawk');
const credentials = {
id: 'John',
key: 'secret',
algorithm: 'sha256'
};
const requestOptions = {
uri: 'http://localhost:3000/',
method: 'GET',
headers: {}
};
const { header } = Hawk.client.header(requestOptions.uri, requestOptions.method, { credentials: credentials, ext: 'some-app-data' });
requestOptions.headers.Authorization = header;
Request(requestOptions, function (error, response, body) {
const isValid = Hawk.client.authenticate(response, credentials, header.artifacts, { payload: body });
console.log(`${response.statusCode}: ${body}` + (isValid ? ' (valid)' : ' (invalid)'));
});
I created a PR for this exact problem :)
https://github.com/hapijs/hawk/pull/259

Resources