Change accounts and oauth to use async for Twitter account

This commit is contained in:
Edimar Cardoso
2022-08-25 17:53:19 -03:00
parent ef508087bb
commit 009cbc4baa
5 changed files with 86 additions and 40 deletions

View File

@@ -2,6 +2,14 @@ import crypto from 'crypto';
import querystring from 'querystring';
import urlModule from 'url';
// TODO Create helper for use in all oauth packages
const toFormUrlencoded = data => {
return Object.entries(data)
.map(([key, value]) => `${key}=${value}`)
.join('&');
};
// An OAuth1 wrapper around http calls which helps get tokens and
// takes care of HTTP headers
//
@@ -19,12 +27,12 @@ export class OAuth1Binding {
this._urls = urls;
}
prepareRequestToken(callbackUrl) {
async prepareRequestToken(callbackUrl) {
const headers = this._buildHeader({
oauth_callback: callbackUrl
});
const response = this._call('POST', this._urls.requestToken, headers);
const response = await this._call({method: 'POST', url: this._urls.requestToken, headers});
const tokens = querystring.parse(response.content);
if (! tokens.oauth_callback_confirmed)
@@ -35,7 +43,7 @@ export class OAuth1Binding {
this.requestTokenSecret = tokens.oauth_token_secret;
}
prepareAccessToken(query, requestTokenSecret) {
async prepareAccessToken(query, requestTokenSecret) {
// support implementations that use request token secrets. This is
// read by this._call.
//
@@ -50,7 +58,7 @@ export class OAuth1Binding {
oauth_verifier: query.oauth_verifier
});
const response = this._call('POST', this._urls.accessToken, headers);
const response = await this._call({ method: 'POST', url: this._urls.accessToken, headers });
const tokens = querystring.parse(response.content);
if (! tokens.oauth_token || ! tokens.oauth_token_secret) {
@@ -66,7 +74,7 @@ export class OAuth1Binding {
this.accessTokenSecret = tokens.oauth_token_secret;
}
call(method, url, params, callback) {
async callAsync(method, url, params, callback) {
const headers = this._buildHeader({
oauth_token: this.accessToken
});
@@ -75,14 +83,29 @@ export class OAuth1Binding {
params = {};
}
return this._call(method, url, headers, params, callback);
return this._call({ method, url, headers, params, callback });
}
async getAsync(url, params, callback) {
return this.callAsync('GET', url, params, callback);
}
async postAsync(url, params, callback) {
return this.callAsync('POST', url, params, callback);
}
call(method, url, params, callback) {
// Require changes when remove Fibers. Exposed to public api.
return Promise.await(this.callAsync(method, url, params, callback));
}
get(url, params, callback) {
// Require changes when remove Fibers. Exposed to public api.
return this.call('GET', url, params, callback);
}
post(url, params, callback) {
// Require changes when remove Fibers. Exposed to public api.
return this.call('POST', url, params, callback);
}
@@ -118,7 +141,7 @@ export class OAuth1Binding {
return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64');
};
_call(method, url, headers = {}, params = {}, callback) {
async _call({method, url, headers = {}, params = {}, callback}) {
// all URLs to be functions to support parameters/customization
if(typeof url === "function") {
url = url(this);
@@ -141,29 +164,55 @@ export class OAuth1Binding {
// Make a authorization string according to oauth1 spec
const authString = this._getAuthHeaderString(headers);
// Make signed request
try {
const response = HTTP.call(method, url, {
params,
return fetch(
method.toUpperCase() === 'POST' ? url : `${url}?${toFormUrlencoded(params)}`,
{
method,
headers: {
Authorization: authString
Authorization: authString,
...(method.toUpperCase() === 'POST' ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}),
},
...(method.toUpperCase() === 'POST' ? { body: toFormUrlencoded(params) } : {}),
}
)
.then((res) =>
res.text().then((content) => {
const responseHeaders = Array.from(res.headers.entries()).reduce(
(acc, [key, val]) => {
return { ...acc, [key.toLowerCase()]: val };
},
{}
);
const data = responseHeaders['content-type'].includes('application/json') ?
JSON.parse(content) : undefined;
return {
content: data ? '' : content,
data,
headers: { ...responseHeaders, nonce: headers.oauth_nonce },
redirected: res.redirected,
ok: res.ok,
statusCode: res.status,
};
})
)
.then((response) => {
if (callback) {
callback(undefined, response);
}
}, callback && ((error, response) => {
if (! error) {
response.nonce = headers.oauth_nonce;
return response;
})
.catch((err) => {
if (callback) {
callback(err);
}
callback(error, response);
}));
// We store nonce so that JWTs can be validated
if (response)
response.nonce = headers.oauth_nonce;
return response;
} catch (err) {
throw Object.assign(new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`),
{response: err.response});
}
};
console.log({ err });
throw Object.assign(
new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`),
{ response: err.response }
);
});
}
_encodeHeader(header) {
return Object.keys(header).reduce((memo, key) => {

View File

@@ -45,7 +45,7 @@ OAuth._requestHandlers['1'] = async (service, query, res) => {
});
// Get a request token to start auth process
oauthBinding.prepareRequestToken(callbackUrl);
await oauthBinding.prepareRequestToken(callbackUrl);
// Keep track of request token so we can verify it on the next step
OAuth._storeRequestToken(
@@ -91,10 +91,10 @@ OAuth._requestHandlers['1'] = async (service, query, res) => {
// subsequent call to the `login` method will be immediate.
// Get the access token for signing requests
oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
await oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret);
// Run service-specific handler.
const oauthResult = service.handleOauthRequest(
const oauthResult = await service.handleOauthRequest(
oauthBinding, { query: query });
const credentialToken = OAuth._credentialTokenFromQuery(query);

View File

@@ -17,8 +17,8 @@ const testPendingCredential = async (test, method) => {
authenticate: "https://example.com/oauth/authenticate"
};
OAuth1Binding.prototype.prepareRequestToken = () => {};
OAuth1Binding.prototype.prepareAccessToken = function() {
OAuth1Binding.prototype.prepareRequestToken = async () => {};
OAuth1Binding.prototype.prepareAccessToken = async function() {
this.accessToken = twitterfooAccessToken;
this.accessTokenSecret = twitterfooAccessTokenSecret;
};
@@ -27,7 +27,7 @@ const testPendingCredential = async (test, method) => {
try {
// register a fake login service
OAuth.registerService(serviceName, 1, urls, query => ({
OAuth.registerService(serviceName, 1, urls, async query => ({
serviceData: {
id: twitterfooId,
screenName: twitterfooName,
@@ -100,7 +100,7 @@ Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (wi
await testPendingCredential(test, "POST");
});
Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => {
Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => {
try {
OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64"));
await testPendingCredential(test, "GET");

View File

@@ -8,10 +8,7 @@ Package.onUse(api => {
api.use('random');
api.use('service-configuration', ['client', 'server']);
api.use('oauth', ['client', 'server']);
api.use([
'check',
'http@1.4.4 || 2.0.0'
], 'server');
api.use(['check', 'fetch'], 'server');
api.use('mongo');

View File

@@ -15,9 +15,9 @@ var urls = {
// https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials
Twitter.whitelistedFields = ['profile_image_url', 'profile_image_url_https', 'lang', 'email'];
OAuth.registerService('twitter', 1, urls, function(oauthBinding) {
var identity = oauthBinding.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true').data;
OAuth.registerService('twitter', 1, urls, async function(oauthBinding) {
const response = await oauthBinding.getAsync('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true');
const { data: identity } = response;
var serviceData = {
id: identity.id_str,
screenName: identity.screen_name,