Skip to content

Commit

Permalink
Refactored and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniele Bernardi committed Mar 12, 2020
1 parent 8f60d4b commit e5f2e5b
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 58 deletions.
1 change: 0 additions & 1 deletion bearer-token/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const bearerToken = async (auth) => {
const response = await post(requestConfig);
if (response.statusCode !== 200) {
throw new BearerTokenError(response);
return null;
}

_bearerToken = response.body.access_token;
Expand Down
45 changes: 5 additions & 40 deletions client/index.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,20 @@
const needle = require('needle');
const crypto = require('crypto');
const OAuth = require('oauth-1.0a');
const oauthSignature = require('oauth-sign');
const package = require('../package.json');
const { URL, URLSearchParams } = require('url');
const qs = require('qs');
const oauth = require('../oauth');
const { oauth } = require('../oauth');
needle.defaults({user_agent: `${package.name}/${package.version}`})

const oauthHeader = (url, method, body, options) => {
const oa = new OAuth({
consumer: {
key: options.oauth.consumer_key,
secret: options.oauth.consumer_secret,
},
signature_method: 'HMAC-SHA1',
hash_function: (base_string, key) => crypto.createHmac('sha1', key).update(base_string).digest('base64'),
});

const token = {
key: options.oauth.token,
secret: options.oauth.token_secret,
};

console.log(token);

const urlObject = new URL(url);
let data = {};
for (const key of urlObject.searchParams.keys()) {
data[key] = urlObject.searchParams.get(key);
const auth = (method, url, body, options) => {
if (Object.prototype.toString.call(options) !== '[object Object]') {
return {};
}


const request = {
url: url,
method: method,
data: data
};


return oa.toHeader(oa.authorize(request, token))['Authorization'];

}

const auth = (method, url, body, options) => {
options.headers = options.headers || {};
if (options.oauth) {
options.headers.authorization = oauth(url, method, body, options);
}

if (options.bearer) {
} else if (options.bearer) {
options.headers.authorization = `Bearer ${options.bearer}`;
}

Expand Down
File renamed without changes.
File renamed without changes.
30 changes: 20 additions & 10 deletions oauth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,37 @@ const encode = (str) =>
.replace(/\)/g,'%29')
.replace(/'/g,'%27');

const nonce = (length = 16) => crypto.randomBytes(length).toString('base64');
const timestamp = () => Math.floor(Date.now() / 1000).toString();
const oAuthFunctions = {
nonceFn: null,
timestampFn: null,
};

const parameters = (url, body, auth) => {
const nonceFn = (length = 16) => crypto.randomBytes(length).toString('base64');
const timestampFn = () => Math.floor(Date.now() / 1000).toString();

const setNonceFn = (fn) => oAuthFunctions.nonceFn = fn;
const setTimestampFn = (fn) => oAuthFunctions.timestampFn = fn;

const parameters = (url, body = {}, auth) => {
let params = {};

const urlObject = new URL(url);
for (const key of urlObject.searchParams.keys()) {
params[key] = urlObject.searchParams.get(key);
}

if (Object.prototype.toString.call(body) === '[object Object]') {
for (const key of Object.keys(body)) {
params[key] = encode(body[key]);
}
if (Object.prototype.toString.call(body) !== '[object Object]') {
throw TypeError('OAuth parameters: body must be an object');
}

for (const key of Object.keys(body)) {
params[key] = encode(body[key]);
}

params.oauth_consumer_key = auth.consumer_key;
params.oauth_token = auth.token;
params.oauth_nonce = nonce();
params.oauth_timestamp = timestamp();
params.oauth_nonce = oAuthFunctions.nonceFn() || nonceFn();
params.oauth_timestamp = oAuthFunctions.timestampFn() || timestampFn();
params.oauth_signature_method = 'HMAC-SHA1';
params.oauth_version = '1.0';
return params;
Expand Down Expand Up @@ -88,4 +98,4 @@ const oauth = (url, method, body, {oauth}) => {
return signatureHeader;
}

module.exports = oauth;
module.exports = {oauth, setNonceFn, setTimestampFn};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twitter-autohook",
"version": "1.4.2",
"version": "1.5.0",
"description": "Automatically setup and serve webhooks for the Twitter Account Activity API",
"repository": {
"type": "git",
Expand All @@ -22,5 +22,6 @@
"dotenv": "^8.0.0",
"needle": "^2.3.3",
"ngrok": "^3.2.1",
"nock": "^12.0.2"
}
}
43 changes: 43 additions & 0 deletions test/bearer-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const nock = require('nock');
const bearerToken = require('../bearer-token');
const assert = require('assert');

const tokenValue = 'access_token_from_api';

(async () => {
const scope = nock('https://api.twitter.com')
.post('/oauth2/token')
.reply(200, {token_type: 'bearer', access_token: 'access_token_from_api'});

let token = null;
await assert.doesNotReject(async () => {
token = await bearerToken({
consumer_key: 'test_consumer_key',
consumer_secret: 'test_consumer_secret',
});
});

assert.equal(token, tokenValue);
scope.done();
})();

(async () => {
const scope = nock('https://api.twitter.com')
.post('/oauth2/token')
.reply(503, {
errors: [{
message: 'test error',
code: 1337,
}],
});

await assert.rejects(async () => {
const token = await bearerToken({
consumer_key: 'test_consumer_key',
consumer_secret: 'test_consumer_secret',
});
}, {
name: 'BearerTokenError',
});
scope.done();
})();
12 changes: 6 additions & 6 deletions test/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,42 @@ const message = 'test error (HTTP status: 200, Twitter code: 1337)';
const rateLimitMessage = 'You exceeded the rate limit for /example. Wait until rate limit resets and try again.';

assert.throws(() => {
throw new TwitterError(response)
throw new TwitterError(response);
}, {
name: 'TwitterError',
message: message,
});

assert.throws(() => {
throw new BearerTokenError(response)
throw new BearerTokenError(response);
}, {
name: 'BearerTokenError',
message: message,
});

assert.throws(() => {
throw new UserSubscriptionError(response)
throw new UserSubscriptionError(response);
}, {
name: 'UserSubscriptionError',
message: message,
});

assert.throws(() => {
throw new WebhookURIError(response)
throw new WebhookURIError(response);
}, {
name: 'WebhookURIError',
message: message,
});

assert.throws(() => {
throw new TooManySubscriptionsError(response)
throw new TooManySubscriptionsError(response);
}, {
name: 'TooManySubscriptionsError',
message: message,
});

assert.throws(() => {
throw new RateLimitError(response)
throw new RateLimitError(response);
}, {
name: 'RateLimitError',
message: rateLimitMessage,
Expand Down
35 changes: 35 additions & 0 deletions test/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const assert = require('assert');
const oauthInstance = require('../oauth');
const URL = require('url').URL;

const oAuthConfig = {
oauth: {
consumer_key: 'consumer_key',
consymer_secret: 'consumer_secret',
token: 'test_user_token',
token_secret: 'test_user_token_secret',
},
};

const signatures = {
baseUrl: 'OAuth oauth_consumer_key="consumer_key", oauth_nonce="GXjhffMbAMz2qblDzzgYbP4ZkfPp7RGmhry5Upatw", oauth_signature="RGpMnOI1WxKDZtgORbi32uc8P%2BY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1583967563", oauth_token="test_user_token", oauth_version="1.0"',
urlWithParams: 'OAuth oauth_consumer_key="consumer_key", oauth_nonce="xaCqEY8Ed9vnfFvZJsu8AjSF1", oauth_signature="LYT0mRAq62MXsnxsTOppEtViTUs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1583967750", oauth_token="test_user_token", oauth_version="1.0"',
};

const baseUrl = new URL('https://api.twitter.com/1.1/account/verify_credentials.json')
oauthInstance.setNonceFn(() => 'GXjhffMbAMz2qblDzzgYbP4ZkfPp7RGmhry5Upatw');
oauthInstance.setTimestampFn(() => '1583967563');
assert.throws(() => {
// non-object body throws TypeError
oauthInstance.oauth(baseUrl, 'GET', '', oAuthConfig);
}, {
name: 'TypeError',
});
assert.equal(oauthInstance.oauth(baseUrl, 'GET', {}, oAuthConfig), signatures.baseUrl);

const urlWithParams = new URL('https://api.twitter.com/1.1/account/verify_credentials.json')
urlWithParams.searchParams.append('param_test', '1');
urlWithParams.searchParams.append('example', '1');
oauthInstance.setNonceFn(() => 'xaCqEY8Ed9vnfFvZJsu8AjSF1');
oauthInstance.setTimestampFn(() => '1583967750');
assert.equal(oauthInstance.oauth(urlWithParams, 'GET', {}, oAuthConfig), signatures.urlWithParams);

0 comments on commit e5f2e5b

Please sign in to comment.