Skip to content

Instantly share code, notes, and snippets.

@adamjmcgrath
Last active November 12, 2016 09:12
Show Gist options
  • Save adamjmcgrath/2640591d937e6a77c3cc2751f068e884 to your computer and use it in GitHub Desktop.
Save adamjmcgrath/2640591d937e6a77c3cc2751f068e884 to your computer and use it in GitHub Desktop.
OAuth 1.0a signature generator using Ramda
const R = require('ramda')
const hmacsha1 = require('hmacsha1');
// Map arguments to OAuth keys.
const OAUTH_KEYS = {
consumerKey: 'oauth_consumer_key',
signatureMethod: 'oauth_signature_method',
oauthToken: 'oauth_token',
version: 'oauth_version',
nonce: 'oauth_nonce',
now: 'oauth_timestamp',
};
// For RFC 3986 Compliant URI Encoding.
const encode = R.pipe(
encodeURIComponent,
R.replace(/\!/g, '%21'),
R.replace(/\'/g, '%27'),
R.replace(/\(/g, '%28'),
R.replace(/\)/g, '%29'),
R.replace(/\*/g, '%2A')
);
// Create a random alphanumeric string of a given length.
const getNonce = R.pipe(
R.times(
R.partial(
chars => chars[Math.floor(Math.random() * chars.length)],
[ '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ]
)
),
R.join('')
);
// Return the current timestamp in seconds
const getNow = R.pipe(
Date.now,
R.flip(R.divide)(1000),
Math.floor
);
// Sort a list of pairs by the first item then the second.
const sortPairs = R.compose(
R.sortBy(R.prop(0)),
R.sortBy(R.prop(1))
);
// Encode a list of pairs.
const encodePairs = R.map(R.map(encode));
// Convert a list of pairs into a query string.
const toParams = R.pipe(
R.map(R.join('=')),
R.join('&')
);
// Join key items with an ampersand and hash with given data.
const hash = R.pipe(
R.join('&'),
R.curry(R.binary(hmacsha1))
);
// Rename an objects keys and remove unknown keys.
const renameKeys = R.curry((keysMap, obj) => {
return R.reduce((acc, key) => {
acc[keysMap[key] || key] = obj[key];
return acc;
}, {}, R.keys(keysMap));
});
// Rename an objects OAuth keys.
const renameOAuthKeys = renameKeys(OAUTH_KEYS);
// reverse concat
const fconcat = R.flip(R.concat);
// Prepend the method and url to the base string.
const prependRequest = R.pipe(
R.adjust(R.toUpper, 0),
R.adjust(R.compose(fconcat('&'), encode), 1),
R.join('&'),
R.concat
)
// Sort and encode the OAuth params.
const encodeParams = R.pipe(
R.toPairs,
encodePairs,
sortPairs,
toParams,
encode
);
// Merge 2 objects into a given object.
const mergeParams = R.compose(R.merge, R.merge);
// Create an OAuth 1.0 signature.
module.exports = ({
consumerKey,
consumerSecret,
oauthToken,
oauthSecret,
url,
params={},
data={},
method='POST',
version='1.0',
signatureMethod='HMAC-SHA1',
nonceSize=32,
nonce=getNonce(nonceSize),
now=getNow(),
}) => R.pipe(
renameOAuthKeys,
mergeParams(params, data),
encodeParams,
prependRequest([method, url]),
hash([consumerSecret, oauthSecret])
)({ consumerKey, signatureMethod, oauthToken, version, nonce, now, })
const assert = require('assert');
const signature = require('../signature.js');
describe('Signature', function() {
it('should calculate the oauth signature', function() {
const url = 'https://api.twitter.com/1/statuses/update.json';
const params = { include_entities: true };
const consumerKey = 'xvz1evFS4wEEPTGEFPHBog';
const consumerSecret = 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw';
const oauthToken = '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb';
const oauthSecret = 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE';
const data = { status: 'Hello Ladies + Gentlemen, a signed OAuth request!' };
const nonce = 'kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg';
const now = 1318622958;
const expected = 'tnnArxj06cWHq44gCs1OSKk/jLY=';
const actual = signature({
url,
params,
consumerKey,
consumerSecret,
oauthToken,
oauthSecret,
data,
nonce,
now
});
assert.equal(actual, expected);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment