node-oauth-1.0a-ts
Version:
OAuth 1.0a Request Authorization for Node and Browser.
355 lines • 18.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const randomstring = require("randomstring");
const crypto = require("crypto");
const signer_1 = require("./signer");
const utils_1 = require("./utils");
/**
* OAuth 1.0a signature generator
*
* @example <caption>Setup</caption>
* let OAuth = require('node-oauth-1.0a');
* let request = require('request');
*
* let oauth = new OAuth({
* consumer: {
* public: '<consumer key>',
* secret: '<consumer secret>',
* }
* });
* let request_data = {
* url: 'https://api.twitter.com/1/statuses/update.json?include_entities=true',
* method: 'POST',
* data: {
* status: 'Hello Ladies + Gentlemen, a signed OAuth request!'
* }
* };
* let token = {
* public: '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb',
* secret: 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE'
* };
*
* @example <caption>Sending in POST body with request library</caption>
* let formData = Object.assign(
* {},
* request_data.data,
* oauth.authorize(request_data, token)
* );
* request({
* url: request_data.url,
* method: request_data.method,
* form: oauth.buildQueryString(formData)
* }, function(error, response, body) {
* // Process data
* });
*
* @example <caption>Sending in Authorization header with request library</caption>
* request({
* url: request_data.url,
* method: request_data.method,
* form: oauth.buildQueryString(request_data.data),
* headers: {
* Authorization: oauth.getHeader(request_data, token)
* }
* }, function(error, response, body) {
* // Process data
* });
*
*/
class OAuth {
/**
* @param {Object} opts
* @param {Object} opts.consumer Consumer token (required)
* @param {string} opts.consumer.public Consumer key (public key)
* @param {string} opts.consumer.private Consumer secret
* @param {number} [opts.nonce_length=32] Length of nonce (oauth_nonce)
* @param {string} [opts.signature_method="HMAC-SHA1"] Signing algorithm
* Supported algorithms:
* - `HMAC-SHA1`
* - `PLAINTEXT`
* - `HMAC-SHA256`
*
* Note that `HMAC-256` is non-standard.
* @param {string} [opts.version=1.0] OAuth version (oauth_version)
* @param {boolean} [opts.last_ampersand=true] Whether to append trailing
* ampersand to signing key
*/
constructor(opts = {}) {
if (!opts.consumer) {
throw new Error('consumer option is required');
}
this._opts = Object.assign({
nonce_length: 32,
signature_method: 'HMAC-SHA1',
version: '1.0',
last_ampersand: true,
parameter_separator: ', ',
}, opts, { consumer: opts.consumer });
}
/**
* Sign a string with key
* @private
* @param {string} str String to sign
* @param {string} key HMAC key
* @return {string} Signed string in base64 format
*/
_sign(str, key) {
if (!this._signer) {
// Cache the signer
this._signer = this._getSigner(this._opts.signature_method);
}
return this._signer(str, key);
}
/**
* Retrieve a signing algorithm by algorithm name
* @private
* @param {SignerType} type Algorithm name
* @throws {Error} Algorithm is not supported
* @return {Function} Algorithm implementation
*/
_getSigner(type) {
if (signer_1.Signer[type]) {
return signer_1.Signer[type];
}
else {
let supported = JSON.stringify(Object.keys(signer_1.Signer));
throw new Error(`Hash type ${type} not supported. Supported: ${supported}`);
}
}
/**
* Create OAuth signing data for attaching to request body
* @param {Object} request
* @param {string} request.method HTTP Method name (eg. `GET`, `POST`, `PUT`)
* @param {string} request.url URL
* @param {Object} request.data Post data as a key, value map
*
* @param {Object} [token={}] User token
* @param {string} [token.key] Token public key
* @param {string} [token.secret] Token secret key
*
* @return {Object} OAuth signing data. You probably want to put this in your POST body
*
* @example
* let request = {
* method: 'POST',
* url: 'https://api.twitter.com/1.1/statuses/update.json',
* data: {
* status: 'Hello, world!'
* }
* };
* let token = {
* public: '<user token>',
* private: '<user token secret>'
* };
* let oauth_data = oauth.authorize(request, token);
* console.log(oauth_data);
*
* @example <caption>Example response</caption>
* {
* "oauth_consumer_key": "xvz1evFS4wEEPTGEFPHBog",
* "oauth_nonce": "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
* "oauth_signature_method": "HMAC-SHA1",
* "oauth_timestamp": 1318622958,
* "oauth_version": "1.0",
* "oauth_token": "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
* "oauth_signature": "tnnArxj06cWHq44gCs1OSKk/jLY="
* }
*/
authorize(request, token) {
let bodyToHash;
if (request.includeBodyHash) {
if (typeof request.data != 'string') {
throw Error("When using includeBodyHash request.data must be the exact data string of the request");
}
bodyToHash = request.data;
}
token = token || {};
bodyToHash = bodyToHash || undefined;
let oauth_data = this._getOAuthData(token, bodyToHash);
oauth_data.oauth_signature = this.getSignature(request, token.secret, oauth_data);
return oauth_data;
}
/**
* Create OAuth Authorization header
* @param {Object} request
* @param {string} request.method HTTP Method name (eg. `GET`, `POST`, `PUT`)
* @param {string} request.url URL
* @param {Object} request.data Post data as a key, value map
*
* @param {Object} [token={}] User token
* @param {string} [token.key] Token public key
* @param {string} [token.secret] Token secret key
*
* @return {string} Authorization header value
*/
getHeader(request, token) {
let oauth_data = this.authorize(request, token);
return utils_1.default.toHeader(oauth_data, this._opts.parameter_separator);
}
/**
* Format OAuth signing data for sending via HTTP Header
* @param {Object} oauth_data OAuth signing data as returned from
* {@link OAuth#authorize}
* @return {Object} Headers required to sign the request
* @deprecated This method is preserved for backward compatibility with
* oauth-1.0a. New implementors should use {@link OAuth#getHeader} instead.
*/
toHeader(oauth_data) {
return {
Authorization: utils_1.default.toHeader(oauth_data, this._opts.parameter_separator)
};
}
/**
* Create oauth_signature from request. Usually you probably want to use
* {@link OAuth#authorize} instead.
* @param {Object} request
* @param {string} request.method HTTP Method name (eg. `GET`, `POST`, `PUT`)
* @param {string} request.url URL
* @param {Object} request.data Post data as a key, value map
*
* @param {string} token signing token
*
* @param {Object} oauth_data
* @param {string} oauth_data.oauth_consumer_key Consumer key
* @param {string} oauth_data.oauth_nonce Nonce string
* @param {string} oauth_data.oauth_signature_method Signing algorithm name
* (only for building signing string, the actual signing algorithm is set by
* class {@link OAuth#constructor} arguments)
* @param {number} oauth_data.oauth_timestamp Current time in seconds
* @param {string} oauth_data.oauth_version OAuth version (should be 1.0)
*
* @return {string} Value of oauth_signature field
*/
getSignature(request, token, oauth_data) {
let baseString = this._getBaseString(request, oauth_data);
return this._sign(baseString, this._getSigningKey(token));
}
/**
* Create new OAuth data
* @private
* @param {Object} [token] User token
* @param {string} token.public Token public key
* @param {string|undefined} bodyToHash the body of the rquest (after decompression) as a string
* @return {Object} OAuth data without oauth_signature
*
* @example <caption>Example response</caption>
* {
* "oauth_consumer_key": "xvz1evFS4wEEPTGEFPHBog",
* "oauth_nonce": "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
* "oauth_signature_method": "HMAC-SHA1",
* "oauth_timestamp": 1318622958,
* "oauth_version": "1.0",
* "oauth_token": "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
* }
*/
_getOAuthData(token, bodyToHash) {
let oauth_data = {
oauth_consumer_key: this._opts.consumer.public,
oauth_nonce: this._getNonce(),
oauth_signature_method: this._opts.signature_method,
oauth_timestamp: this._getTimeStamp(),
oauth_version: this._opts.version,
};
if (token && token.public) {
oauth_data.oauth_token = token.public;
}
if (bodyToHash) {
oauth_data.oauth_body_hash = this._getBodyHash(bodyToHash);
}
return oauth_data;
}
/**
* Get the body hash for the request according to
* [OAuth Request Body Hash]{@link http://web.archive.org/web/20160413130001/https://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html} // tslint:disable-line
* @param {Object} [token] User token
* @param {string} token.secret Token secret key
* @param {string} bodyToHash the body of the rquest (after decompression) as a string
* @return {string} the hash of the body, using the same algorithm as for signing the request
*/
_getBodyHash(bodyToHash) {
return crypto.createHash('sha1').update(bodyToHash).digest('base64');
}
/**
* Build authorization string to sign.
*
* An authorization string composes of HTTP method name, base URL and
* parameters (both request parameters and OAuth data)
* @private
* @param {Object} request Request object
* @param {Object} oauth_data OAuth parameters
* @return {string} Authorization string
*/
_getBaseString(request, oauth_data) {
let out = [
request.method.toUpperCase(),
utils_1.default.getBaseUrl(request.url),
utils_1.default.getParameterString(request, oauth_data)
];
return out.map(item => utils_1.default.percentEncode(item))
.join('&');
}
/**
* Build a query string similar to {@link querystring.encode}, but
* escape things correctly per OAuth spec
*
* @param {Object} data Object to encode as query string
* @return {string} Query string object
*/
buildQueryString(data) {
data = utils_1.default.toSortedMap(data);
return utils_1.default.stringifyQueryMap(data, '&', '=', {
encodeURIComponent: utils_1.default.percentEncode
});
}
/**
* Build signing key.
*
* A signing key composes of consumer secret and,
* optionally, user token secret.
*
* This method will append trailing ampersand when `token_secret` is unset
* if `last_ampersand` constructor option is set.
*
* @private
* @param {string} [token_secret] User token secret
* @return {string} Signing Key
*/
_getSigningKey(token_secret) {
let out = [
this._opts.consumer.secret
];
if (this._opts.last_ampersand || token_secret) {
out.push(token_secret || '');
}
return out.map(item => utils_1.default.percentEncode(item))
.join('&');
}
/**
* Create nonce.
*
* Nonce is a random string to prevent replaying of requests.
*
* Default nonce length is 32 characters, and can be specified by
* `nonce_length` constructor option.
*
* @private
* @return {string} Nonce string
*/
_getNonce() {
return randomstring.generate({
length: this._opts.nonce_length || 32,
charset: 'alphanumeric'
});
}
/**
* Create timestamp from current time
* @private
* @return {number} Current time in seconds
*/
_getTimeStamp() {
return Math.floor(Date.now() / 1000); // tslint:disable-line
}
}
exports.OAuth = OAuth;
exports.default = OAuth; // tslint:disable-line
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2F1dGguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvb2F1dGgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSw2Q0FBNkM7QUFDN0MsaUNBQWlDO0FBRWpDLHFDQUE0QztBQUM1QyxtQ0FBNEI7QUFFNUI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1ERztBQUNIO0lBSUk7Ozs7Ozs7Ozs7Ozs7Ozs7T0FnQkc7SUFDSCxZQUFZLE9BQTJCLEVBQUU7UUFDckMsRUFBRSxDQUFBLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztZQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLDZCQUE2QixDQUFDLENBQUM7UUFDbkQsQ0FBQztRQUVELElBQUksQ0FBQyxLQUFLLEdBQWMsTUFBTSxDQUFDLE1BQU0sQ0FBQztZQUNsQyxZQUFZLEVBQUUsRUFBRTtZQUNoQixnQkFBZ0IsRUFBRSxXQUFXO1lBQzdCLE9BQU8sRUFBRSxLQUFLO1lBQ2QsY0FBYyxFQUFFLElBQUk7WUFDcEIsbUJBQW1CLEVBQUUsSUFBSTtTQUM1QixFQUFFLElBQUksRUFBRSxFQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFDLENBQUMsQ0FBQztJQUN4QyxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsS0FBSyxDQUFDLEdBQVcsRUFBRSxHQUFXO1FBQzFCLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFDaEIsbUJBQW1CO1lBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDaEUsQ0FBQztRQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUNsQyxDQUFDO0lBRUQ7Ozs7OztPQU1H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