diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..52e8547 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,274 @@ +{ + "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": "error", + "array-element-newline": "error", + "arrow-body-style": "error", + "arrow-parens": "error", + "arrow-spacing": "error", + "block-scoped-var": "error", + "block-spacing": [ + "error", + "never" + ], + "brace-style": "error", + "callback-return": "error", + "camelcase": "error", + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": "error", + "comma-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "complexity": "error", + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "off", + "consistent-this": "error", + "curly": "off", + "default-case": "error", + "dot-location": [ + "error", + "property" + ], + "dot-notation": "off", + "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": "error", + "guard-for-in": "error", + "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": "error", + "max-statements-per-line": "error", + "multiline-comment-style": [ + "error", + "separate-lines" + ], + "multiline-ternary": "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", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-confusing-arrow": "error", + "no-continue": "error", + "no-console": ["error", { "allow": ["warn"] }], + "no-div-regex": "error", + "no-duplicate-imports": "error", + "no-else-return": "error", + "no-empty-function": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "error", + "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": "off", + "no-native-reassign": "error", + "no-negated-condition": "error", + "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": "off", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "off", + "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": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "off", + "no-undef-init": "error", + "no-undefined": "error", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "off", + "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", + "any" + ], + "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": "error", + "operator-linebreak": "error", + "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": "off", + "quotes": "off", + "radix": "error", + "require-await": "error", + "require-jsdoc": "off", + "rest-spread-spacing": "error", + "semi": "off", + "semi-spacing": "off", + "semi-style": [ + "error", + "last" + ], + "sort-imports": "error", + "sort-keys": "off", + "sort-vars": "error", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-in-parens": "off", + "space-infix-ops": "off", + "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": "error", + "yield-star-spacing": "error", + "yoda": [ + "error", + "never" + ] + } +} diff --git a/.travis.yml b/.travis.yml index a6d7edc..686f216 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,9 @@ language: node_js node_js: - '8.6.0' + - '9.11.1' git: depth: 3 -before_install: - - 'sudo apt-get update && sudo apt-get install linux-image-generic ' after_success: 'npm run coveralls' cache: directiories: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f144b56 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +## CHANGELOG + +Initial + diff --git a/mocha-test.html b/mocha-test.html index 351143a..0005979 100644 --- a/mocha-test.html +++ b/mocha-test.html @@ -2,7 +2,7 @@ Mocha Tests - +
diff --git a/package.json b/package.json index f43c4da..bb30dd9 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "lint": "eslint src/AccessToken.js", "instrument": "istanbul instrument src/AccessToken.js --output src/AccessToken_instrumented.js", - "browserify": "./node_modules/browserify/bin/cmd.js test/accesstoken.js -o test/accesstoken_bundle.js", - "mocha-headless": "mocha-headless-chrome -f mocha-test.html -c test/coverage.json", + "browserify": "./node_modules/browserify/bin/cmd.js test/accesstoken.js --exclude btoa -o test/accesstoken_bundle.js", + "mocha-headless": "mocha-headless-chrome -a no-sandbox -f mocha-test.html -c test/coverage.json", "report": "istanbul report --root test/ lcov", "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", "test": "npm run instrument && npm run browserify && npm run mocha-headless && npm run report" @@ -15,15 +15,23 @@ "author": "github.com/gits2501", "license": "MIT", "dependencies": { - "twiz-client-oauth": "file:../twiz-client-oauth", - "twiz-client-redirect": "file:../twiz-client-redirect" + "twiz-client-oauth": "https://github.com/gits2501/twiz-client-oauth", + "twiz-client-redirect": "https://github.com/gits2501/twiz-client-redirect", + "twiz-client-utils": "https://github.com/gits2501/twiz-client-utils" + }, + "repository": { + "type": "git", + "url": "https://github.com/gits2501/twiz-client-accesstoken.git" + }, + "bugs": { + "url": "https://github.com/gits2501/twiz-client-accesstoken/issues" }, "devDependencies": { - "browserify": "^16.2.0", - "coveralls": "^3.0.0", + "browserify": "^16.2.2", + "coveralls": "^3.0.2", "eslint": "^4.19.1", "istanbul": "^0.4.5", - "mocha": "^5.1.1", - "mocha-headless-chrome": "^2.0.0" + "mocha": "^5.2.0", + "mocha-headless-chrome": "^2.0.1" } } diff --git a/src/AccessToken.js b/src/AccessToken.js index 29ee030..97e990b 100644 --- a/src/AccessToken.js +++ b/src/AccessToken.js @@ -1,7 +1,8 @@ var OAuth = require('twiz-client-oauth'); var deliverData = require('twiz-client-redirect').prototype.deliverData; - function AccessToken (){ // checks that oauth data is in redirection(callback) url, and makes sure + function AccessToken (){ // Prepares data for Access Token leg + // Checks that oauth data is in redirection(callback) url, and makes sure // that oauth_token from url matches the one we saved in first step. OAuth.call(this); this.name = this.leg[2]; @@ -20,7 +21,9 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; requestTokenNotSet: 'Request token was not set', requestTokenNotSaved: 'Request token was not saved. Check that page url from which you make request match your redirection_url.', noRepeat: "Cannot make another request with same redirection(callback) url", - noStringProvided: "Expected string was not provided" + urlNotFound: "Current window location (url) not found", + noSessionData: 'Unable to find session data in current url', + spaWarning: 'Authorization data not found in url' }) } @@ -28,18 +31,16 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; AccessToken.prototype.setAuthorizedTokens = function(){ - this.authorizeRedirectionUrl(), - // set params for access token leg explicitly - this.oauth[this.prefix + 'verifier'] = this.authorized.oauth_verifier // Put authorized verifier - this.oauth[this.prefix + 'token'] = this.authorized.oauth_token; // Authorized token + this.parseRedirectionUrl(this.winLoc); // parse url + /* istanbul ignore else */ + if(this.isAuthorizationDataInURL()){ + this.authorize(this.redirectionData); // authorize token + // set params for access token leg explicitly + this.oauth[this.prefix + 'verifier'] = this.authorized.oauth_verifier // Put authorized verifier + this.oauth[this.prefix + 'token'] = this.authorized.oauth_token; // Authorized token + } } - AccessToken.prototype.authorizeRedirectionUrl = function(){// makes sure we have needed data in redirection url - this.parseRedirectionUrl(this.winLoc); // parse - return this.authorize(this.redirectionData); // authorize token - - } - AccessToken.prototype.parseRedirectionUrl = function(url){ // parses data in url // console.log('in parseRedirectionUrl'); @@ -48,12 +49,12 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; this.redirectionUrlParsed = true; // indicate that the url was already parsed - // console.log(this.redirectionData.twiz_); + // console.log('redirectionData: >>', this.redirectionData); } AccessToken.prototype.parse = function(str, delimiter1, delimiter2){ // parses substring of a string (str) - if(!str) throw this.CustomError('noStringProvided'); + if(!str) throw this.CustomError('urlNotFound'); var start = str.search(delimiter1); // calculate from which index to take var end ; @@ -69,13 +70,11 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; AccessToken.prototype.parseQueryParams = function (str){ var arr = []; - if(!str) throw this.CustomError('noStringProvided'); - if(str[0] === "?") str = str.substring(1); // remove "?" if we have one at beggining arr = str.split('&') // make new array element on each "&" - .map( function(el, i){ + .map( function(el){ var arr2 = el.split("="); // for each element make new array element on each "=" return arr2; @@ -101,14 +100,19 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; return data; } - + // + AccessToken.prototype.isAuthorizationDataInURL = function(){ // check that we have valid twitter redirection url + if(!this.redirectionData.oauth_token && !this.redirectionData.oauth_verifier){ // not a redirection url + throw this.CustomError('spaWarning'); + } + else return true + } AccessToken.prototype.authorize = function(sent){ // check that sent data from redirection url has needed info + //console.log('in authorize'); if(this.isRequestTokenUsed(window.localStorage)) throw this.CustomError('noRepeat'); - - // console.log('in authorize') if(!sent.oauth_verifier) throw this.CustomError('verifierNotFound'); if(!sent.oauth_token) throw this.CustomError('tokenNotFound'); @@ -123,13 +127,13 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; AccessToken.prototype.isRequestTokenUsed = function(storage){ // check that we have a token to use if(storage.requestToken_ === "null") return true; // token whould be "null" only when loadRequestToken() - // run twice on same redirection(callback) url + // runs twice on same redirection(callback) url return false; } - AccessToken.prototype.loadRequestToken = function(storage, sent){ + AccessToken.prototype.loadRequestToken = function(storage){ if(!storage.hasOwnProperty('requestToken_')) throw this.CustomError('requestTokenNotSaved'); @@ -141,33 +145,33 @@ var deliverData = require('twiz-client-redirect').prototype.deliverData; storage.requestToken_ = null; // since we've loaded the token, mark it as // used/erased with null // console.log('after erasing storage.requestToken :', storage.requestToken_); - - if (!this.loadedRequestToken) throw this.CustomError('requestTokenNotSet'); + // console.log('loadedRequestToken',this.loadedRequestToken); + if(!this.loadedRequestToken) throw this.CustomError('requestTokenNotSet'); } AccessToken.prototype.getSessionData = function(){ // gets session data from redirection url - console.log('in getSessionData') - if(!this.redirectionUrlParsed); - this.parseRedirectionUrl(window.location.href); // parse data from url + // console.log('in getSessionData') + if(!this.redirectionUrlParsed) + this.parseRedirectionUrl(window.location.href); // parse data from url - if(!this.redirectionData.data){ // return if no session data - console.log(this.messages.noSessionData); - return; - } + if(!this.redirectionData.data){ // return if no session data + console.warn(this.messages['noSessionData']); + return; + } this.sessionData = this.parseSessionData(this.redirectionData.data) // further parsing of session data - console.log(this.sessionData); + //console.log('sessionData: ',this.sessionData); return this.sessionData; } AccessToken.prototype.parseSessionData = function(str){ - if(/%[0-9][0-9]/g.test(str)) // See if there are percent encoded chars + if(/%[0-9A-Z][0-9A-Z]/g.test(str)) // See if there are percent encoded chars str = decodeURIComponent(decodeURIComponent(str)); // Decoding twice, since it was encoded twice // (by OAuth 1.0a specification). See genSBS function. return this.parseQueryParams(str); // Making an object from parsed key/values. } - AccessToken.prototype.deliverData = deliverData; // borrow function from Redirect module + AccessToken.prototype.deliverData = deliverData; // borrow function from Redirect module module.exports = AccessToken; diff --git a/test/accesstoken.js b/test/accesstoken.js index 7039cd3..a790038 100644 --- a/test/accesstoken.js +++ b/test/accesstoken.js @@ -1,24 +1,138 @@ var AccessToken = require('../src/AccessToken_instrumented'); var assert = require('assert'); +function errorValidation(name, err){ // used to check thrown errors by name + if(err.name === name) return true; +} describe('Access Token', function(){ + var at = new AccessToken(); // make instance + + var session_data = '?data=quote%3DIf%2520one%2520way%2520be%2520better%2520than%2520another%252C%2520that%2520you%2520may%2520be%2520sure%2520is%2520natures%2520way.%2520%26author%3DAristotle'; + var request_token = '&oauth_token=l4eELQAAAAAA0d0BAAABYxZJrAM'; + var verifier = '&oauth_verifier=oWP8wRebbcfArwyV0oh4YxAWMeHUFrRC'; + var query = session_data + request_token + verifier; // mock twitter redirection (callback) url with tokens + + describe('Success', function(){ - describe('Access Token leg', function(){ - var request_token = 'longStringOfAlphanumerics33521' // mock request token from first leg (request token leg); - var query = '?oauth_token='+request_token+'&oauth_verifier=similarStringOfAlphanumerics4224'; // make query string - window.localStorage.requestToken_ = request_token // mock saved request token (in request token leg) - - var at = new AccessToken(); // make instance - at.winLoc += query // mock authorized url (query string from twitter) + + + window.localStorage.requestToken_ = request_token.substring(13); // mock saved request token (in request + // token leg). Remove 'oauth_token=' + it('ready ', function(){ - assert.doesNotThrow(at.setAuthorizedTokens.bind(at)) + at.winLoc += query; // mock curent location with tokens from twitter + assert.doesNotThrow(at.setAuthorizedTokens.bind(at)); + }) + + it('oauth_verifier from url parsed', function(){ // check that oauth verifier is parsed + assert.ok(at.redirectionData.oauth_token); + }) + + it('oauth_token from url parsed', function(){ // check that oauth token is parsed + assert.ok(at.redirectionData.oauth_verifier); + }) + + it('load (saved) request token',function(){ // make sure that saved token is loaded + assert.ok(at.loadedRequestToken); + }) + + it('mark request token as used', function(){ // check that loaded token is marked as used + var rToken = window.localStorage.requestToken_; + var used = rToken === 'null' ? false : rToken + + assert.ok(!used); }) + + describe('session data', function(){ // get session data + it('get session data', function(){ + assert.ok(typeof at.getSessionData() === 'object'); + }) + + + it('redirection data', function(){ // check redirection data + var rdata = at.redirectionData.data; + assert.ok(typeof rdata === 'string' && rdata.length != 0); + }) + + it('session data', function(){ + assert.ok(typeof at.sessionData === 'object') + }) + + }) }) - describe('not ready') + describe('Failure', function(){ + var pageUrl = at.winLoc + query; // save current page + + it('window location not found - throw error', function(){ + at.winLoc = ''; // current url not present + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'urlNotFound')) + }) + + it('request token already used - throw error', function(){ // request token is allready used + at.winLoc = pageUrl; + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation(null, 'noRepeat')); + }) + + it('oauth_token missing - throw error', function(){ + at.winLoc = session_data + verifier; // leave out oauth_token + window.localStorage.requestToken_ = request_token; // make token fresh + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'tokenNotFound')) + }) + + it('oauth_verifier missing - throw error', function(){ + at.winLoc = session_data + request_token; // leave out oauth_verifier + window.localStorage.requestToken_ = request_token; // make token fresh + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'verifierNotFound')); + }) + + it('request token not saved - throw error', function(){ + at.winLoc = session_data + request_token + verifier; // set current location (url) + delete window.localStorage.requestToken_ ; // make like token was not saved + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'requestTokenNotSaved')); + }) + + it('token missmatch - throw error', function(){ // Check that received request_token + // is same as the one that is sent + at.winLoc = pageUrl + session_data + request_token + verifier; // Set current location (url) + window.localStorage.requestToken_ = 'NotSameAsTheOneReceived'; // Make saved request_token different + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'tokenMissmatch')); + }) + + it('request token not set', function(){ // property is there but has no value + at.winLoc = session_data + request_token + verifier; // set current location (url) + window.localStorage.requestToken_ = ''; // make token fresh + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'requestTokenNotSet')); + }) + + + + + describe('session data', function(){ + + it('session data not found - log warning on console', function(){ + at.winLoc = pageUrl + '?' + request_token.substring(1) + verifier; // leave out session data + window.localStorage.requestToken_ = request_token; // make token fresh + at.redirectionUrlParsed = false; // parse again + assert.doesNotThrow(at.getSessionData.bind(at), undefined); + }) + + }) + + describe('spa apps warning', function(){ + + it('Authorization data not found in url - throw error', function(){ + at.winLoc = 'https://myApp.com/noQueryString'; // simulate no authorization data (request token + // and verifier) + assert.throws(at.setAuthorizedTokens.bind(at), errorValidation.bind(null, 'spaWarning')); + }) + + }) + }) + })