Last active
November 12, 2016 09:12
-
-
Save adamjmcgrath/2640591d937e6a77c3cc2751f068e884 to your computer and use it in GitHub Desktop.
OAuth 1.0a signature generator using Ramda
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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