Node set variable from text file - node.js

I am working on oauth2 for an API client, and I am having trouble getting the promise back in time to continue with the other calls to the API. My tokens expire every 30 minutes and my node runs every 10 minutes. I thought I could set a text file to the newest token each time the script runs, and grab it at the beginning and always have a good token to use for auth. The variable doesn't get set in time to make the calls, so the header has undefined next the Bearer. I can't figure out how to get the variable set before the call goes off.
Here is the script I am using to make the calls
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Axios = require("axios");
const moment = require("moment");
const config = require("../config/default");
const oauth = require("axios-oauth-client");
const fs = require('fs');
let accessToken;
var token = fs.readFile('token.txt', 'utf8', function(err, data) {
if (err) throw err;
return data;
});
const getOwnerCredentials = oauth.client(Axios.create(), {
url: 'url',
grant_type: 'password',
client_id: 'username',
client_secret: 'secret',
username: 'username',
password: 'password',
scope: 'scope'
});
const tokenCall = async () => {
const result = await getOwnerCredentials();
return result
}
const getToken = async () => {
const accessToken = await tokenCall();
const fs = require('fs')
fs.writeFile('/root/qt-cwsedona/token.txt', '', function(){console.log('done')})
fs.writeFile('token.txt', accessToken.access_token, function (err) {
if (err) return console.log(err);
});
};
getToken();
class Sedona {
constructor() {
this.baseUrl = config.sedonaUrl;
this.client = Axios.default.create({
baseURL: this.baseUrl,
auth: {
Authorization: 'Bearer ' + token
},
});
console.log(this.client);
}
getCustomerBillId(customer_id) {
return this.client.get('/CustomerBill/' + customer_id).then(response => {
let result = parseInt(response['data'][0]['CustomerBillId']);
if (isNaN(result)) {
return "";
}
else {
return result.toString();
}
}).catch(error => {
throw error;
});
}
Then I am this code to actually kick off these functions
const Sedona = require("./services/SedonaService");
Promise.all([
sedona.getCustomerBillId(customerId)
]).then(sedona_results => {

This is probably not the right way to do this, but I was able to set the default header for axios in the async function and it worked.
const getToken = async () => {
const accessToken = await tokenCall();
const token = accessToken.access_token;
Axios.defaults.headers.common['Authorization'] = 'Bearer '+accessToken.access_token;
};

Related

passport-apple Node.js login error - Failed to obtain access token

I am a junior engineer working in a start-up in Seoul, Korea.
In the current project, I am trying to use the passport module to develop apple login.
I have already finished developing google social login, but I faced some problems while trying to do the same with apple.
The problem is that I get an error that states : "Failed to obtain access token".
I got really confused that you have to use the POST method in order to get the profile info from apple.
Can someone please help me?? Thanks in advance!
IT would be wonderful if I could success. Thanks again
The main problem I am expecting is that
passport.authenticate('apple') calls the function which handles passport module for google that I have already developed.
I send the redirect url to Frontend, in order to open the browser in my application.
ROUTER.get('/apple/login', async function (req, res) {
const result = { status: 'N' };
const config = {
client_id: APPLE_AUTH.CLIENT_ID, // This is the service ID we created.
redirect_uri: APPLE_AUTH.REDIRECT_URI, // As registered along with our service ID
response_type: 'code id_token',
// state: 'origin:web', // Any string of your choice that you may use for some logic. It's optional and you may omit it.
scope: 'name email', // To tell apple we want the user name and emails fields in the response it sends us.
response_mode: 'form_post',
m: 11,
v: '1.5.4',
};
const queryString = Object.entries(config)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
const redirectUrl = `https://appleid.apple.com/auth/authorize?${queryString}`;
result['redirectUrl'] = redirectUrl;
result['status'] = 'Y';
res.json(result);
});
I get the url, and open the browser
async openAppleSignIn() {
const result = await this.authService.postAppleLogin();
// await this.authService.postAppleLogin();
if (result.data.status === 'Y') {
const { redirectUrl } = result.data;
console.log(redirectUrl);
if (this.deviceInfo.platform !== 'web') {
// this.browserService.openBrowser(redirectUrl);
window.open(redirectUrl, '_self');
console.log('enter');
} else {
const timer = setInterval(async () => {
if (this.newWindowForLogin.closed) {
clearInterval(timer);
}
}, 500);
const isChrome = this.commonService.checkBrowserIsChrome();
if (!this.newWindowForLogin || isChrome) {
// this.newWindowForLogin = window.open();
window.open(redirectUrl);
}
// this.newWindowForLogin.location.href = redirectUrl;
}
} else {
}
Apple strategy
const passport = require('passport');
// var AppleStrategy = require('passport-google-oauth20').Strategy;
var AppleStrategy = require('passport-apple').Strategy;
const jwt = require('jsonwebtoken');
// var fs = require('fs');
const APPLE_AUTH = require('../../../config/secret_key').get('APPLE_AUTH');
const UserModelService = require('../../api/user/model/user_model_service');
// const applePrivateKey = fs.readFileSync('config/AuthKey_Y8BG5JY7P3.p8');
// console.log(applePrivateKey);
module.exports = () => {
passport.use(
'apple',
new AppleStrategy(
{
clientID: APPLE_AUTH.CLIENT_ID,
teamID: APPLE_AUTH.TEAM_ID,
callbackURL: APPLE_AUTH.LOGIN_CALLBACK_URL,
keyID: APPLE_AUTH.KEY_ID,
privateKeyLocation: 'config/AuthKey_Y8BG5JY7P3.p8',
privateKeyString: APPLE_AUTH.privateKey,
passReqToCallback: true,
},
async (accessToken, refreshToken, idToken, profile, done) => {
try {
// console.log(profile._json.email);
// const socialLoginEmail = req.user.emails[0].value;
// console.log(email);
//화면에서 백으로
// console.log(profile._json.email);
// const user = await UserModelService.getUser(profile._json.email);
// console.log(user);
console.log('jwt', jwt.decode(idToken));
console.log('strategy', req);
done(null, idToken);
} catch (error) {
console.error(error);
// done(error);
}
},
),
);
};
index.js
const passport = require('passport');
const apple = require('./apple_auth');
const google = require('./google_auth');
module.exports = () => {
apple();
google();
};
5.Then, I intended to get the results in callback
ROUTER.post(
'/apple/callback',
passport.authenticate('apple', { failureRedirect: '/', session: false }),
async (req, res) => {
try {
console.log(res);
console.log(req);
} catch (err) {}
},
);
I customized the passport-apple usage into this way, not following the instructions in the passport docs, because the way they listed in the official document did not work for my code.
Thanks again, and I hope to find an answer in stack overflow!!

Mocking function to unit test Serverless Lambda

I am really struggling to understand unit testing within a Serverless Application. So I obviously have my handler, and I have a single Lambda function
const responses = require('../utils/jsonResponse');
const someConnector = require('../services/connectToService/connectToService');
module.exports = async (event) => {
const connectionParams = {
//some env variables
};
try {
const token = await someConnector.connectToService(connectionParams);
return responses.status(token, 200);
} catch (e) {
return responses.status(
`Issue connecting to service - ${e.message}`,
500,
);
}
};
So this Lambda function is pretty straight forward, gets some environment variables, and awaits a response from a service. It then returns the response.
So I have already done integration tests for this which is fine, but now I wanted to do a Unit test. I wanted to test this function in isolation, so essentially I want to mock connectToService to return my own responses.
So I came up with the following
require('dotenv').config();
const { expect } = require('chai');
const sinon = require('sinon');
let sandbox = require("sinon").createSandbox();
const LambdaTester = require('lambda-tester');
const handler = require('../../../handler');
const msConnector = require('../../../services/connectToService/connectToService');
describe('Testing handler', async (done) => {
describe('endpoint someEndpoint returns 200', () => {
it('Should resolve with 200', async () => {
before(() => {
sandbox = sinon.createSandbox();
sandbox.stub(msConnector, 'connectToService').resolves('some-token');
});
afterEach(() => {
sandbox.restore();
});
await LambdaTester(handler.someEndpoint)
.expectResult((result) => {
console.log(result);
expect(result.statusCode).to.equal(200);
});
});
});
done();
});
msConnector is the filename of the service, connectToService is the function name. What I want to do is not invoke this function, but return some-token when my Lambda calls it.
However, I have the console.log, and what I get from that is the real token, not some-token.
This tells me that the mocked function is really being called and executed and returning the real value.
So how can I mock this to make sure it returns some-token?
Thanks
Service function
const { DOMParser } = require('#xmldom/xmldom');
const axios = require('axios');
const { loginRequest } = require('./xml/login');
const connectToService = async (connectionParams) => {
//this injects config details into XML
const xmlRequest = loginRequest(
connectionParams.username,
connectionParams.password,
connectionParams.url,
);
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': xmlRequest.length,
},
};
const token = await axios
.post(connectionParams.msHost, xmlRequest, config)
.then((res) => {
const dom = new DOMParser().parseFromString(res.data, 'text/xml');
if (
dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0)
) {
return dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0).firstChild.nodeValue;
}
throw new Error('Invalid Username/Password');
})
.catch((err) => {
throw new Error(`Error making connection - ${err.message}`);
});
return token;
};
module.exports = {
connectToService,
};
The function connectToService may be not same copy between you mocked and called.
Because you overwrote a new object by module.exports = .... This causes you probably get different object for each require.
Try to do the below approach sharing the same object for all require.
const { DOMParser } = require('#xmldom/xmldom');
const axios = require('axios');
const { loginRequest } = require('./xml/login');
const connectToService = async (connectionParams) => {
//this injects config details into XML
const xmlRequest = loginRequest(
connectionParams.username,
connectionParams.password,
connectionParams.url,
);
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': xmlRequest.length,
},
};
const token = await axios
.post(connectionParams.msHost, xmlRequest, config)
.then((res) => {
const dom = new DOMParser().parseFromString(res.data, 'text/xml');
if (
dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0)
) {
return dom.documentElement
.getElementsByTagName('wsse:secToken')
.item(0).firstChild.nodeValue;
}
throw new Error('Invalid Username/Password');
})
.catch((err) => {
throw new Error(`Error making connection - ${err.message}`);
});
return token;
};
module.exports.connectToService = connectToService;

Using axios in a web action in IBM Cloud using node.js

I've been trying to get a simple web action to make an authenticated GET request to an API (i've removed the actual url, and secrets from example code).
I have run this successfully locally, but when I test the web action, it simply dies after logging "calling axios".
It doesn't report an error, and I have tried to implement a promise thinking that the thread was ending before the api responded, but no effect. Any pointers?
/**
*
* main() will be run when you invoke this action
*
* #param Cloud Functions actions accept a single parameter, which must be a JSON object.
*
* #return The output of this action, which must be a JSON object.
*
*/
function main(params) {
getData().then(function(result) {
console.log("In the THEN of original method call");
return "hello";
})
.catch(function(err) {
console.log("In catch of original method call");
});
}
function getData(){
const crypto = require('crypto');
const axios = require('axios');
const secretKey = "ENTER KEY HERE";
const apiId = 99999;
const apiBaseUrl = "https://acmewebservice.com";
const apiPath = "/customer/9";
const apiFullPath = apiBaseUrl + apiPath;
const sharedSecretHash = crypto.createHash('sha256').update(secretKey).digest('hex');
console.log("In getData");
var authToken = apiId + ":" + sharedSecretHash;
return new Promise((resolve, reject) => {
console.log("Calling axios");
axios.get(apiFullPath, {
headers: {
'Authentication': authToken
}
}).then(response => {
console.log("Did this work?")
var x = JSON.stringify(response.data);
console.log(x);
resolve(response);
})
.catch(error => {
console.log("In Catch")
console.log(error);
reject(error);
});
});
You don't need to re-wrap the axios call, it's already a promise.
The return is needed to make the engine effectively wait for the result of the async call.
function getData(){
const crypto = require('crypto');
const axios = require('axios');
const secretKey = "ENTER KEY HERE";
const apiId = 99999;
const apiBaseUrl = "https://acmewebservice.com";
const apiPath = "/customer/9";
const apiFullPath = apiBaseUrl + apiPath;
const sharedSecretHash = crypto.createHash('sha256').update(secretKey).digest('hex');
console.log("In getData");
var authToken = apiId + ":" + sharedSecretHash;
console.log("Calling axios");
return axios.get(apiFullPath, {
headers: {
'Authentication': authToken
}
}).then(response => {
console.log("Did this work?")
var x = JSON.stringify(response.data);
console.log(x);
return response;
})
.catch(error => {
console.log("In Catch")
console.log(error);
});

Axios request returns promise instead of the data itself

I have a function that checks the existence and validity of a token and makes a new request to get a new token if the token does not exists or is expired.
But the function always returns a promise instead of the data, and can not use it further.
const checkToken = (token) => {
if (!token || !(token.hasOwnProperty('expiry') && Object.prototype.toString.call(token.expiry) === '[object Date]') && !isExpired(token.expiry)) {
token = axios(getOptions()).then(resp => {
console.log(resp.data.expires_in);
let today = new Date();
let expiry = new Date(today.getTime() + 1000*(resp.data.expires_in));
return ({
'token': resp.data.access_token,
'expiry': expiry
});
});
return token;
}
return token;
}
checkToken is consumed like so:
const axios = require('axios');
let token;
exports.handler = (event, context, callback) => {
console.log(token);
console.log(event);
token = checkToken(token);
let options = getReqOptions(event,token);
console.log(options)
return options
};
Thanks for help!
Yes Axios return a promise but you're again returning a promise using then. You're using then value for token. That's why you're seeing token as a promise. You can simply use async/await to get same(more readable).
const checkToken = async (token) => {
if (!token || !(token.hasOwnProperty('expiry') && Object.prototype.toString.call(token.expiry) === '[object Date]') && !isExpired(token.expiry)) {
try {
const reponse = await axios(getOptions())
console.log(reponse.data.expires_in);
const expiry = new Date(today.getTime() + 1000 * (reponse.data.expires_in));
return {
token: resp.data.access_token,
expiry
}
} catch(err) {
console.log('Error while getting token ', err)
// handle error here if any
}
}
return token
}
const axios = require('axios');
let token;
exports.handler = async (event, context, callback) => {
console.log(token);
console.log(event);
try {
token = await checkToken(token);
let options = getReqOptions(event, token);
console.log(options)
return options
} catch (err) {
console.log(err)
}
};

Issue with Google oAuth2 callback using Firebase functions

I would like use Firebase Functions to use the Google Developer API. Authentification is required to use this API.
I follow the doc: https://github.com/googleapis/google-api-nodejs-client
I have some troubles to get the authorization code in the callback url.
var {google} = require('googleapis');
google.options({ auth: oauth2Client });
var oauth2Client = new google.auth.OAuth2(
'XXXX.apps.googleusercontent.com',
'XXXX',
'https://us-central1-XXXX.cloudfunctions.net/oauth2callback'
);
function generateAuthenticationUrl() {
return oauth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: 'https://www.googleapis.com/auth/androidpublisher'
});
}
exports.oauth2Callback = functions.https.onRequest((req, res) => {
console.log(req.query.code);
const code = req.query.code;
//do something
return null;
});
exports.hello = functions.https.onRequest((req, res) => {
var url = generateAuthenticationUrl();
console.log(url);
//-> url print in the console is : https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fandroidpublisher&response_type=code&client_id=XXXXX-XXX.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fus-central1-XXX.cloudfunctions.net%2Foauth2callback
res.redirect(url);
});
Redirect url is set in the Google Console Developer:
When I call the url https://us-central1-XXX.cloudfunctions.net/hello, I got "Error: could not handle the request" and "finished with status: 'timeout'" in the Firebase logs.
What's wrong?
I found a solution.
Full code using JWT to authenticate, then get the list of app's reviews:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
var {google} = require('googleapis');
const serviceAccount = require('./client_secret.json');
const { JWT } = require('google-auth-library');
const getAuthorizedClient = () => new JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: ['https://www.googleapis.com/auth/androidpublisher']
});
const getAndroidpublisher = () => google.androidpublisher({
version: 'v3',
auth: getAuthorizedClient()
});
const requestProductValidation = () => new Promise((resolve, reject) => {
getAndroidpublisher().reviews.list({
packageName: "com.my.packagename"
}, (err, response) => {
if (err) {
console.log(`The API returned an error: ${err}`);
resolve({status: "Error"});
} else {
return resolve(response);
}
});
});
exports.hello = functions.https.onRequest((req, res) => {
return requestProductValidation();
});

Resources