diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index aab9629605..45bdb07f80 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -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) => { diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 8ad132c198..d0c8e3732a 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -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); diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index c8a65fa86e..d4b283a97a 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -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"); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 550fdc2448..dcbf9b4642 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -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'); diff --git a/packages/twitter-oauth/twitter_server.js b/packages/twitter-oauth/twitter_server.js index d597f0db1e..393c3a3da3 100644 --- a/packages/twitter-oauth/twitter_server.js +++ b/packages/twitter-oauth/twitter_server.js @@ -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,