diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..22ff670 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,282 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "extends": "eslint:recommended", + "rules": { + "accessor-pairs": "error", + "array-bracket-newline": "error", + "array-bracket-spacing": "error", + "array-callback-return": "off", + "array-element-newline": "error", + "arrow-body-style": "error", + "arrow-parens": "error", + "arrow-spacing": "error", + "block-scoped-var": "error", + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "error", + "stroustrup", + { + "allowSingleLine": true + } + ], + "callback-return": "error", + "camelcase": "error", + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": "error", + "comma-spacing": "off", + "comma-style": "error", + "complexity": "error", + "computed-property-spacing": "off", + "consistent-return": "error", + "consistent-this": "error", + "curly": "off", + "default-case": "error", + "dot-location": [ + "error", + "property" + ], + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "off", + "for-direction": "error", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": [ + "error", + "never" + ], + "func-style": [ + "error", + "declaration" + ], + "function-paren-newline": "error", + "generator-star-spacing": "error", + "getter-return": "error", + "global-require": "off", + "guard-for-in": "off", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": "error", + "indent": "off", + "indent-legacy": "off", + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "error", + "keyword-spacing": "off", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "error", + "lines-around-directive": "error", + "lines-between-class-members": "error", + "max-depth": "error", + "max-len": "off", + "max-lines": "error", + "max-nested-callbacks": "error", + "max-params": "error", + "max-statements": "off", + "max-statements-per-line": "error", + "multiline-comment-style": [ + "error", + "separate-lines" + ], + "multiline-ternary": "off", + "new-cap": "error", + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "error", + "no-alert": "error", + "no-array-constructor": "error", + "no-await-in-loop": "error", + "no-bitwise": [ + "error", + { + "int32Hint": true + } + ], + "no-buffer-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-confusing-arrow": "error", + "no-continue": "off", + "no-div-regex": "off", + "no-duplicate-imports": "error", + "no-else-return": "error", + "no-empty-function": "error", + "no-eq-null": "off", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-inner-declarations": [ + "error", + "functions" + ], + "no-invalid-this": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "error", + "no-mixed-requires": "error", + "no-multi-assign": "error", + "no-multi-spaces": "off", + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": [ + "error", + { + "allowForLoopAfterthoughts": true + } + ], + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "off", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sync": "error", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "off", + "no-undef-init": "error", + "no-undefined": "error", + "no-underscore-dangle": "error", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "error", + "no-use-before-define": "error", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "off", + "no-void": "error", + "no-warning-comments": "error", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "error", + "object-curly-spacing": "error", + "object-property-newline": "error", + "object-shorthand": "error", + "one-var": "off", + "one-var-declaration-per-line": "error", + "operator-assignment": "off", + "operator-linebreak": [ + "error", + "after" + ], + "padded-blocks": "off", + "padding-line-between-statements": "error", + "prefer-arrow-callback": "off", + "prefer-const": "error", + "prefer-destructuring": "off", + "prefer-numeric-literals": "error", + "prefer-promise-reject-errors": "error", + "prefer-reflect": "off", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": "error", + "quotes": "off", + "radix": "error", + "require-await": "error", + "require-jsdoc": "off", + "rest-spread-spacing": "error", + "semi": "off", + "semi-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "semi-style": [ + "error", + "last" + ], + "sort-imports": "error", + "sort-keys": "error", + "sort-vars": "error", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-in-parens": [ + "error", + "never" + ], + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "off", + "strict": [ + "error", + "never" + ], + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": "error", + "template-tag-spacing": "error", + "unicode-bom": [ + "error", + "never" + ], + "valid-jsdoc": "error", + "vars-on-top": "off", + "wrap-iife": "error", + "wrap-regex": "off", + "yield-star-spacing": "error", + "yoda": [ + "error", + "never" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9cd90f7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - '6.0.0' + - '8.6.0' +git: + depth: 3 +cache: + directories: + - "node_modules" diff --git a/package.json b/package.json new file mode 100644 index 0000000..fbbf84e --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "twiz-client-oauth", + "version": "1.0.0", + "description": "Twitter OAuth 1.0a strings assembler for twiz-client", + "main": "src/OAuth.js", + "scripts": { + "lint": "eslint src/OAuth.js", + "tap": "tap -Rspec test/*.js", + "test": "nyc npm run tap && nyc report --reporter=text-lcov | coveralls", + "testLocal": "nyc npm run tap && nyc report --reporter=text-lcov" + }, + "author": "github.com/gits2501", + "license": "MIT", + "dependencies": { + "twiz-client-options": "https://github.com/gits2501/twiz-client-options", + "twiz-client-utils": "https://github.com/gits2501/twiz-client-utils" + }, + "repository": { + "type": "git", + "url": "https://github.com/gits2501/twiz-client-oauth.git" + }, + "bugs": { + "url": "https://github.com/gits2501/twiz-client-oauth/issues" + }, + "devDependencies": { + "btoa": "^1.2.1", + "eslint": "^4.19.1", + "nyc": "^11.7.3", + "tap": "^11.1.3" + } +} diff --git a/src/OAuth.js b/src/OAuth.js new file mode 100644 index 0000000..4156d07 --- /dev/null +++ b/src/OAuth.js @@ -0,0 +1,207 @@ +var Options = require('twiz-client-options'); +var percentEncode = require('twiz-client-utils').percentEncode; +var formEncode = require('twiz-client-utils').formEncode; +var btoa; + +if(typeof window === 'object' && window != null) btoa = window.btoa; +else btoa = require('btoa'); // in node require node implementation of browser's btoa (used when testing) + + function OAuth(){ // Prepares oauth strings for a request + Options.call(this); + + this.leadPrefix = "OAuth " // leading string afther all key-value pairs go. Notice space at the end. + this.prefix = "oauth_"; // Prefix for each oauth key in a http request + + this.oauth = {} // Holds parameters that are used to generate SBS and AH + this.oauth[ this.prefix + 'consumer_key'] = "";// This is very sensitive data. Server sets the value. + this.oauth[ this.prefix + 'signature'] = ""; // This value is also inserted in server code. + this.oauth[ this.prefix + 'nonce'] = ""; // Session id, twitter api uses this to determines duplicates + this.oauth[ this.prefix + 'signature_method'] = ""; // Signature method we are using + this.oauth[ this.prefix + 'timestamp'] = ""; // Unix epoch timestamp + this.oauth[ this.prefix + 'version'] = "" // all request use ver 1.0 + + this[this.leg[0]] = {}; // oauth param for request token step + this[this.leg[0]][ this.prefix + 'callback'] = ''; // User is return to this link, + // if approval is confirmed + // this[this.leg[1]] = {} // there is no oauth params for authorize step. request_token // is sent as redirection url query parameter. + + this[this.leg[2]] = {} // oauth params for access token step + this[this.leg[2]][ this.prefix + 'token'] = ''; + this[this.leg[2]][ this.prefix + 'verifier'] = ''; + + this.apiCall = {} + this.apiCall[ this.prefix + 'token'] = ''; // oauth param for api calls. Here goes just users acess token + // (inserted by server code) + + this.OAuthParams = function(action, o1, o2){ // Props found in o2 adds or removes from o1 + Object.getOwnPropertyNames(o2) + .map(function(key){ + if(action === 'add') o1[key] = o2[key]; // add property name and value from o2 to o1 + else delete o1[key]; // removes property name we found in o2 from o1 + }) + return o1; + } + + + } + + OAuth.prototype = Object.create(Options.prototype); + + OAuth.prototype.setNonUserParams = function(){ // sets all "calculated" oauth params + this.setSignatureMethod(); + this.setNonce(); + this.setTimestamp(); + this.setVersion(); + } + + OAuth.prototype.setSignatureMethod = function(method){ + this.oauth[this.prefix + 'signature_method'] = method || "HMAC-SHA1"; + } + + OAuth.prototype.setVersion = function(version){ + this.oauth[ this.prefix + 'version'] = version || "1.0"; + } + + OAuth.prototype.setNonce = function(){ // Generates string from random sequence of 32 numbers, + // then returns base64 encoding of that string, striped of "=" sign. + var seeds = "AaBb1CcDd2EeFf3GgHh4IiJjK5kLl6MmN7nOo8PpQqR9rSsTtU0uVvWwXxYyZz"; + var nonce = ""; + + for(var i = 0; i < 31; i++){ + nonce += seeds[Math.round(Math.random() * (seeds.length - 1))];// pick a random ascii from seeds string + } + + nonce = btoa(nonce).replace(/=/g,""); // encode to base64 and strip the "=" sign + // console.log("nonce: " + nonce) + this.oauth[ this.prefix + 'nonce'] = nonce; // set twitter session identifier (nonce) + } + + OAuth.prototype.setTimestamp = function(){ + this.oauth[ this.prefix + 'timestamp'] = (Date.now() / 1000 | 0) + 1;// cuting off decimal part by + // converting it to 32 bit integer in bitwise OR operation. + } + + OAuth.prototype.addQueryParams = function(phase, leg){ // 'phase' indicates for which type of request we are + // adding params. + // console.log('addQueryParams phase:', phase +'' ); + this.options.queryParams[phase + 'Host'] = this.twtUrl.domain; + this.options.queryParams[phase + 'Path'] = phase === 'leg' ? this.twtUrl.path + leg : + this.twtUrl.api_path + + this.UserOptions.path + + this.UserOptions.paramsEncoded; + + this.options.queryParams[phase + 'Method'] = phase === 'leg' ? this.httpMethods[leg] : this.UserOptions.method; + this.options.queryParams[phase + 'SBS'] = this.genSignatureBaseString(leg); + this.options.queryParams[phase + 'AH'] = this.genHeaderString(); + } + + OAuth.prototype.genSignatureBaseString = function(leg){ // generates SBS + this.signatureBaseString = ''; + var a = []; + for(var name in this.oauth){ // takes every oauth params name + if(this.oauth.hasOwnProperty(name)) a.push(name); // and pushes them to array + } + + a.sort(); // sorts alphabeticaly + + var pair; // key value pair + var key; // parameter name + var value; // param value + // Collects oauth params + for(var i = 0; i < a.length; i++){ // Percent encodes every key value, puts "=" between those, and + // between each pair of key/value it puts "&" sign. + key = a[i]; // Thakes key that was sorted alphabeticaly + switch(key){ // In case of consumer and user keys we leave them to server logic + case "oauth_callback": // Callback url to which users are redirected by twitter + // Check to see if there is data to append to calback as query string: + value = this.session_data ? this.appendToCallback(this.session_data) : + this.oauth[this.prefix + 'callback']; + break; + case "oauth_consumer_key": + value = ""; // Sensitive data we leave for server to add + break; + case "oauth_signature": + continue; // We dont add signature to singatureBaseString at all (notice no break) + default: + value = this.oauth[key]; // Takes value of that key + } + pair = percentEncode(key) + "=" + percentEncode(value); // Encodes key value and inserts "=" + //console.log(pair) // in between. + if(i !== a.length - 1) pair += "&"; // Dont append "&" on last pair + this.signatureBaseString += pair; // Add pair to SBS + } + + var method; // Collecting the reqest method and url + var url; + + if(typeof leg === 'string'){ // we are in 3-leg dance, take well known params + method = this.httpMethods[leg] // Get the method for this leg + method = method.toUpperCase() + "&"; // upercase the method, add "&" + + url = this.absoluteUrls[leg]; // Get the absolute url for this leg of authentication + url = percentEncode(url) + "&"; // Encode the url, add "&". + } + else { // 'leg' is the options object user provided + method = leg.method.toUpperCase() + "&"; // Upercase the method, add "&" + url = this.twtUrl.protocol + this.twtUrl.domain + this.twtUrl.api_path + leg.path; + // Get the absoute url for api call + user provided path + url = percentEncode(url) + "&"; // Encode the url, add "&". + } + // Finaly we assemble the sbs string. PercentEncoding again the signature base string. + this.signatureBaseString = method + url + percentEncode(this.signatureBaseString); + return this.signatureBaseString; + } + + OAuth.prototype.genHeaderString = function(){ + var a = []; + + Object.getOwnPropertyNames(this.oauth) + .forEach(function(el){ if(!/^oauth/.test(el)) delete this[el] }, this.oauth) // delete non oauth params + + for(var name in this.oauth){ + a.push(name); + } + //console.log("a; " + a); + a.sort(); // Aphabeticaly sort array of property names + + var headerString = this.leadPrefix; // Adding "OAuth " in front everthing + var key; // Temp vars + var value; + var pair; + + for(var i = 0; i < a.length; i++){ // iterate oauth + + key = a[i]; // Take the key name (sorted in a) + + value = this.oauth[key]; // Get it from oauth object + + key = percentEncode(key); // Encode the key + value = "\"" + percentEncode(value) + "\""; // Adding double quotes to value + + pair = key + "=" + value; // Adding "=" between + if(i !== (a.length - 1)) pair = pair + ", " // Add trailing comma and space, until end + + headerString += pair; + } + return headerString; + } + + OAuth.prototype.appendToCallback = function(data, name){ // appends data object as querystring to // oauth_callback url. + //console.log('Data: ==> ', data) + if(!name) name = "data"; + var callback = this.oauth[ this.prefix + 'callback']; + var fEncoded = formEncode(data, true); + + //console.log(fEncoded); + var queryString = name + '=' + percentEncode(fEncoded); // Make string from object then // percent encode it. + //console.log("queryString: ", queryString) + + if(!/\?/.test(callback)) callback += "?"; // Add "?" if one not exist + else queryString = '&' + queryString // other queryString exists, so add '&' to this qs + this.oauth[ this.prefix + 'callback'] = callback + queryString; // Add queryString to callback + + // console.log("OAUTH CALLBACK: "+this.oauth[ this.prefix + 'callback']) + return this.oauth[ this.prefix + 'callback']; + }; + + module.exports = OAuth; diff --git a/test/oauth.js b/test/oauth.js new file mode 100644 index 0000000..956a84d --- /dev/null +++ b/test/oauth.js @@ -0,0 +1,286 @@ +var test = require('tap').test; +var OAuth = require('../src/OAuth'); +var oa = new OAuth(); + + +var mockOAuth = { + + leadPrefix: 'OAuth ', + prefix: 'oauth_', + + oauth: { + oauth_consumer_key: '', + oauth_signature: '', + oauth_nonce: '', + oauth_signature_method: '', + oauth_timestamp: '', + oauth_version: '' + }, + + request_token: { oauth_callback: '' }, + access_token: { oauth_token: '', oauth_verifier: '' }, + apiCall: { oauth_token: '' } + +} + +test('OAuth parts',function(t){ + t.plan(6); + + t.equals(oa.leadPrefix, mockOAuth.leadPrefix, 'lead header prefix'); + t.equals(oa.prefix, mockOAuth.prefix, 'parameter header prefix'); + + t.deepEquals(oa.oauth, mockOAuth.oauth, 'basic oauth params'); + t.deepEquals(oa[oa.leg[0]], mockOAuth.request_token, 'oauth params for request token leg'); + t.deepEquals(oa[oa.leg[2]], mockOAuth.access_token, 'oauth params for access token leg'); + t.deepEquals(oa.apiCall, mockOAuth.apiCall, 'oauth params for twitter api calls (after oauth)'); + + +}) + + var userOptions = { + method: 'POST', + path:'statuses/update.json', + params:{ + status: "A bug walks carelessly." + }, + body: 'of a rhino bug', + encoding: 'json', + beforeSend: function(){} + + } + + var args = { + + server_url: 'https://myserver.com', + redirection_url: 'https://myapp.com/redirUrl', + new_window :{ + name: "nature's pocket", + features: 'resizable=yes,height=613,width=400,left=400,top=300' + }, + callback_func: function(){}, + session_data: { + id: 342, + data: 'user data' + }, + options: userOptions + + }; + +test('set general OAuth params', function(t){ // sets oauth params needed for every oauth leg (step) + + t.plan(6); + + oa.setUserParams(args); + + oa.setNonUserParams(); + + t.ok(typeof oa.oauth[oa.prefix + 'signature_method'] === 'string', 'signature_method'); + t.ok(oa.oauth[oa.prefix + 'nonce'].length === 42, 'nonce - length 42 chars'); + t.ok(typeof(oa.oauth[oa.prefix + 'timestamp']/10000) === 'number', 'timestamp'); + t.ok(typeof(oa.oauth[oa.prefix + 'version']/1) === 'number', 'version'); + + t.equals(oa.oauth[oa.prefix + 'consumer_key'], '', 'consumer_key = \'\''); + t.equals(oa.oauth[oa.prefix + 'signature'], '', 'signature = \'\''); +}) + +test('request token OAuth params',function(t){ // check request token params + + t.plan(1); + + t.ok(oa[oa.leg[0]].oauth_callback, 'oauth_callback'); +}) + +test('access token OAuth params', function(t){ // check access token params + + t.plan(2); + + t.ok(oa[oa.leg[2]].hasOwnProperty('oauth_token'),'oauth_token'); + t.ok(oa[oa.leg[2]].hasOwnProperty('oauth_verifier'),'oauth_verifier'); + +}) + +test('api call OAuth params', function(t){ // check api call params + + t.plan(1); + + t.ok(oa.apiCall.hasOwnProperty('oauth_token'),'oauth_token'); + +}) + +test('add request token params to OAuth', function(t){ + + t.plan(1); + + oa.OAuthParams('add', oa.oauth, oa[oa.leg[0]]); // add request token params to oauth + t.ok(oa.oauth['oauth_callback'], 'oauth_callback') +}) + +test('add access token params to OAuth', function(t){ + + t.plan(3); + + oa.OAuthParams('remove', oa.oauth, oa[oa.leg[0]]) // remove oauth_callback + t.notOk(oa.oauth.hasOwnProperty('oauth_callback'), ' removed - oauth_callback'); + + oa.OAuthParams('add', oa.oauth, oa[oa.leg[2]]); // add request token params to oauth + t.ok(oa.oauth.hasOwnProperty('oauth_token'), 'oauth_token') + t.ok(oa.oauth.hasOwnProperty('oauth_verifier'), 'oauth_verifier') +}) + +test('add Query String parametars (request_token leg)', function(t){ + t.plan(6); + + var mockQp = { // mock query params object + legHost: 'api.twitter.com', + legPath: '/oauth/request_token', + legMethod: 'POST', + legSBS: 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttps%253A%252F%252Fmyapp.com%252FredirUrl%253Fdata%253Did%25253D342%252526data%25253Duser%25252520data%26oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_version%3D1.0', + legAH: 'OAuth oauth_callback="https%3A%2F%2Fmyapp.com%2FredirUrl%3Fdata%3Did%253D342%2526data%253Duser%252520data", oauth_consumer_key="", oauth_nonce="ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ", oauth_signature="", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1523528027", oauth_version="1.0"' } + + oa.OAuthParams('remove', oa.oauth, oa[oa.leg[2]]) // remove token and verifier params + oa.OAuthParams('add', oa.oauth, oa[oa.leg[0]]) // add oauth_callback + + oa.setRequestOptions(oa.leg[0]); + + + oa.oauth.oauth_nonce = "ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ"; // set nonce so it doesnt change + // (needs to be tested with t.equals()) + oa.oauth.oauth_timestamp = '1523528027'; // set timestamp so it doesnt change + + oa.addQueryParams('leg', oa.leg[0]) // add query params for request token leg + + var qp = oa.options.queryParams; + + t.equals(qp.legHost, mockQp.legHost,'(leg) Host'); + t.equals(qp.legPath, mockQp.legPath,'(leg) Path'); + t.equals(qp.legMethod, mockQp.legMethod,'(leg) Method'); + + // + t.equals(qp.legSBS, mockQp.legSBS, '(leg) Signature Base String - with session data') + t.equals(qp.legAH, mockQp.legAH, '(leg) Authorization Header String') + + + mockQp.legSBS = 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttps%253A%252F%252Fmyapp.com%252FredirUrl%26oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_version%3D1.0'; + + oa.session_data = ''; // prepare for testing SBS with no session data + oa.oauth.oauth_callback = args.redirection_url // reseting redir url so it doesnt contain session data + + oa.addQueryParams('leg', oa.leg[0]) // set again params so it can regenerate SBS without session data + + t.equals(qp.legSBS, mockQp.legSBS, '(leg) Signature Base String'); + +}) + +test('add Query Parameters (request token leg + api call)', function(t){ + t.plan(6); + + var mockQp = { // mock request token and api call params + legHost: 'api.twitter.com', + legPath: '/oauth/request_token', + legMethod: 'POST', + legSBS: 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttps%253A%252F%252Fmyapp.com%252FredirUrl%26oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_version%3D1.0', + legAH: 'OAuth oauth_callback="https%3A%2F%2Fmyapp.com%2FredirUrl", oauth_consumer_key="", oauth_nonce="ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ", oauth_signature="", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1523528027", oauth_version="1.0"', + apiHost: 'api.twitter.com', + apiPath: '/1.1/statuses/update.json?status=A%20bug%20walks%20carelessly.', + apiMethod: 'POST', + apiSBS: 'POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&oauth_callback%3Dhttps%253A%252F%252Fmyapp.com%252FredirUrl%26oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_version%3D1.0', + apiAH: 'OAuth oauth_callback="https%3A%2F%2Fmyapp.com%2FredirUrl", oauth_consumer_key="", oauth_nonce="ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ", oauth_signature="", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1523528027", oauth_version="1.0"' + } + + oa.OAuthParams('add', oa.oauth, oa[oa.leg[0]]); // add oauth params for request token leg + oa.addQueryParams('api', oa.UserOptions); // add params needed for api + + var qp = oa.options.queryParams; + t.equals(qp.apiHost, mockQp.apiHost, '(api) Host'); + t.equals(qp.apiPath, mockQp.apiPath, '(api) Path'); + t.equals(qp.apiMethod, mockQp.apiMethod, '(api) Method'); + + t.equals(qp.apiSBS, mockQp.apiSBS, '(api) Signature Base String'); + t.equals(qp.apiAH, mockQp.apiAH, '(api) Authorization Header String'); + + t.deepEquals(qp, mockQp, '(request token) + (api) query params') + + + +}) + +test('add Query String parametars (access_token leg)', function(t){ + t.plan(5); + + + var mockQp = { + legHost: 'api.twitter.com', + legPath: '/oauth/access_token', + legMethod: 'POST', + legSBS: 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Faccess_token&oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_token%3D%26oauth_verifier%3D%26oauth_version%3D1.0', + legAH: 'OAuth oauth_consumer_key="", oauth_nonce="ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ", oauth_signature="", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1523528027", oauth_token="", oauth_verifier="", oauth_version="1.0"' + + } + + oa.OAuthParams('remove', oa.oauth, oa[oa.leg[0]]) // remove callback + oa.OAuthParams('add', oa.oauth, oa[oa.leg[2]]) // add token and verifier params + + oa.setRequestOptions(oa.leg[2]); + + oa.oauth.oauth_nonce = "ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ"; // set nonce so it doesnt change + // (needs to be tested with t.equals()) + oa.oauth.oauth_timestamp = '1523528027'; // set timestamp so it doesnt change + + oa.addQueryParams('leg', oa.leg[2]) // add query params for access token leg + + var qp = oa.options.queryParams; + + t.equals(qp.legHost, mockQp.legHost,'(leg) Host'); + t.equals(qp.legPath, mockQp.legPath,'(leg) Path'); + t.equals(qp.legMethod, mockQp.legMethod,'(leg) Method'); + + // + t.equals(qp.legSBS, mockQp.legSBS, '(leg) Signature Base String') + t.equals(qp.legAH, mockQp.legAH, '(leg) Authorization Header String') + +}) + +test('add Query String parametars (access_token plus)', function(t){ // test access token and api params + t.plan(6); + + + var mockQp = { // mocks query params needed for "oauth access token plus" request + legHost: 'api.twitter.com', + legPath: '/oauth/access_token', + legMethod: 'POST', + legSBS: 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Faccess_token&oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_token%3D%26oauth_verifier%3D%26oauth_version%3D1.0', + legAH: 'OAuth oauth_consumer_key="", oauth_nonce="ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ", oauth_signature="", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1523528027", oauth_token="", oauth_verifier="", oauth_version="1.0"', + apiHost: 'api.twitter.com', + apiPath: '/1.1/statuses/update.json?status=A%20bug%20walks%20carelessly.', + apiMethod: 'POST', + apiSBS: 'POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&oauth_consumer_key%3D%26oauth_nonce%3Dajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1523528027%26oauth_token%3D%26oauth_version%3D1.0', + apiAH: 'OAuth oauth_consumer_key="", oauth_nonce="ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ", oauth_signature="", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1523528027", oauth_token="", oauth_version="1.0"' + } + + oa.OAuthParams('remove', oa.oauth, oa[oa.leg[2]]) // remove token and verifier params + oa.OAuthParams('add', oa.oauth, oa.apiCall) // add callback + + oa.setRequestOptions(oa.leg[2]); + + oa.oauth.oauth_nonce = "ajdud0QydHRnYU9XeHJ5b29kSGpWdmY2bXAxTTk4VQ"; // set nonce so it doesnt change + // (sbs needs to be tested with t.equals()) + oa.oauth.oauth_timestamp = '1523528027'; // set timestamp so it doesnt change + + oa.addQueryParams('api', oa.UserOptions) // add query params for access token leg + + var qp = oa.options.queryParams + + t.equals(qp.apiHost, mockQp.apiHost, '(api) Host'); + t.equals(qp.apiPath, mockQp.apiPath, '(api) Path'); + t.equals(qp.apiMethod, mockQp.apiMethod, '(api) Method'); + + t.equals(qp.apiSBS, mockQp.apiSBS, '(api) Signature Base String'); + t.equals(qp.apiAH, mockQp.apiAH, '(api) Authorization Header String'); + + t.deepEquals(qp, mockQp, '(access token) + (api) query params') + + +}) + + +