]> git.r.bdr.sh - rbdr/dasein/commitdiff
Add Login (#2)
authorRubén Beltrán del Río <redacted>
Thu, 19 Jan 2017 06:25:01 +0000 (00:25 -0600)
committerGitHub <redacted>
Thu, 19 Jan 2017 06:25:01 +0000 (00:25 -0600)
* Add dependencies

* Add barebones app

* Ignore .DS_Store

* Add base static assets

* Expose port on docker

* Ignore env files

* Add instructions to use env.dist

* Add twitter configs

* Use latest node version

* Read env file properly

* Add dependencies to start parsing twitter

* Add helper for twitter login operations

* Add handler for titter login routes

* Use twitter handler

* Add JWT related keys

* Add jwt dependencies

* Move index static file

* Rename twitter handler to auth, add jwt support

* Add new yarn lock

* Add instructions for twitter

* Remove twitter image

* Fix return value lint errors

* Ignore require-yield errors in linter

16 files changed:
.eslintrc
.gitignore
Dockerfile
README.md
bin/dasein.js
config/config.js [new file with mode: 0644]
config/env.dist [new file with mode: 0644]
docker-compose.yml
lib/dasein.js
lib/handlers/auth.js [new file with mode: 0644]
lib/twitter_helper.js [new file with mode: 0644]
package.json
static/css/app.css [new file with mode: 0644]
static/favicon.ico [new file with mode: 0644]
static/windex.html [new file with mode: 0644]
yarn.lock

index d3090530d74ac31dcaae8f79536ae9f8fff87f4c..f91f59218211780721f1612dd74eddbf20683d17 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
@@ -5,6 +5,7 @@
       2,
       2
     ],
-    "no-undef": 2
+    "no-undef": 2,
+    "require-yield": 0
   }
 }
index 5148e527a7e286a1efcc44d65a7f8241267dce9b..ce59e9867fe25ea6e54600f110ac40e13535c1cd 100644 (file)
@@ -35,3 +35,9 @@ jspm_packages
 
 # Optional REPL history
 .node_repl_history
+
+# macos files
+.DS_Store
+
+# Env files
+.env
index 38d991aad35f9f8e2606e8b71bd3e836ce298f83..5e2f7f99058f11c3e71b575e1f3279eab19d92db 100644 (file)
@@ -1,4 +1,4 @@
-FROM node:6.9.2
+FROM node:6.9.4
 
 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
 RUN echo "deb http://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
index 9cd6edb1b3a1c998207c8b8487f8b302395c53ef..584a258f6f200212706d4a19215d2fdb22dca46b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,6 +1,16 @@
 # dasein
 A social network
 
+## Configuring
+
+This project usese environment variables to work. For most cases, the
+defaults work but some sensitive info like keys must be overridden. Copy
+the file in `config/env.dist` to `.env` in the project root and override
+the values.
+
+When running with `make run`, it'll pick up these values automatically.
+If you're doing it the hard way, you'll have to source them.
+
 ## Running Locally
 
 You'll need [Docker][docker] to run the project.
@@ -28,6 +38,12 @@ You can also do some other operations
 * Push and build the image with `make upload`
 * Clean the environment with `make clean`
 
