├── .gitignore ├── LICENSE.md ├── OSS_NOTICE.pdf ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── sample ├── package.json └── sample.js └── test └── token.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | .idea 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ###GE Software Development License Agreement – General Release 2 | 3 | THIS SOFTWARE LICENSE AGREEMENT (the “License”) describes the rights granted by the General Electric Company, operating through GE Digital (also referred to as “GE Software”), located at 2623 Camino Ramon, San Ramon, CA 94583 (herein referred to as “Licensor”) to any entity (the “Licensee”) receiving a copy of any of the following GE Digital development materials: Predix DevBox; Predix Reference Application (“RefApp”); Predix Dashboard Seed; Predix Px, Predix Security Service redistributable .jar files; Predix Machine redistributable .jar files; and Predix Machine SDK . These materials may include scripts, compiled code, supporting components, and documentation and are collectively referred to as the “Licensed Programs”. Both Licensor and Licensee are referred to hereinafter as a “Party” and collectively as the “Parties” to this License 4 | 5 | ### Section 1 – Conditional Grant. 6 | No Licensee is required to accept this License for use of the Licensed Programs. In the absence of a signed license agreement between Licensor and Licensee specifying alternate terms, any use of the Licensed Programs by the Licensee shall be considered acceptance of these terms. The Licensed Programs are copyrighted and are licensed, not sold, to you. If you are not willing to be bound by the terms of this License, do not install, copy or use the Licensed Programs. If you received this software from any source other than the Licensor, your access to the Licensed Programs is NOT permitted under this License, and you must delete the software and any copies from your systems. 7 | 8 | ###Section 2 – Warranty Disclaimer. 9 | NO WARRANTIES. LICENSOR AND OUR AFFILIATES, RESELLERS, DISTRIBUTORS, AND VENDORS, MAKE NO WARRANTIES, EXPRESS OR IMPLIED, GUARANTEES OR CONDITIONS WITH RESPECT TO USE OF THE LICENSED PROGRAMS. LICENSEE’S USE OF ALL SUCH PROGRAMS ARE AT LICENSEE’S AND CUSTOMERS’ OWN RISK. LICENSOR PROVIDES THE LICENSED PROGRAMS ON AN “AS IS” BASIS “WITH ALL FAULTS” AND “AS AVAILABLE.” LICENSOR DOES NOT GUARANTEE THE ACCURACY OR TIMELINESS OF INFORMATION AVAILABLE FROM, OR PROCESSED BY, THE LICENSED PROGRAMS. TO THE EXTENT PERMITTED UNDER LAW, LICENSOR EXCLUDES ANY IMPLIED WARRANTIES, INCLUDING FOR MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE, WORKMANLIKE EFFORT, AND NON-INFRINGEMENT. NO GUARANTEE OF UNINTERRUPTED, TIMELY, SECURE, OR ERROR-FREE OPERATION IS MADE. 10 | 11 | THESE LICENSED PROGRAMS MAY BE USED AS PART OF A DEVELOPMENT ENVIRONMENT, AND MAY BE COMBINED WITH OTHER CODE BY END-USERS. LICENSOR IS NOT ABLE TO GUARANTEE THAT THE LICENSED PROGRAMS WILL OPERATE WITHOUT DEFECTS WHEN USED IN COMBINATION WITH END-USER SOFTWARE. LICENSEE IS ADVISED TO SAFEGUARD IMPORTANT DATA, TO USE CAUTION, AND NOT TO RELY IN ANY WAY ON THE CORRECT FUNCTIONING OR PERFORMANCE OF ANY COMBINATION OF END-USER SOFTWARE AND THE LICENSED PROGRAMS AND/OR ACCOMPANYING MATERIALS. LICENSEE IS ADVISED NOT TO USE ANY COMBINATION OF LICENSED PROGRAMS AND END-USER PROVIDED SOFTWARE IN A PRODUCTION ENVIRONMENT WITHOUT PRIOR SUITABILITY AND DEFECT TESTING. 12 | 13 | ###Section 3 – Feedback. 14 | It is expressly understood, acknowledged and agreed that you may provide GE reasonable suggestions, comments and feedback regarding the Software, including but not limited to usability, bug reports and test results, with respect to Software testing (collectively, "Feedback"). If you provide such Feedback to GE, you shall grant GE the following worldwide, non-exclusive, perpetual, irrevocable, royalty free, fully paid up rights: 15 | 16 | a. to make, use, copy, modify, sell, distribute, sub-license, and create derivative works of, the Feedback as part of any product, technology, service, specification or other documentation developed or offered by GE or any of its affiliates (individually and collectively, "GE Products"); 17 | b. to publicly perform or display, import, broadcast, transmit, distribute, license, offer to sell, and sell, rent, lease or lend copies of the Feedback (and derivative works thereof) as part of any GE Product; 18 | c. solely with respect to Licensee's copyright and trade secret rights, to sublicense to third parties the foregoing rights, including the right to sublicense to further third parties; and 19 | d. to sublicense to third parties any claims of any patents owned or licensable by Licensee that are necessarily infringed by a third party product, technology or service that uses, interfaces, interoperates or communicates with the Feedback or portion thereof incorporated into a GE Product, technology or service. Further, you represent and warrant that your Feedback is not subject to any license terms that would purport to require GE to comply with any additional obligations with respect to any GE Products that incorporate any Feedback. 20 | 21 | ###Section 4 – Reserved 22 | 23 | ###Section 5 – Limitation of Liability. 24 | LIABILITY ARISING UNDER THIS LICENSE, WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), SHALL BE LIMITED TO DIRECT, OBJECTIVELY MEASURABLE DAMAGES. LICENSOR SHALL HAVE NO LIABILITY TO THE OTHER PARTY OR TO ANY THIRD PARTY, FOR ANY INCIDENTAL, PUNITIVE, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LIABILITY FOR ANY SOFTWARE LICENSED FROM THIRD PARTIES FOR USE WITH THE SERVICES IS EXPLICILTLY DISCLAIMED AND LIMITED TO THE MAXIMUM EXTENT PERMITTED BY LAW. 25 | 26 | Notwithstanding anything to the contrary, the aggregate liability of Licensor and its suppliers under this License shall not exceed the total amounts paid by Licensee to Licensor hereunder during the one-year period immediately preceding the event which gave rise to the claims. 27 | 28 | ###Section 6 – License. 29 | 30 | A. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants Licensee a worldwide, perpetual, royalty-free, non-exclusive license to: 31 | 32 | 1. install the Licensed Programs on Licensee’s premises, and permit Licensee’s users to use the Licensed Programs so installed, solely for Licensee’s own development, testing, demonstration, staging, and production of Licensee’s own software that makes use of the Licensed Programs in a way that adds substantial functionality not present in the Licensed Programs (the result, a “Licensee Application”); 33 | 34 | 2. permit Licensee to permit third-party hosts (“Hosts”) to install the Licensee Application on such Hosts’ respective premises on Licensee’s behalf, and permit Licensee’s users to access and use the Licensed Programs so installed, solely for Licensee’s own development, testing, demonstration, staging and production purposes 35 | 36 | 3. install the Licensee Application on Licensee’s own premises and permit its own users to use the Licensee Application so installed on the same terms as sub-sections (i) and (ii) above. 37 | 38 | B. For the purposes of this License, the right to “use” the Licensed Programs shall include the right to utilize, run, access, store, copy, test or display the Licensed Programs. No right or license is granted or agreed to be granted to disassemble or decompile any Licensed Programs furnished in object code form, and Licensee agrees not to engage in any such conduct unless permitted by law. Reverse engineering of Licensed Programs provided in object code form is prohibited, unless such a right is explicitly granted by any explicit license subject to sub-section (d) below or as a matter of law, and then only to the extent explicitly permitted. Licensor shall have no obligation to support any such reverse engineering, any product or derivative of such reverse engineering, or any use of the Licensed Programs with any modified versions of any of their components under this License. 39 | 40 | C. Licensee shall ensure that any Licensee Applications incorporate the Licensed Programs in such a way as to prevent third parties (other than Hosts) from viewing the code of the Licensed Programs or gaining access to any programmatic interface or other hidden aspect of the Licensed Programs. Licensee shall also restrict distribution of the Licensed Programs, including as part of Licensee Applications, to only those parties who are notified of, and subject to, an enforceable obligation to refrain from any of the prohibited activities listed herein, such as reverse engineering or disassembling the Licensed Programs. 41 | 42 | 43 | D. Use of some open source and third party software applications or components included in or accessed through the Licensed Programs may be subject to other terms and conditions found in a separate license agreement, terms of use or “Notice” file located at the download page. The Licensed Programs are accompanied by additional software components solely to enable the Licensed Programs to operate as designed. Licensee is not permitted to use such additional software independently of the Licensed Programs unless Licensee secures a separate license for use from the named vendor. Do not use any third party code unless you agree with the applicable license terms for that code. 44 | 45 | E. Title. Title to and ownership of the Licensed Programs shall at all times remain with Licensor. 46 | 47 | ###Section 7 – Termination. 48 | 49 | A) The Licensor reserves the right to cease distribution and grant of further licenses to any or all of the Licensed Programs at any time in its sole discretion. 50 | 51 | B) The Licensor reserves the right to at any time and at its sole discretion provide updated versions of any or all of the Licensed Programs that supercede and replace the prior version of that Licensed Program. 52 | 53 | C) Your license rights under Section 6 are effective until terminated as described below: 54 | 55 | i) This license and all rights under it will terminate or cease to be effective without notice if Licensee breaches the terms of the License and does not correct or remedy such breach promptly. 56 | 57 | ii) Notwithstanding the foregoing, Licensee may terminate this License at any time for any reason or no reason by providing the Licensor written notice thereof. 58 | 59 | D) Upon any expiration or termination of this License, the rights and licenses granted to you under this License shall immediately terminate, and you shall immediately cease using and delete the Licensed Programs. Licensee Applications based upon the Licensed Programs (see Section 6(a) above) are not subject to this limitation. 60 | 61 | In the event of any expiration or termination of this Licensee, any Confidentiality provision, disclaimers of GE’s representations and warranties, choice of applicable law and limitations of GE’s liability shall survive. 62 | 63 | ###Section 8 – Applicable Law. 64 | The License shall be governed by and interpreted in accordance with the substantive law of the State of California, U.S.A., excluding its conflicts of law provisions, and by the courts of that state. 65 | -------------------------------------------------------------------------------- /OSS_NOTICE.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PredixDev/predix-fast-token/9aba668924fc49b9a9b908e3478de044cbdf6084/OSS_NOTICE.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # predix-fast-token 2 | Node module to verify UAA tokens used when protecting REST endpoints 3 | 4 | ## Intro 5 | Security is important, but having to make a POST call to UAA with every request to verify a token adds latency. Predix-fast-token reduces the number of network calls and is ~200+ times faster. We ran a test of 100,000 predix-fast-token calls and it came out to <1 ms. Don't trust us though, try youself and see what improvements you can get compared to your current method. 6 | 7 | ## Installation 8 | Install via npm 9 | 10 | ``` 11 | npm install --save predix-fast-token 12 | ``` 13 | 14 | ## Usage 15 | 16 | ### Verify (Local) 17 | Validates the token is a valid JWT, isn't expired, and was issued by a trusted issuer. 18 | 19 | 20 | Basic usage with a JWT token and list of trusted issuers 21 | 22 | ```javascript 23 | const pft = require('predix-fast-token'); 24 | pft.verify(token, trusted_issuers).then((decoded) => { 25 | // The token is valid, not expired and from a trusted issuer 26 | // Use the value of the decoded token as you wish. 27 | console.log('Good token for', decoded.user_name); 28 | }).catch((err) => { 29 | // Token is not valid, or expired, or from an untrusted issuer. 30 | console.log('No access for you', err); 31 | }); 32 | ``` 33 | 34 | As an expressjs middleware 35 | 36 | ```javascript 37 | 'use strict'; 38 | const express = require('express'); 39 | const bearerToken = require('express-bearer-token'); 40 | const predixFastToken = require('predix-fast-token'); 41 | const app = express(); 42 | 43 | const trusted_issuers = ['https://example.uaa.predix.io/oauth/token', 'https://another.uaa.predix.io/oauth/token']; 44 | 45 | app.get('/hello', (req, res, next) => { 46 | res.send('Howdy my unsecured friend!'); 47 | }); 48 | 49 | // Ensure Authorization header has a bearer token 50 | app.all('*', bearerToken(), function(req, res, next) { 51 | console.log('Req Headers', req.headers); 52 | if(req.token) { 53 | predixFastToken.verify(req.token, trusted_issuers).then((decoded) => { 54 | req.decoded = decoded; 55 | console.log('Looks good'); 56 | next(); 57 | }).catch((err) => { 58 | console.log('Nope', err); 59 | res.status(403).send('Unauthorized'); 60 | }); 61 | } else { 62 | console.log('Nope, no token'); 63 | res.status(401).send('Authentication Required'); 64 | } 65 | }); 66 | 67 | app.get('/secure', (req, res, next) => { 68 | res.send('Hello ' + req.decoded.user_name + ', my authenticated chum!'); 69 | }); 70 | 71 | // Need to let CF set the port if we're deploying there. 72 | const port = process.env.PORT || 9001; 73 | app.listen(port); 74 | console.log('Started on port ' + port); 75 | 76 | ``` 77 | ### Remote Verification 78 | Validates a token is valid by sending it against the UAA's `/check_token` endpoint, with optional in-memory caching. 79 | Using remote verification adds network latency to the verification request, compared to local verification, 80 | but is necessary if you need to validate [opaque tokens](https://www.cloudfoundry.org/opaque-access-tokens-cloud-foundry/) 81 | or check if a token has been revoked. 82 | 83 | #### Parameters 84 | - `token` - The access token 85 | - `issuer`- The UAA issuer URI to validate against 86 | - `clientId` - Your client ID issued by the UAA service 87 | - `clientSecret` - Your client secret issued by the UAA service 88 | - `opts` 89 | - `ttl` - The maximum time to live in cache for a validated token, in seconds. If zero, does not cache. `Default: 0` 90 | - `useCache` - Whether or not to look for the token in cache. `Default: true` 91 | 92 | #### Example 93 | ```javascript 94 | const pft = require('predix-fast-token'); 95 | const opts = {ttl: 60*60*2, useCache: true}; // Cache tokens for 2 hours 96 | pft.remoteVerify(token, issuer, clientId, clientSecret, opts).then((decoded) => { 97 | // The token is valid, not expired or revoked, and from the given issuer 98 | // Use the value of the decoded token as you wish. 99 | console.log('Good token for', decoded.user_name); 100 | }).catch((err) => { 101 | // Token is not valid, revoked, expired, or from a different issuer. 102 | console.log('No access for you', err); 103 | }); 104 | ``` 105 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const jwt = require('jsonwebtoken'); 3 | const rp = require('request-promise'); 4 | const url = require('url'); 5 | const debug = require('debug')('predix-fast-token'); 6 | const NodeCache = require('node-cache'); 7 | const tokenCache = new NodeCache(); 8 | 9 | let token_utils = {}; 10 | let oauthKeyCache = {}; 11 | token_utils._tokenCache = tokenCache; // Exposed for testing 12 | 13 | // This will fetch and cache the public key of the UAA used for this tenant. 14 | // This key can them be used to verify the JWT token presented so that the 15 | // details contained within the token can be trusted. Such as the user, expiry and scopes. 16 | const getKey = (keyURL, tenantUuid) => { 17 | // URL for the token is /token_key 18 | return new Promise((resolve, reject) => { 19 | if (tenantUuid && oauthKeyCache[keyURL + '-' + tenantUuid]) { 20 | // Already have it w/ tenantUuid. 21 | resolve(oauthKeyCache[keyURL + '-' + tenantUuid]); 22 | } else if (!tenantUuid && oauthKeyCache[keyURL]) { 23 | // Already have it. 24 | resolve(oauthKeyCache[keyURL]); 25 | } else { 26 | // Fetch it and cache it for later 27 | debug('Fetching key from:', keyURL); 28 | const options = tenantUuid ? { 29 | uri: keyURL, 30 | headers: { 31 | tenant: tenantUuid, 32 | }, 33 | } : {uri: keyURL}; 34 | rp.get(options).then(body => { 35 | debug('Fetched key'); 36 | const data = JSON.parse(body); 37 | // Cache it 38 | if (tenantUuid) { 39 | oauthKeyCache[keyURL + '-' + tenantUuid] = data.value; 40 | } else { oauthKeyCache[keyURL] = data.value; } 41 | resolve(data.value); 42 | }).catch(err => { 43 | debug('Error reading token key from', keyURL, err); 44 | reject(err); 45 | }); 46 | } 47 | }); 48 | }; 49 | 50 | token_utils.clearCache = () => { 51 | debug('Clearing key cache'); 52 | oauthKeyCache = {}; 53 | tokenCache.flushAll(); 54 | }; 55 | 56 | /** 57 | * Verifies that a token was signed by a trusted UAA server and that it's still valid. 58 | * 59 | * @param {string} token - The access token. 60 | * @param {string} trusted_issuers - A list of trusted issuer URIs 61 | * @param {string} tenantUuid - (optional) Used for tenant based UAA 62 | * @returns {promise} - A promise to verify the token. 63 | * Resolves with the decoded token if valid. 64 | * Rejected with an error if invalid or an error occurs. 65 | */ 66 | token_utils.verify = (token, trusted_issuers, tenantUuid) => { 67 | return new Promise((resolve, reject) => { 68 | // Decode the token to get the issuer 69 | let prelim = null; 70 | try { 71 | prelim = jwt.decode(token); 72 | } catch (err) { 73 | debug('Error decoding token', err); 74 | } 75 | // Is this token claiming to be from a trusted issuer. 76 | if (prelim && trusted_issuers.indexOf(prelim.iss) > -1) { 77 | const issuer = url.parse(prelim.iss); 78 | // Just want the protocol and host, and any path before '/oauth/token'. 79 | const uaa_path = issuer.pathname.replace('/oauth/token', ''); 80 | const uaa_server = url.format({ protocol: issuer.protocol, host: issuer.host, pathname: uaa_path + '/token_key' }); 81 | // Grab the key for this UAA server and check. 82 | getKey(uaa_server, tenantUuid).then((key) => { 83 | jwt.verify(token, key, (err, decoded) => { 84 | if (err) { 85 | debug('Invalid', err); 86 | reject(err); 87 | } else { 88 | resolve(decoded); 89 | } 90 | }); 91 | }).catch((error) => { 92 | debug('get UAA key error', error); 93 | reject(error); 94 | }); 95 | } else if (prelim) { 96 | // The decoded issuer is not in the trusted list. 97 | // No need to check that it's signed correctly 98 | reject(new Error('Not a trusted issuer')); 99 | } else { 100 | // The token is not a valid JWT token 101 | reject(new Error('Not a valid token')); 102 | } 103 | }); 104 | }; 105 | 106 | /** 107 | * Verifies that a token was signed by a trusted UAA server and that it's still valid and has not been revoked 108 | * by checking it against the UAA check_token endpoint. 109 | * 110 | * @param {string} token - The access token. 111 | * @param {string} issuer - The UAA issuer URI 112 | * @param {string} clientId - Your client id for the UAA issuer 113 | * @param {string} clientSecret - Your client secret for the UAA issuer 114 | * @param {Object} [opts={}] - A dictionary of options 115 | * @param {int} [opts.ttl=0] - The maximum time to live in cache for a validated token, in seconds. 116 | * @param {bool} [opts.useCache=true] - Whether cached values should be used for verification. 117 | * @returns {promise} - A promise to verify the token. 118 | * Resolves with the decoded/assocaited token if valid. 119 | * Rejected with an error if invalid or an error occurs. 120 | */ 121 | token_utils.remoteVerify = (token, issuer, clientId, clientSecret, opts) => { 122 | opts = opts || {}; 123 | debug('remoteVerify called with options', opts); 124 | opts.ttl = opts.ttl || 0; // Default to don't cache 125 | opts.useCache = (typeof opts.useCache === 'undefined') ? true : opts.useCache; 126 | // See if token is in cache 127 | debug('remoteVerify default options applied', opts); 128 | if (opts.useCache) { 129 | const cachedJwt = tokenCache.get(token); 130 | if (cachedJwt && cachedJwt.exp * 1000 > Date.now()) { 131 | // Valid cached token 132 | debug('Valid token found in cache', cachedJwt); 133 | return Promise.resolve(cachedJwt); 134 | } else { 135 | debug('No valid token found in cache', cachedJwt); 136 | } 137 | } 138 | // If not cached, send against the check_token endpoint 139 | const issuer_url = url.parse(issuer); 140 | const check_token_url = url.format({ protocol: issuer_url.protocol, host: issuer_url.host, pathname: '/check_token' }); 141 | debug(`Using ${check_token_url} to test token`); 142 | const request_opts = { 143 | uri: check_token_url, 144 | auth: { 145 | username: clientId, 146 | password: clientSecret 147 | }, 148 | method: 'POST', 149 | form: { 150 | token: token 151 | }, 152 | json: true 153 | }; 154 | return rp.post(request_opts) 155 | .then((jwt) => { 156 | // Successful 157 | debug('Check_token returned success', jwt); 158 | if (opts.ttl > 0) { 159 | const cacheExpire = Date.now() + opts.ttl * 1000; 160 | tokenCache.set(token, jwt, opts.ttl); 161 | debug(`Token cached until ${cacheExpire}`); 162 | } else { 163 | debug('Token caching disabled'); 164 | } 165 | 166 | return jwt; 167 | }) 168 | .catch((error) => { 169 | // Invalid token or failed request 170 | debug('UAA check_token returned error', error); 171 | if (error.error) { 172 | throw error.error; 173 | } else { 174 | throw error; 175 | } 176 | }); 177 | }; 178 | module.exports = token_utils; 179 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "predix-fast-token", 3 | "version": "1.3.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abbrev": { 8 | "version": "1.0.9", 9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", 10 | "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", 11 | "dev": true 12 | }, 13 | "ajv": { 14 | "version": "5.5.2", 15 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 16 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 17 | "requires": { 18 | "co": "^4.6.0", 19 | "fast-deep-equal": "^1.0.0", 20 | "fast-json-stable-stringify": "^2.0.0", 21 | "json-schema-traverse": "^0.3.0" 22 | } 23 | }, 24 | "align-text": { 25 | "version": "0.1.4", 26 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 27 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", 28 | "dev": true, 29 | "requires": { 30 | "kind-of": "^3.0.2", 31 | "longest": "^1.0.1", 32 | "repeat-string": "^1.5.2" 33 | } 34 | }, 35 | "amdefine": { 36 | "version": "1.0.1", 37 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 38 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 39 | "dev": true 40 | }, 41 | "argparse": { 42 | "version": "1.0.9", 43 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 44 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 45 | "dev": true, 46 | "requires": { 47 | "sprintf-js": "~1.0.2" 48 | } 49 | }, 50 | "asn1": { 51 | "version": "0.2.3", 52 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 53 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 54 | }, 55 | "assert-plus": { 56 | "version": "1.0.0", 57 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 58 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 59 | }, 60 | "assertion-error": { 61 | "version": "1.0.2", 62 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", 63 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", 64 | "dev": true 65 | }, 66 | "async": { 67 | "version": "1.5.2", 68 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 69 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 70 | "dev": true 71 | }, 72 | "asynckit": { 73 | "version": "0.4.0", 74 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 75 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 76 | }, 77 | "aws-sign2": { 78 | "version": "0.7.0", 79 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 80 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 81 | }, 82 | "aws4": { 83 | "version": "1.7.0", 84 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", 85 | "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" 86 | }, 87 | "balanced-match": { 88 | "version": "0.4.2", 89 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", 90 | "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", 91 | "dev": true 92 | }, 93 | "bcrypt-pbkdf": { 94 | "version": "1.0.1", 95 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 96 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 97 | "optional": true, 98 | "requires": { 99 | "tweetnacl": "^0.14.3" 100 | } 101 | }, 102 | "bluebird": { 103 | "version": "3.5.1", 104 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 105 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 106 | }, 107 | "brace-expansion": { 108 | "version": "1.1.7", 109 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", 110 | "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", 111 | "dev": true, 112 | "requires": { 113 | "balanced-match": "^0.4.1", 114 | "concat-map": "0.0.1" 115 | } 116 | }, 117 | "browser-stdout": { 118 | "version": "1.3.1", 119 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 120 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 121 | "dev": true 122 | }, 123 | "buffer-equal-constant-time": { 124 | "version": "1.0.1", 125 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 126 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 127 | }, 128 | "camelcase": { 129 | "version": "1.2.1", 130 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 131 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", 132 | "dev": true, 133 | "optional": true 134 | }, 135 | "caseless": { 136 | "version": "0.12.0", 137 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 138 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 139 | }, 140 | "center-align": { 141 | "version": "0.1.3", 142 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 143 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", 144 | "dev": true, 145 | "optional": true, 146 | "requires": { 147 | "align-text": "^0.1.3", 148 | "lazy-cache": "^1.0.3" 149 | } 150 | }, 151 | "chai": { 152 | "version": "3.5.0", 153 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 154 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 155 | "dev": true, 156 | "requires": { 157 | "assertion-error": "^1.0.1", 158 | "deep-eql": "^0.1.3", 159 | "type-detect": "^1.0.0" 160 | } 161 | }, 162 | "chai-as-promised": { 163 | "version": "6.0.0", 164 | "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-6.0.0.tgz", 165 | "integrity": "sha1-GgKkM6byTa+sY7nJb6FoTbGqjaY=", 166 | "dev": true, 167 | "requires": { 168 | "check-error": "^1.0.2" 169 | } 170 | }, 171 | "check-error": { 172 | "version": "1.0.2", 173 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 174 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 175 | "dev": true 176 | }, 177 | "cliui": { 178 | "version": "2.1.0", 179 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 180 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", 181 | "dev": true, 182 | "optional": true, 183 | "requires": { 184 | "center-align": "^0.1.1", 185 | "right-align": "^0.1.1", 186 | "wordwrap": "0.0.2" 187 | }, 188 | "dependencies": { 189 | "wordwrap": { 190 | "version": "0.0.2", 191 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 192 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", 193 | "dev": true, 194 | "optional": true 195 | } 196 | } 197 | }, 198 | "clone": { 199 | "version": "2.1.1", 200 | "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", 201 | "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" 202 | }, 203 | "co": { 204 | "version": "4.6.0", 205 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 206 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 207 | }, 208 | "combined-stream": { 209 | "version": "1.0.6", 210 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 211 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 212 | "requires": { 213 | "delayed-stream": "~1.0.0" 214 | } 215 | }, 216 | "commander": { 217 | "version": "2.15.1", 218 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 219 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 220 | "dev": true 221 | }, 222 | "concat-map": { 223 | "version": "0.0.1", 224 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 225 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 226 | "dev": true 227 | }, 228 | "core-util-is": { 229 | "version": "1.0.2", 230 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 231 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 232 | }, 233 | "dashdash": { 234 | "version": "1.14.1", 235 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 236 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 237 | "requires": { 238 | "assert-plus": "^1.0.0" 239 | } 240 | }, 241 | "debug": { 242 | "version": "2.6.9", 243 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 244 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 245 | "requires": { 246 | "ms": "2.0.0" 247 | } 248 | }, 249 | "decamelize": { 250 | "version": "1.2.0", 251 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 252 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 253 | "dev": true, 254 | "optional": true 255 | }, 256 | "deep-eql": { 257 | "version": "0.1.3", 258 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 259 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 260 | "dev": true, 261 | "requires": { 262 | "type-detect": "0.1.1" 263 | }, 264 | "dependencies": { 265 | "type-detect": { 266 | "version": "0.1.1", 267 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 268 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 269 | "dev": true 270 | } 271 | } 272 | }, 273 | "deep-is": { 274 | "version": "0.1.3", 275 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 276 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 277 | "dev": true 278 | }, 279 | "delayed-stream": { 280 | "version": "1.0.0", 281 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 282 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 283 | }, 284 | "diff": { 285 | "version": "3.2.0", 286 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 287 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 288 | "dev": true 289 | }, 290 | "ecc-jsbn": { 291 | "version": "0.1.1", 292 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 293 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 294 | "optional": true, 295 | "requires": { 296 | "jsbn": "~0.1.0" 297 | } 298 | }, 299 | "ecdsa-sig-formatter": { 300 | "version": "1.0.10", 301 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", 302 | "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", 303 | "requires": { 304 | "safe-buffer": "^5.0.1" 305 | } 306 | }, 307 | "escape-string-regexp": { 308 | "version": "1.0.5", 309 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 310 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 311 | "dev": true 312 | }, 313 | "escodegen": { 314 | "version": "1.8.1", 315 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", 316 | "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", 317 | "dev": true, 318 | "requires": { 319 | "esprima": "^2.7.1", 320 | "estraverse": "^1.9.1", 321 | "esutils": "^2.0.2", 322 | "optionator": "^0.8.1", 323 | "source-map": "~0.2.0" 324 | } 325 | }, 326 | "esprima": { 327 | "version": "2.7.3", 328 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", 329 | "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", 330 | "dev": true 331 | }, 332 | "estraverse": { 333 | "version": "1.9.3", 334 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", 335 | "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", 336 | "dev": true 337 | }, 338 | "esutils": { 339 | "version": "2.0.2", 340 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 341 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 342 | "dev": true 343 | }, 344 | "extend": { 345 | "version": "3.0.1", 346 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 347 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 348 | }, 349 | "extsprintf": { 350 | "version": "1.3.0", 351 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 352 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 353 | }, 354 | "fast-deep-equal": { 355 | "version": "1.1.0", 356 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 357 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 358 | }, 359 | "fast-json-stable-stringify": { 360 | "version": "2.0.0", 361 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 362 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 363 | }, 364 | "fast-levenshtein": { 365 | "version": "2.0.6", 366 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 367 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 368 | "dev": true 369 | }, 370 | "forever-agent": { 371 | "version": "0.6.1", 372 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 373 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 374 | }, 375 | "form-data": { 376 | "version": "2.3.2", 377 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 378 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 379 | "requires": { 380 | "asynckit": "^0.4.0", 381 | "combined-stream": "1.0.6", 382 | "mime-types": "^2.1.12" 383 | } 384 | }, 385 | "formatio": { 386 | "version": "1.2.0", 387 | "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", 388 | "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", 389 | "dev": true, 390 | "requires": { 391 | "samsam": "1.x" 392 | } 393 | }, 394 | "fs.realpath": { 395 | "version": "1.0.0", 396 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 397 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 398 | "dev": true 399 | }, 400 | "getpass": { 401 | "version": "0.1.7", 402 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 403 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 404 | "requires": { 405 | "assert-plus": "^1.0.0" 406 | } 407 | }, 408 | "glob": { 409 | "version": "5.0.15", 410 | "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", 411 | "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", 412 | "dev": true, 413 | "requires": { 414 | "inflight": "^1.0.4", 415 | "inherits": "2", 416 | "minimatch": "2 || 3", 417 | "once": "^1.3.0", 418 | "path-is-absolute": "^1.0.0" 419 | } 420 | }, 421 | "growl": { 422 | "version": "1.10.5", 423 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 424 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 425 | "dev": true 426 | }, 427 | "handlebars": { 428 | "version": "4.0.10", 429 | "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", 430 | "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", 431 | "dev": true, 432 | "requires": { 433 | "async": "^1.4.0", 434 | "optimist": "^0.6.1", 435 | "source-map": "^0.4.4", 436 | "uglify-js": "^2.6" 437 | }, 438 | "dependencies": { 439 | "source-map": { 440 | "version": "0.4.4", 441 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", 442 | "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", 443 | "dev": true, 444 | "requires": { 445 | "amdefine": ">=0.0.4" 446 | } 447 | } 448 | } 449 | }, 450 | "har-schema": { 451 | "version": "2.0.0", 452 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 453 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 454 | }, 455 | "har-validator": { 456 | "version": "5.0.3", 457 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 458 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 459 | "requires": { 460 | "ajv": "^5.1.0", 461 | "har-schema": "^2.0.0" 462 | } 463 | }, 464 | "has-flag": { 465 | "version": "1.0.0", 466 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 467 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 468 | "dev": true 469 | }, 470 | "he": { 471 | "version": "1.1.1", 472 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 473 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 474 | "dev": true 475 | }, 476 | "http-signature": { 477 | "version": "1.2.0", 478 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 479 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 480 | "requires": { 481 | "assert-plus": "^1.0.0", 482 | "jsprim": "^1.2.2", 483 | "sshpk": "^1.7.0" 484 | } 485 | }, 486 | "inflight": { 487 | "version": "1.0.6", 488 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 489 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 490 | "dev": true, 491 | "requires": { 492 | "once": "^1.3.0", 493 | "wrappy": "1" 494 | } 495 | }, 496 | "inherits": { 497 | "version": "2.0.3", 498 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 499 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 500 | "dev": true 501 | }, 502 | "is-buffer": { 503 | "version": "1.1.5", 504 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", 505 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", 506 | "dev": true 507 | }, 508 | "is-typedarray": { 509 | "version": "1.0.0", 510 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 511 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 512 | }, 513 | "isarray": { 514 | "version": "0.0.1", 515 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 516 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 517 | "dev": true 518 | }, 519 | "isexe": { 520 | "version": "2.0.0", 521 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 522 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 523 | "dev": true 524 | }, 525 | "isstream": { 526 | "version": "0.1.2", 527 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 528 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 529 | }, 530 | "istanbul": { 531 | "version": "0.4.5", 532 | "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", 533 | "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", 534 | "dev": true, 535 | "requires": { 536 | "abbrev": "1.0.x", 537 | "async": "1.x", 538 | "escodegen": "1.8.x", 539 | "esprima": "2.7.x", 540 | "glob": "^5.0.15", 541 | "handlebars": "^4.0.1", 542 | "js-yaml": "3.x", 543 | "mkdirp": "0.5.x", 544 | "nopt": "3.x", 545 | "once": "1.x", 546 | "resolve": "1.1.x", 547 | "supports-color": "^3.1.0", 548 | "which": "^1.1.1", 549 | "wordwrap": "^1.0.0" 550 | } 551 | }, 552 | "js-yaml": { 553 | "version": "3.8.4", 554 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz", 555 | "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=", 556 | "dev": true, 557 | "requires": { 558 | "argparse": "^1.0.7", 559 | "esprima": "^3.1.1" 560 | }, 561 | "dependencies": { 562 | "esprima": { 563 | "version": "3.1.3", 564 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", 565 | "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", 566 | "dev": true 567 | } 568 | } 569 | }, 570 | "jsbn": { 571 | "version": "0.1.1", 572 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 573 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 574 | "optional": true 575 | }, 576 | "json-schema": { 577 | "version": "0.2.3", 578 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 579 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 580 | }, 581 | "json-schema-traverse": { 582 | "version": "0.3.1", 583 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 584 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 585 | }, 586 | "json-stringify-safe": { 587 | "version": "5.0.1", 588 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 589 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 590 | }, 591 | "jsonwebtoken": { 592 | "version": "8.2.2", 593 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.2.tgz", 594 | "integrity": "sha512-rFFq7ow/JpPzwgaz4IyRL9cp7f4ptjW92eZgsQyqkysLBmDjSSBhnKfQESoq0GU+qJXK/CQ0o4shgwbUPiFCdw==", 595 | "requires": { 596 | "jws": "^3.1.5", 597 | "lodash.includes": "^4.3.0", 598 | "lodash.isboolean": "^3.0.3", 599 | "lodash.isinteger": "^4.0.4", 600 | "lodash.isnumber": "^3.0.3", 601 | "lodash.isplainobject": "^4.0.6", 602 | "lodash.isstring": "^4.0.1", 603 | "lodash.once": "^4.0.0", 604 | "ms": "^2.1.1", 605 | "xtend": "^4.0.1" 606 | }, 607 | "dependencies": { 608 | "ms": { 609 | "version": "2.1.1", 610 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 611 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 612 | } 613 | } 614 | }, 615 | "jsprim": { 616 | "version": "1.4.1", 617 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 618 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 619 | "requires": { 620 | "assert-plus": "1.0.0", 621 | "extsprintf": "1.3.0", 622 | "json-schema": "0.2.3", 623 | "verror": "1.10.0" 624 | } 625 | }, 626 | "jwa": { 627 | "version": "1.1.6", 628 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", 629 | "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", 630 | "requires": { 631 | "buffer-equal-constant-time": "1.0.1", 632 | "ecdsa-sig-formatter": "1.0.10", 633 | "safe-buffer": "^5.0.1" 634 | } 635 | }, 636 | "jws": { 637 | "version": "3.1.5", 638 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", 639 | "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", 640 | "requires": { 641 | "jwa": "^1.1.5", 642 | "safe-buffer": "^5.0.1" 643 | } 644 | }, 645 | "kind-of": { 646 | "version": "3.2.2", 647 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 648 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 649 | "dev": true, 650 | "requires": { 651 | "is-buffer": "^1.1.5" 652 | } 653 | }, 654 | "lazy-cache": { 655 | "version": "1.0.4", 656 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 657 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", 658 | "dev": true, 659 | "optional": true 660 | }, 661 | "levn": { 662 | "version": "0.3.0", 663 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 664 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 665 | "dev": true, 666 | "requires": { 667 | "prelude-ls": "~1.1.2", 668 | "type-check": "~0.3.2" 669 | } 670 | }, 671 | "lodash": { 672 | "version": "4.17.10", 673 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 674 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 675 | }, 676 | "lodash.includes": { 677 | "version": "4.3.0", 678 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 679 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 680 | }, 681 | "lodash.isboolean": { 682 | "version": "3.0.3", 683 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 684 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 685 | }, 686 | "lodash.isinteger": { 687 | "version": "4.0.4", 688 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 689 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 690 | }, 691 | "lodash.isnumber": { 692 | "version": "3.0.3", 693 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 694 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 695 | }, 696 | "lodash.isplainobject": { 697 | "version": "4.0.6", 698 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 699 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 700 | }, 701 | "lodash.isstring": { 702 | "version": "4.0.1", 703 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 704 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 705 | }, 706 | "lodash.once": { 707 | "version": "4.1.1", 708 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 709 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 710 | }, 711 | "lolex": { 712 | "version": "1.6.0", 713 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", 714 | "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", 715 | "dev": true 716 | }, 717 | "longest": { 718 | "version": "1.0.1", 719 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 720 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", 721 | "dev": true 722 | }, 723 | "mime-db": { 724 | "version": "1.33.0", 725 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 726 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 727 | }, 728 | "mime-types": { 729 | "version": "2.1.18", 730 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 731 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 732 | "requires": { 733 | "mime-db": "~1.33.0" 734 | } 735 | }, 736 | "minimatch": { 737 | "version": "3.0.4", 738 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 739 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 740 | "dev": true, 741 | "requires": { 742 | "brace-expansion": "^1.1.7" 743 | } 744 | }, 745 | "minimist": { 746 | "version": "0.0.10", 747 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 748 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", 749 | "dev": true 750 | }, 751 | "mkdirp": { 752 | "version": "0.5.1", 753 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 754 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 755 | "dev": true, 756 | "requires": { 757 | "minimist": "0.0.8" 758 | }, 759 | "dependencies": { 760 | "minimist": { 761 | "version": "0.0.8", 762 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 763 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 764 | "dev": true 765 | } 766 | } 767 | }, 768 | "mocha": { 769 | "version": "5.2.0", 770 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 771 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 772 | "dev": true, 773 | "requires": { 774 | "browser-stdout": "1.3.1", 775 | "commander": "2.15.1", 776 | "debug": "3.1.0", 777 | "diff": "3.5.0", 778 | "escape-string-regexp": "1.0.5", 779 | "glob": "7.1.2", 780 | "growl": "1.10.5", 781 | "he": "1.1.1", 782 | "minimatch": "3.0.4", 783 | "mkdirp": "0.5.1", 784 | "supports-color": "5.4.0" 785 | }, 786 | "dependencies": { 787 | "debug": { 788 | "version": "3.1.0", 789 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 790 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 791 | "dev": true, 792 | "requires": { 793 | "ms": "2.0.0" 794 | } 795 | }, 796 | "diff": { 797 | "version": "3.5.0", 798 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 799 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 800 | "dev": true 801 | }, 802 | "glob": { 803 | "version": "7.1.2", 804 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 805 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 806 | "dev": true, 807 | "requires": { 808 | "fs.realpath": "^1.0.0", 809 | "inflight": "^1.0.4", 810 | "inherits": "2", 811 | "minimatch": "^3.0.4", 812 | "once": "^1.3.0", 813 | "path-is-absolute": "^1.0.0" 814 | } 815 | }, 816 | "has-flag": { 817 | "version": "3.0.0", 818 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 819 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 820 | "dev": true 821 | }, 822 | "supports-color": { 823 | "version": "5.4.0", 824 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 825 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 826 | "dev": true, 827 | "requires": { 828 | "has-flag": "^3.0.0" 829 | } 830 | } 831 | } 832 | }, 833 | "ms": { 834 | "version": "2.0.0", 835 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 836 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 837 | }, 838 | "native-promise-only": { 839 | "version": "0.8.1", 840 | "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", 841 | "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", 842 | "dev": true 843 | }, 844 | "node-cache": { 845 | "version": "4.2.0", 846 | "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.0.tgz", 847 | "integrity": "sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==", 848 | "requires": { 849 | "clone": "2.x", 850 | "lodash": "4.x" 851 | } 852 | }, 853 | "nopt": { 854 | "version": "3.0.6", 855 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", 856 | "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", 857 | "dev": true, 858 | "requires": { 859 | "abbrev": "1" 860 | } 861 | }, 862 | "oauth-sign": { 863 | "version": "0.8.2", 864 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 865 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 866 | }, 867 | "once": { 868 | "version": "1.4.0", 869 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 870 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 871 | "dev": true, 872 | "requires": { 873 | "wrappy": "1" 874 | } 875 | }, 876 | "optimist": { 877 | "version": "0.6.1", 878 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 879 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 880 | "dev": true, 881 | "requires": { 882 | "minimist": "~0.0.1", 883 | "wordwrap": "~0.0.2" 884 | }, 885 | "dependencies": { 886 | "wordwrap": { 887 | "version": "0.0.3", 888 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 889 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 890 | "dev": true 891 | } 892 | } 893 | }, 894 | "optionator": { 895 | "version": "0.8.2", 896 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 897 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 898 | "dev": true, 899 | "requires": { 900 | "deep-is": "~0.1.3", 901 | "fast-levenshtein": "~2.0.4", 902 | "levn": "~0.3.0", 903 | "prelude-ls": "~1.1.2", 904 | "type-check": "~0.3.2", 905 | "wordwrap": "~1.0.0" 906 | } 907 | }, 908 | "path-is-absolute": { 909 | "version": "1.0.1", 910 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 911 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 912 | "dev": true 913 | }, 914 | "path-to-regexp": { 915 | "version": "1.7.0", 916 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 917 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 918 | "dev": true, 919 | "requires": { 920 | "isarray": "0.0.1" 921 | } 922 | }, 923 | "performance-now": { 924 | "version": "2.1.0", 925 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 926 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 927 | }, 928 | "prelude-ls": { 929 | "version": "1.1.2", 930 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 931 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 932 | "dev": true 933 | }, 934 | "punycode": { 935 | "version": "1.4.1", 936 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 937 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 938 | }, 939 | "qs": { 940 | "version": "6.5.2", 941 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 942 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 943 | }, 944 | "repeat-string": { 945 | "version": "1.6.1", 946 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 947 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", 948 | "dev": true 949 | }, 950 | "request": { 951 | "version": "2.87.0", 952 | "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", 953 | "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", 954 | "requires": { 955 | "aws-sign2": "~0.7.0", 956 | "aws4": "^1.6.0", 957 | "caseless": "~0.12.0", 958 | "combined-stream": "~1.0.5", 959 | "extend": "~3.0.1", 960 | "forever-agent": "~0.6.1", 961 | "form-data": "~2.3.1", 962 | "har-validator": "~5.0.3", 963 | "http-signature": "~1.2.0", 964 | "is-typedarray": "~1.0.0", 965 | "isstream": "~0.1.2", 966 | "json-stringify-safe": "~5.0.1", 967 | "mime-types": "~2.1.17", 968 | "oauth-sign": "~0.8.2", 969 | "performance-now": "^2.1.0", 970 | "qs": "~6.5.1", 971 | "safe-buffer": "^5.1.1", 972 | "tough-cookie": "~2.3.3", 973 | "tunnel-agent": "^0.6.0", 974 | "uuid": "^3.1.0" 975 | } 976 | }, 977 | "request-promise": { 978 | "version": "4.2.2", 979 | "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", 980 | "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", 981 | "requires": { 982 | "bluebird": "^3.5.0", 983 | "request-promise-core": "1.1.1", 984 | "stealthy-require": "^1.1.0", 985 | "tough-cookie": ">=2.3.3" 986 | } 987 | }, 988 | "request-promise-core": { 989 | "version": "1.1.1", 990 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 991 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", 992 | "requires": { 993 | "lodash": "^4.13.1" 994 | } 995 | }, 996 | "resolve": { 997 | "version": "1.1.7", 998 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", 999 | "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", 1000 | "dev": true 1001 | }, 1002 | "right-align": { 1003 | "version": "0.1.3", 1004 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 1005 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", 1006 | "dev": true, 1007 | "optional": true, 1008 | "requires": { 1009 | "align-text": "^0.1.1" 1010 | } 1011 | }, 1012 | "safe-buffer": { 1013 | "version": "5.1.1", 1014 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1015 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 1016 | }, 1017 | "samsam": { 1018 | "version": "1.3.0", 1019 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", 1020 | "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", 1021 | "dev": true 1022 | }, 1023 | "sinon": { 1024 | "version": "2.4.1", 1025 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", 1026 | "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", 1027 | "dev": true, 1028 | "requires": { 1029 | "diff": "^3.1.0", 1030 | "formatio": "1.2.0", 1031 | "lolex": "^1.6.0", 1032 | "native-promise-only": "^0.8.1", 1033 | "path-to-regexp": "^1.7.0", 1034 | "samsam": "^1.1.3", 1035 | "text-encoding": "0.6.4", 1036 | "type-detect": "^4.0.0" 1037 | }, 1038 | "dependencies": { 1039 | "type-detect": { 1040 | "version": "4.0.8", 1041 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 1042 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 1043 | "dev": true 1044 | } 1045 | } 1046 | }, 1047 | "source-map": { 1048 | "version": "0.2.0", 1049 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", 1050 | "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", 1051 | "dev": true, 1052 | "optional": true, 1053 | "requires": { 1054 | "amdefine": ">=0.0.4" 1055 | } 1056 | }, 1057 | "sprintf-js": { 1058 | "version": "1.0.3", 1059 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1060 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1061 | "dev": true 1062 | }, 1063 | "sshpk": { 1064 | "version": "1.14.1", 1065 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", 1066 | "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", 1067 | "requires": { 1068 | "asn1": "~0.2.3", 1069 | "assert-plus": "^1.0.0", 1070 | "bcrypt-pbkdf": "^1.0.0", 1071 | "dashdash": "^1.12.0", 1072 | "ecc-jsbn": "~0.1.1", 1073 | "getpass": "^0.1.1", 1074 | "jsbn": "~0.1.0", 1075 | "tweetnacl": "~0.14.0" 1076 | } 1077 | }, 1078 | "stealthy-require": { 1079 | "version": "1.1.1", 1080 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 1081 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 1082 | }, 1083 | "supports-color": { 1084 | "version": "3.2.3", 1085 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", 1086 | "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", 1087 | "dev": true, 1088 | "requires": { 1089 | "has-flag": "^1.0.0" 1090 | } 1091 | }, 1092 | "text-encoding": { 1093 | "version": "0.6.4", 1094 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", 1095 | "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", 1096 | "dev": true 1097 | }, 1098 | "tough-cookie": { 1099 | "version": "2.3.3", 1100 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", 1101 | "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", 1102 | "requires": { 1103 | "punycode": "^1.4.1" 1104 | } 1105 | }, 1106 | "tunnel-agent": { 1107 | "version": "0.6.0", 1108 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1109 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1110 | "requires": { 1111 | "safe-buffer": "^5.0.1" 1112 | } 1113 | }, 1114 | "tweetnacl": { 1115 | "version": "0.14.5", 1116 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1117 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1118 | "optional": true 1119 | }, 1120 | "type-check": { 1121 | "version": "0.3.2", 1122 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1123 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1124 | "dev": true, 1125 | "requires": { 1126 | "prelude-ls": "~1.1.2" 1127 | } 1128 | }, 1129 | "type-detect": { 1130 | "version": "1.0.0", 1131 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 1132 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 1133 | "dev": true 1134 | }, 1135 | "uglify-js": { 1136 | "version": "2.8.28", 1137 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.28.tgz", 1138 | "integrity": "sha512-WqKNbmNJKzIdIEQu/U2ytgGBbhCy2PVks94GoetczOAJ/zCgVu2CuO7gguI5KPFGPtUtI1dmPQl6h0D4cPzypA==", 1139 | "dev": true, 1140 | "optional": true, 1141 | "requires": { 1142 | "source-map": "~0.5.1", 1143 | "uglify-to-browserify": "~1.0.0", 1144 | "yargs": "~3.10.0" 1145 | }, 1146 | "dependencies": { 1147 | "source-map": { 1148 | "version": "0.5.6", 1149 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 1150 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", 1151 | "dev": true, 1152 | "optional": true 1153 | } 1154 | } 1155 | }, 1156 | "uglify-to-browserify": { 1157 | "version": "1.0.2", 1158 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 1159 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 1160 | "dev": true, 1161 | "optional": true 1162 | }, 1163 | "uuid": { 1164 | "version": "3.2.1", 1165 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 1166 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 1167 | }, 1168 | "verror": { 1169 | "version": "1.10.0", 1170 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1171 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1172 | "requires": { 1173 | "assert-plus": "^1.0.0", 1174 | "core-util-is": "1.0.2", 1175 | "extsprintf": "^1.2.0" 1176 | } 1177 | }, 1178 | "which": { 1179 | "version": "1.2.14", 1180 | "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", 1181 | "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", 1182 | "dev": true, 1183 | "requires": { 1184 | "isexe": "^2.0.0" 1185 | } 1186 | }, 1187 | "window-size": { 1188 | "version": "0.1.0", 1189 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 1190 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", 1191 | "dev": true, 1192 | "optional": true 1193 | }, 1194 | "wordwrap": { 1195 | "version": "1.0.0", 1196 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1197 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 1198 | "dev": true 1199 | }, 1200 | "wrappy": { 1201 | "version": "1.0.2", 1202 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1203 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1204 | "dev": true 1205 | }, 1206 | "xtend": { 1207 | "version": "4.0.1", 1208 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1209 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 1210 | }, 1211 | "yargs": { 1212 | "version": "3.10.0", 1213 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 1214 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", 1215 | "dev": true, 1216 | "optional": true, 1217 | "requires": { 1218 | "camelcase": "^1.0.2", 1219 | "cliui": "^2.1.0", 1220 | "decamelize": "^1.0.0", 1221 | "window-size": "0.1.0" 1222 | } 1223 | } 1224 | } 1225 | } 1226 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "predix-fast-token", 3 | "version": "1.3.1", 4 | "description": "Node module to verify UAA tokens used when protecting REST endpoints", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha -R spec test/token.spec", 8 | "coverage": "istanbul cover _mocha -- -R spec test/token.spec" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/PredixDev/predix-fast-token.git" 13 | }, 14 | "keywords": [ 15 | "predix", 16 | "uaa", 17 | "node", 18 | "fast", 19 | "token" 20 | ], 21 | "author": "Ben Sykes ", 22 | "license": "SEE LICENSE IN LICENSE.md", 23 | "devDependencies": { 24 | "chai": "^3.5.0", 25 | "chai-as-promised": "^6.0.0", 26 | "istanbul": "^0.4.3", 27 | "mocha": "^5.2.0", 28 | "sinon": "^2.4.1" 29 | }, 30 | "dependencies": { 31 | "debug": "^2.6.9", 32 | "jsonwebtoken": "^8.2.2", 33 | "node-cache": "^4.2.0", 34 | "request": "^2.87.0", 35 | "request-promise": "^4.2.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "predix-fast-token-sample", 3 | "version": "0.1.0", 4 | "description": "Node module to verify UAA tokens used when protecting REST endpoints", 5 | "main": "sample.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.build.ge.com/hubs/predix-fast-token.git" 9 | }, 10 | "keywords": [ 11 | "uaa", 12 | "node", 13 | "fast", 14 | "token" 15 | ], 16 | "author": "Ben Sykes ", 17 | "license": "SEE LICENSE IN LICENSE.md", 18 | "dependencies": { 19 | "express": "^4.15.3", 20 | "express-bearer-token": "^2.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/sample.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const express = require('express'); 3 | const bearerToken = require('express-bearer-token'); 4 | const predixFastToken = require('../'); 5 | const app = express(); 6 | 7 | const trusted_issuers = ['https://95e4a01d-0fa4-4bcf-bbe2-76c504322360.predix-uaa.run.aws-usw02-pr.ice.predix.io/oauth/token']; 8 | 9 | app.get('/hello', (req, res, next) => { 10 | res.send('Howdy my unsecured friend!'); 11 | }); 12 | 13 | // Ensure Authorization header has a bearer token 14 | app.get('/fast', bearerToken(), function(req, res, next) { 15 | console.log('Req Headers', req.headers); 16 | if(req.token) { 17 | predixFastToken.verify(req.token, trusted_issuers).then((decoded) => { 18 | req.decoded = decoded; 19 | console.log('Looks good'); 20 | res.send('Hello ' + req.decoded.user_name + ', my fast-token authenticated chum!'); 21 | }).catch((err) => { 22 | console.log('Nope', err); 23 | res.status(403).send('Unauthorized'); 24 | }); 25 | } else { 26 | console.log('Nope, no token'); 27 | res.status(401).send('Authentication Required'); 28 | } 29 | }); 30 | 31 | // Ensure Authorization header has a bearer token 32 | app.get('/remote', bearerToken(), function(req, res, next) { 33 | console.log('Req Headers', req.headers); 34 | if(req.token) { 35 | predixFastToken.remoteVerify(req.token, trusted_issuers[0], 'token_check_user', '4751a79c-de03-4b1a-a573-0980cce4b4ca', { ttl: 5, useCache: true }).then((decoded) => { 36 | req.decoded = decoded; 37 | console.log('Looks good'); 38 | res.send('Hello ' + req.decoded.user_name + ', my remote authenticated chum!'); 39 | }).catch((err) => { 40 | console.log('Nope', err); 41 | res.status(403).send('Unauthorized'); 42 | }); 43 | } else { 44 | console.log('Nope, no token'); 45 | res.status(401).send('Authentication Required'); 46 | } 47 | }); 48 | 49 | // Need to let CF set the port if we're deploying there. 50 | const port = process.env.PORT || 9001; 51 | app.listen(port); 52 | console.log('Started on port ' + port); 53 | -------------------------------------------------------------------------------- /test/token.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const chai = require('chai'); 3 | const chaiAsPromised = require('chai-as-promised'); 4 | chai.use(chaiAsPromised); 5 | const expect = chai.expect; 6 | const rp = require('request-promise'); 7 | const sinon = require('sinon'); 8 | const token_util = require('../index'); 9 | 10 | const key1 = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0m59l2u9iDnMbrXHfqkO\nrn2dVQ3vfBJqcDuFUK03d+1PZGbVlNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7\nfYb3d8TjhV86Y997Fl4DBrxgM6KTJOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQB\nLCl0vpcXBtFLMaSbpv1ozi8h7DJyVZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDO\nkqwIn7Glry9n9Suxygbf8g5AzpWcusZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPo\njfj9Cw2QICsc5+Pwf21fP+hzf+1WSRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nI\nJwIDAQAB\n-----END PUBLIC KEY-----\n"; 11 | const key2 = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5VXMZBf2fUqNViwhkaKC\ntpnKX4MgKAcFA8KGiFYgChss8v/yB41wA8f+UfJmCOMIswRELKjHOp4tm9XtkCqy\nO/09RHqkrxG33za5tUhXSLaYX9MyMJcvbAXJ8cE9uu5Hv6Q4Gs65q/brwchh87Yb\nlCCvqGQ7QggEjqt2+bWGgjHDw9pKBXXRkA8t3fsH+sh2YgGCoRHH5Dd5QKpVkIGW\nnXlNIjRTd4g7rjE4Y3F1TaAhHpCoMOdviR++RIs3PdCi8ZUoS7V+mCWwOr61D7At\nxBjdnDDu/PZgLxlt1JEXt07V0xTzjztJ4r8qz5PkBZJeuZpHmiZoDNEquOhMQPhB\nIQIDAQAB\n-----END PUBLIC KEY-----\n" 12 | 13 | const token1_valid = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIwOTkxNTYzZjVjYTI0YjM5YjAxYzAzZjVlMTBmMTY0YiIsInN1YiI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJ0ZXN0IiwiY2lkIjoidGVzdCIsImF6cCI6InRlc3QiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6InRlc3RlciIsImVtYWlsIjoidGVzdGVyQGRlbW8ubG9jYWwiLCJhdXRoX3RpbWUiOjE0NjM1ODE1NjQsInJldl9zaWciOiI4YTBkMzVjZSIsImlhdCI6MTQ2MzU4MTYxNSwiZXhwIjozNjExMDY1MjYyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbInRlc3QiLCJvcGVuaWQiXX0.bG36YmWafz1B7ZH-kMX4Wh_xDRpwGUNYGn2Cizxr3ywmWE7gsupDrIpzmGnlG389IGzMGfqEb_nwtHT8mqhLpxN-IwT1SIz9qWDH4kt07qsJGWnzAIDH_fF6np_iMghz6JQJsLYG5rIKoR7ibNJl4xK6PhoIk4F7Rw2GuLcKuq9ILQRRAJTfuzZEBjVIwqTbDulXgOveCbagjPF455i_QxsxMzpq001nlCN6OfjCbNpPnLjpFUp4eZ3K-gGQfdLTxMEgjnfl7B-U45vtOPBJ0sXIXvfOUXWneSt6BkPka3GCcz3GdqmYDbNvsZD5IRyCDjQ0sZv7IHZHQf-vgLReLg'; 14 | const token1_expired = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIwMzE1ZjFmYTFhOWE0MDFjOTc0Y2U0ZGUyMjA1MDQ5MiIsInN1YiI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJ0ZXN0IiwiY2lkIjoidGVzdCIsImF6cCI6InRlc3QiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6InRlc3RlciIsImVtYWlsIjoidGVzdGVyQGRlbW8ubG9jYWwiLCJhdXRoX3RpbWUiOjE0NjM1ODE1NjQsInJldl9zaWciOiI4YTBkMzVjZSIsImlhdCI6MTQ2MzU4MTU3NiwiZXhwIjoxNDYzNTgxNTc3LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbInRlc3QiLCJvcGVuaWQiXX0.pZIIZlKMmNeMz-Rh_np1Rfo3Cj-cFW9M6i5c9U6ZUHy1I2Xz7r6Esan-ED16yxpayYfCTE40s0ukSAFkqpxDO3gtmFjvZqIv-APclZXklIJthR8l8KgBkwZ2I5eGIi__qKl1ydkTmPke9qXyDqIQQnRnqoSzA5aI5rza9XDbT7rJwJCbhvGYpP2GQ2roapSweTkagTmrcgyhKWxf8NA36yQ4eFh_JZ4Qj8zHRWFU3PvdR812a7mvm8o6ECsIPqKwg10kXh61sjASoFsO6bxlw6dGgP8j5PrHfcWO74MYuGa1S1IaaeafHm2i29zJ2iBdNq3PCQuPrvxQdiFW_L7wdg'; 15 | const token1_tampered = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIwOTkxNTYzZjVjYTI0YjM5YjAxYzAzZjVlMTBmMTY0YiIsInN1YiI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsInNjb3BlIjpbIm9wZW5pZCIsImFkbWluIl0sImNsaWVudF9pZCI6InRlc3QiLCJjaWQiOiJ0ZXN0IiwiYXpwIjoidGVzdCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiMzFmYmNkZTUtNzY1ZS00MDhlLWEyOGMtMGEyMzQ5NDVjOTFhIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoidGVzdGVyIiwiZW1haWwiOiJ0ZXN0ZXJAZGVtby5sb2NhbCIsImF1dGhfdGltZSI6MTQ2MzU4MTU2NCwicmV2X3NpZyI6IjhhMGQzNWNlIiwiaWF0IjoxNDYzNTgxNjE1LCJleHAiOjM2MTEwNjUyNjIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsidGVzdCIsIm9wZW5pZCJdfQ.bG36YmWafz1B7ZH-kMX4Wh_xDRpwGUNYGn2Cizxr3ywmWE7gsupDrIpzmGnlG389IGzMGfqEb_nwtHT8mqhLpxN-IwT1SIz9qWDH4kt07qsJGWnzAIDH_fF6np_iMghz6JQJsLYG5rIKoR7ibNJl4xK6PhoIk4F7Rw2GuLcKuq9ILQRRAJTfuzZEBjVIwqTbDulXgOveCbagjPF455i_QxsxMzpq001nlCN6OfjCbNpPnLjpFUp4eZ3K-gGQfdLTxMEgjnfl7B-U45vtOPBJ0sXIXvfOUXWneSt6BkPka3GCcz3GdqmYDbNvsZD5IRyCDjQ0sZv7IHZHQf-vgLReLg'; 16 | const token_malformed = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIwOTkxNTYzZjVjYTI0YjM5YjAxYzAzZjVlMTBmMTY0YiIsInN1YiI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOjJ0ZXN0IiwiY2lkIjoidGVzdCIsImF6cCI6InRlc3QiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6IjMxZmJjZGU1LTc2NWUtNDA4ZS1hMjhjLTBhMjM0OTQ1YzkxYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6InRlc3RlciIsImVtYWlsIjoidGVzdGVyQGRlbW8ubG9jYWwiLCJhdXRoX3RpbWUiOjE0NjM1ODE1NjQsInJldl9zaWciOiI4YTBkMzVjZSIsImlhdCI6MTQ2MzU4MTYxNSwiZXhwIjozNjExMDY1MjYyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbInRlc3QiLCJvcGVuaWQiXX0.EmHihs0D2OXg3bilcq0rH2Rd31BunqKDY9etUOZva1jyXhUe7Im79KmOqwFpMujTe4ONyN2rm70m8vhsJjfxBiS-n6-84ZJRKrN4FIIpil8gqQXNRUQSUn513lj0_suZAl5_4jxwrDyk1L00q3hdfHO2IP9hxcKiXp_jtRZlHumUpR0pG411gNMnZYxmrQio08prPGqcUA2LOLFHBtg6QVYF_Ho0jOBl4AAHqVxpMfPHHrOuX5aYhTXbp__3Gefsv44TmfNvzK_LnVtC5LWoCJvuiUhz45agkeMIR5NDsNc_cA7G148-TjwCYIfJFEUut6j2y4qNJSrum-J-1T7IYg'; 17 | const token_not_jwt = 'This is not a JWT'; 18 | const token_opaque = 'dfbe8dbc2d814438897c6cbb6e2363f5'; 19 | // Note: this is not a valid JWT - hand modified for test results 20 | const token_opaque_decoded = { 21 | user_id: '0bc9fe45-6c9e-4ae8-bde4-bde5a7d12345', 22 | user_name: 'testuser', 23 | email: 'test_user@predix.io', 24 | client_id: 'uaaClient', 25 | exp: 3497020914, 26 | scope: ['openid'], 27 | jti: 'dfbe8dbc2d814438897c6cbb6e2363f5', 28 | aud: ['openid', 'uaaClient'], 29 | sub: '0bc9fe45-6c9e-4ae8-bde4-cde3a7d12932', 30 | iss: 'https://uaa.example.predix.io/oauth/token', 31 | iat: 1477334362, 32 | cid: 'uaaClient', 33 | grant_type: 'authorization_code', 34 | azp: 'uaaClient', 35 | auth_time: 1477334357, 36 | zid: 'a8a2ffc4-b04e-4ec1-bfed-bde5a7d12345', 37 | rev_sig: '91a62430', 38 | nonce: 'cb296893856f20c0b1bf56b0a9ca8914', 39 | origin: 'example-uaa', 40 | revocable: true 41 | }; 42 | const badTokenResponse = token => ({ error: { error: 'invalid_token', error_description: `The token expired, was revoked, or the token ID is incorrect: ${token}` }}); 43 | const badCredentialsResponse = () => ({ error: { error: 'unauthorized', error_description: 'Bad credentials' }}); 44 | const validClient = { issuer: 'https://uaa.example.predix.io/oauth/token', clientId: 'uaaClient', clientSecret: 'secret' }; 45 | const invalidClient = { issuer: 'https://uaa.example.predix.io/oauth/token', clientId: 'uaaClient', clientSecret: 'nogood' }; 46 | const missingClient = { issuer: 'https://no.uaa.here.com/oauth/token', clientId: 'uaaClient', clientSecret: 'nogood' }; 47 | const trusted_issuers = ['http://localhost:8080/uaa/oauth/token', 'https://uaa.example.com/oauth/token']; 48 | const trusted_issuers2 = ['https://uaa.evil.gov/oauth/token']; 49 | 50 | let reqStub; 51 | let postStub; 52 | let cacheSetSpy; 53 | let cacheGetSpy; 54 | 55 | // ==================================================== 56 | // MOCKS 57 | 58 | beforeEach((done) => { 59 | // Mock out the get call for fetching the key (happy path) 60 | reqStub = sinon.stub(rp, 'get'); 61 | reqStub.returns(Promise.resolve(JSON.stringify({ value: key1 }))); 62 | 63 | // Mock out the post call for check_token (happy path) 64 | postStub = sinon.stub(rp, 'post'); 65 | postStub.returns(Promise.resolve(token_opaque_decoded)); 66 | 67 | // Clean out any cached keys 68 | token_util.clearCache(); 69 | 70 | // Spy on cache 71 | cacheSetSpy = sinon.spy(token_util._tokenCache, 'set'); 72 | cacheGetSpy = sinon.spy(token_util._tokenCache, 'get'); 73 | 74 | done(); 75 | }); 76 | 77 | afterEach((done) => { 78 | rp.get.restore(); 79 | rp.post.restore(); 80 | cacheSetSpy.restore(); 81 | cacheGetSpy.restore(); 82 | done(); 83 | }); 84 | 85 | // ==================================================== 86 | // TESTS 87 | 88 | describe('#verify', () => { 89 | it('verify a token', (done) => { 90 | // Use a token that expires 68 years in future 91 | // It is valid and signed by the correct key 92 | token_util.verify(token1_valid, trusted_issuers).then((decoded) => { 93 | try { 94 | expect(reqStub.calledOnce, '/token_key called once').to.be.true; 95 | expect(reqStub.calledWith({uri: 'http://localhost:8080/uaa/token_key'}), '/token_key at right URI').to.be.true; 96 | expect(decoded).to.exist; 97 | expect(decoded.user_name).to.equal('tester'); 98 | done(); 99 | } catch (e) { 100 | return done(e); 101 | } 102 | }); 103 | }); 104 | 105 | it('verify a token with tenant uuid', (done) => { 106 | // Use a token that expires 68 years in future 107 | // It is valid and signed by the correct key 108 | token_util.verify(token1_valid, trusted_issuers, 'xxxxxxx').then((decoded) => { 109 | try { 110 | expect(reqStub.calledOnce, '/token_key called once').to.be.true; 111 | expect(reqStub.calledWith({uri: 'http://localhost:8080/uaa/token_key', headers: {tenant: 'xxxxxxx'}}), '/token_key at right URI').to.be.true; 112 | expect(decoded).to.exist; 113 | expect(decoded.user_name).to.equal('tester'); 114 | done(); 115 | } catch (e) { 116 | return done(e); 117 | } 118 | }); 119 | }); 120 | 121 | it('cache the key', (done) => { 122 | // Call verify twice. It should not call request on the second attempt 123 | token_util.verify(token1_valid, trusted_issuers).then((decoded) => { 124 | try { 125 | expect(reqStub.calledOnce, '/token_key called only once').to.be.true; 126 | expect(decoded).to.exist; 127 | expect(decoded.user_name).to.equal('tester'); 128 | } catch (e) { 129 | return done(e); 130 | } 131 | 132 | token_util.verify(token1_valid, trusted_issuers).then((decoded) => { 133 | try { 134 | expect(reqStub.calledOnce, '/token_key called only once').to.be.true; 135 | expect(decoded).to.exist; 136 | expect(decoded.user_name).to.equal('tester'); 137 | done(); 138 | } catch (e) { 139 | return done(e); 140 | } 141 | }); 142 | }); 143 | }); 144 | 145 | it('cache the key with tenant uuid', (done) => { 146 | // Call verify twice. It should not call request on the second attempt 147 | token_util.verify(token1_valid, trusted_issuers, 'xxxxxxx').then((decoded) => { 148 | try { 149 | expect(reqStub.calledOnce, '/token_key called only once').to.be.true; 150 | expect(decoded).to.exist; 151 | expect(decoded.user_name).to.equal('tester'); 152 | } catch (e) { 153 | return done(e); 154 | } 155 | 156 | token_util.verify(token1_valid, trusted_issuers, 'xxxxxxx').then((decoded) => { 157 | try { 158 | expect(reqStub.calledOnce, '/token_key called only once').to.be.true; 159 | expect(decoded).to.exist; 160 | expect(decoded.user_name).to.equal('tester'); 161 | done(); 162 | } catch (e) { 163 | return done(e); 164 | } 165 | }); 166 | }); 167 | }); 168 | 169 | it('fail if unable to get the key', (done) => { 170 | // Mock out the get call for fetching the key to give an error 171 | rp.get.restore(); 172 | reqStub = sinon.stub(rp, 'get'); 173 | reqStub.returns(Promise.reject(JSON.stringify({ msg: 'nope' }))); 174 | 175 | token_util.verify(token1_valid, trusted_issuers).then((decoded) => { 176 | done(new Error('Should fail if unable to get the key')); 177 | }).catch(() => { 178 | // We expect an error here 179 | done(); 180 | }); 181 | }); 182 | 183 | it('fail if no response getting the key', (done) => { 184 | // Mock out the get call for fetching the key to give an error 185 | rp.get.restore(); 186 | reqStub = sinon.stub(rp, 'get'); 187 | reqStub.returns(Promise.reject()); 188 | 189 | token_util.verify(token1_valid, trusted_issuers).then((decoded) => { 190 | done(new Error('Should fail no response getting the key')); 191 | }).catch(() => { 192 | // We expect an error here 193 | done(); 194 | }); 195 | }); 196 | 197 | it('fail expired token', (done) => { 198 | // Use a token that has already expired 199 | // Although it is valid and signed by the correct key, verify should fail 200 | token_util.verify(token1_expired, trusted_issuers).then((decoded) => { 201 | done(new Error('Should fail if token is expired')); 202 | }).catch((err) => { 203 | // We expect an error here 204 | try { 205 | expect(err.name).to.equal('TokenExpiredError'); 206 | expect(err.message).to.equal('jwt expired'); 207 | done(); 208 | } catch (e) { 209 | return done(e); 210 | } 211 | }); 212 | }); 213 | 214 | it('fail a tampered token', (done) => { 215 | // Use a token that has been modified, verification should fail 216 | token_util.verify(token1_tampered, trusted_issuers).then((decoded) => { 217 | done(new Error('Should fail if token has been tampered with')); 218 | }).catch((err) => { 219 | // We expect an error here 220 | try { 221 | expect(err.name).to.equal('JsonWebTokenError'); 222 | expect(err.message).to.equal('invalid signature'); 223 | done(); 224 | } catch (e) { 225 | return done(e); 226 | } 227 | }); 228 | }); 229 | 230 | it('fail a malformed token', (done) => { 231 | // Use something that is not a token, verification should fail 232 | token_util.verify(token_malformed, trusted_issuers).then((decoded) => { 233 | done(new Error('Should fail if token is not a token')); 234 | }).catch((err) => { 235 | // We expect an error here 236 | try { 237 | expect(err.name).to.equal('Error'); 238 | expect(err.message).to.equal('Not a valid token'); 239 | done(); 240 | } catch (e) { 241 | return done(e); 242 | } 243 | }); 244 | }); 245 | 246 | it('fail a non JWT token', (done) => { 247 | // Use something that is not a token, verification should fail 248 | token_util.verify(token_not_jwt, trusted_issuers).then((decoded) => { 249 | done(new Error('Should fail if token is not a token')); 250 | }).catch((err) => { 251 | // We expect an error here 252 | try { 253 | expect(err.name).to.equal('Error'); 254 | expect(err.message).to.equal('Not a valid token'); 255 | done(); 256 | } catch (e) { 257 | return done(e); 258 | } 259 | }); 260 | }); 261 | 262 | it('fail a null token', (done) => { 263 | // Pass no token, verification should fail 264 | token_util.verify(null, trusted_issuers).then((decoded) => { 265 | done(new Error('Should fail if token is not supplied')); 266 | }).catch((err) => { 267 | // We expect an error here 268 | try { 269 | expect(err.name).to.equal('Error'); 270 | expect(err.message).to.equal('Not a valid token'); 271 | done(); 272 | } catch (e) { 273 | return done(e); 274 | } 275 | }); 276 | }); 277 | 278 | it('fail signed with a different key', (done) => { 279 | // Mock out the get call for fetching the key to give an error 280 | rp.get.restore(); 281 | reqStub = sinon.stub(rp, 'get'); 282 | reqStub.returns(Promise.resolve(JSON.stringify({ value: key2 }))); 283 | 284 | // Using a key from another server, verification should fail 285 | token_util.verify(token1_valid, trusted_issuers).then((decoded) => { 286 | done(new Error('Should fail if token and key mismatch')); 287 | }).catch((err) => { 288 | // We expect an error here 289 | try { 290 | expect(err.name).to.equal('JsonWebTokenError'); 291 | expect(err.message).to.equal('invalid signature'); 292 | done(); 293 | } catch (e) { 294 | return done(e); 295 | } 296 | }); 297 | }); 298 | 299 | it('fail if not a trusted issuer', (done) => { 300 | // If the issuer is not trusted, verification should fail 301 | token_util.verify(token1_valid, trusted_issuers2).then((decoded) => { 302 | done(new Error('Should fail if issuer is not trusted')); 303 | }).catch((err) => { 304 | // We expect an error here 305 | try { 306 | expect(err.name).to.equal('Error'); 307 | expect(err.message).to.equal('Not a trusted issuer'); 308 | done(); 309 | } catch (e) { 310 | return done(e); 311 | } 312 | }); 313 | }); 314 | }); 315 | 316 | describe('#remoteVerify', () => { 317 | it('returns decoded on valid token', () => { 318 | let verifyPromise = token_util.remoteVerify(token_opaque, validClient.issuer, validClient.clientId, validClient.clientSecret); 319 | return expect(verifyPromise).to.eventually.be.fulfilled 320 | .and.deep.equal(token_opaque_decoded); 321 | }); 322 | it('returns error from UAA on invalid token', () => { 323 | rp.post.restore(); 324 | postStub = sinon.stub(rp, 'post'); 325 | postStub.returns(Promise.reject(badTokenResponse(token1_expired))); 326 | 327 | let verifyPromise = token_util.remoteVerify(token1_expired, validClient.issuer, validClient.clientId, validClient.clientSecret); 328 | return expect(verifyPromise).to.eventually.be.rejected 329 | .and.have.property('error', 'invalid_token'); 330 | }); 331 | it('returns error from UAA on bad client credentials', () => { 332 | rp.post.restore(); 333 | postStub = sinon.stub(rp, 'post'); 334 | postStub.returns(Promise.reject(badCredentialsResponse())); 335 | 336 | let verifyPromise = token_util.remoteVerify(token_opaque, invalidClient.issuer, invalidClient.clientId, invalidClient.clientSecret); 337 | return expect(verifyPromise).to.eventually.be.rejected 338 | .and.have.property('error', 'unauthorized'); 339 | }); 340 | it('caches until TTL expires', () => { 341 | const ttl = 1000; 342 | token_util.remoteVerify(token_opaque, validClient.issuer, validClient.clientId, validClient.clientSecret, { ttl: ttl }) 343 | .then((jwt) => { 344 | expect(cacheSetSpy.calledOnce).to.be.true; 345 | }); 346 | }); 347 | it('does not cache for 0 TTL', () => { 348 | token_util.remoteVerify(token_opaque, validClient.issuer, validClient.clientId, validClient.clientSecret, { ttl: 0 }) 349 | .then((jwt) => { 350 | return expect(cacheSetSpy.callCount).to.equal(0); 351 | }); 352 | }); 353 | it('does not access cache if useCache disabled', () => { 354 | const ttl = 1000; 355 | return token_util.remoteVerify(token_opaque, validClient.issuer, validClient.clientId, validClient.clientSecret, { ttl: ttl, useCache: false }) 356 | .then((jwt) => { 357 | return expect(cacheGetSpy.callCount).to.equal(0); 358 | }); 359 | }); 360 | it('returns cached value if valid', () => { 361 | const ttl = 1000; 362 | return token_util.remoteVerify(token_opaque, validClient.issuer, validClient.clientId, validClient.clientSecret, { ttl: ttl, useCache: true }) 363 | .then((firstJwt) => { 364 | expect(cacheSetSpy.calledOnce, 'cache set called only once').to.be.true; 365 | expect(postStub.calledOnce, '/check_token called only once').to.be.true; 366 | expect(cacheGetSpy.calledOnce, 'cache get called only once').to.be.true; 367 | return token_util.remoteVerify(token_opaque, validClient.issuer, validClient.clientId, validClient.clientSecret, { ttl: ttl, useCache: true }) 368 | .then((secondJwt) => { 369 | expect(cacheSetSpy.calledOnce, 'cache set called only once on second query').to.be.true; 370 | expect(postStub.calledOnce, '/check_token called only once on second query').to.be.true; 371 | expect(cacheGetSpy.calledTwice, 'cache get called twice on second query').to.be.true; 372 | expect(secondJwt).to.deep.equal(firstJwt); 373 | }); 374 | }); 375 | }); 376 | it('returns 404 on unknown url', () => { 377 | rp.post.restore(); 378 | postStub = sinon.stub(rp, 'post'); 379 | postStub.returns(Promise.reject({ statusCode: 404 })); 380 | 381 | let verifyPromise = token_util.remoteVerify(token1_expired, missingClient.issuer, missingClient.clientId, missingClient.clientSecret); 382 | return expect(verifyPromise).to.eventually.be.rejected 383 | .and.have.property('statusCode', 404); 384 | }); 385 | }); 386 | --------------------------------------------------------------------------------