├── .gitignore ├── README.md ├── config.example.js ├── controllers ├── helloWorld.js └── user.js ├── index.js ├── middlewares └── auth.js ├── package.json ├── server.js ├── test ├── controllers │ ├── helloWorld.js │ └── user.js └── middlewares │ └── auth.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config.js 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Express.js with JWT example 2 | 3 | This is an example project to show how to create login route and some other 4 | route that is protected - could be called only if valid JWT token is provided. 5 | 6 | Everything is well tested with [mocha](https://mochajs.org), [chai](http://chaijs.com), [sinon](http://sinonjs.org) and [supertest](https://github.com/visionmedia/supertest). 7 | 8 | ## Dependencies 9 | 10 | * express 11 | * body-parser 12 | * jsonwebtoken 13 | * chai 14 | * mocha 15 | * sinon 16 | * supertest 17 | 18 | ## Scripts 19 | 20 | `npm run start` 21 | 22 | `npm run test` 23 | 24 | ## How to run the project 25 | 26 | Install dependencies : 27 | 28 | ``` 29 | yarn install 30 | 31 | # or 32 | 33 | npm install 34 | ``` 35 | 36 | Edit `config.example.js` and save it as `config.js` : 37 | 38 | ``` 39 | module.exports = { 40 | port: 8080, 41 | jwtSecret: 'your jwt secret' 42 | }; 43 | ``` 44 | 45 | Run tests : 46 | 47 | ``` 48 | npm run test 49 | ``` 50 | 51 | If everything is OK, run the project : 52 | 53 | ``` 54 | npm start 55 | ``` 56 | 57 | ## How to test the project 58 | 59 | When you run the project, you should be able to load the URL `http://localhost:8080/`, but you shouldn't be able to access `http://localhost:8080/api/hello-world`. 60 | 61 | You can log in by sending a post on `http://localhost:8080/login` and send there username and password, both set to _admin_. 62 | 63 | ``` 64 | curl -XPOST -H "Content-Type: application/json" 'http://localhost:8080/login' -d '{"username":"admin","password":"admin"}' 65 | ``` 66 | 67 | You should get back something like : 68 | 69 | ``` 70 | { 71 | "id":1, 72 | "username":"admin", 73 | "jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNDg3NjM3OTg0LCJleHAiOjE0ODc2NDE1ODR9.1jMwROveQeR64baJOPdZV4SdpmKKVRvgPg0wJX9sHnI" 74 | } 75 | ``` 76 | 77 | Now, when you want to load `http://localhost:8080/api/hello-world` and you send there `Authorization` header with _jwt token_ from the previous response, you should be successful : 78 | 79 | ``` 80 | curl -XGET -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNDg3NjM3OTg0LCJleHAiOjE0ODc2NDE1ODR9.1jMwROveQeR64baJOPdZV4SdpmKKVRvgPg0wJX9sHnI' 'http://localhost:8080/api/hello-world' 81 | ``` 82 | -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | PORT: 8080, 3 | JWT_SECRET: 'your jwt secret' 4 | }; 5 | -------------------------------------------------------------------------------- /controllers/helloWorld.js: -------------------------------------------------------------------------------- 1 | module.exports = function(router) { 2 | router.get('/hello-world', function(req, res) { 3 | res.json({data: 'Hello World!'}); 4 | }); 5 | 6 | return router; 7 | }; 8 | -------------------------------------------------------------------------------- /controllers/user.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var config = require('../config.js'); 3 | 4 | module.exports = function(router) { 5 | router.post('/login', function(req, res) { 6 | /* 7 | * Check if the username and password is correct 8 | */ 9 | if( req.body.username === 'admin' && req.body.password === 'admin' ) { 10 | res.json({ 11 | id: 1, 12 | username: 'admin', 13 | jwt: jwt.sign({ 14 | id: 1, 15 | }, config.JWT_SECRET, { expiresIn: 60*60 }) 16 | }); 17 | } else { 18 | /* 19 | * If the username or password was wrong, return 401 ( Unauthorized ) 20 | * status code and JSON error message 21 | */ 22 | res.status(401).json({ 23 | error: { 24 | message: 'Wrong username or password!' 25 | } 26 | }); 27 | } 28 | }); 29 | 30 | return router; 31 | }; 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var app = require('./server.js'); 2 | var config = require('./config.js'); 3 | 4 | /* 5 | * Start server 6 | */ 7 | app.listen(config.PORT); 8 | -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | var config = require('../config.js'); 3 | 4 | module.exports = function(req, res, next) { 5 | /* 6 | * Check if authorization header is set 7 | */ 8 | if( req.hasOwnProperty('headers') && req.headers.hasOwnProperty('authorization') ) { 9 | try { 10 | /* 11 | * Try to decode & verify the JWT token 12 | * The token contains user's id ( it can contain more informations ) 13 | * and this is saved in req.user object 14 | */ 15 | req.user = jwt.verify(req.headers['authorization'], config.JWT_SECRET); 16 | } catch(err) { 17 | /* 18 | * If the authorization header is corrupted, it throws exception 19 | * So return 401 status code with JSON error message 20 | */ 21 | return res.status(401).json({ 22 | error: { 23 | msg: 'Failed to authenticate token!' 24 | } 25 | }); 26 | } 27 | } else { 28 | /* 29 | * If there is no autorization header, return 401 status code with JSON 30 | * error message 31 | */ 32 | return res.status(401).json({ 33 | error: { 34 | msg: 'No token!' 35 | } 36 | }); 37 | } 38 | next(); 39 | return; 40 | }; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-jwt-example", 3 | "version": "1.0.0", 4 | "description": "Example project for Express.js with JWT blogpost", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.16.1", 9 | "chai": "^3.5.0", 10 | "express": "^4.14.1", 11 | "jsonwebtoken": "^7.3.0", 12 | "mocha": "^3.2.0", 13 | "sinon": "^1.17.7", 14 | "supertest": "^3.0.0" 15 | }, 16 | "scripts": { 17 | "start": "node index.js", 18 | "test": "mocha --recursive" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var router = express.Router(); 4 | var bodyParser = require('body-parser'); 5 | 6 | app.use(bodyParser.json()); 7 | /* 8 | * Add middleware. Because we defined the first parameter ( '/api' ), it will run 9 | * only for urls that starts with '/api/*' 10 | */ 11 | app.use('/api', require('./middlewares/auth.js')); 12 | /* 13 | * Add the protected route '/hello-world' after '/api' 14 | * So now it is available on /api/hello-world 15 | */ 16 | app.use('/api', require('./controllers/helloWorld.js')(router)); 17 | /* 18 | * Add the '/login' route handler 19 | */ 20 | app.use('/', require('./controllers/user.js')(router)); 21 | 22 | module.exports = app; 23 | -------------------------------------------------------------------------------- /test/controllers/helloWorld.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var app = require('../../server.js'); 3 | var jwt = require('jsonwebtoken'); 4 | var config = require('../../config.js'); 5 | 6 | describe('GET /hello-world', function(){ 7 | it('it responds with 401 status code if no authorization header', function(done) { 8 | request(app).get('/api/hello-world').expect(401).end(function(err, res) { 9 | if (err) return done(err); 10 | done(); 11 | }); 12 | }); 13 | 14 | it('it responds with JSON if no authorization header', function(done) { 15 | request(app).get('/api/hello-world').expect('Content-Type', /json/).end(function(err, res) { 16 | if (err) return done(err); 17 | done(); 18 | }); 19 | }); 20 | 21 | it('it responds with 200 status code if good authorization header', function(done) { 22 | var token = jwt.sign({ 23 | id: 1, 24 | }, config.JWT_SECRET, { expiresIn: 60*60 }); 25 | request(app) 26 | .get('/api/hello-world') 27 | .set('Authorization', token) 28 | .expect(200) 29 | .end(function(err, res) { 30 | if (err) return done(err); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('it responds with JSON if good authorization header', function(done) { 36 | var token = jwt.sign({ 37 | id: 1, 38 | }, config.JWT_SECRET, { expiresIn: 60*60 }); 39 | request(app) 40 | .get('/api/hello-world') 41 | .set('Authorization', token) 42 | .expect('Content-Type', /json/) 43 | .end(function(err, res) { 44 | if (err) return done(err); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/controllers/user.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var request = require('supertest'); 4 | var app = require('../../server.js'); 5 | 6 | describe('POST /login', function(){ 7 | it('it responds with 401 status code if bad username or password', function(done) { 8 | request(app) 9 | .post('/login') 10 | .type('json') 11 | .send('{"username":"bad","password":"wrong"}') 12 | .expect(401) 13 | .end(function(err, res) { 14 | if (err) return done(err); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('it responds with 200 status code if good username or password', function(done) { 20 | request(app) 21 | .post('/login') 22 | .type('json') 23 | .send('{"username":"admin","password":"admin"}') 24 | .expect(200) 25 | .end(function(err, res) { 26 | if (err) return done(err); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('it returns JWT token if good username or password', function(done) { 32 | request(app) 33 | .post('/login') 34 | .type('json') 35 | .send('{"username":"admin","password":"admin"}') 36 | .end(function(err, res) { 37 | if (err) return done(err); 38 | 39 | expect(res.body).have.property('jwt'); 40 | 41 | done(); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var sinon = require('sinon'); 4 | var auth = require('../../middlewares/auth.js'); 5 | var jwt = require('jsonwebtoken'); 6 | var config = require('../../config.js'); 7 | 8 | describe('Test Auth Middleware', function(){ 9 | var request; 10 | var response; 11 | var next; 12 | 13 | beforeEach(function() { 14 | request = {}; 15 | response = { 16 | status: sinon.stub().returnsThis(), 17 | json: sinon.spy() 18 | }; 19 | next = sinon.spy(); 20 | }); 21 | 22 | it('next should not be called if no token provided', function() { 23 | auth(request, response, next); 24 | expect(next.called).to.equal(false); 25 | }); 26 | 27 | it('should return 401 status code if no token provided', function() { 28 | auth(request, response, next); 29 | expect(response.status.getCall(0).args[0]).to.equal(401); 30 | }); 31 | 32 | it('next should not be called if bad token was provided', function() { 33 | request.headers = {}; 34 | request.headers.authorization = 'some authorization header'; 35 | auth(request, response, next); 36 | expect(next.called).to.equal(false); 37 | }); 38 | 39 | it('shoudl return 401 status code if bad token was provided', function() { 40 | request.headers = {}; 41 | request.headers.authorization = 'some authorization header'; 42 | auth(request, response, next); 43 | expect(response.status.getCall(0).args[0]).to.equal(401); 44 | }); 45 | 46 | it('request should contain user info if good token was provided', function() { 47 | request.headers = {}; 48 | request.headers.authorization = jwt.sign({ id: 1 }, config.JWT_SECRET); 49 | auth(request, response, next); 50 | expect(request).to.have.property('user'); 51 | expect(request.user).to.have.property('id'); 52 | expect(request.user.id).to.be.equal(1); 53 | }); 54 | 55 | it('next should be called once if good token was provided', function() { 56 | request.headers = {}; 57 | request.headers.authorization = jwt.sign({ id: 1 }, config.JWT_SECRET); 58 | auth(request, response, next); 59 | expect(next.calledOnce).to.equal(true); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.3: 6 | version "1.3.3" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 8 | dependencies: 9 | mime-types "~2.1.11" 10 | negotiator "0.6.1" 11 | 12 | array-flatten@1.1.1: 13 | version "1.1.1" 14 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 15 | 16 | assertion-error@^1.0.1: 17 | version "1.0.2" 18 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 19 | 20 | asynckit@^0.4.0: 21 | version "0.4.0" 22 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 23 | 24 | balanced-match@^0.4.1: 25 | version "0.4.2" 26 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 27 | 28 | base64url@2.0.0, base64url@^2.0.0: 29 | version "2.0.0" 30 | resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" 31 | 32 | body-parser@^1.16.1: 33 | version "1.16.1" 34 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.1.tgz#51540d045adfa7a0c6995a014bb6b1ed9b802329" 35 | dependencies: 36 | bytes "2.4.0" 37 | content-type "~1.0.2" 38 | debug "2.6.1" 39 | depd "~1.1.0" 40 | http-errors "~1.5.1" 41 | iconv-lite "0.4.15" 42 | on-finished "~2.3.0" 43 | qs "6.2.1" 44 | raw-body "~2.2.0" 45 | type-is "~1.6.14" 46 | 47 | brace-expansion@^1.0.0: 48 | version "1.1.6" 49 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 50 | dependencies: 51 | balanced-match "^0.4.1" 52 | concat-map "0.0.1" 53 | 54 | browser-stdout@1.3.0: 55 | version "1.3.0" 56 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" 57 | 58 | buffer-equal-constant-time@1.0.1: 59 | version "1.0.1" 60 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 61 | 62 | buffer-shims@^1.0.0: 63 | version "1.0.0" 64 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 65 | 66 | bytes@2.4.0: 67 | version "2.4.0" 68 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 69 | 70 | chai@^3.5.0: 71 | version "3.5.0" 72 | resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" 73 | dependencies: 74 | assertion-error "^1.0.1" 75 | deep-eql "^0.1.3" 76 | type-detect "^1.0.0" 77 | 78 | combined-stream@^1.0.5: 79 | version "1.0.5" 80 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 81 | dependencies: 82 | delayed-stream "~1.0.0" 83 | 84 | commander@2.9.0: 85 | version "2.9.0" 86 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 87 | dependencies: 88 | graceful-readlink ">= 1.0.0" 89 | 90 | component-emitter@^1.2.0: 91 | version "1.2.1" 92 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" 93 | 94 | concat-map@0.0.1: 95 | version "0.0.1" 96 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 97 | 98 | content-disposition@0.5.2: 99 | version "0.5.2" 100 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 101 | 102 | content-type@~1.0.2: 103 | version "1.0.2" 104 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 105 | 106 | cookie-signature@1.0.6: 107 | version "1.0.6" 108 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 109 | 110 | cookie@0.3.1: 111 | version "0.3.1" 112 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 113 | 114 | cookiejar@^2.0.6: 115 | version "2.1.0" 116 | resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.0.tgz#86549689539b6d0e269b6637a304be508194d898" 117 | 118 | core-util-is@~1.0.0: 119 | version "1.0.2" 120 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 121 | 122 | debug@2.2.0, debug@^2.2.0, debug@~2.2.0: 123 | version "2.2.0" 124 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 125 | dependencies: 126 | ms "0.7.1" 127 | 128 | debug@2.6.1: 129 | version "2.6.1" 130 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" 131 | dependencies: 132 | ms "0.7.2" 133 | 134 | deep-eql@^0.1.3: 135 | version "0.1.3" 136 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" 137 | dependencies: 138 | type-detect "0.1.1" 139 | 140 | delayed-stream@~1.0.0: 141 | version "1.0.0" 142 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 143 | 144 | depd@~1.1.0: 145 | version "1.1.0" 146 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" 147 | 148 | destroy@~1.0.4: 149 | version "1.0.4" 150 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 151 | 152 | diff@1.4.0: 153 | version "1.4.0" 154 | resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" 155 | 156 | ecdsa-sig-formatter@1.0.9: 157 | version "1.0.9" 158 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" 159 | dependencies: 160 | base64url "^2.0.0" 161 | safe-buffer "^5.0.1" 162 | 163 | ee-first@1.1.1: 164 | version "1.1.1" 165 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 166 | 167 | encodeurl@~1.0.1: 168 | version "1.0.1" 169 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 170 | 171 | escape-html@~1.0.3: 172 | version "1.0.3" 173 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 174 | 175 | escape-string-regexp@1.0.5: 176 | version "1.0.5" 177 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 178 | 179 | etag@~1.7.0: 180 | version "1.7.0" 181 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" 182 | 183 | express@^4.14.1: 184 | version "4.14.1" 185 | resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33" 186 | dependencies: 187 | accepts "~1.3.3" 188 | array-flatten "1.1.1" 189 | content-disposition "0.5.2" 190 | content-type "~1.0.2" 191 | cookie "0.3.1" 192 | cookie-signature "1.0.6" 193 | debug "~2.2.0" 194 | depd "~1.1.0" 195 | encodeurl "~1.0.1" 196 | escape-html "~1.0.3" 197 | etag "~1.7.0" 198 | finalhandler "0.5.1" 199 | fresh "0.3.0" 200 | merge-descriptors "1.0.1" 201 | methods "~1.1.2" 202 | on-finished "~2.3.0" 203 | parseurl "~1.3.1" 204 | path-to-regexp "0.1.7" 205 | proxy-addr "~1.1.3" 206 | qs "6.2.0" 207 | range-parser "~1.2.0" 208 | send "0.14.2" 209 | serve-static "~1.11.2" 210 | type-is "~1.6.14" 211 | utils-merge "1.0.0" 212 | vary "~1.1.0" 213 | 214 | extend@^3.0.0: 215 | version "3.0.0" 216 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" 217 | 218 | finalhandler@0.5.1: 219 | version "0.5.1" 220 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd" 221 | dependencies: 222 | debug "~2.2.0" 223 | escape-html "~1.0.3" 224 | on-finished "~2.3.0" 225 | statuses "~1.3.1" 226 | unpipe "~1.0.0" 227 | 228 | form-data@^2.1.1: 229 | version "2.1.2" 230 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" 231 | dependencies: 232 | asynckit "^0.4.0" 233 | combined-stream "^1.0.5" 234 | mime-types "^2.1.12" 235 | 236 | formatio@1.1.1: 237 | version "1.1.1" 238 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" 239 | dependencies: 240 | samsam "~1.1" 241 | 242 | formidable@^1.1.1: 243 | version "1.1.1" 244 | resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" 245 | 246 | forwarded@~0.1.0: 247 | version "0.1.0" 248 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" 249 | 250 | fresh@0.3.0: 251 | version "0.3.0" 252 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" 253 | 254 | fs.realpath@^1.0.0: 255 | version "1.0.0" 256 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 257 | 258 | glob@7.0.5: 259 | version "7.0.5" 260 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95" 261 | dependencies: 262 | fs.realpath "^1.0.0" 263 | inflight "^1.0.4" 264 | inherits "2" 265 | minimatch "^3.0.2" 266 | once "^1.3.0" 267 | path-is-absolute "^1.0.0" 268 | 269 | "graceful-readlink@>= 1.0.0": 270 | version "1.0.1" 271 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 272 | 273 | growl@1.9.2: 274 | version "1.9.2" 275 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 276 | 277 | has-flag@^1.0.0: 278 | version "1.0.0" 279 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 280 | 281 | hoek@2.x.x: 282 | version "2.16.3" 283 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 284 | 285 | http-errors@~1.5.1: 286 | version "1.5.1" 287 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" 288 | dependencies: 289 | inherits "2.0.3" 290 | setprototypeof "1.0.2" 291 | statuses ">= 1.3.1 < 2" 292 | 293 | iconv-lite@0.4.15: 294 | version "0.4.15" 295 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" 296 | 297 | inflight@^1.0.4: 298 | version "1.0.6" 299 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 300 | dependencies: 301 | once "^1.3.0" 302 | wrappy "1" 303 | 304 | inherits@2, inherits@2.0.3, inherits@~2.0.1: 305 | version "2.0.3" 306 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 307 | 308 | inherits@2.0.1: 309 | version "2.0.1" 310 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 311 | 312 | ipaddr.js@1.2.0: 313 | version "1.2.0" 314 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4" 315 | 316 | isarray@~1.0.0: 317 | version "1.0.0" 318 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 319 | 320 | isemail@1.x.x: 321 | version "1.2.0" 322 | resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" 323 | 324 | joi@^6.10.1: 325 | version "6.10.1" 326 | resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06" 327 | dependencies: 328 | hoek "2.x.x" 329 | isemail "1.x.x" 330 | moment "2.x.x" 331 | topo "1.x.x" 332 | 333 | json3@3.3.2: 334 | version "3.3.2" 335 | resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" 336 | 337 | jsonwebtoken@^7.3.0: 338 | version "7.3.0" 339 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.3.0.tgz#85118d6a70e3fccdf14389f4e7a1c3f9c8a9fbba" 340 | dependencies: 341 | joi "^6.10.1" 342 | jws "^3.1.4" 343 | lodash.once "^4.0.0" 344 | ms "^0.7.1" 345 | xtend "^4.0.1" 346 | 347 | jwa@^1.1.4: 348 | version "1.1.5" 349 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" 350 | dependencies: 351 | base64url "2.0.0" 352 | buffer-equal-constant-time "1.0.1" 353 | ecdsa-sig-formatter "1.0.9" 354 | safe-buffer "^5.0.1" 355 | 356 | jws@^3.1.4: 357 | version "3.1.4" 358 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" 359 | dependencies: 360 | base64url "^2.0.0" 361 | jwa "^1.1.4" 362 | safe-buffer "^5.0.1" 363 | 364 | lodash._baseassign@^3.0.0: 365 | version "3.2.0" 366 | resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" 367 | dependencies: 368 | lodash._basecopy "^3.0.0" 369 | lodash.keys "^3.0.0" 370 | 371 | lodash._basecopy@^3.0.0: 372 | version "3.0.1" 373 | resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" 374 | 375 | lodash._basecreate@^3.0.0: 376 | version "3.0.3" 377 | resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" 378 | 379 | lodash._getnative@^3.0.0: 380 | version "3.9.1" 381 | resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" 382 | 383 | lodash._isiterateecall@^3.0.0: 384 | version "3.0.9" 385 | resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" 386 | 387 | lodash.create@3.1.1: 388 | version "3.1.1" 389 | resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" 390 | dependencies: 391 | lodash._baseassign "^3.0.0" 392 | lodash._basecreate "^3.0.0" 393 | lodash._isiterateecall "^3.0.0" 394 | 395 | lodash.isarguments@^3.0.0: 396 | version "3.1.0" 397 | resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" 398 | 399 | lodash.isarray@^3.0.0: 400 | version "3.0.4" 401 | resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" 402 | 403 | lodash.keys@^3.0.0: 404 | version "3.1.2" 405 | resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" 406 | dependencies: 407 | lodash._getnative "^3.0.0" 408 | lodash.isarguments "^3.0.0" 409 | lodash.isarray "^3.0.0" 410 | 411 | lodash.once@^4.0.0: 412 | version "4.1.1" 413 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 414 | 415 | lolex@1.3.2: 416 | version "1.3.2" 417 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" 418 | 419 | media-typer@0.3.0: 420 | version "0.3.0" 421 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 422 | 423 | merge-descriptors@1.0.1: 424 | version "1.0.1" 425 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 426 | 427 | methods@^1.1.1, methods@~1.1.2: 428 | version "1.1.2" 429 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 430 | 431 | mime-db@~1.26.0: 432 | version "1.26.0" 433 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" 434 | 435 | mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13: 436 | version "2.1.14" 437 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee" 438 | dependencies: 439 | mime-db "~1.26.0" 440 | 441 | mime@1.3.4, mime@^1.3.4: 442 | version "1.3.4" 443 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 444 | 445 | minimatch@^3.0.2: 446 | version "3.0.3" 447 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 448 | dependencies: 449 | brace-expansion "^1.0.0" 450 | 451 | minimist@0.0.8: 452 | version "0.0.8" 453 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 454 | 455 | mkdirp@0.5.1: 456 | version "0.5.1" 457 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 458 | dependencies: 459 | minimist "0.0.8" 460 | 461 | mocha@^3.2.0: 462 | version "3.2.0" 463 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.2.0.tgz#7dc4f45e5088075171a68896814e6ae9eb7a85e3" 464 | dependencies: 465 | browser-stdout "1.3.0" 466 | commander "2.9.0" 467 | debug "2.2.0" 468 | diff "1.4.0" 469 | escape-string-regexp "1.0.5" 470 | glob "7.0.5" 471 | growl "1.9.2" 472 | json3 "3.3.2" 473 | lodash.create "3.1.1" 474 | mkdirp "0.5.1" 475 | supports-color "3.1.2" 476 | 477 | moment@2.x.x: 478 | version "2.17.1" 479 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" 480 | 481 | ms@0.7.1: 482 | version "0.7.1" 483 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 484 | 485 | ms@0.7.2, ms@^0.7.1: 486 | version "0.7.2" 487 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 488 | 489 | negotiator@0.6.1: 490 | version "0.6.1" 491 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 492 | 493 | on-finished@~2.3.0: 494 | version "2.3.0" 495 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 496 | dependencies: 497 | ee-first "1.1.1" 498 | 499 | once@^1.3.0: 500 | version "1.4.0" 501 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 502 | dependencies: 503 | wrappy "1" 504 | 505 | parseurl@~1.3.1: 506 | version "1.3.1" 507 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" 508 | 509 | path-is-absolute@^1.0.0: 510 | version "1.0.1" 511 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 512 | 513 | path-to-regexp@0.1.7: 514 | version "0.1.7" 515 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 516 | 517 | process-nextick-args@~1.0.6: 518 | version "1.0.7" 519 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 520 | 521 | proxy-addr@~1.1.3: 522 | version "1.1.3" 523 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074" 524 | dependencies: 525 | forwarded "~0.1.0" 526 | ipaddr.js "1.2.0" 527 | 528 | qs@6.2.0, qs@^6.1.0: 529 | version "6.2.0" 530 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" 531 | 532 | qs@6.2.1: 533 | version "6.2.1" 534 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" 535 | 536 | range-parser@~1.2.0: 537 | version "1.2.0" 538 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 539 | 540 | raw-body@~2.2.0: 541 | version "2.2.0" 542 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" 543 | dependencies: 544 | bytes "2.4.0" 545 | iconv-lite "0.4.15" 546 | unpipe "1.0.0" 547 | 548 | readable-stream@^2.0.5: 549 | version "2.2.2" 550 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" 551 | dependencies: 552 | buffer-shims "^1.0.0" 553 | core-util-is "~1.0.0" 554 | inherits "~2.0.1" 555 | isarray "~1.0.0" 556 | process-nextick-args "~1.0.6" 557 | string_decoder "~0.10.x" 558 | util-deprecate "~1.0.1" 559 | 560 | safe-buffer@^5.0.1: 561 | version "5.0.1" 562 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 563 | 564 | samsam@1.1.2, samsam@~1.1: 565 | version "1.1.2" 566 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" 567 | 568 | send@0.14.2: 569 | version "0.14.2" 570 | resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" 571 | dependencies: 572 | debug "~2.2.0" 573 | depd "~1.1.0" 574 | destroy "~1.0.4" 575 | encodeurl "~1.0.1" 576 | escape-html "~1.0.3" 577 | etag "~1.7.0" 578 | fresh "0.3.0" 579 | http-errors "~1.5.1" 580 | mime "1.3.4" 581 | ms "0.7.2" 582 | on-finished "~2.3.0" 583 | range-parser "~1.2.0" 584 | statuses "~1.3.1" 585 | 586 | serve-static@~1.11.2: 587 | version "1.11.2" 588 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7" 589 | dependencies: 590 | encodeurl "~1.0.1" 591 | escape-html "~1.0.3" 592 | parseurl "~1.3.1" 593 | send "0.14.2" 594 | 595 | setprototypeof@1.0.2: 596 | version "1.0.2" 597 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" 598 | 599 | sinon@^1.17.7: 600 | version "1.17.7" 601 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" 602 | dependencies: 603 | formatio "1.1.1" 604 | lolex "1.3.2" 605 | samsam "1.1.2" 606 | util ">=0.10.3 <1" 607 | 608 | "statuses@>= 1.3.1 < 2", statuses@~1.3.1: 609 | version "1.3.1" 610 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 611 | 612 | string_decoder@~0.10.x: 613 | version "0.10.31" 614 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 615 | 616 | superagent@^3.0.0: 617 | version "3.4.4" 618 | resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.4.4.tgz#85d1d9a8f439ed0fb5c00accf4e029417a117195" 619 | dependencies: 620 | component-emitter "^1.2.0" 621 | cookiejar "^2.0.6" 622 | debug "^2.2.0" 623 | extend "^3.0.0" 624 | form-data "^2.1.1" 625 | formidable "^1.1.1" 626 | methods "^1.1.1" 627 | mime "^1.3.4" 628 | qs "^6.1.0" 629 | readable-stream "^2.0.5" 630 | 631 | supertest@^3.0.0: 632 | version "3.0.0" 633 | resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.0.0.tgz#8d4bb68fd1830ee07033b1c5a5a9a4021c965296" 634 | dependencies: 635 | methods "~1.1.2" 636 | superagent "^3.0.0" 637 | 638 | supports-color@3.1.2: 639 | version "3.1.2" 640 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" 641 | dependencies: 642 | has-flag "^1.0.0" 643 | 644 | topo@1.x.x: 645 | version "1.1.0" 646 | resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5" 647 | dependencies: 648 | hoek "2.x.x" 649 | 650 | type-detect@0.1.1: 651 | version "0.1.1" 652 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" 653 | 654 | type-detect@^1.0.0: 655 | version "1.0.0" 656 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" 657 | 658 | type-is@~1.6.14: 659 | version "1.6.14" 660 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2" 661 | dependencies: 662 | media-typer "0.3.0" 663 | mime-types "~2.1.13" 664 | 665 | unpipe@1.0.0, unpipe@~1.0.0: 666 | version "1.0.0" 667 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 668 | 669 | util-deprecate@~1.0.1: 670 | version "1.0.2" 671 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 672 | 673 | "util@>=0.10.3 <1": 674 | version "0.10.3" 675 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" 676 | dependencies: 677 | inherits "2.0.1" 678 | 679 | utils-merge@1.0.0: 680 | version "1.0.0" 681 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 682 | 683 | vary@~1.1.0: 684 | version "1.1.0" 685 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140" 686 | 687 | wrappy@1: 688 | version "1.0.2" 689 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 690 | 691 | xtend@^4.0.1: 692 | version "4.0.1" 693 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 694 | --------------------------------------------------------------------------------