+## Setting up Twitter for login
+
+1. Create an app on https://apps.twitter.com/
+2. Make sure you check "Allow this application to be used to Sign in with Twitter"
+3. Make sure you specify a callback URL (eg. http://localhost:1927/login-callback)
+
 ## Checking the code
 
 This project uses the [Hapi Style Guide][hapi-style-guide] for
index 10edfc3bc8b746336c0a51963ca2f60df39f408d..b6f7944a5aa60fbcfa080c5d2c5662c0e3651fa4 100755 (executable)
@@ -1,18 +1,16 @@
 #!/usr/bin/env node
 'use strict';
 
+const Config = require('../config/config');
 const Dasein = require('..');
 
 const internals = {};
 
-internals.dasein = new Dasein();
+internals.dasein = new Dasein(Config);
 
 internals.main = () => {
 
-  internals.dasein.run().then(() => {
-
-    process.exit(0);
-  }).catch((err) => {
+  internals.dasein.run().catch((err) => {
 
     console.error(err.stack || err.message || err);
     process.exit(1);
diff --git a/config/config.js b/config/config.js
new file mode 100644 (file)
index 0000000..1c1c8a0
--- /dev/null
@@ -0,0 +1,21 @@
+'use strict';
+
+const Getenv = require('getenv');
+
+const internals = {};
+
+module.exports = internals.Config = {
+  cookieKeys: Getenv.array('DASEIN_COOKIE_KEYS'), // Signatures for the cookies
+  port: Getenv.int('DASEIN_PORT', 1927), // Port to listen on
+  hostname: Getenv('DASEIN_HOSTNAME', 'localhost'), // Domain to listen on, used for cookies
+  staticDirectory: Getenv('DASEIN_STATIC_DIRECTORY', 'static'), // Location of static assets
+  jwt: {
+    cookieName: Getenv('DASEIN_JWT_COOKIE_NAME', 'dasein_jwt'), // Name of cookie where jwt is stored
+    duration: Getenv('DASEIN_JWT_DURATION', 86400), // Duration of JWT (24 hours)
+    secret: Getenv('DASEIN_JWT_SECRET') // Secret to sign JWT
+  },
+  twitter: {
+    consumerKey: Getenv('DASEIN_TWITTER_CONSUMER_KEY'), // Consumer key for twitter
+    consumerSecret: Getenv('DASEIN_TWITTER_CONSUMER_SECRET') // Consumer secret for twitter
+  }
+};
diff --git a/config/env.dist b/config/env.dist
new file mode 100644 (file)
index 0000000..ee23ee1
--- /dev/null
@@ -0,0 +1,4 @@
+DASEIN_COOKIE_KEYS=comma,separated,keys
+DASEIN_TWITTER_CONSUMER_KEY=your_twitter_key
+DASEIN_TWITTER_CONSUMER_SECRET=your_twitter_secret
+DASEIN_JWT_SECRET=some_random_string
index d442e69882b7d904c4d9c01cff0485cec011738d..92205a959d19aa88c547627970dc657ac85c7f27 100644 (file)
@@ -3,4 +3,7 @@ version: '2'
 services:
   dasein:
     build: .
+    env_file: .env
     image: rbdr/dasein
+    ports:
+      - "1927:1927"
index 8de32c8eb05a611661ff257022d576d7b7c25674..38654a4822261fe447ece81d6fbba384ce16ef8a 100644 (file)
 'use strict';
 
+const Koa = require('koa');
+const KoaJwt = require('koa-jwt');
+const KoaStatic = require('koa-static');
+const KoaRoute = require('koa-route');
+
+const AuthHandler = require('./handlers/auth');
+
 const internals = {};
 
+internals.k401Location = '/401.html';
+internals.kMainLocation = '/';
+
 module.exports = internals.Dasein = class Dasein {
 
+  constructor(config) {
+
+    Object.assign(this, config);
+  }
+
   run() {
 
-    console.log('OK');
+    this._initializeServer();
+    this._startServer();
+    this._printBanner();
 
     return Promise.resolve();
   }
+
+  _initializeServer() {
+
+    this._app = Koa();
+
+    this._app.keys = this.cookieKeys;
+
+    this._app.use(KoaStatic(this.staticDirectory));
+
+    // Redirect all 401s to the 401 static page
+
+    this._app.use(function * (next) {
+
+      try {
+        yield next;
+      }
+      catch (err) {
+        if (err.status === 401) {
+          return this.redirect(internals.k401Location);
+        }
+
+        throw err;
+      }
+    });
+
+    this._app.use(KoaJwt({
+      secret: this.jwt.secret,
+      passthrough: true,
+      cookie: this.jwt.cookieName
+    }));
+
+    // Handlers for Twitter Auth Related Routes
+
+    const authHandler = new AuthHandler({
+      hostname: this.hostname,
+      jwt: this.jwt,
+      twitter: this.twitter
+    });
+    this._app.use(KoaRoute.get('/login', authHandler.login()));
+    this._app.use(KoaRoute.get('/login-callback', authHandler.callback()));
+    this._app.use(KoaRoute.get('/logout', authHandler.logout()));
+
+    // The index
+
+    this._app.use(function * () {
+
+      if (this.state.user) {
+        this.body = `<img src="${this.state.user.profile_image_url_https}"> Hello ${this.state.user.screen_name}`;
+        return;
+      }
+
+      this.body = 'Go to /login to login';
+      return;
+    });
+  }
+
+  _startServer() {
+
+    this._app.listen(this.port);
+  }
+
+  // Prints the banner.
+  _printBanner() {
+
+    console.log('        .');
+    console.log('       /');
+    console.log('    +-----+');
+    console.log(`    | o o |  - Listening Gladly, Try me on port: ${this.port}`);
+    console.log('    +-----+');
+    console.log('  +---------+');
+    console.log(' /|    [][] |\\');
+    console.log(' ||         | |');
+    console.log(' ||         |  \\c');
+    console.log(' ^+---------+');
+    console.log('      (.) ');
+  }
 };
diff --git a/lib/handlers/auth.js b/lib/handlers/auth.js
new file mode 100644 (file)
index 0000000..d16e15d
--- /dev/null
@@ -0,0 +1,98 @@
+'use strict';
+
+const Co = require('co');
+const TwitterHelper = require('../twitter_helper');
+const JsonWebToken = require('jsonwebtoken');
+const Pify = require('pify');
+
+const internals = {};
+
+internals.kRedirectUrl = 'https://api.twitter.com/oauth/authenticate?oauth_token=';
+internals.kMainLocation = '/';
+
+internals.signJsonWebToken = Pify(JsonWebToken.sign);
+
+module.exports = internals.AuthHandler = class AuthHandler {
+
+  constructor(config) {
+
+    this._twitterHelper = new TwitterHelper(config.twitter);
+    this._jwtConfig = config.jwt;
+    this._hostname = config.hostname;
+  }
+
+  login() {
+
+    const twitterHelper = this._twitterHelper;
+
+    return function *handleLogin() {
+
+      if (this.state.user) {
+        return this.redirect(internals.kMainLocation);
+      }
+
+      const requestToken = yield twitterHelper.getRequestToken();
+      this.redirect(`${internals.kRedirectUrl}${requestToken.oAuthToken}`);
+    };
+  }
+
+  callback() {
+
+    const self = this;
+
+    return function *handleCallback() {
+
+      if (this.request.query.denied) {
+        return this.throw(401);
+      }
+
+      const oAuthToken = this.request.query.oauth_token;
+      const oAuthVerifier = this.request.query.oauth_verifier;
+      let user;
+
+      try {
+        const accessToken = yield self._twitterHelper.getAccessToken(oAuthToken, oAuthVerifier);
+        user = yield self._twitterHelper.getUser(accessToken.oAuthAccessToken, accessToken.oAuthAccessTokenSecret);
+      }
+      catch (err) {
+        console.error(err.stack || err.message || err);
+        return this.throw(401);
+      }
+
+      yield self._setJWT(user, this);
+
+      this.redirect(internals.kMainLocation);
+    };
+  }
+
+  logout() {
+
+    const self = this;
+
+    return function * () {
+
+      this.cookies.set(self._jwtConfig.cookieName, null);
+      this.redirect(internals.kMainLocation);
+    };
+  }
+
+  // Sets a JSON Web Token Cookie
+  _setJWT(payload, context) {
+
+    const self = this;
+
+    return Co(function * () {
+
+      const token = yield internals.signJsonWebToken(payload, self._jwtConfig.secret, {
+        expiresIn: self._jwtConfig.duration
+      });
+
+      context.cookies.set(self._jwtConfig.cookieName, token, {
+        maxAge: self._jwtConfig.duration * 1000,
+        signed: true,
+        domain: self._hostname,
+        overwrite: true
+      });
+    });
+  }
+};
diff --git a/lib/twitter_helper.js b/lib/twitter_helper.js
new file mode 100644 (file)
index 0000000..3ab51fa
--- /dev/null
@@ -0,0 +1,78 @@
+'use strict';
+
+const Co = require('co');
+const OAuth = require('oauth');
+const Pify = require('pify');
+
+const internals = {};
+
+internals.kRequestTokenUrl = 'https://api.twitter.com/oauth/request_token';
+internals.kAccessTokenUrl = 'https://api.twitter.com/oauth/access_token';
+internals.kVerifyCredentialsUrl = 'https://api.twitter.com/1.1/account/verify_credentials.json';
+internals.kOauthVersion = '1.0A';
+internals.kOauthSignatureMethod = 'HMAC-SHA1';
+
+module.exports = internals.TwitterHelper = class TwitterHelper {
+
+  constructor(config) {
+
+    this._oAuth = new OAuth.OAuth(
+      internals.kRequestTokenUrl,
+      internals.kAccessTokenUrl,
+      config.consumerKey,
+      config.consumerSecret,
+      internals.kOauthVersion,
+      null,
+      internals.kOauthSignatureMethod
+    );
+  }
+
+  getRequestToken() {
+
+    const self = this;
+
+    return Co(function * () {
+
+      const getOAuthRequestToken = Pify(self._oAuth.getOAuthRequestToken.bind(self._oAuth), { multiArgs: true });
+      const [oAuthToken, oAuthTokenSecret] = yield getOAuthRequestToken();
+
+      return {
+        oAuthToken,
+        oAuthTokenSecret
+      };
+    });
+  }
+
+  getAccessToken(oAuthToken, oAuthVerifier) {
+
+    const self = this;
+
+    return Co(function * () {
+
+      const getOAuthAccessToken = Pify(self._oAuth.getOAuthAccessToken.bind(self._oAuth), { multiArgs: true });
+      const [oAuthAccessToken, oAuthAccessTokenSecret] = yield getOAuthAccessToken(oAuthToken,
+                                                                                   '',
+                                                                                   oAuthVerifier);
+
+      return {
+        oAuthAccessToken,
+        oAuthAccessTokenSecret
+      };
+    });
+  }
+
+  getUser(oAuthAccessToken, oAuthAccessTokenSecret) {
+
+    const self = this;
+
+    return Co(function * () {
+
+      const get = Pify(self._oAuth.get.bind(self._oAuth), { multiArgs: true });
+      const [userResponse] = yield get(internals.kVerifyCredentialsUrl,
+                                       oAuthAccessToken,
+                                       oAuthAccessTokenSecret);
+
+      return JSON.parse(userResponse);
+    });
+  }
+};
index 6e8d9f1c1bc4af599616f32de2d2bce70cf63db6..ba898d240e360dacd4bb722dff31b66438bfbbb3 100644 (file)
     "eslint-plugin-hapi": "^4.0.0"
   },
   "dependencies": {
+    "getenv": "^0.7.0",
+    "jsonwebtoken": "^7.2.1",
+    "koa": "^1.2.4",
+    "koa-jwt": "^1.2.0",
+    "koa-route": "^2.4.2",
+    "koa-static": "^2.0.0",
+    "oauth": "^0.9.15",
+    "pify": "^2.3.0"
   }
 }
diff --git a/static/css/app.css b/static/css/app.css
new file mode 100644 (file)
index 0000000..956a281
--- /dev/null
@@ -0,0 +1,42 @@
+* {
+  margin: 0;
+  padding: 0;
+}
+
+/* General styles */
+
+body {
+  font-family: "VT323", sans-serif;
+}
+
+a {
+       color: #00e;
+}
+
+a:active {
+       color: #e00;
+}
+
+a:visited {
+       color: #551a8b;
+}
+
+h1 {
+       font-size: 48px;
+}
+
+section {
+       padding: 24px;
+}
+
+/* Header */
+
+#dasein .dasein-header {
+       text-align: center;
+}
+
+#dasein .dasein-header a {
+  font-size: 96px;
+}
+
+/* The stream */
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644 (file)
index 0000000..24318f3
Binary files /dev/null and b/static/favicon.ico differ
diff --git a/static/windex.html b/static/windex.html
new file mode 100644 (file)
index 0000000..f4a6ac5
--- /dev/null
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="description" content="A social network.">
+
+    <title>✨ dasein ✨</title>
+
+    <link href='https://fonts.googleapis.com/css?family=VT323' rel='stylesheet' >
+    <link href="/css/app.css" rel="stylesheet">
+
+  </head>
+  <body>
+    <div id="dasein">
+      <header class="dasein-header">
+        <a href="/">Dasein</a>
+      </header>
+      <section class="dasein-login">
+        <h1>Login.</h1>
+      </section>
+      <section class="dasein-stream">
+        <h1>@rbdr</h1>
+                               <article class="dasein-post">
+                                       <aside>
+                                               <img
+                                                src="https://pbs.twimg.com/profile_images/795825767649132544/tHHYHC3-_mini.jpg"
+                        alt="Avatar for @rbdr">
+                                               <a href="/@rbdr">@rbdr</a> on <time datetime="2016-12-17T18:16">2016-12-17 18:16</time>
+                                       </aside>
+                                       <div>Hi, this is a test. 💸</div>     
+                               </article>
+      </section>
+    </div>
+  </body>
+</html>
+
index cc376fb92625e466160c9786b361f2c54b2f7053..b8597b56ad07219079ac3c9176dadf487cd9360e 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -1,5 +1,14 @@
 # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 # yarn lockfile v1
+
+
+accepts@^1.2.2:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
+  dependencies:
+    mime-types "~2.1.11"
+    negotiator "0.6.1"
+
 acorn-jsx@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
@@ -37,6 +46,10 @@ ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
+any-promise@^1.0.0, any-promise@^1.1.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
+
 argparse@^1.0.7:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
@@ -69,6 +82,10 @@ balanced-match@^0.4.1:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
 
+base64url@2.0.0, base64url@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
+
 brace-expansion@^1.0.0:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9"
@@ -76,6 +93,10 @@ brace-expansion@^1.0.0:
     balanced-match "^0.4.1"
     concat-map "0.0.1"
 
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+
 caller-path@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -110,7 +131,7 @@ cli-width@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
 
-co@^4.6.0:
+co@^4.0.2, co@^4.4.0, co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
 
@@ -118,6 +139,13 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
+composition@^2.1.1:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/composition/-/composition-2.3.0.tgz#742805374cab550c520a33662f5a732e0208d6f2"
+  dependencies:
+    any-promise "^1.1.0"
+    co "^4.0.2"
+
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -130,6 +158,21 @@ concat-stream@^1.4.6:
     readable-stream "~2.0.0"
     typedarray "~0.0.5"
 
+content-disposition@~0.5.0:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-type@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
+
+cookies@~0.6.1:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.6.2.tgz#6ac1b052895208e8fc4c4f5f86a9ed31b9cb5ccf"
+  dependencies:
+    depd "~1.1.0"
+    keygrip "~1.0.1"
+
 core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -140,12 +183,16 @@ d@^0.1.1, d@~0.1.1:
   dependencies:
     es5-ext "~0.10.2"
 
-debug@^2.1.1:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c"
+debug@*, debug@^2.1.1:
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.4.4.tgz#c04d17a654e9202464803f096153f70a6f31f4be"
   dependencies:
     ms "0.7.2"
 
+deep-equal@~1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+
 deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -162,6 +209,18 @@ del@^2.0.2:
     pinkie-promise "^2.0.0"
     rimraf "^2.2.8"
 
+delegates@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+
+depd@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
+
+destroy@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
 doctrine@^1.2.2:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -169,6 +228,21 @@ doctrine@^1.2.2:
     esutils "^2.0.2"
     isarray "^1.0.0"
 
+ecdsa-sig-formatter@1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
+  dependencies:
+    base64url "^2.0.0"
+    safe-buffer "^5.0.1"
+
+ee-first@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+error-inject@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
+
 es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7:
   version "0.10.12"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047"
@@ -205,7 +279,7 @@ es6-set@~0.1.3:
     es6-symbol "3"
     event-emitter "~0.3.4"
 
-es6-symbol@~3.1, es6-symbol@~3.1.0, es6-symbol@3:
+es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa"
   dependencies:
@@ -221,6 +295,10 @@ es6-weak-map@^2.0.1:
     es6-iterator "2"
     es6-symbol "3"
 
+escape-html@~1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+
 escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -354,6 +432,10 @@ flat-cache@^1.2.1:
     graceful-fs "^4.1.2"
     write "^0.2.1"
 
+fresh@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -368,6 +450,10 @@ generate-object-property@^1.1.0:
   dependencies:
     is-property "^1.0.0"
 
+getenv:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/getenv/-/getenv-0.7.0.tgz#39b91838707e2086fd1cf6ef8777d1c93e14649e"
+
 glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
@@ -416,6 +502,32 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
+hoek@2.x.x:
+  version "2.16.3"
+  resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+
+http-assert@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.2.0.tgz#d6392e6f6519def4e340266b35096db6d3feba00"
+  dependencies:
+    deep-equal "~1.0.0"
+    http-errors "~1.4.0"
+
+http-errors@^1.2.8, http-errors@~1.5.0:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750"
+  dependencies:
+    inherits "2.0.3"
+    setprototypeof "1.0.2"
+    statuses ">= 1.3.1 < 2"
+
+http-errors@~1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf"
+  dependencies:
+    inherits "2.0.1"
+    statuses ">= 1.2.1 < 2"
+
 ignore@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"
@@ -431,10 +543,14 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@~2.0.1, inherits@2:
+inherits@2, inherits@2.0.3, inherits@~2.0.1:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
 
+inherits@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
+
 inquirer@^0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
@@ -502,10 +618,27 @@ is-resolvable@^1.0.0:
   dependencies:
     tryit "^1.0.1"
 
+isarray@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+
 isarray@^1.0.0, isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
 
+isemail@1.x.x:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
+
+joi@^6.10.1:
+  version "6.10.1"
+  resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
+  dependencies:
+    hoek "2.x.x"
+    isemail "1.x.x"
+    moment "2.x.x"
+    topo "1.x.x"
+
 js-tokens@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5"
@@ -531,6 +664,117 @@ jsonpointer@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.0.tgz#6661e161d2fc445f19f98430231343722e1fcbd5"
 
+jsonwebtoken@5.x.x:
+  version "5.7.0"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-5.7.0.tgz#1c90f9a86ce5b748f5f979c12b70402b4afcddb4"
+  dependencies:
+    jws "^3.0.0"
+    ms "^0.7.1"
+    xtend "^4.0.1"
+
+jsonwebtoken@^7.2.1:
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.2.1.tgz#0fc7217473fc02b4c9aa1e188aa70b51bba4fccb"
+  dependencies:
+    joi "^6.10.1"
+    jws "^3.1.4"
+    lodash.once "^4.0.0"
+    ms "^0.7.1"
+    xtend "^4.0.1"
+
+jwa@^1.1.4:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
+  dependencies:
+    base64url "2.0.0"
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.9"
+    safe-buffer "^5.0.1"
+
+jws@^3.0.0, jws@^3.1.4:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
+  dependencies:
+    base64url "^2.0.0"
+    jwa "^1.1.4"
+    safe-buffer "^5.0.1"
+
+keygrip@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.1.tgz#b02fa4816eef21a8c4b35ca9e52921ffc89a30e9"
+
+koa:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/koa/-/koa-1.2.4.tgz#6ef6d17a7bea8ec778a8572b55a0d0562e488654"
+  dependencies:
+    accepts "^1.2.2"
+    co "^4.4.0"
+    composition "^2.1.1"
+    content-disposition "~0.5.0"
+    content-type "^1.0.0"
+    cookies "~0.6.1"
+    debug "*"
+    delegates "^1.0.0"
+    destroy "^1.0.3"
+    error-inject "~1.0.0"
+    escape-html "~1.0.1"
+    fresh "^0.3.0"
+    http-assert "^1.1.0"
+    http-errors "^1.2.8"
+    koa-compose "^2.3.0"
+    koa-is-json "^1.0.0"
+    mime-types "^2.0.7"
+    on-finished "^2.1.0"
+    only "0.0.2"
+    parseurl "^1.3.0"
+    statuses "^1.2.0"
+    type-is "^1.5.5"
+    vary "^1.0.0"
+
+koa-compose@^2.3.0:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-2.5.1.tgz#726cfb17694de5cb9fbf03c0adf172303f83f156"
+
+koa-is-json@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14"
+
+koa-jwt@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/koa-jwt/-/koa-jwt-1.2.0.tgz#c0589c2c27d7e0b497a22cb28575d0bc7dc0f959"
+  dependencies:
+    jsonwebtoken "5.x.x"
+    koa-unless "0.0.1"
+    thunkify "~2.1.x"
+
+koa-route@^2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/koa-route/-/koa-route-2.4.2.tgz#0de227989e6aa7334768abbfb16c519ad9a7fa71"
+  dependencies:
+    debug "*"
+    methods "~1.1.0"
+    path-to-regexp "^1.2.0"
+
+koa-send@~3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-3.1.1.tgz#ef0af0f9a531ec817e88056b52b8e32d2e0ba91a"
+  dependencies:
+    co "^4.6.0"
+    debug "*"
+    mz "^2.3.1"
+    resolve-path "^1.3.1"
+
+koa-static:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-2.0.0.tgz#2693482e1a4c0219e6d926be1703a2658b754f26"
+  dependencies:
+    debug "*"
+    koa-send "~3.1.0"
+
+koa-unless@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/koa-unless/-/koa-unless-0.0.1.tgz#cfcdd1cdfbaf066e05d843e6a412df8a7c69b218"
+
 levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -538,10 +782,32 @@ levn@^0.3.0, levn@~0.3.0:
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
 
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+
 lodash@^4.0.0, lodash@^4.3.0:
   version "4.17.2"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42"
 
+media-typer@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+methods@~1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+
+mime-db@~1.25.0:
+  version "1.25.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392"
+
+mime-types@^2.0.7, mime-types@~2.1.11, mime-types@~2.1.13:
+  version "2.1.13"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88"
+  dependencies:
+    mime-db "~1.25.0"
+
 minimatch@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
@@ -558,7 +824,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1:
   dependencies:
     minimist "0.0.8"
 
-ms@0.7.2:
+moment@2.x.x:
+  version "2.17.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
+
+ms@0.7.2, ms@^0.7.1:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
 
@@ -566,10 +836,22 @@ mute-stream@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
 
+mz@^2.3.1:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce"
+  dependencies:
+    any-promise "^1.0.0"
+    object-assign "^4.0.1"
+    thenify-all "^1.0.0"
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
 
+negotiator@0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+
 no-arrowception@1.x.x:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/no-arrowception/-/no-arrowception-1.0.0.tgz#5bf3e95eb9c41b57384a805333daa3b734ee327a"
@@ -578,10 +860,20 @@ number-is-nan@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
 
+oauth:
+  version "0.9.15"
+  resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
+
 object-assign@^4.0.1, object-assign@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
 
+on-finished@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+  dependencies:
+    ee-first "1.1.1"
+
 once@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -592,6 +884,10 @@ onetime@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
 
+only@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
+
 optionator@^0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
@@ -607,7 +903,11 @@ os-homedir@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
 
-path-is-absolute@^1.0.0:
+parseurl@^1.3.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
+
+path-is-absolute@1.0.1, path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
 
@@ -615,7 +915,13 @@ path-is-inside@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
 
-pify@^2.0.0:
+path-to-regexp@^1.2.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
+  dependencies:
+    isarray "0.0.1"
+
+pify, pify@^2.0.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
 
@@ -681,6 +987,13 @@ resolve-from@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
 
+resolve-path@^1.3.1:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.3.3.tgz#4d83aba6468c2b8e632a575e3f52b0fa0dbe1a5c"
+  dependencies:
+    http-errors "~1.5.0"
+    path-is-absolute "1.0.1"
+
 resolve@^1.1.6:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
@@ -708,6 +1021,14 @@ rx-lite@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
 
+safe-buffer@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+
+setprototypeof@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08"
+
 shelljs@^0.7.5:
   version "0.7.5"
   resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.5.tgz#2eef7a50a21e1ccf37da00df767ec69e30ad0675"
@@ -724,9 +1045,9 @@ sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
 
-string_decoder@~0.10.x:
-  version "0.10.31"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+"statuses@>= 1.2.1 < 2", "statuses@>= 1.3.1 < 2", statuses@^1.2.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
 
 string-width@^1.0.1:
   version "1.0.2"
@@ -743,6 +1064,10 @@ string-width@^2.0.0:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^3.0.0"
 
+string_decoder@~0.10.x:
+  version "0.10.31"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+
 strip-ansi@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -776,10 +1101,32 @@ text-table@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
 
+thenify-all@^1.0.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
+  dependencies:
+    thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.2.1.tgz#251fd1c80aff6e5cf57cb179ab1fcb724269bd11"
+  dependencies:
+    any-promise "^1.0.0"
+
 through@^2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
 
+thunkify@~2.1.x:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
+
+topo@1.x.x:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5"
+  dependencies:
+    hoek "2.x.x"
+
 tryit@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
@@ -790,6 +1137,13 @@ type-check@~0.3.2:
   dependencies:
     prelude-ls "~1.1.2"
 
+type-is@^1.5.5:
+  version "1.6.14"
+  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2"
+  dependencies:
+    media-typer "0.3.0"
+    mime-types "~2.1.13"
+
 typedarray@~0.0.5:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -804,6 +1158,10 @@ util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
+vary@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"
+
 wordwrap@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
@@ -818,7 +1176,6 @@ write@^0.2.1:
   dependencies:
     mkdirp "^0.5.1"
 
-xtend@^4.0.0:
+xtend@^4.0.0, xtend@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
-