diff --git a/.gitignore b/.gitignore
index 3c3629e..49d7ee0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,51 @@
+# Mac Resource files
+._*
+*.DS_Store
+
+# Archives
+*.gz
+
+# Cache and build output directories
+.tscache
+dist
+
+# CI Test results
+testresults.xml
+
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
diff --git a/Makefile b/Makefile
index 753c847..03a96dc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
test:
- clear && mocha --recursive --reporter spec --slow 1
-
+ clear && npm run build && mocha --recursive --reporter spec --slow 1
+
coveralls:
istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec --recursive && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..5fd4e17
--- /dev/null
+++ b/index.js
@@ -0,0 +1,2 @@
+module.exports = require('./dist/oauth').OAuth;
+module.exports.default = module.exports;
diff --git a/package.json b/package.json
index 3d2075e..61a18eb 100644
--- a/package.json
+++ b/package.json
@@ -3,9 +3,11 @@
"version": "3.0.0",
"description": "OAuth 1.0a Request Authorization for Node and Browser.",
"scripts": {
+ "build": "tsc --project .",
+ "lint": "tslint --project tsconfig.json --type-check",
"test": "make test"
},
- "main": "src/oauth.js",
+ "main": "index.js",
"repository": "https://github.com/whs/node-oauth-1.0a.git",
"keywords": [
"oauth",
@@ -14,17 +16,33 @@
"authorize",
"signature",
"nonce",
- "consumer"
+ "consumer",
+ "typescript"
],
"license": "MIT",
"devDependencies": {
- "mocha": "~2.4.5",
- "chai": "~3.5.0",
- "request": "~2.69.0",
- "istanbul": "^0.4.2",
- "coveralls": "^2.10.0"
+ "@types/chai": "^3.5.0",
+ "@types/istanbul": "^0.4.29",
+ "@types/mocha": "^2.2.40",
+ "@types/node": "^7.0.12",
+ "@types/randomstring": "^1.1.5",
+ "@types/request": "0.0.42",
+ "chai": "^3.5.0",
+ "coveralls": "^2.13.0",
+ "istanbul": "^0.4.5",
+ "mocha": "^3.2.0",
+ "request": "^2.81.0",
+ "tslint": "^5.1.0",
+ "typescript": "^2.2.2"
},
"dependencies": {
- "randomstring": "^1.1.4"
- }
+ "randomstring": "^1.1.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "files": [
+ "dist",
+ "index.js"
+ ]
}
diff --git a/src/oauth.js b/src/oauth.js
deleted file mode 100644
index d3ffd51..0000000
--- a/src/oauth.js
+++ /dev/null
@@ -1,359 +0,0 @@
-'use strict';
-
-const querystring = require('querystring');
-const randomstring = require('randomstring');
-
-const Signer = require('./signer');
-const Utils = require('./utils');
-
-/**
- * OAuth 1.0a signature generator
- *
- * @example
Setup
- * let OAuth = require('node-oauth-1.0a');
- * let request = require('request');
- *
- * let oauth = new OAuth({
- * consumer: {
- * public: '',
- * 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 Sending in POST body with request library
- * 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 Sending in Authorization header with request library
- * 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 algorithm:
- * - `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){
- opts = 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_seperator: ', ',
- }, opts);
- }
-
- /**
- * 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 {string} type Algorithm name
- * @throws {Error} Algorithm is not supported
- * @return {Function} Algorithm implementation
- */
- _getSigner(type){
- if(Signer[type]){
- return Signer[type];
- }else{
- let supported = JSON.stringify(Object.keys(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: '',
- * private: ''
- * };
- * let oauth_data = oauth.authorize(request, token);
- * console.log(oauth_data);
- *
- * @example Example response
- * {
- * "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){
- token = token || {};
-
- let oauth_data = this._getOAuthData(token);
- 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.toHeader(oauth_data, this._opts.parameter_seperator);
- }
-
- /**
- * 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.toHeader(oauth_data, this._opts.parameter_seperator)
- };
- }
-
- /**
- * 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 {Object} [token={}] User token
- * @param {string} [token.key] Token public key
- * @param {string} [token.secret] Token secret key
- *
- * @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){
- return this._sign(
- this._getBaseString(request, oauth_data),
- this._getSigningKey(token)
- );
- }
-
- /**
- * Create new OAuth data
- * @private
- * @param {Object} [token] User token
- * @param {string} token.public Token public key
- * @return {Object} OAuth data without oauth_signature
- *
- * @example Example response
- * {
- * "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){
- 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;
- }
-
- return oauth_data;
- }
-
- /**
- * 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.getBaseUrl(request.url),
- Utils.getParameterString(request, oauth_data)
- ];
-
- return out.map(item => Utils.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.toSortedMap(data);
-
- return Utils.stringifyQueryMap(data, '&', '=', {
- encodeURIComponent: Utils.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.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 parseInt(new Date().getTime()/1000, 10);
- }
-}
-
-module.exports = OAuth;
diff --git a/src/oauth.ts b/src/oauth.ts
new file mode 100644
index 0000000..e80a657
--- /dev/null
+++ b/src/oauth.ts
@@ -0,0 +1,395 @@
+import * as querystring from "querystring";
+import * as randomstring from "randomstring";
+
+import {Signer, SignerType} from "./signer";
+import Utils from "./utils";
+
+/**
+ * OAuth 1.0a signature generator
+ *
+ * @example Setup
+ * let OAuth = require('node-oauth-1.0a');
+ * let request = require('request');
+ *
+ * let oauth = new OAuth({
+ * consumer: {
+ * public: '',
+ * 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 Sending in POST body with request library
+ * 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 Sending in Authorization header with request library
+ * 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
+ * });
+ *
+ */
+export class OAuth {
+ private _opts: OAuthOpts;
+ private _signer: Function;
+
+ /**
+ * @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: Partial = {}) {
+ 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: string, key: string) {
+ 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: SignerType) {
+ if (Signer[type]) {
+ return Signer[type];
+ } else {
+ let supported = JSON.stringify(Object.keys(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: '',
+ * private: ''
+ * };
+ * let oauth_data = oauth.authorize(request, token);
+ * console.log(oauth_data);
+ *
+ * @example Example response
+ * {
+ * "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: RequestOpts, token?: Token) {
+ token = token || {};
+
+ let oauth_data: OAuthData = this._getOAuthData(token);
+ 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: RequestOpts, token: Token) {
+ let oauth_data = this.authorize(request, token);
+ return Utils.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: OAuthData) {
+ return {
+ Authorization: Utils.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: RequestOpts, token: string | undefined, oauth_data: OAuthData) {
+ return this._sign(
+ this._getBaseString(request, oauth_data),
+ this._getSigningKey(token)
+ );
+ }
+
+ /**
+ * Create new OAuth data
+ * @private
+ * @param {Object} [token] User token
+ * @param {string} token.public Token public key
+ * @return {Object} OAuth data without oauth_signature
+ *
+ * @example Example response
+ * {
+ * "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: Token): OAuthData {
+ let oauth_data: OAuthData = {
+ 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;
+ }
+
+ return oauth_data;
+ }
+
+ /**
+ * 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: RequestOpts, oauth_data: any) {
+ let out = [
+ request.method.toUpperCase(),
+ Utils.getBaseUrl(request.url),
+ Utils.getParameterString(request, oauth_data)
+ ];
+
+ return out.map(item => Utils.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: any) {
+ data = Utils.toSortedMap(data);
+
+ return Utils.stringifyQueryMap(data, '&', '=', {
+ encodeURIComponent: Utils.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?: string) {
+ let out = [
+ this._opts.consumer.secret
+ ];
+
+ if (this._opts.last_ampersand || token_secret) {
+ out.push(token_secret || '');
+ }
+
+ return out.map(item => Utils.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, // tslint:disable-line
+ charset: 'alphanumeric'
+ });
+ }
+
+ /**
+ * Create timestamp from current time
+ * @private
+ * @return {number} Current time in seconds
+ */
+ _getTimeStamp() {
+ return Math.floor(Date.now() / 1000); // tslint:disable-line
+ }
+}
+
+export default OAuth; // tslint:disable-line
+
+export interface OAuthOpts {
+ consumer: OAuthConsumer;
+ nonce_length: number;
+ signature_method: SignerType;
+ version: string;
+ last_ampersand: boolean;
+ parameter_separator: string;
+}
+
+export interface OAuthConsumer {
+ public: string; // consumer key
+ secret: string; // shared secret
+}
+
+export interface RequestOpts {
+ method: string; // HTTP method
+ url: string; // URL
+ data?: any; // Post data as a key, value map
+}
+
+export interface OAuthData {
+ oauth_consumer_key: string; // Consumer key
+ oauth_nonce: string; // Nonce string
+ // Signing algorithm name
+ // (only for building signing string, the actual signing algorithm is set by
+ // class {@link OAuth#constructor} arguments)
+ oauth_signature_method: SignerType;
+ oauth_timestamp: number; // Current Unix time in seconds
+ oauth_version: string; // OAuth version
+ oauth_signature?: string; // Signature of the request data
+ oauth_token?: string;
+}
+
+export interface Token {
+ key?: string; // Token key
+ public?: string; // Token public key
+ secret?: string; // Token secret
+}
diff --git a/src/signer/hmac-sha1.js b/src/signer/hmac-sha1.js
deleted file mode 100644
index eda41f3..0000000
--- a/src/signer/hmac-sha1.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-const crypto = require('crypto');
-
-module.exports = (base_string, key) => {
- return crypto.createHmac('sha1', key).update(base_string).digest('base64')
-};
diff --git a/src/signer/hmac-sha1.ts b/src/signer/hmac-sha1.ts
new file mode 100644
index 0000000..5ca22db
--- /dev/null
+++ b/src/signer/hmac-sha1.ts
@@ -0,0 +1,12 @@
+import * as crypto from "crypto";
+
+/**
+ * Sign message
+ * @param {string} base_string message string
+ * @param {string} key signing key
+ */
+export function sign(base_string: string, key: string) {
+ return crypto.createHmac('sha1', key).update(base_string).digest('base64');
+}
+
+export default sign; // tslint:disable-line
diff --git a/src/signer/hmac-sha256.js b/src/signer/hmac-sha256.js
deleted file mode 100644
index 1a109d8..0000000
--- a/src/signer/hmac-sha256.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-const crypto = require('crypto');
-
-module.exports = (base_string, key) => {
- return crypto.createHmac('sha256', key).update(base_string).digest('base64')
-};
\ No newline at end of file
diff --git a/src/signer/hmac-sha256.ts b/src/signer/hmac-sha256.ts
new file mode 100644
index 0000000..d8b1708
--- /dev/null
+++ b/src/signer/hmac-sha256.ts
@@ -0,0 +1,12 @@
+import * as crypto from "crypto";
+
+/**
+ * Sign message
+ * @param {string} base_string message string
+ * @param {string} key signing key
+ */
+export function sign(base_string: string, key: string) {
+ return crypto.createHmac('sha256', key).update(base_string).digest('base64');
+}
+
+export default sign; // tslint:disable-line
diff --git a/src/signer/index.js b/src/signer/index.js
deleted file mode 100644
index 5aba8a7..0000000
--- a/src/signer/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-module.exports = {
- 'HMAC-SHA1': require('./hmac-sha1'),
- 'HMAC-SHA256': require('./hmac-sha256'),
- 'PLAINTEXT': require('./plaintext'),
-};
diff --git a/src/signer/index.ts b/src/signer/index.ts
new file mode 100644
index 0000000..a97a73d
--- /dev/null
+++ b/src/signer/index.ts
@@ -0,0 +1,12 @@
+import signSha1 from "./hmac-sha1";
+import signSha256 from "./hmac-sha256";
+import signPlaintext from "./plaintext";
+
+export const Signer = Object.freeze<{[key: string]: Function}>({
+ 'HMAC-SHA1': signSha1,
+ 'HMAC-SHA256': signSha256,
+ PLAINTEXT: signPlaintext,
+});
+
+export default Signer; // tslint:disable-line
+export type SignerType = "HMAC-SHA1" | "HMAC-SHA256" | "PLAINTEXT";
diff --git a/src/signer/plaintext.js b/src/signer/plaintext.js
deleted file mode 100644
index fb38602..0000000
--- a/src/signer/plaintext.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-
-module.exports = (base_string, key) => {
- return key;
-};
diff --git a/src/signer/plaintext.ts b/src/signer/plaintext.ts
new file mode 100644
index 0000000..5e4d506
--- /dev/null
+++ b/src/signer/plaintext.ts
@@ -0,0 +1,10 @@
+/**
+ * Plaintext signing method just returns the key.
+ * @param {string} base_string message string
+ * @param {string} key signing key
+ */
+export function sign(base_string: string, key: string) {
+ return key;
+}
+
+export default sign; // tslint:disable-line
diff --git a/src/utils.js b/src/utils.js
deleted file mode 100644
index ba77be3..0000000
--- a/src/utils.js
+++ /dev/null
@@ -1,141 +0,0 @@
-'use strict';
-
-const querystring = require('querystring');
-const url = require('url');
-
-/**
- * @private
- */
-const Utils = {
- /**
- * Escape string according to [OAuth 1.0 section 3.6]{@link https://tools.ietf.org/html/rfc5849#section-3.6}
- * @param {String} str String to encode
- * @return {String} Encoded string
- */
- percentEncode: (str) => {
- return encodeURIComponent(str)
- .replace(/\!/g, '%21')
- .replace(/\*/g, '%2A')
- .replace(/\'/g, '%27')
- .replace(/\(/g, '%28')
- .replace(/\)/g, '%29');
- },
-
- /**
- * Build OAuth Authorization header
- * @param {Object} oauth_data
- * @param {string} [separator=", "] Separator between items
- * @return {String} Authorization header string
- */
- toHeader: (oauth_data, separator) => {
- separator = separator || ', ';
- oauth_data = Utils.toSortedMap(oauth_data);
-
- let params = [];
-
- // encode each items as key="value"
- for(let item of oauth_data){
- let key = Utils.percentEncode(item[0]);
- let value = Utils.percentEncode(item[1]);
- params.push(`${key}="${value}"`);
- }
-
- let joinedParams = params.join(separator);
-
- return `OAuth ${joinedParams}`;
- },
-
- /**
- * Build parameter string part of the signing string.
- *
- * Parameter string consists of all request parameters and OAuth data
- * sorted by key alphabetically.
- *
- * @param {Object} request
- * @param {Object} oauth_data
- * @return {Object} string Parameter string
- */
- getParameterString: (request, oauth_data) => {
- let parsedUrl = url.parse(request.url, true);
- let data = Object.assign({}, parsedUrl.query, request.data || {}, oauth_data);
- data = Utils.toSortedMap(data);
-
- return Utils.stringifyQueryMap(data, '&', '=', {
- encodeURIComponent: Utils.percentEncode
- });
- },
-
- /**
- * Build query string from {@link Map}
- *
- * This method should be the same as {@link querystring#stringify}
- * but accept a `Map` instead of {@link Object}
- *
- * @param {Map.} obj Input
- * @param {string} [sep="&"] Separator between items
- * @param {string} [eq="="] Separator between key and value
- * @param {Object} [options]
- * @param {Function} [options.encodeURIComponent=encodeURIComponent]
- * Key and value escaping algorithm
- * @return {string} Query string
- */
- stringifyQueryMap: (obj, sep, eq, options) => {
- sep = sep || '&';
- eq = eq || '=';
- options = Object.assign({
- encodeURIComponent: encodeURIComponent
- }, options);
-
- let out = [];
-
- for(let item of obj){
- if(!Array.isArray(item[1])){
- item[1] = [item[1]];
- }
- item[1].sort();
-
- let key = options.encodeURIComponent(item[0]);
- for(let value of item[1]){
- // if value is an array, repeat the key multiple time
- value = options.encodeURIComponent(value);
- out.push(`${key}${eq}${value}`);
- }
- }
-
- return out.join(sep);
- },
-
- /**
- * Strip query string from URL
- *
- * @param {String} url URL to strip
- * @return {String} Stripped URL
- */
- getBaseUrl: (url) => {
- return url.split('?')[0];
- },
-
- /**
- * Return a ES6 Map with same key/value pairs as object.
- *
- * Iterating over this map would yield key/value pairs in alphabetical
- * order of keys.
- *
- * @param {Object} object Object to sort
- * @return {Map}
- */
- toSortedMap: (object) => {
- let keys = Object.keys(object);
- keys.sort();
-
- let out = new Map();
-
- for(let key of keys){
- out.set(key, object[key]);
- }
-
- return out;
- },
-};
-
-module.exports = Utils;
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..a38a93f
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,136 @@
+import * as querystring from "querystring";
+import * as url from "url";
+
+/**
+ * @private
+ */
+export namespace Utils {
+ /**
+ * Escape string according to [OAuth 1.0 section 3.6]{@link https://tools.ietf.org/html/rfc5849#section-3.6}
+ * @param {String} str String to encode
+ * @return {String} Encoded string
+ */
+ export function percentEncode(str: string) {
+ return encodeURIComponent(str).replace(/[!'()*]/g, (c) =>
+ `%${c.charCodeAt(0).toString(16).toUpperCase()}` // tslint:disable-line
+ );
+ }
+
+ /**
+ * Build OAuth Authorization header
+ * @param {Object} oauth_data
+ * @param {string} [separator=", "] Separator between items
+ * @return {String} Authorization header string
+ */
+ export function toHeader(oauth_data: any, separator?: string) {
+ separator = separator || ', ';
+ oauth_data = Utils.toSortedMap(oauth_data);
+
+ let params = [];
+
+ // encode each items as key="value"
+ for(let item of oauth_data){
+ let key = Utils.percentEncode(item[0]);
+ let value = Utils.percentEncode(item[1]);
+ params.push(`${key}="${value}"`);
+ }
+
+ let joinedParams = params.join(separator);
+
+ return `OAuth ${joinedParams}`;
+ }
+
+ /**
+ * Build parameter string part of the signing string.
+ *
+ * Parameter string consists of all request parameters and OAuth data
+ * sorted by key alphabetically.
+ *
+ * @param {Object} request
+ * @param {Object} oauth_data
+ * @return {Object} string Parameter string
+ */
+ export function getParameterString(request: any, oauth_data: any) {
+ let parsedUrl = url.parse(request.url, true);
+ let data = Object.assign({}, parsedUrl.query, request.data || {}, oauth_data);
+ data = Utils.toSortedMap(data);
+
+ return Utils.stringifyQueryMap(data, '&', '=', {
+ encodeURIComponent: Utils.percentEncode
+ });
+ }
+
+ /**
+ * Build query string from {@link Map}
+ *
+ * This method should be the same as {@link querystring#stringify}
+ * but accept a `Map` instead of {@link Object}
+ *
+ * @param {Object} obj Input
+ * @param {string} [sep="&"] Separator between items
+ * @param {string} [eq="="] Separator between key and value
+ * @param {Object} [options]
+ * @param {Function} [options.encodeURIComponent=encodeURIComponent]
+ * Key and value escaping algorithm
+ * @return {string} Query string
+ */
+ export function stringifyQueryMap(obj: any, sep: string, eq: string, options: any) {
+ sep = sep || '&';
+ eq = eq || '=';
+ options = Object.assign({
+ encodeURIComponent: encodeURIComponent
+ }, options);
+
+ let out = [];
+
+ for(let item of obj){
+ if(!Array.isArray(item[1])) {
+ item[1] = [item[1]];
+ }
+ item[1].sort();
+
+ let key = options.encodeURIComponent(item[0]);
+ for(let value of item[1]){
+ // if value is an array, repeat the key multiple time
+ value = options.encodeURIComponent(value);
+ out.push(`${key}${eq}${value}`);
+ }
+ }
+
+ return out.join(sep);
+ }
+
+ /**
+ * Strip query string from URL
+ *
+ * @param {String} url URL to strip
+ * @return {String} Stripped URL
+ */
+ export function getBaseUrl(url: string) {
+ return url.split('?')[0];
+ }
+
+ /**
+ * Return a ES6 Map with same key/value pairs as object.
+ *
+ * Iterating over this map would yield key/value pairs in alphabetical
+ * order of keys.
+ *
+ * @param {Object} object Object to sort
+ * @return {Map}
+ */
+ export function toSortedMap(object: any) {
+ let keys = Object.keys(object);
+ keys.sort();
+
+ let out = new Map();
+
+ for(let key of keys){
+ out.set(key, object[key]);
+ }
+
+ return out;
+ }
+}
+
+export default Utils; // tslint:disable-line
diff --git a/test/options/parameter_seperator.js b/test/options/parameter_seperator.js
index 94909e9..f3ce9a4 100644
--- a/test/options/parameter_seperator.js
+++ b/test/options/parameter_seperator.js
@@ -8,7 +8,7 @@ if(typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
expect = chai.expect;
}
-describe("parameter_seperator option", function() {
+describe("parameter_separator option", function() {
describe("default (', ')", function() {
var oauth = generateTest({
consumer: {
@@ -41,12 +41,12 @@ describe("parameter_seperator option", function() {
public: 'xvz1evFS4wEEPTGEFPHBog',
secret: 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw'
},
- parameter_seperator: '-'
+ parameter_separator: '-',
});
var token = {
public: '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb',
- secret: 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE'
+ secret: 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE',
};
var request = {
@@ -54,11 +54,15 @@ describe("parameter_seperator option", function() {
method: 'POST',
data: {
status: 'Hello Ladies + Gentlemen, a signed OAuth request!'
- }
+ },
};
it("header should be correct", function() {
- expect(oauth.toHeader(oauth.authorize(request, token))).to.have.property('Authorization', 'OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog"-oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg"-oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D"-oauth_signature_method="HMAC-SHA1"-oauth_timestamp="1318622958"-oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb"-oauth_version="1.0"');
+ const authData = oauth.authorize(request, token);
+ const authHeader = oauth.toHeader(authData);
+ const prop = "Authorization";
+ const expectedVal = 'OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog"-oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg"-oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D"-oauth_signature_method="HMAC-SHA1"-oauth_timestamp="1318622958"-oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb"-oauth_version="1.0"';
+ expect(authHeader).to.have.property(prop, expectedVal);
});
});
});
diff --git a/test/options/signer.js b/test/options/signer.js
index f7fec30..b844d06 100644
--- a/test/options/signer.js
+++ b/test/options/signer.js
@@ -1,11 +1,9 @@
-'use strict';
-
let expect;
//Node.js
if(typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
expect = require('chai').expect;
- var Signer = require('../../src/signer');
+ var Signer = require('../../dist/signer').default;
} else { //Browser
expect = chai.expect;
}
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 0000000..98a3729
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compileOnSave": true,
+ "compilerOptions": {
+ "declaration": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "importHelpers": true,
+ "inlineSourceMap": true,
+ "lib": ["DOM", "ES2016", "ES6", "ScriptHost"],
+ "module": "commonjs",
+ "noImplicitAny": true,
+ "preserveConstEnums": true,
+ "removeComments": false,
+ "strictNullChecks": true,
+ "target": "ES2016"
+ },
+ "include": [
+ "**/*"
+ ]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..6cd4449
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compileOnSave": true,
+ "compilerOptions": {
+ "declaration": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "importHelpers": true,
+ "inlineSourceMap": true,
+ "lib": ["DOM", "ES2016", "ES6", "ScriptHost"],
+ "module": "commonjs",
+ "noImplicitAny": true,
+ "outDir": "dist",
+ "preserveConstEnums": true,
+ "removeComments": false,
+ "strictNullChecks": true,
+ "target": "ES2016"
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..c742bc0
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,89 @@
+{
+ "rules": {
+ "adjacent-overload-signatures": true,
+ "align": [
+ true,
+ "statements"
+ ],
+ "ban": [
+ true,
+ [ "_", "forEach" ],
+ [ "_", "map" ],
+ [ "_", "filter" ],
+ [ "_", "each" ]
+ ],
+ "class-name": true,
+ "comment-format": [ true, "check-space" ],
+ "completed-docs": [false, "classes", "functions", "methods"],
+ "curly": true,
+ "cyclomatic-complexity": true,
+ "eofline": true,
+ "forin": true,
+ "indent": [ true, "spaces" ],
+ "jsdoc-format": true,
+ "label-position": true,
+ "max-classes-per-file": [true, 1],
+ "max-line-length": [ true, 140 ],
+ "new-parens": true,
+ "no-angle-bracket-type-assertion": false,
+ "no-any": false,
+ "no-arg": true,
+ "no-bitwise": false,
+ "no-conditional-assignment": true,
+ "no-consecutive-blank-lines": false,
+ "no-construct": true,
+ "no-debugger": true,
+ "no-default-export": true,
+ "no-duplicate-variable": true,
+ "no-empty": false,
+ "no-eval": true,
+ "no-for-in-array": true,
+ "no-inferrable-types": false,
+ "no-internal-module": true,
+ "no-magic-numbers": true,
+ "no-namespace": [false, "allow-declarations"],
+ "no-null-keyword": false,
+ "no-reference": false,
+ "no-require-imports": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unsafe-finally": true,
+ "no-unused-expression": true,
+ "no-var-keyword": true,
+ "no-var-requires": true,
+ "object-literal-key-quotes": [true, "as-needed"],
+ "one-line": [ true, "check-open-brace", "check-whitespace" ],
+ "only-arrow-functions": [ true, "allow-declarations", "allow-named-functions" ],
+ "prefer-for-of": true,
+ "radix": true,
+ "semicolon": [true, "always"],
+ "switch-default": true,
+ "trailing-comma": [ true, { "singleline": "never" } ],
+ "triple-equals": [ false, "allow-null-check", "allow-undefined-check" ],
+ "typedef": [
+ true,
+ "parameter",
+ "property-declaration"
+ ],
+ "typeof-compare": true,
+ "use-isnan": true,
+ "variable-name": [
+ false,
+ "allow-leading-underscore",
+ "allow-pascal-case",
+ "ban-keywords",
+ "check-format"
+ ]
+ },
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ]
+}