├── .env.example ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── Procfile ├── README.md ├── app.json ├── docs └── deployment.md ├── index.js ├── lib ├── controller-layer.js ├── identity-provider.js ├── middleware.js ├── model-layer.js ├── pusher-pub-sub-gateway.js ├── server.js └── test-server.js ├── migrations ├── 1496746086060_shared-buffers.js ├── 1496928350013_shared-buffer-name.js ├── 1497002608106_shared-editors.js ├── 1497446752046_portals.js ├── 1497971047085_move-last-side-id-to-portals.js ├── 1498094681465_sites.js ├── 1499865233000_peer-to-peer.js ├── 1510143431963_create-events.js └── 1510307606770_alter-events-portal-id.js ├── newrelic.js ├── package-lock.json ├── package.json ├── script └── server └── test ├── controller-layer.test.js ├── helpers ├── condition.js └── request.js ├── identity-provider.test.js ├── middleware.test.js └── setup.js /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://localhost:5432/teletype-server-dev 2 | TEST_DATABASE_URL=postgres://localhost:5432/teletype-server-test 3 | PORT=3000 4 | PUSHER_APP_ID=348824 5 | PUSHER_KEY=redacted 6 | PUSHER_SECRET=redacted 7 | PUSHER_CLUSTER=mt1 8 | TWILIO_ACCOUNT=redacted 9 | TWILIO_AUTH_TOKEN=redacted 10 | NEW_RELIC_ENABLED=false 11 | NEW_RELIC_APP_NAME=atom-teletype-development 12 | NEW_RELIC_LICENSE_KEY=redacted 13 | GITHUB_API_URL=https://api.github.com 14 | GITHUB_CLIENT_ID=redacted 15 | GITHUB_CLIENT_SECRET=redacted 16 | GITHUB_OAUTH_TOKEN=redacted 17 | HASH_SECRET=redacted 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | newrelic_agent.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | 5 | services: 6 | - postgresql 7 | 8 | before_script: 9 | - 'createdb teletype-server-test' 10 | - 'cp .env.example .env' 11 | 12 | notifications: 13 | email: 14 | on_success: never 15 | on_failure: change 16 | slack: 17 | on_success: change 18 | rooms: 19 | - secure: mkxeBEUqnis57DyGzzH1gpbEsSDdmYH0wWvl/kdUemBd3nhUVdTnkceBCdHLsKixd5ZInt7yaxgqpIxS+CpNKOH97ZPwl7LAquPikVcJ+ol9ppds3WB/dX9oJaP/5gD/CdMZASB+guFhnj2CUuVZKS+HAEtYzTXi5iYUAS7Vn9kKWWnTrc1PZUNhu3wvXuDB+ODM3ZaZ4/prux1NBLK26RKTyQI25517MY8ZdcqF8tYfmqoD05SfL73yZphp5Dn8e6CL2FYRQLKgtbmgJf0zC9DWpmo/YSlNZeBuloVbQdRCyhrltbd+ftzWGuxe3HbxG8ZQ+RFjoS/w+XDc2LZP2z3tafPoSJ7kAAbM66NAqNzQB2cULlkimoPx2K1gc7aGXUh4X5cWSvvX98hSi68O45oOm0XlFBB4wNJUqQUUp48u83EvS1KmdYzTFaP+0tlYMsBkEfw5EdaFxkzMNm1WDcVGf9LA8PwtoOmuHXV10Wd81oCvy8utNoDbC5bCgRWTPVzUDFeFExUhle6Q7YC/fHnGMK3AMRQmplt/zkR+21EIS9X78DiYegaOAFMyAf2QyaX7Yon9354XvvG0eMQkADSMBW5DzlnPqD0/ebmtfpUf6oIjh6LIYpbD5Ff5Akca5BHzb19Q/2o6pUbDZlkmeuokNzXCnRYknhbGeNH91Yg= 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 GitHub 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: npm run migrate up 2 | web: node script/server 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # teletype-server 3 | 4 | The server-side application that facilitates peer discovery for collaborative editing sessions in [Teletype](https://github.com/atom/teletype). 5 | 6 | ## Hacking 7 | 8 | ### Dependencies 9 | 10 | To run teletype-server locally, you'll first need to have: 11 | 12 | - Node 7+ 13 | - PostgreSQL 9.x 14 | - An app on [pusher.com](https://pusher.com/docs/javascript_quick_start#get-your-free-API-keys) 15 | - An [OAuth app on github.com](https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/registering-oauth-apps/) 16 | 17 | ### Running locally 18 | 19 | 1. Clone and bootstrap 20 | 21 | ``` 22 | git clone https://github.com/atom/teletype-server.git 23 | cd teletype-server 24 | cp .env.example .env 25 | createdb teletype-server-dev 26 | createdb teletype-server-test 27 | npm install 28 | npm run migrate up 29 | ``` 30 | 31 | 2. Copy the `app_id`, `key`, and `secret` for your app on pusher.com, and set those values in your `.env` file 32 | 33 | 3. Copy the client ID and client secret for your OAuth app on github.com, and set those values in your `.env` file 34 | 35 | 4. Start the server 36 | 37 | ``` 38 | ./script/server 39 | ``` 40 | 41 | 5. Run the tests 42 | 43 | ``` 44 | npm test 45 | ``` 46 | 47 | ## Deploying 48 | 49 | Atom core team members can use [this guide](./docs/deployment.md) to test pull requests and deploy changes to production. 50 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teletype-server", 3 | "scripts": {}, 4 | "env": { 5 | "BOOMTOWN_SECRET": { 6 | "required": true 7 | }, 8 | "BUGSNAG_API_KEY": { 9 | "required": true 10 | }, 11 | "GITHUB_CLIENT_ID": { 12 | "required": true 13 | }, 14 | "GITHUB_CLIENT_SECRET": { 15 | "required": true 16 | }, 17 | "HASH_SECRET": { 18 | "required": true 19 | }, 20 | "NEW_RELIC_APP_NAME": { 21 | "required": true 22 | }, 23 | "NEW_RELIC_LICENSE_KEY": { 24 | "required": true 25 | }, 26 | "PAPERTRAIL_API_TOKEN": { 27 | "required": true 28 | }, 29 | "PUSHER_APP_ID": { 30 | "required": true 31 | }, 32 | "PUSHER_KEY": { 33 | "required": true 34 | }, 35 | "PUSHER_SECRET": { 36 | "required": true 37 | }, 38 | "TWILIO_ACCOUNT": { 39 | "required": true 40 | }, 41 | "TWILIO_AUTH_TOKEN": { 42 | "required": true 43 | } 44 | }, 45 | "formation": {}, 46 | "addons": [ 47 | "heroku-postgresql" 48 | ], 49 | "buildpacks": [ 50 | { 51 | "url": "heroku/nodejs" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /docs/deployment.md: -------------------------------------------------------------------------------- 1 | # Deploying 2 | 3 | Atom core team members can follow the instructions below to test pull requests and deploy changes to production. 4 | 5 | ## Testing pull requests 6 | 7 | Visit the [atom-teletype pipeline dashboard](https://dashboard.heroku.com/pipelines/7b6e5b11-ca97-402a-8b3f-b48f2d1645cf) on Heroku. 8 | 9 | ![pipeline](https://user-images.githubusercontent.com/482957/32596993-87956fe4-c535-11e7-8d3a-651cf4348795.png) 10 | 11 | In the left-hand column, you'll see a *review application* for each open pull request on this repository. Alternatively, you can also wait for a deployment notification to be displayed directly on the pull-request. 12 | 13 | The teletype package can be instructed to use one of these servers by opening the package's settings page and assigning the base URL. 14 | 15 | ![package-settings](https://user-images.githubusercontent.com/482957/32597088-dd1907aa-c535-11e7-937e-ba254300fca3.png) 16 | 17 | You'll need to reload the window for the change to take effect. You should not normally need to touch the Pusher key as we use the same Pusher account on both staging and production. 18 | 19 | ## Deploying 20 | 21 | 1. Ask @as-cii, @nathansobo, @jasonrudolph, or @iolsen to grant you deploy access on https://dashboard.heroku.com/apps/atom-teletype. 22 | 23 | 2. Visit the [atom-teletype pipeline dashboard](https://dashboard.heroku.com/pipelines/7b6e5b11-ca97-402a-8b3f-b48f2d1645cf) on Heroku. 24 | 25 | 3. Whenever someone pushes to `master`, its contents are automatically deployed to staging. Use the instructions from the [testing section](#testing-pull-requests) above with the base URL of `https://atom-teletype-staging.herokuapp.com/` if you'd like to test the Atom package against staging before deploying. 26 | 27 | 4. Once you're satisfied with your testing, click **Promote to production...** on the staging app to move the staging slug to the production app. 28 | 29 | ![promote-to-production](https://user-images.githubusercontent.com/482957/32597196-44b75ba0-c536-11e7-9c6d-92a4ebb85cfc.png) 30 | 31 | ## Chat-ops 32 | 33 | You can also interact with our Heroku pipeline on Slack via ChatOps. Check out [this page](https://devcenter.heroku.com/articles/chatops) for more information. 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | async function startServer (id) { 2 | require('dotenv').config() 3 | require('newrelic') 4 | 5 | const bugsnag = require('bugsnag') 6 | bugsnag.register(process.env.BUGSNAG_API_KEY) 7 | 8 | process.on('unhandledRejection', (error) => { 9 | console.error(error.stack) 10 | bugsnag.notify(error) 11 | }) 12 | 13 | const Server = require('./lib/server') 14 | const server = new Server({ 15 | databaseURL: process.env.DATABASE_URL, 16 | pusherAppId: process.env.PUSHER_APP_ID, 17 | pusherKey: process.env.PUSHER_KEY, 18 | pusherSecret: process.env.PUSHER_SECRET, 19 | pusherCluster: process.env.PUSHER_CLUSTER || "mt1", 20 | twilioAccount: process.env.TWILIO_ACCOUNT, 21 | twilioAuthToken: process.env.TWILIO_AUTH_TOKEN, 22 | githubApiUrl: process.env.GITHUB_API_URL || "https://api.github.com", 23 | githubClientId: process.env.GITHUB_CLIENT_ID, 24 | githubClientSecret: process.env.GITHUB_CLIENT_SECRET, 25 | githubOauthToken: process.env.GITHUB_OAUTH_TOKEN, 26 | boomtownSecret: process.env.BOOMTOWN_SECRET, 27 | hashSecret: process.env.HASH_SECRET, 28 | port: process.env.PORT || 3000 29 | }) 30 | await server.start() 31 | console.log(`Worker ${id} (pid: ${process.pid}): listening on port ${server.port}`) 32 | return server 33 | } 34 | 35 | async function startTestServer (params) { 36 | const TestServer = require('./lib/test-server') 37 | const server = new TestServer(params) 38 | await server.start() 39 | return server 40 | } 41 | 42 | module.exports = {startServer, startTestServer} 43 | -------------------------------------------------------------------------------- /lib/controller-layer.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const bugsnag = require('bugsnag') 3 | const cors = require('cors') 4 | const express = require('express') 5 | const bodyParser = require('body-parser') 6 | const {authenticate, enforceProtocol} = require('./middleware') 7 | const ModelLayer = require('./model-layer') 8 | 9 | const PERCENTAGE_OF_TWILIO_TTL_TO_USE_FOR_CACHE_HEADER = 0.95 10 | 11 | module.exports = ({ 12 | modelLayer, 13 | pubSubGateway, 14 | identityProvider, 15 | fetchICEServers, 16 | boomtownSecret, 17 | enableExceptionReporter, 18 | }) => { 19 | const app = express() 20 | app.use(cors()) 21 | app.use(bodyParser.json({limit: '1mb'})) 22 | app.use(enforceProtocol) 23 | app.use(authenticate({identityProvider, ignoredPaths: ['/', '/protocol-version', '/boomtown', '/_ping']})) 24 | if (enableExceptionReporter) { 25 | app.use(bugsnag.requestHandler) 26 | } 27 | 28 | app.get('/protocol-version', function (req, res) { 29 | res.set('Cache-Control', 'no-store') 30 | res.send({version: 9}) 31 | }) 32 | 33 | app.get('/ice-servers', async function (req, res) { 34 | if (fetchICEServers) { 35 | const {servers, ttl} = await fetchICEServers() 36 | const maxAgeInSeconds = ttl * PERCENTAGE_OF_TWILIO_TTL_TO_USE_FOR_CACHE_HEADER 37 | res.set('Cache-Control', `private, max-age=${maxAgeInSeconds}`) 38 | res.send(servers) 39 | } else { 40 | res.send([]) 41 | } 42 | }) 43 | 44 | app.post('/peers/:id/signals', async function (req, res) { 45 | const {senderId, signal, sequenceNumber, testEpoch} = req.body 46 | const message = {senderId, signal, sequenceNumber} 47 | if (testEpoch != null) message.testEpoch = testEpoch 48 | 49 | if (sequenceNumber === 0) message.senderIdentity = res.locals.identity 50 | 51 | pubSubGateway.broadcast(`/peers/${req.params.id}`, 'signal', message) 52 | res.send({}) 53 | }) 54 | 55 | app.post('/portals', async function (req, res) { 56 | const id = await modelLayer.createPortal({hostPeerId: req.body.hostPeerId}) 57 | 58 | modelLayer.createEvent({ 59 | name: 'create-portal', 60 | identity: res.locals.identity, 61 | portalId: id 62 | }) 63 | 64 | res.send({id}) 65 | }) 66 | 67 | app.get('/portals/:id', async function (req, res) { 68 | modelLayer.createEvent({ 69 | name: 'lookup-portal', 70 | identity: res.locals.identity, 71 | portalId: req.params.id 72 | }) 73 | 74 | const portal = await modelLayer.findPortal(req.params.id) 75 | if (portal) { 76 | res.send({hostPeerId: portal.hostPeerId}) 77 | } else { 78 | res.status(404).send({}) 79 | } 80 | }) 81 | 82 | app.get('/identity', async function (req, res) { 83 | res.send(res.locals.identity) 84 | }) 85 | 86 | // For use in testing exception reporting (i.e., bugsnag integration) 87 | app.get('/boomtown', function (req, res) { 88 | if (req.query.secret !== boomtownSecret) { 89 | res.status(404).send({}) 90 | return 91 | } 92 | 93 | throw new Error('boom') 94 | }) 95 | 96 | app.get('/_ping', async function (req, res) { 97 | const statuses = await Promise.all([ 98 | pubSubGateway.isOperational(), 99 | modelLayer.isOperational(), 100 | isICEServerProviderOperational(), 101 | identityProvider.isOperational() 102 | ]) 103 | 104 | const unhealthyServices = [] 105 | if (!statuses[0]) unhealthyServices.push('pubSubGateway') 106 | if (!statuses[1]) unhealthyServices.push('db') 107 | if (!statuses[2]) unhealthyServices.push('iceServerProvider') 108 | if (!statuses[3]) unhealthyServices.push('identityProvider') 109 | 110 | if (unhealthyServices.length === 0) { 111 | res.status(200).send({ 112 | now: Date.now(), 113 | status: 'ok' 114 | }) 115 | } else { 116 | res.status(503).send({ 117 | now: Date.now(), 118 | status: 'failures', 119 | failures: unhealthyServices 120 | }) 121 | } 122 | }) 123 | 124 | async function isICEServerProviderOperational () { 125 | try { 126 | await fetchICEServers() 127 | return true 128 | } catch (_) { 129 | return false 130 | } 131 | } 132 | 133 | if (enableExceptionReporter) { 134 | app.use(bugsnag.errorHandler) 135 | } 136 | return app 137 | } 138 | -------------------------------------------------------------------------------- /lib/identity-provider.js: -------------------------------------------------------------------------------- 1 | const {StatusCodeError} = require('request-promise-core/lib/errors') 2 | 3 | module.exports = 4 | class IdentityProvider { 5 | constructor ({request, apiUrl, oauthToken}) { 6 | this.request = request 7 | this.apiUrl = apiUrl 8 | this.oauthToken = oauthToken 9 | } 10 | 11 | async identityForToken (oauthToken) { 12 | try { 13 | const headers = { 14 | 'User-Agent': 'api.teletype.atom.io', 15 | 'Authorization': `token ${oauthToken}` 16 | } 17 | const response = await this.request.get(this.apiUrl + '/user', {headers}) 18 | const user = JSON.parse(response) 19 | return {id: user.id.toString(), login: user.login} 20 | } catch (e) { 21 | let errorMessage, statusCode 22 | if (e instanceof StatusCodeError) { 23 | const error = JSON.parse(e.error) 24 | const description = (error.message != null) ? error.message : e.error 25 | errorMessage = `${this.apiUrl} responded with ${e.statusCode}: ${description}` 26 | statusCode = e.statusCode 27 | } else { 28 | errorMessage = `Failed to query ${this.apiUrl}: ${e.message}` 29 | statusCode = 500 30 | } 31 | 32 | const error = new Error(errorMessage) 33 | error.statusCode = statusCode 34 | throw error 35 | } 36 | } 37 | 38 | async isOperational () { 39 | try { 40 | const headers = { 41 | 'User-Agent': 'api.teletype.atom.io', 42 | 'Authorization': `token ${this.oauthToken}` 43 | } 44 | 45 | await this.request.get(this.apiUrl, {headers}) 46 | 47 | return true 48 | } catch (e) { 49 | return false 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/middleware.js: -------------------------------------------------------------------------------- 1 | exports.enforceProtocol = function (req, res, next) { 2 | if (req.app.get('env') === 'production' && req.headers['x-forwarded-proto'] !== 'https') { 3 | res.status(403).send({ message: 'HTTPS required' }) 4 | } else { 5 | next() 6 | } 7 | } 8 | 9 | exports.authenticate = function ({identityProvider, ignoredPaths}) { 10 | const middleware = async function authenticate (req, res, next) { 11 | if (ignoredPaths.includes(req.path)) return next() 12 | 13 | const oauthToken = req.headers['github-oauth-token'] 14 | if (oauthToken == null) { 15 | res.status(401).send({message: 'Authentication required'}) 16 | return 17 | } 18 | 19 | try { 20 | res.locals.identity = await identityProvider.identityForToken(oauthToken) 21 | } catch (error) { 22 | res.status(401).send({message: 'Error resolving identity for token: ' + error.message}) 23 | return 24 | } 25 | 26 | next() 27 | } 28 | 29 | return middleware 30 | } 31 | -------------------------------------------------------------------------------- /lib/model-layer.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const bugsnag = require('bugsnag') 3 | const crypto = require('crypto') 4 | const uuid = require('uuid/v4') 5 | 6 | module.exports = 7 | class ModelLayer { 8 | constructor ({db, hashSecret}) { 9 | this.db = db 10 | this.hashSecret = hashSecret 11 | assert(this.hashSecret != null, 'Hash secret cannot be empty') 12 | } 13 | 14 | async createPortal ({hostPeerId}) { 15 | const id = uuid() 16 | await this.db.none('INSERT INTO portals (id, host_peer_id) VALUES ($1, $2)', [id, hostPeerId]) 17 | return id 18 | } 19 | 20 | async findPortal (id) { 21 | try { 22 | const result = await this.db.oneOrNone('SELECT * FROM portals where id = $1', [id]) 23 | return (result == null) ? null : {hostPeerId: result.host_peer_id} 24 | } catch (e) { 25 | const malformedUUIDErrorCode = '22P02' 26 | if (e.code === malformedUUIDErrorCode) return null 27 | } 28 | } 29 | 30 | async createEvent (event) { 31 | try { 32 | const {name, identity, portalId} = event 33 | const loginHash = crypto.createHash('sha1').update(identity.id + this.hashSecret).digest('hex') 34 | await this.db.none( 35 | 'INSERT INTO events (name, user_id, portal_id, created_at) VALUES ($1, $2, $3, now())', 36 | [name, loginHash, portalId] 37 | ) 38 | } catch (error) { 39 | bugsnag.notify(error, event) 40 | } 41 | } 42 | 43 | getEvents () { 44 | return this.db.manyOrNone('SELECT * FROM events ORDER BY created_at ASC') 45 | } 46 | 47 | async isOperational () { 48 | try { 49 | await this.db.one('select') 50 | return true 51 | } catch (error) { 52 | return false 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pusher-pub-sub-gateway.js: -------------------------------------------------------------------------------- 1 | const Pusher = require('pusher') 2 | 3 | module.exports = 4 | class PusherPubSubGateway { 5 | constructor ({appId, key, secret, cluster}) { 6 | this.pusherClient = new Pusher({ 7 | appId, 8 | key, 9 | secret, 10 | cluster: cluster, 11 | encrypted: true 12 | }) 13 | } 14 | 15 | broadcast (channelName, eventName, data) { 16 | channelName = channelName.replace(/\//g, '.') 17 | this.pusherClient.trigger(channelName, eventName, data) 18 | } 19 | 20 | async isOperational () { 21 | return new Promise((resolve) => { 22 | this.pusherClient.get({path: '/channels/unexisting-channel'}, (error, request, response) => { 23 | if (error) { 24 | resolve(false) 25 | return 26 | } 27 | 28 | const success = 200 <= response.statusCode && response.statusCode < 400 29 | resolve(success) 30 | }) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | const buildControllerLayer = require('./controller-layer') 2 | const pgp = require('pg-promise')() 3 | const request = require('request-promise-native') 4 | const IdentityProvider = require('./identity-provider') 5 | const ModelLayer = require('./model-layer') 6 | const PubSubGateway = require('./pusher-pub-sub-gateway') 7 | 8 | module.exports = 9 | class Server { 10 | constructor (options) { 11 | this.databaseURL = options.databaseURL 12 | this.pusherAppId = options.pusherAppId 13 | this.pusherKey = options.pusherKey 14 | this.pusherSecret = options.pusherSecret 15 | this.pusherCluster = options.pusherCluster 16 | this.twilioAccount = options.twilioAccount 17 | this.twilioAuthToken = options.twilioAuthToken 18 | this.githubApiUrl = options.githubApiUrl 19 | this.githubClientId = options.githubClientId 20 | this.githubClientSecret = options.githubClientSecret 21 | this.githubOauthToken = options.githubOauthToken 22 | this.boomtownSecret = options.boomtownSecret 23 | this.hashSecret = options.hashSecret 24 | this.port = options.port 25 | } 26 | 27 | async start () { 28 | const modelLayer = new ModelLayer({db: pgp(this.databaseURL), hashSecret: this.hashSecret}) 29 | const identityProvider = new IdentityProvider({ 30 | request, 31 | apiUrl: this.githubApiUrl, 32 | clientId: this.githubClientId, 33 | clientSecret: this.githubClientSecret, 34 | oauthToken: this.githubOauthToken 35 | }) 36 | const pubSubGateway = new PubSubGateway({ 37 | appId: this.pusherAppId, 38 | key: this.pusherKey, 39 | secret: this.pusherSecret, 40 | cluster: this.pusherCluster 41 | }) 42 | 43 | const twilioICEServerURL = `https://${this.twilioAccount}:${this.twilioAuthToken}@api.twilio.com/2010-04-01/Accounts/${this.twilioAccount}/Tokens.json` 44 | 45 | const controllerLayer = buildControllerLayer({ 46 | modelLayer, 47 | pubSubGateway, 48 | identityProvider, 49 | boomtownSecret: this.boomtownSecret, 50 | fetchICEServers: async () => { 51 | const response = JSON.parse(await request.post(twilioICEServerURL)) 52 | return {ttl: parseInt(response.ttl), servers: response.ice_servers} 53 | }, 54 | enableExceptionReporter: true 55 | }) 56 | 57 | return new Promise((resolve) => { 58 | this.server = controllerLayer.listen(this.port, resolve) 59 | }) 60 | } 61 | 62 | stop () { 63 | return new Promise((resolve) => this.server.close(resolve)) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/test-server.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const crypto = require('crypto') 3 | const enableDestroy = require('server-destroy') 4 | const path = require('path') 5 | const childProcess = require('child_process') 6 | const temp = require('temp') 7 | const pgp = require('pg-promise')() 8 | const buildControllerLayer = require('./controller-layer') 9 | const ModelLayer = require('./model-layer') 10 | const PusherPubSubGateway = require('../lib/pusher-pub-sub-gateway') 11 | 12 | module.exports = 13 | class TestServer { 14 | constructor ({databaseURL, pusherCredentials, hashSecret} = {}) { 15 | this.databaseURL = databaseURL 16 | this.pusherCredentials = pusherCredentials 17 | this.hashSecret = hashSecret || 'secret' 18 | } 19 | 20 | start () { 21 | if (this.databaseURL) this.migrateDatabase() 22 | return this.listen() 23 | } 24 | 25 | stop () { 26 | return new Promise((resolve) => this.server.destroy(resolve)) 27 | } 28 | 29 | async reset () { 30 | if (this.databaseURL) { 31 | const tables = [ 32 | 'portals', 33 | 'events' 34 | ] 35 | for (const table of tables) { 36 | await this.db.query(`DELETE FROM ${table}`) 37 | } 38 | } 39 | 40 | if (this.pubSubGateway) this.pubSubGateway.reset() 41 | this.identityProvider.reset() 42 | } 43 | 44 | migrateDatabase () { 45 | const pgMigratePath = require.resolve('node-pg-migrate/bin/node-pg-migrate') 46 | const migrateUpResult = childProcess.spawnSync( 47 | pgMigratePath, 48 | ['up', '--migrations-dir', path.join(__dirname, '..', 'migrations')], 49 | {env: Object.assign({}, process.env, {DATABASE_URL: this.databaseURL})} 50 | ) 51 | if (migrateUpResult.status !== 0) { 52 | throw new Error('Error running migrations:\n\n' + migrateUpResult.output) 53 | } 54 | } 55 | 56 | listen () { 57 | let pubSubGateway 58 | if (this.pusherCredentials) { 59 | pubSubGateway = new PusherPubSubGateway(this.pusherCredentials) 60 | } else { 61 | pubSubGateway = new LocalPubSubGateway() 62 | this.pubSubGateway = pubSubGateway 63 | } 64 | 65 | this.identityProvider = new LocalIdentityProvider() 66 | 67 | if (this.databaseURL) this.db = pgp(this.databaseURL) 68 | this.modelLayer = new ModelLayer({db: this.db, hashSecret: this.hashSecret}) 69 | const controllerLayer = buildControllerLayer({ 70 | modelLayer: this.modelLayer, 71 | pubSubGateway, 72 | identityProvider: this.identityProvider 73 | }) 74 | return new Promise((resolve) => { 75 | const randomPortSelectionFlag = 0 76 | this.server = controllerLayer.listen(randomPortSelectionFlag, 'localhost', () => { 77 | enableDestroy(this.server) 78 | this.address = `http://localhost:${this.server.address().port}` 79 | resolve() 80 | }) 81 | }) 82 | } 83 | } 84 | 85 | class LocalPubSubGateway { 86 | constructor () { 87 | this.callbacksByChannelName = new Map() 88 | this.nextClientId = 1 89 | } 90 | 91 | getClientId () { 92 | return Promise.resolve((this.nextClientId++).toString()) 93 | } 94 | 95 | reset () { 96 | this.callbacksByChannelName.clear() 97 | } 98 | 99 | broadcast (channelName, eventName, message) { 100 | const messageText = JSON.stringify(message) 101 | 102 | setTimeout(() => { 103 | const channelCallbacks = this.callbacksByChannelName.get(channelName) 104 | if (channelCallbacks) { 105 | channelCallbacks.forEach((callback) => { 106 | callback(eventName, JSON.parse(messageText)) 107 | }) 108 | } 109 | }, Math.random() * 5) 110 | } 111 | 112 | subscribe (channelName, eventName, callback) { 113 | return new Promise((resolve) => { 114 | process.nextTick(() => { 115 | const wrapper = (receivedEventName, data) => { 116 | if (receivedEventName === eventName) callback(data) 117 | } 118 | let channelCallbacks = this.callbacksByChannelName.get(channelName) 119 | if (!channelCallbacks) { 120 | channelCallbacks = new Set() 121 | this.callbacksByChannelName.set(channelName, channelCallbacks) 122 | } 123 | 124 | channelCallbacks.add(wrapper) 125 | resolve({ 126 | dispose () { 127 | channelCallbacks.delete(wrapper) 128 | } 129 | }) 130 | }) 131 | }) 132 | } 133 | } 134 | 135 | class LocalIdentityProvider { 136 | constructor () { 137 | this.identitiesByToken = {} 138 | } 139 | 140 | identityForToken (oauthToken) { 141 | let identity 142 | if (this.identitiesByToken.hasOwnProperty(oauthToken)) { 143 | identity = this.identitiesByToken[oauthToken] 144 | } else { 145 | identity = {login: `user-with-token-${oauthToken}`} 146 | } 147 | 148 | if (identity) { 149 | if (identity.id == null) identity.id = identity.login + '-id' 150 | 151 | return Promise.resolve(identity) 152 | } else { 153 | const error = new Error('Error resolving identity for token') 154 | error.statusCode = 401 155 | return Promise.reject(error) 156 | } 157 | } 158 | 159 | setIdentitiesByToken (identitiesByToken) { 160 | this.identitiesByToken = identitiesByToken 161 | } 162 | 163 | reset () { 164 | this.identitiesByToken = {} 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /migrations/1496746086060_shared-buffers.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.createTable('shared_buffers', { 3 | id: {type: 'bigserial', primaryKey: true}, 4 | last_site_id: {type: 'int', default: 1} 5 | }) 6 | pgm.createTable('shared_buffer_operations', { 7 | id: {type: 'bigserial', primaryKey: true}, 8 | data: {type: 'text'}, 9 | shared_buffer_id: {type: 'bigint', references: 'shared_buffers'} 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /migrations/1496928350013_shared-buffer-name.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.addColumns('shared_buffers', { 3 | uri: {type: 'varchar(1024)'} 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /migrations/1497002608106_shared-editors.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.createTable('shared_editors', { 3 | id: {type: 'bigserial', primaryKey: true}, 4 | shared_buffer_id: {type: 'bigint', references: 'shared_buffers'} 5 | }) 6 | 7 | pgm.createTable('shared_editor_selection_marker_layers', { 8 | id: {type: 'bigserial', primaryKey: true}, 9 | site_id: {type: 'bigint'}, 10 | shared_editor_id: {type: 'bigint', references: 'shared_editors'}, 11 | marker_ranges: {type: 'text'} 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /migrations/1497446752046_portals.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.createTable('portals', { 3 | id: {type: 'uuid', primaryKey: true}, 4 | active_shared_editor_id: {type: 'bigint', references: 'shared_editors'} 5 | }) 6 | } 7 | -------------------------------------------------------------------------------- /migrations/1497971047085_move-last-side-id-to-portals.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.addColumns('portals', {last_site_id: {type: 'int', default: 1}}) 3 | pgm.dropColumns('shared_buffers', ['last_site_id']) 4 | } 5 | -------------------------------------------------------------------------------- /migrations/1498094681465_sites.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.createTable('sites', { 3 | id: {type: 'int'}, 4 | portal_id: {type: 'uuid', references: 'portals'}, 5 | last_heartbeat_at: {type: 'timestamp'} 6 | }) 7 | pgm.sql(`ALTER TABLE sites ADD PRIMARY KEY (id, portal_id)`) 8 | pgm.createIndex('sites', 'portal_id') 9 | pgm.dropColumns('portals', 'last_site_id') 10 | } 11 | -------------------------------------------------------------------------------- /migrations/1499865233000_peer-to-peer.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.dropTable('sites') 3 | pgm.dropColumns('portals', ['active_shared_editor_id']) 4 | pgm.addColumns('portals', { 5 | host_peer_id: {type: 'string'} 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /migrations/1510143431963_create-events.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.createType('event_enum', ['create-portal', 'lookup-portal']) 3 | pgm.createTable('events', { 4 | id: {type: 'bigserial', primaryKey: true}, 5 | name: {type: 'event_enum'}, 6 | user_id: {type: 'string'}, 7 | portal_id: {type: 'uuid'}, 8 | created_at: {type: 'timestamp with time zone'} 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /migrations/1510307606770_alter-events-portal-id.js: -------------------------------------------------------------------------------- 1 | exports.up = (pgm) => { 2 | pgm.alterColumn('events', 'portal_id', {type: 'string'}) 3 | } 4 | -------------------------------------------------------------------------------- /newrelic.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | logging: { 3 | level: 'info' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@atom/teletype-server", 3 | "version": "0.18.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@newrelic/koa": { 8 | "version": "1.0.5", 9 | "resolved": "https://registry.npmjs.org/@newrelic/koa/-/koa-1.0.5.tgz", 10 | "integrity": "sha512-1zTojq9gW2mi0YblGrS86gCyL56+gbCn6o2+1UJJL3pFmBgp8IAMzZ93PkHHtdrbL3BnVMBrD2Q2WR32FbhIAg==", 11 | "requires": { 12 | "methods": "^1.1.2" 13 | } 14 | }, 15 | "@newrelic/native-metrics": { 16 | "version": "3.1.0", 17 | "resolved": "https://registry.npmjs.org/@newrelic/native-metrics/-/native-metrics-3.1.0.tgz", 18 | "integrity": "sha512-45OhFKiRT5L+hBzAkDkR5UTuHWIUNMjwlMlu65Y8HmyrTPtgUMKoqMmHERRX4aRqb/EEaW3oWYYLOpLnB6fiHg==", 19 | "optional": true, 20 | "requires": { 21 | "nan": "^2.10.0" 22 | } 23 | }, 24 | "@types/events": { 25 | "version": "1.2.0", 26 | "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", 27 | "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" 28 | }, 29 | "@types/node": { 30 | "version": "9.4.6", 31 | "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.6.tgz", 32 | "integrity": "sha512-CTUtLb6WqCCgp6P59QintjHWqzf4VL1uPA27bipLAPxFqrtK1gEYllePzTICGqQ8rYsCbpnsNypXjjDzGAAjEQ==" 33 | }, 34 | "@types/pg": { 35 | "version": "7.4.5", 36 | "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.4.5.tgz", 37 | "integrity": "sha512-DV9A1X9duAnZrF+ANT9i7Z3k+49Dfl96hJlmpz8KCZtBaB7ck3eaAX/37P/vOtpb1VBS5C7xfYI1oRnAfL71DQ==", 38 | "requires": { 39 | "@types/events": "*", 40 | "@types/node": "*", 41 | "@types/pg-types": "*" 42 | } 43 | }, 44 | "@types/pg-types": { 45 | "version": "1.11.4", 46 | "resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-1.11.4.tgz", 47 | "integrity": "sha512-WdIiQmE347LGc1Vq3Ki8sk3iyCuLgnccqVzgxek6gEHp2H0p3MQ3jniIHt+bRODXKju4kNQ+mp53lmP5+/9moQ==", 48 | "requires": { 49 | "moment": ">=2.14.0" 50 | } 51 | }, 52 | "@tyriar/fibonacci-heap": { 53 | "version": "2.0.7", 54 | "resolved": "https://registry.npmjs.org/@tyriar/fibonacci-heap/-/fibonacci-heap-2.0.7.tgz", 55 | "integrity": "sha512-DANf9u0VN5oWrRk31B+xCy9mMNx1H9YhWUaTzCzU0uBruj/zg8u9JSw5qpArntvfJxaW/gWGWbQtzpAkYO6VBg==" 56 | }, 57 | "accepts": { 58 | "version": "1.3.5", 59 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 60 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 61 | "requires": { 62 | "mime-types": "~2.1.18", 63 | "negotiator": "0.6.1" 64 | }, 65 | "dependencies": { 66 | "mime-db": { 67 | "version": "1.35.0", 68 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", 69 | "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" 70 | }, 71 | "mime-types": { 72 | "version": "2.1.19", 73 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", 74 | "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", 75 | "requires": { 76 | "mime-db": "~1.35.0" 77 | } 78 | } 79 | } 80 | }, 81 | "agent-base": { 82 | "version": "4.2.1", 83 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", 84 | "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", 85 | "requires": { 86 | "es6-promisify": "^5.0.0" 87 | } 88 | }, 89 | "ajv": { 90 | "version": "5.5.2", 91 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 92 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 93 | "requires": { 94 | "co": "^4.6.0", 95 | "fast-deep-equal": "^1.0.0", 96 | "fast-json-stable-stringify": "^2.0.0", 97 | "json-schema-traverse": "^0.3.0" 98 | } 99 | }, 100 | "ansi-regex": { 101 | "version": "2.1.1", 102 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 103 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 104 | }, 105 | "array-flatten": { 106 | "version": "1.1.1", 107 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 108 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 109 | }, 110 | "asap": { 111 | "version": "2.0.6", 112 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 113 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 114 | }, 115 | "asn1": { 116 | "version": "0.2.3", 117 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 118 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 119 | }, 120 | "assert-plus": { 121 | "version": "1.0.0", 122 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 123 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 124 | }, 125 | "async": { 126 | "version": "2.5.0", 127 | "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", 128 | "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", 129 | "requires": { 130 | "lodash": "^4.14.0" 131 | } 132 | }, 133 | "asynckit": { 134 | "version": "0.4.0", 135 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 136 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 137 | }, 138 | "aws-sign2": { 139 | "version": "0.7.0", 140 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 141 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 142 | }, 143 | "aws4": { 144 | "version": "1.6.0", 145 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 146 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 147 | }, 148 | "balanced-match": { 149 | "version": "1.0.0", 150 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 151 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 152 | "dev": true 153 | }, 154 | "bcrypt-pbkdf": { 155 | "version": "1.0.2", 156 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 157 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 158 | "optional": true, 159 | "requires": { 160 | "tweetnacl": "^0.14.3" 161 | } 162 | }, 163 | "body-parser": { 164 | "version": "1.18.3", 165 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 166 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 167 | "requires": { 168 | "bytes": "3.0.0", 169 | "content-type": "~1.0.4", 170 | "debug": "2.6.9", 171 | "depd": "~1.1.2", 172 | "http-errors": "~1.6.3", 173 | "iconv-lite": "0.4.23", 174 | "on-finished": "~2.3.0", 175 | "qs": "6.5.2", 176 | "raw-body": "2.3.3", 177 | "type-is": "~1.6.16" 178 | }, 179 | "dependencies": { 180 | "debug": { 181 | "version": "2.6.9", 182 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 183 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 184 | "requires": { 185 | "ms": "2.0.0" 186 | } 187 | }, 188 | "depd": { 189 | "version": "1.1.2", 190 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 191 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 192 | }, 193 | "http-errors": { 194 | "version": "1.6.3", 195 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 196 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 197 | "requires": { 198 | "depd": "~1.1.2", 199 | "inherits": "2.0.3", 200 | "setprototypeof": "1.1.0", 201 | "statuses": ">= 1.4.0 < 2" 202 | } 203 | }, 204 | "iconv-lite": { 205 | "version": "0.4.23", 206 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 207 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 208 | "requires": { 209 | "safer-buffer": ">= 2.1.2 < 3" 210 | } 211 | }, 212 | "qs": { 213 | "version": "6.5.2", 214 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 215 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 216 | }, 217 | "raw-body": { 218 | "version": "2.3.3", 219 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 220 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 221 | "requires": { 222 | "bytes": "3.0.0", 223 | "http-errors": "1.6.3", 224 | "iconv-lite": "0.4.23", 225 | "unpipe": "1.0.0" 226 | } 227 | }, 228 | "setprototypeof": { 229 | "version": "1.1.0", 230 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 231 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 232 | }, 233 | "statuses": { 234 | "version": "1.5.0", 235 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 236 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 237 | } 238 | } 239 | }, 240 | "boom": { 241 | "version": "4.3.1", 242 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 243 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", 244 | "requires": { 245 | "hoek": "4.x.x" 246 | } 247 | }, 248 | "brace-expansion": { 249 | "version": "1.1.11", 250 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 251 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 252 | "dev": true, 253 | "requires": { 254 | "balanced-match": "^1.0.0", 255 | "concat-map": "0.0.1" 256 | } 257 | }, 258 | "browser-stdout": { 259 | "version": "1.3.1", 260 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 261 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 262 | "dev": true 263 | }, 264 | "buffer-from": { 265 | "version": "1.1.0", 266 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", 267 | "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" 268 | }, 269 | "buffer-writer": { 270 | "version": "1.0.1", 271 | "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", 272 | "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" 273 | }, 274 | "bugsnag": { 275 | "version": "1.12.2", 276 | "resolved": "https://registry.npmjs.org/bugsnag/-/bugsnag-1.12.2.tgz", 277 | "integrity": "sha512-QLNXll+cMJGouLaDyszDKH77wCIWgvVvC+IxZK9GyI4hLlJLyTkWMOgNnA/Grk7s4+lWctK37gwuuhkAzmtn/g==", 278 | "requires": { 279 | "json-stringify-safe": "~5.0.1", 280 | "promise": "7.x", 281 | "request": "^2.81.0", 282 | "stack-trace": "~0.0.9" 283 | } 284 | }, 285 | "bytes": { 286 | "version": "3.0.0", 287 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 288 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 289 | }, 290 | "camelcase": { 291 | "version": "4.1.0", 292 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", 293 | "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" 294 | }, 295 | "caseless": { 296 | "version": "0.12.0", 297 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 298 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 299 | }, 300 | "cliui": { 301 | "version": "4.0.0", 302 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", 303 | "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", 304 | "requires": { 305 | "string-width": "^2.1.1", 306 | "strip-ansi": "^4.0.0", 307 | "wrap-ansi": "^2.0.0" 308 | }, 309 | "dependencies": { 310 | "ansi-regex": { 311 | "version": "3.0.0", 312 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 313 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 314 | }, 315 | "strip-ansi": { 316 | "version": "4.0.0", 317 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 318 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 319 | "requires": { 320 | "ansi-regex": "^3.0.0" 321 | } 322 | } 323 | } 324 | }, 325 | "co": { 326 | "version": "4.6.0", 327 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 328 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 329 | }, 330 | "code-point-at": { 331 | "version": "1.1.0", 332 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 333 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 334 | }, 335 | "combined-stream": { 336 | "version": "1.0.5", 337 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 338 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", 339 | "requires": { 340 | "delayed-stream": "~1.0.0" 341 | } 342 | }, 343 | "concat-map": { 344 | "version": "0.0.1", 345 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 346 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 347 | "dev": true 348 | }, 349 | "concat-stream": { 350 | "version": "1.6.2", 351 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 352 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 353 | "requires": { 354 | "buffer-from": "^1.0.0", 355 | "inherits": "^2.0.3", 356 | "readable-stream": "^2.2.2", 357 | "typedarray": "^0.0.6" 358 | }, 359 | "dependencies": { 360 | "process-nextick-args": { 361 | "version": "2.0.0", 362 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 363 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 364 | }, 365 | "readable-stream": { 366 | "version": "2.3.6", 367 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 368 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 369 | "requires": { 370 | "core-util-is": "~1.0.0", 371 | "inherits": "~2.0.3", 372 | "isarray": "~1.0.0", 373 | "process-nextick-args": "~2.0.0", 374 | "safe-buffer": "~5.1.1", 375 | "string_decoder": "~1.1.1", 376 | "util-deprecate": "~1.0.1" 377 | } 378 | }, 379 | "string_decoder": { 380 | "version": "1.1.1", 381 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 382 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 383 | "requires": { 384 | "safe-buffer": "~5.1.0" 385 | } 386 | } 387 | } 388 | }, 389 | "config": { 390 | "version": "1.30.0", 391 | "resolved": "https://registry.npmjs.org/config/-/config-1.30.0.tgz", 392 | "integrity": "sha1-HWCp81NIoTwXV5jThOgaWhbDum4=", 393 | "optional": true, 394 | "requires": { 395 | "json5": "0.4.0", 396 | "os-homedir": "1.0.2" 397 | } 398 | }, 399 | "content-disposition": { 400 | "version": "0.5.2", 401 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 402 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 403 | }, 404 | "content-type": { 405 | "version": "1.0.4", 406 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 407 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 408 | }, 409 | "cookie": { 410 | "version": "0.3.1", 411 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 412 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 413 | }, 414 | "cookie-signature": { 415 | "version": "1.0.6", 416 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 417 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 418 | }, 419 | "core-util-is": { 420 | "version": "1.0.2", 421 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 422 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 423 | }, 424 | "cors": { 425 | "version": "2.8.4", 426 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", 427 | "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", 428 | "requires": { 429 | "object-assign": "^4", 430 | "vary": "^1" 431 | } 432 | }, 433 | "cross-spawn": { 434 | "version": "5.1.0", 435 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", 436 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", 437 | "requires": { 438 | "lru-cache": "^4.0.1", 439 | "shebang-command": "^1.2.0", 440 | "which": "^1.2.9" 441 | } 442 | }, 443 | "cryptiles": { 444 | "version": "3.1.2", 445 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", 446 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", 447 | "requires": { 448 | "boom": "5.x.x" 449 | }, 450 | "dependencies": { 451 | "boom": { 452 | "version": "5.2.0", 453 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", 454 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", 455 | "requires": { 456 | "hoek": "4.x.x" 457 | } 458 | } 459 | } 460 | }, 461 | "dashdash": { 462 | "version": "1.14.1", 463 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 464 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 465 | "requires": { 466 | "assert-plus": "^1.0.0" 467 | }, 468 | "dependencies": { 469 | "assert-plus": { 470 | "version": "1.0.0", 471 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 472 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 473 | } 474 | } 475 | }, 476 | "debug": { 477 | "version": "3.1.0", 478 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 479 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 480 | "requires": { 481 | "ms": "2.0.0" 482 | } 483 | }, 484 | "decamelize": { 485 | "version": "1.2.0", 486 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 487 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 488 | }, 489 | "deep-equal": { 490 | "version": "1.0.1", 491 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 492 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 493 | "dev": true 494 | }, 495 | "delayed-stream": { 496 | "version": "1.0.0", 497 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 498 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 499 | }, 500 | "depd": { 501 | "version": "1.1.1", 502 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 503 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 504 | }, 505 | "destroy": { 506 | "version": "1.0.4", 507 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 508 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 509 | }, 510 | "diff": { 511 | "version": "3.5.0", 512 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 513 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 514 | "dev": true 515 | }, 516 | "dotenv": { 517 | "version": "4.0.0", 518 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", 519 | "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" 520 | }, 521 | "ecc-jsbn": { 522 | "version": "0.1.1", 523 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 524 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 525 | "optional": true, 526 | "requires": { 527 | "jsbn": "~0.1.0" 528 | } 529 | }, 530 | "ee-first": { 531 | "version": "1.1.1", 532 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 533 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 534 | }, 535 | "encodeurl": { 536 | "version": "1.0.2", 537 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 538 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 539 | }, 540 | "es6-promise": { 541 | "version": "4.2.4", 542 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", 543 | "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" 544 | }, 545 | "es6-promisify": { 546 | "version": "5.0.0", 547 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 548 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 549 | "requires": { 550 | "es6-promise": "^4.0.3" 551 | } 552 | }, 553 | "escape-html": { 554 | "version": "1.0.3", 555 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 556 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 557 | }, 558 | "escape-string-regexp": { 559 | "version": "1.0.5", 560 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 561 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 562 | "dev": true 563 | }, 564 | "etag": { 565 | "version": "1.8.1", 566 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 567 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 568 | }, 569 | "execa": { 570 | "version": "0.7.0", 571 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", 572 | "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", 573 | "requires": { 574 | "cross-spawn": "^5.0.1", 575 | "get-stream": "^3.0.0", 576 | "is-stream": "^1.1.0", 577 | "npm-run-path": "^2.0.0", 578 | "p-finally": "^1.0.0", 579 | "signal-exit": "^3.0.0", 580 | "strip-eof": "^1.0.0" 581 | } 582 | }, 583 | "express": { 584 | "version": "4.16.3", 585 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", 586 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 587 | "requires": { 588 | "accepts": "~1.3.5", 589 | "array-flatten": "1.1.1", 590 | "body-parser": "1.18.2", 591 | "content-disposition": "0.5.2", 592 | "content-type": "~1.0.4", 593 | "cookie": "0.3.1", 594 | "cookie-signature": "1.0.6", 595 | "debug": "2.6.9", 596 | "depd": "~1.1.2", 597 | "encodeurl": "~1.0.2", 598 | "escape-html": "~1.0.3", 599 | "etag": "~1.8.1", 600 | "finalhandler": "1.1.1", 601 | "fresh": "0.5.2", 602 | "merge-descriptors": "1.0.1", 603 | "methods": "~1.1.2", 604 | "on-finished": "~2.3.0", 605 | "parseurl": "~1.3.2", 606 | "path-to-regexp": "0.1.7", 607 | "proxy-addr": "~2.0.3", 608 | "qs": "6.5.1", 609 | "range-parser": "~1.2.0", 610 | "safe-buffer": "5.1.1", 611 | "send": "0.16.2", 612 | "serve-static": "1.13.2", 613 | "setprototypeof": "1.1.0", 614 | "statuses": "~1.4.0", 615 | "type-is": "~1.6.16", 616 | "utils-merge": "1.0.1", 617 | "vary": "~1.1.2" 618 | }, 619 | "dependencies": { 620 | "body-parser": { 621 | "version": "1.18.2", 622 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 623 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 624 | "requires": { 625 | "bytes": "3.0.0", 626 | "content-type": "~1.0.4", 627 | "debug": "2.6.9", 628 | "depd": "~1.1.1", 629 | "http-errors": "~1.6.2", 630 | "iconv-lite": "0.4.19", 631 | "on-finished": "~2.3.0", 632 | "qs": "6.5.1", 633 | "raw-body": "2.3.2", 634 | "type-is": "~1.6.15" 635 | } 636 | }, 637 | "debug": { 638 | "version": "2.6.9", 639 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 640 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 641 | "requires": { 642 | "ms": "2.0.0" 643 | } 644 | }, 645 | "depd": { 646 | "version": "1.1.2", 647 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 648 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 649 | }, 650 | "mime-db": { 651 | "version": "1.35.0", 652 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", 653 | "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" 654 | }, 655 | "mime-types": { 656 | "version": "2.1.19", 657 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", 658 | "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", 659 | "requires": { 660 | "mime-db": "~1.35.0" 661 | } 662 | }, 663 | "setprototypeof": { 664 | "version": "1.1.0", 665 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 666 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 667 | }, 668 | "statuses": { 669 | "version": "1.4.0", 670 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 671 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 672 | }, 673 | "type-is": { 674 | "version": "1.6.16", 675 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 676 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 677 | "requires": { 678 | "media-typer": "0.3.0", 679 | "mime-types": "~2.1.18" 680 | } 681 | }, 682 | "vary": { 683 | "version": "1.1.2", 684 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 685 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 686 | } 687 | } 688 | }, 689 | "extend": { 690 | "version": "3.0.1", 691 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 692 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 693 | }, 694 | "extsprintf": { 695 | "version": "1.3.0", 696 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 697 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 698 | }, 699 | "fast-deep-equal": { 700 | "version": "1.1.0", 701 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 702 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 703 | }, 704 | "fast-json-stable-stringify": { 705 | "version": "2.0.0", 706 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 707 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 708 | }, 709 | "finalhandler": { 710 | "version": "1.1.1", 711 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 712 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 713 | "requires": { 714 | "debug": "2.6.9", 715 | "encodeurl": "~1.0.2", 716 | "escape-html": "~1.0.3", 717 | "on-finished": "~2.3.0", 718 | "parseurl": "~1.3.2", 719 | "statuses": "~1.4.0", 720 | "unpipe": "~1.0.0" 721 | }, 722 | "dependencies": { 723 | "debug": { 724 | "version": "2.6.9", 725 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 726 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 727 | "requires": { 728 | "ms": "2.0.0" 729 | } 730 | }, 731 | "statuses": { 732 | "version": "1.4.0", 733 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 734 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 735 | } 736 | } 737 | }, 738 | "find-up": { 739 | "version": "2.1.0", 740 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", 741 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", 742 | "requires": { 743 | "locate-path": "^2.0.0" 744 | } 745 | }, 746 | "forever-agent": { 747 | "version": "0.6.1", 748 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 749 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 750 | }, 751 | "form-data": { 752 | "version": "2.3.2", 753 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 754 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 755 | "requires": { 756 | "asynckit": "^0.4.0", 757 | "combined-stream": "1.0.6", 758 | "mime-types": "^2.1.12" 759 | }, 760 | "dependencies": { 761 | "combined-stream": { 762 | "version": "1.0.6", 763 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 764 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 765 | "requires": { 766 | "delayed-stream": "~1.0.0" 767 | } 768 | } 769 | } 770 | }, 771 | "forwarded": { 772 | "version": "0.1.2", 773 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 774 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 775 | }, 776 | "fresh": { 777 | "version": "0.5.2", 778 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 779 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 780 | }, 781 | "fs.realpath": { 782 | "version": "1.0.0", 783 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 784 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 785 | "dev": true 786 | }, 787 | "get-caller-file": { 788 | "version": "1.0.2", 789 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", 790 | "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" 791 | }, 792 | "get-stream": { 793 | "version": "3.0.0", 794 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", 795 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" 796 | }, 797 | "getpass": { 798 | "version": "0.1.7", 799 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 800 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 801 | "requires": { 802 | "assert-plus": "^1.0.0" 803 | }, 804 | "dependencies": { 805 | "assert-plus": { 806 | "version": "1.0.0", 807 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 808 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 809 | } 810 | } 811 | }, 812 | "glob": { 813 | "version": "7.1.2", 814 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 815 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 816 | "dev": true, 817 | "requires": { 818 | "fs.realpath": "^1.0.0", 819 | "inflight": "^1.0.4", 820 | "inherits": "2", 821 | "minimatch": "^3.0.4", 822 | "once": "^1.3.0", 823 | "path-is-absolute": "^1.0.0" 824 | } 825 | }, 826 | "growl": { 827 | "version": "1.10.5", 828 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 829 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 830 | "dev": true 831 | }, 832 | "har-schema": { 833 | "version": "2.0.0", 834 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 835 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 836 | }, 837 | "har-validator": { 838 | "version": "5.0.3", 839 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 840 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 841 | "requires": { 842 | "ajv": "^5.1.0", 843 | "har-schema": "^2.0.0" 844 | } 845 | }, 846 | "has-flag": { 847 | "version": "3.0.0", 848 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 849 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 850 | "dev": true 851 | }, 852 | "hawk": { 853 | "version": "6.0.2", 854 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", 855 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", 856 | "requires": { 857 | "boom": "4.x.x", 858 | "cryptiles": "3.x.x", 859 | "hoek": "4.x.x", 860 | "sntp": "2.x.x" 861 | } 862 | }, 863 | "he": { 864 | "version": "1.1.1", 865 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 866 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 867 | "dev": true 868 | }, 869 | "hoek": { 870 | "version": "4.2.1", 871 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 872 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 873 | }, 874 | "http-errors": { 875 | "version": "1.6.2", 876 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 877 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 878 | "requires": { 879 | "depd": "1.1.1", 880 | "inherits": "2.0.3", 881 | "setprototypeof": "1.0.3", 882 | "statuses": ">= 1.3.1 < 2" 883 | } 884 | }, 885 | "http-signature": { 886 | "version": "1.2.0", 887 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 888 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 889 | "requires": { 890 | "assert-plus": "^1.0.0", 891 | "jsprim": "^1.2.2", 892 | "sshpk": "^1.7.0" 893 | } 894 | }, 895 | "https-proxy-agent": { 896 | "version": "2.2.1", 897 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", 898 | "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", 899 | "requires": { 900 | "agent-base": "^4.1.0", 901 | "debug": "^3.1.0" 902 | } 903 | }, 904 | "iconv-lite": { 905 | "version": "0.4.19", 906 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 907 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 908 | }, 909 | "inflight": { 910 | "version": "1.0.6", 911 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 912 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 913 | "dev": true, 914 | "requires": { 915 | "once": "^1.3.0", 916 | "wrappy": "1" 917 | } 918 | }, 919 | "inherits": { 920 | "version": "2.0.3", 921 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 922 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 923 | }, 924 | "invert-kv": { 925 | "version": "1.0.0", 926 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", 927 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" 928 | }, 929 | "ipaddr.js": { 930 | "version": "1.6.0", 931 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 932 | "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 933 | }, 934 | "is-fullwidth-code-point": { 935 | "version": "2.0.0", 936 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 937 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 938 | }, 939 | "is-stream": { 940 | "version": "1.1.0", 941 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 942 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 943 | }, 944 | "is-typedarray": { 945 | "version": "1.0.0", 946 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 947 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 948 | }, 949 | "isarray": { 950 | "version": "1.0.0", 951 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 952 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 953 | }, 954 | "isexe": { 955 | "version": "2.0.0", 956 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 957 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 958 | }, 959 | "isstream": { 960 | "version": "0.1.2", 961 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 962 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 963 | }, 964 | "js-string-escape": { 965 | "version": "1.0.1", 966 | "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", 967 | "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" 968 | }, 969 | "jsbn": { 970 | "version": "0.1.1", 971 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 972 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 973 | "optional": true 974 | }, 975 | "json-schema": { 976 | "version": "0.2.3", 977 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 978 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 979 | }, 980 | "json-schema-traverse": { 981 | "version": "0.3.1", 982 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 983 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 984 | }, 985 | "json-stringify-safe": { 986 | "version": "5.0.1", 987 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 988 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 989 | }, 990 | "json5": { 991 | "version": "0.4.0", 992 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", 993 | "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=", 994 | "optional": true 995 | }, 996 | "jsprim": { 997 | "version": "1.4.1", 998 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 999 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 1000 | "requires": { 1001 | "assert-plus": "1.0.0", 1002 | "extsprintf": "1.3.0", 1003 | "json-schema": "0.2.3", 1004 | "verror": "1.10.0" 1005 | }, 1006 | "dependencies": { 1007 | "assert-plus": { 1008 | "version": "1.0.0", 1009 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 1010 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 1011 | } 1012 | } 1013 | }, 1014 | "lcid": { 1015 | "version": "1.0.0", 1016 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", 1017 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", 1018 | "requires": { 1019 | "invert-kv": "^1.0.0" 1020 | } 1021 | }, 1022 | "locate-path": { 1023 | "version": "2.0.0", 1024 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", 1025 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", 1026 | "requires": { 1027 | "p-locate": "^2.0.0", 1028 | "path-exists": "^3.0.0" 1029 | } 1030 | }, 1031 | "lodash": { 1032 | "version": "4.17.10", 1033 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 1034 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 1035 | }, 1036 | "lodash.defaults": { 1037 | "version": "4.2.0", 1038 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 1039 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" 1040 | }, 1041 | "lru-cache": { 1042 | "version": "4.1.1", 1043 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", 1044 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", 1045 | "requires": { 1046 | "pseudomap": "^1.0.2", 1047 | "yallist": "^2.1.2" 1048 | } 1049 | }, 1050 | "manakin": { 1051 | "version": "0.5.1", 1052 | "resolved": "https://registry.npmjs.org/manakin/-/manakin-0.5.1.tgz", 1053 | "integrity": "sha1-xKcRb2sA3z1fGjetPKUV0iBlplg=" 1054 | }, 1055 | "media-typer": { 1056 | "version": "0.3.0", 1057 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1058 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 1059 | }, 1060 | "mem": { 1061 | "version": "1.1.0", 1062 | "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", 1063 | "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", 1064 | "requires": { 1065 | "mimic-fn": "^1.0.0" 1066 | } 1067 | }, 1068 | "merge-descriptors": { 1069 | "version": "1.0.1", 1070 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1071 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 1072 | }, 1073 | "methods": { 1074 | "version": "1.1.2", 1075 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1076 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 1077 | }, 1078 | "mime": { 1079 | "version": "1.4.1", 1080 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 1081 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 1082 | }, 1083 | "mime-db": { 1084 | "version": "1.30.0", 1085 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 1086 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 1087 | }, 1088 | "mime-types": { 1089 | "version": "2.1.17", 1090 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 1091 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 1092 | "requires": { 1093 | "mime-db": "~1.30.0" 1094 | } 1095 | }, 1096 | "mimic-fn": { 1097 | "version": "1.2.0", 1098 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 1099 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" 1100 | }, 1101 | "minimatch": { 1102 | "version": "3.0.4", 1103 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1104 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1105 | "dev": true, 1106 | "requires": { 1107 | "brace-expansion": "^1.1.7" 1108 | } 1109 | }, 1110 | "minimist": { 1111 | "version": "0.0.8", 1112 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1113 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 1114 | }, 1115 | "mkdirp": { 1116 | "version": "0.5.1", 1117 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1118 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1119 | "requires": { 1120 | "minimist": "0.0.8" 1121 | } 1122 | }, 1123 | "mocha": { 1124 | "version": "5.2.0", 1125 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 1126 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 1127 | "dev": true, 1128 | "requires": { 1129 | "browser-stdout": "1.3.1", 1130 | "commander": "2.15.1", 1131 | "debug": "3.1.0", 1132 | "diff": "3.5.0", 1133 | "escape-string-regexp": "1.0.5", 1134 | "glob": "7.1.2", 1135 | "growl": "1.10.5", 1136 | "he": "1.1.1", 1137 | "minimatch": "3.0.4", 1138 | "mkdirp": "0.5.1", 1139 | "supports-color": "5.4.0" 1140 | }, 1141 | "dependencies": { 1142 | "commander": { 1143 | "version": "2.15.1", 1144 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 1145 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 1146 | "dev": true 1147 | }, 1148 | "debug": { 1149 | "version": "3.1.0", 1150 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 1151 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 1152 | "dev": true, 1153 | "requires": { 1154 | "ms": "2.0.0" 1155 | } 1156 | }, 1157 | "supports-color": { 1158 | "version": "5.4.0", 1159 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 1160 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 1161 | "dev": true, 1162 | "requires": { 1163 | "has-flag": "^3.0.0" 1164 | } 1165 | } 1166 | } 1167 | }, 1168 | "moment": { 1169 | "version": "2.21.0", 1170 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", 1171 | "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" 1172 | }, 1173 | "ms": { 1174 | "version": "2.0.0", 1175 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1176 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1177 | }, 1178 | "nan": { 1179 | "version": "2.10.0", 1180 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", 1181 | "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", 1182 | "optional": true 1183 | }, 1184 | "negotiator": { 1185 | "version": "0.6.1", 1186 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 1187 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 1188 | }, 1189 | "newrelic": { 1190 | "version": "4.6.0", 1191 | "resolved": "https://registry.npmjs.org/newrelic/-/newrelic-4.6.0.tgz", 1192 | "integrity": "sha512-F649bL3HAW3SCBblWk9XnB0g8D5SKso24ftBkf5Pi8YvMneP7UUIEOHyZ4JuqIi4UGYPN1YxQYEBvxpovQNzfA==", 1193 | "requires": { 1194 | "@newrelic/koa": "^1.0.0", 1195 | "@newrelic/native-metrics": "^3.0.0", 1196 | "@tyriar/fibonacci-heap": "^2.0.7", 1197 | "async": "^2.1.4", 1198 | "concat-stream": "^1.5.0", 1199 | "https-proxy-agent": "^2.2.1", 1200 | "json-stringify-safe": "^5.0.0", 1201 | "readable-stream": "^2.1.4", 1202 | "semver": "^5.3.0" 1203 | }, 1204 | "dependencies": { 1205 | "process-nextick-args": { 1206 | "version": "2.0.0", 1207 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 1208 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 1209 | }, 1210 | "readable-stream": { 1211 | "version": "2.3.6", 1212 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 1213 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 1214 | "requires": { 1215 | "core-util-is": "~1.0.0", 1216 | "inherits": "~2.0.3", 1217 | "isarray": "~1.0.0", 1218 | "process-nextick-args": "~2.0.0", 1219 | "safe-buffer": "~5.1.1", 1220 | "string_decoder": "~1.1.1", 1221 | "util-deprecate": "~1.0.1" 1222 | } 1223 | }, 1224 | "string_decoder": { 1225 | "version": "1.1.1", 1226 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1227 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1228 | "requires": { 1229 | "safe-buffer": "~5.1.0" 1230 | } 1231 | } 1232 | } 1233 | }, 1234 | "node-pg-migrate": { 1235 | "version": "2.25.0", 1236 | "resolved": "https://registry.npmjs.org/node-pg-migrate/-/node-pg-migrate-2.25.0.tgz", 1237 | "integrity": "sha512-LCu/DjH6YQSZXwjZtk0VVrS9BxJc1jlS+EUilz7JHp65l2iKF6OH6CegVLkvUj2Kg12zh8t0OdQBGKZ7ClH2BQ==", 1238 | "requires": { 1239 | "@types/pg": "^7.4.5", 1240 | "config": ">=1.0.0", 1241 | "dotenv": ">=1.0.0", 1242 | "lodash": "~4.17.0", 1243 | "mkdirp": "~0.5.1", 1244 | "yargs": "~11.0.0" 1245 | } 1246 | }, 1247 | "npm-run-path": { 1248 | "version": "2.0.2", 1249 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 1250 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 1251 | "requires": { 1252 | "path-key": "^2.0.0" 1253 | } 1254 | }, 1255 | "number-is-nan": { 1256 | "version": "1.0.1", 1257 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1258 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1259 | }, 1260 | "oauth-sign": { 1261 | "version": "0.8.2", 1262 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 1263 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 1264 | }, 1265 | "object-assign": { 1266 | "version": "4.1.1", 1267 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1268 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1269 | }, 1270 | "on-finished": { 1271 | "version": "2.3.0", 1272 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1273 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1274 | "requires": { 1275 | "ee-first": "1.1.1" 1276 | } 1277 | }, 1278 | "once": { 1279 | "version": "1.4.0", 1280 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1281 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1282 | "dev": true, 1283 | "requires": { 1284 | "wrappy": "1" 1285 | } 1286 | }, 1287 | "os-homedir": { 1288 | "version": "1.0.2", 1289 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1290 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 1291 | "optional": true 1292 | }, 1293 | "os-locale": { 1294 | "version": "2.1.0", 1295 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", 1296 | "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", 1297 | "requires": { 1298 | "execa": "^0.7.0", 1299 | "lcid": "^1.0.0", 1300 | "mem": "^1.1.0" 1301 | } 1302 | }, 1303 | "os-tmpdir": { 1304 | "version": "1.0.2", 1305 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1306 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1307 | }, 1308 | "p-finally": { 1309 | "version": "1.0.0", 1310 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 1311 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" 1312 | }, 1313 | "p-limit": { 1314 | "version": "1.2.0", 1315 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", 1316 | "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", 1317 | "requires": { 1318 | "p-try": "^1.0.0" 1319 | } 1320 | }, 1321 | "p-locate": { 1322 | "version": "2.0.0", 1323 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", 1324 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", 1325 | "requires": { 1326 | "p-limit": "^1.1.0" 1327 | } 1328 | }, 1329 | "p-try": { 1330 | "version": "1.0.0", 1331 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", 1332 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" 1333 | }, 1334 | "packet-reader": { 1335 | "version": "0.3.1", 1336 | "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", 1337 | "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" 1338 | }, 1339 | "parseurl": { 1340 | "version": "1.3.2", 1341 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 1342 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 1343 | }, 1344 | "path-exists": { 1345 | "version": "3.0.0", 1346 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 1347 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" 1348 | }, 1349 | "path-is-absolute": { 1350 | "version": "1.0.1", 1351 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1352 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1353 | "dev": true 1354 | }, 1355 | "path-key": { 1356 | "version": "2.0.1", 1357 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1358 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 1359 | }, 1360 | "path-to-regexp": { 1361 | "version": "0.1.7", 1362 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1363 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1364 | }, 1365 | "performance-now": { 1366 | "version": "2.1.0", 1367 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1368 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 1369 | }, 1370 | "pg": { 1371 | "version": "7.4.1", 1372 | "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", 1373 | "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", 1374 | "requires": { 1375 | "buffer-writer": "1.0.1", 1376 | "js-string-escape": "1.0.1", 1377 | "packet-reader": "0.3.1", 1378 | "pg-connection-string": "0.1.3", 1379 | "pg-pool": "~2.0.3", 1380 | "pg-types": "~1.12.1", 1381 | "pgpass": "1.x", 1382 | "semver": "4.3.2" 1383 | }, 1384 | "dependencies": { 1385 | "semver": { 1386 | "version": "4.3.2", 1387 | "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", 1388 | "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" 1389 | } 1390 | } 1391 | }, 1392 | "pg-connection-string": { 1393 | "version": "0.1.3", 1394 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", 1395 | "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" 1396 | }, 1397 | "pg-minify": { 1398 | "version": "0.5.4", 1399 | "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-0.5.4.tgz", 1400 | "integrity": "sha512-GHB2v4OiMHDgwiHH86ZWNfvgEPVijrnfuWLQocseX6Zlf30k+x0imA65zBy4skIpEwfBBEplIEEKP4n3q9KkVA==" 1401 | }, 1402 | "pg-pool": { 1403 | "version": "2.0.3", 1404 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", 1405 | "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" 1406 | }, 1407 | "pg-promise": { 1408 | "version": "8.1.1", 1409 | "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-8.1.1.tgz", 1410 | "integrity": "sha512-+iyIcRXFunFB96uoUPAwqADm2KfAE0ur5iRtZjl8/pOM3g0IgOEtarXNiStf1G1ToiFoXhGoLiaUg11s3wINQg==", 1411 | "requires": { 1412 | "manakin": "~0.5.1", 1413 | "pg": "~7.4.1", 1414 | "pg-minify": "~0.5.4", 1415 | "spex": "~2.0.2" 1416 | } 1417 | }, 1418 | "pg-types": { 1419 | "version": "1.12.1", 1420 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", 1421 | "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", 1422 | "requires": { 1423 | "postgres-array": "~1.0.0", 1424 | "postgres-bytea": "~1.0.0", 1425 | "postgres-date": "~1.0.0", 1426 | "postgres-interval": "^1.1.0" 1427 | } 1428 | }, 1429 | "pgpass": { 1430 | "version": "1.0.2", 1431 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", 1432 | "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", 1433 | "requires": { 1434 | "split": "^1.0.0" 1435 | } 1436 | }, 1437 | "postgres-array": { 1438 | "version": "1.0.2", 1439 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", 1440 | "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" 1441 | }, 1442 | "postgres-bytea": { 1443 | "version": "1.0.0", 1444 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", 1445 | "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" 1446 | }, 1447 | "postgres-date": { 1448 | "version": "1.0.3", 1449 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", 1450 | "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" 1451 | }, 1452 | "postgres-interval": { 1453 | "version": "1.1.1", 1454 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", 1455 | "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", 1456 | "requires": { 1457 | "xtend": "^4.0.0" 1458 | } 1459 | }, 1460 | "promise": { 1461 | "version": "7.3.1", 1462 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 1463 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 1464 | "requires": { 1465 | "asap": "~2.0.3" 1466 | } 1467 | }, 1468 | "proxy-addr": { 1469 | "version": "2.0.3", 1470 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", 1471 | "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", 1472 | "requires": { 1473 | "forwarded": "~0.1.2", 1474 | "ipaddr.js": "1.6.0" 1475 | } 1476 | }, 1477 | "pseudomap": { 1478 | "version": "1.0.2", 1479 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 1480 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 1481 | }, 1482 | "psl": { 1483 | "version": "1.1.28", 1484 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.28.tgz", 1485 | "integrity": "sha512-+AqO1Ae+N/4r7Rvchrdm432afjT9hqJRyBN3DQv9At0tPz4hIFSGKbq64fN9dVoCow4oggIIax5/iONx0r9hZw==" 1486 | }, 1487 | "punycode": { 1488 | "version": "1.4.1", 1489 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1490 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 1491 | }, 1492 | "pusher": { 1493 | "version": "2.0.1", 1494 | "resolved": "https://registry.npmjs.org/pusher/-/pusher-2.0.1.tgz", 1495 | "integrity": "sha512-mvVt0OToaiUiRD2AGqitCLN2ca3I8JtPBTwnbAgIoLkA6TZ1UwYisKqRJ3dOYXsbk01oW+ghhsGX8YQ0nJh4Iw==", 1496 | "requires": { 1497 | "request": "2.85.0" 1498 | }, 1499 | "dependencies": { 1500 | "request": { 1501 | "version": "2.85.0", 1502 | "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", 1503 | "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", 1504 | "requires": { 1505 | "aws-sign2": "~0.7.0", 1506 | "aws4": "^1.6.0", 1507 | "caseless": "~0.12.0", 1508 | "combined-stream": "~1.0.5", 1509 | "extend": "~3.0.1", 1510 | "forever-agent": "~0.6.1", 1511 | "form-data": "~2.3.1", 1512 | "har-validator": "~5.0.3", 1513 | "hawk": "~6.0.2", 1514 | "http-signature": "~1.2.0", 1515 | "is-typedarray": "~1.0.0", 1516 | "isstream": "~0.1.2", 1517 | "json-stringify-safe": "~5.0.1", 1518 | "mime-types": "~2.1.17", 1519 | "oauth-sign": "~0.8.2", 1520 | "performance-now": "^2.1.0", 1521 | "qs": "~6.5.1", 1522 | "safe-buffer": "^5.1.1", 1523 | "stringstream": "~0.0.5", 1524 | "tough-cookie": "~2.3.3", 1525 | "tunnel-agent": "^0.6.0", 1526 | "uuid": "^3.1.0" 1527 | } 1528 | } 1529 | } 1530 | }, 1531 | "qs": { 1532 | "version": "6.5.1", 1533 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 1534 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 1535 | }, 1536 | "range-parser": { 1537 | "version": "1.2.0", 1538 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1539 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1540 | }, 1541 | "raw-body": { 1542 | "version": "2.3.2", 1543 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 1544 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 1545 | "requires": { 1546 | "bytes": "3.0.0", 1547 | "http-errors": "1.6.2", 1548 | "iconv-lite": "0.4.19", 1549 | "unpipe": "1.0.0" 1550 | } 1551 | }, 1552 | "request": { 1553 | "version": "2.87.0", 1554 | "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", 1555 | "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", 1556 | "requires": { 1557 | "aws-sign2": "~0.7.0", 1558 | "aws4": "^1.6.0", 1559 | "caseless": "~0.12.0", 1560 | "combined-stream": "~1.0.5", 1561 | "extend": "~3.0.1", 1562 | "forever-agent": "~0.6.1", 1563 | "form-data": "~2.3.1", 1564 | "har-validator": "~5.0.3", 1565 | "http-signature": "~1.2.0", 1566 | "is-typedarray": "~1.0.0", 1567 | "isstream": "~0.1.2", 1568 | "json-stringify-safe": "~5.0.1", 1569 | "mime-types": "~2.1.17", 1570 | "oauth-sign": "~0.8.2", 1571 | "performance-now": "^2.1.0", 1572 | "qs": "~6.5.1", 1573 | "safe-buffer": "^5.1.1", 1574 | "tough-cookie": "~2.3.3", 1575 | "tunnel-agent": "^0.6.0", 1576 | "uuid": "^3.1.0" 1577 | }, 1578 | "dependencies": { 1579 | "assert-plus": { 1580 | "version": "1.0.0", 1581 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 1582 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 1583 | }, 1584 | "aws-sign2": { 1585 | "version": "0.7.0", 1586 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 1587 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 1588 | }, 1589 | "caseless": { 1590 | "version": "0.12.0", 1591 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 1592 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 1593 | }, 1594 | "form-data": { 1595 | "version": "2.3.2", 1596 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 1597 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 1598 | "requires": { 1599 | "asynckit": "^0.4.0", 1600 | "combined-stream": "1.0.6", 1601 | "mime-types": "^2.1.12" 1602 | }, 1603 | "dependencies": { 1604 | "combined-stream": { 1605 | "version": "1.0.6", 1606 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 1607 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 1608 | "requires": { 1609 | "delayed-stream": "~1.0.0" 1610 | } 1611 | } 1612 | } 1613 | }, 1614 | "har-validator": { 1615 | "version": "5.0.3", 1616 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 1617 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 1618 | "requires": { 1619 | "ajv": "^5.1.0", 1620 | "har-schema": "^2.0.0" 1621 | } 1622 | }, 1623 | "http-signature": { 1624 | "version": "1.2.0", 1625 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 1626 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 1627 | "requires": { 1628 | "assert-plus": "^1.0.0", 1629 | "jsprim": "^1.2.2", 1630 | "sshpk": "^1.7.0" 1631 | } 1632 | }, 1633 | "tough-cookie": { 1634 | "version": "2.3.4", 1635 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", 1636 | "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", 1637 | "requires": { 1638 | "punycode": "^1.4.1" 1639 | } 1640 | }, 1641 | "tunnel-agent": { 1642 | "version": "0.6.0", 1643 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1644 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1645 | "requires": { 1646 | "safe-buffer": "^5.0.1" 1647 | } 1648 | } 1649 | } 1650 | }, 1651 | "request-promise-core": { 1652 | "version": "1.1.1", 1653 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 1654 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", 1655 | "requires": { 1656 | "lodash": "^4.13.1" 1657 | } 1658 | }, 1659 | "request-promise-native": { 1660 | "version": "1.0.5", 1661 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", 1662 | "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", 1663 | "requires": { 1664 | "request-promise-core": "1.1.1", 1665 | "stealthy-require": "^1.1.0", 1666 | "tough-cookie": ">=2.3.3" 1667 | }, 1668 | "dependencies": { 1669 | "tough-cookie": { 1670 | "version": "2.4.3", 1671 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 1672 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 1673 | "requires": { 1674 | "psl": "^1.1.24", 1675 | "punycode": "^1.4.1" 1676 | } 1677 | } 1678 | } 1679 | }, 1680 | "require-directory": { 1681 | "version": "2.1.1", 1682 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1683 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 1684 | }, 1685 | "require-main-filename": { 1686 | "version": "1.0.1", 1687 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 1688 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" 1689 | }, 1690 | "rimraf": { 1691 | "version": "2.2.8", 1692 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", 1693 | "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" 1694 | }, 1695 | "safe-buffer": { 1696 | "version": "5.1.1", 1697 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1698 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 1699 | }, 1700 | "safer-buffer": { 1701 | "version": "2.1.2", 1702 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1703 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1704 | }, 1705 | "semver": { 1706 | "version": "5.5.0", 1707 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 1708 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" 1709 | }, 1710 | "send": { 1711 | "version": "0.16.2", 1712 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 1713 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 1714 | "requires": { 1715 | "debug": "2.6.9", 1716 | "depd": "~1.1.2", 1717 | "destroy": "~1.0.4", 1718 | "encodeurl": "~1.0.2", 1719 | "escape-html": "~1.0.3", 1720 | "etag": "~1.8.1", 1721 | "fresh": "0.5.2", 1722 | "http-errors": "~1.6.2", 1723 | "mime": "1.4.1", 1724 | "ms": "2.0.0", 1725 | "on-finished": "~2.3.0", 1726 | "range-parser": "~1.2.0", 1727 | "statuses": "~1.4.0" 1728 | }, 1729 | "dependencies": { 1730 | "debug": { 1731 | "version": "2.6.9", 1732 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1733 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1734 | "requires": { 1735 | "ms": "2.0.0" 1736 | } 1737 | }, 1738 | "depd": { 1739 | "version": "1.1.2", 1740 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1741 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 1742 | }, 1743 | "statuses": { 1744 | "version": "1.4.0", 1745 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 1746 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 1747 | } 1748 | } 1749 | }, 1750 | "serve-static": { 1751 | "version": "1.13.2", 1752 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 1753 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 1754 | "requires": { 1755 | "encodeurl": "~1.0.2", 1756 | "escape-html": "~1.0.3", 1757 | "parseurl": "~1.3.2", 1758 | "send": "0.16.2" 1759 | } 1760 | }, 1761 | "server-destroy": { 1762 | "version": "1.0.1", 1763 | "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", 1764 | "integrity": "sha1-8Tv5KOQrnD55OD5hzDmYtdFObN0=" 1765 | }, 1766 | "set-blocking": { 1767 | "version": "2.0.0", 1768 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1769 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1770 | }, 1771 | "setprototypeof": { 1772 | "version": "1.0.3", 1773 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1774 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 1775 | }, 1776 | "shebang-command": { 1777 | "version": "1.2.0", 1778 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1779 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1780 | "requires": { 1781 | "shebang-regex": "^1.0.0" 1782 | } 1783 | }, 1784 | "shebang-regex": { 1785 | "version": "1.0.0", 1786 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1787 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 1788 | }, 1789 | "signal-exit": { 1790 | "version": "3.0.2", 1791 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1792 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 1793 | }, 1794 | "sntp": { 1795 | "version": "2.1.0", 1796 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", 1797 | "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", 1798 | "requires": { 1799 | "hoek": "4.x.x" 1800 | } 1801 | }, 1802 | "spex": { 1803 | "version": "2.0.2", 1804 | "resolved": "https://registry.npmjs.org/spex/-/spex-2.0.2.tgz", 1805 | "integrity": "sha512-LU6TS3qTEpRth+FnNs/fIWEmridYN7JmaN2k1Jk31XVC4ex7+wYxiHMnKguRxS7oKjbOFl4H6seeWNDFFgkVRg==" 1806 | }, 1807 | "split": { 1808 | "version": "1.0.1", 1809 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", 1810 | "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", 1811 | "requires": { 1812 | "through": "2" 1813 | } 1814 | }, 1815 | "sshpk": { 1816 | "version": "1.14.2", 1817 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", 1818 | "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", 1819 | "requires": { 1820 | "asn1": "~0.2.3", 1821 | "assert-plus": "^1.0.0", 1822 | "bcrypt-pbkdf": "^1.0.0", 1823 | "dashdash": "^1.12.0", 1824 | "ecc-jsbn": "~0.1.1", 1825 | "getpass": "^0.1.1", 1826 | "jsbn": "~0.1.0", 1827 | "safer-buffer": "^2.0.2", 1828 | "tweetnacl": "~0.14.0" 1829 | }, 1830 | "dependencies": { 1831 | "assert-plus": { 1832 | "version": "1.0.0", 1833 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 1834 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 1835 | } 1836 | } 1837 | }, 1838 | "stack-trace": { 1839 | "version": "0.0.10", 1840 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 1841 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 1842 | }, 1843 | "statuses": { 1844 | "version": "1.3.1", 1845 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 1846 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 1847 | }, 1848 | "stealthy-require": { 1849 | "version": "1.1.1", 1850 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 1851 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 1852 | }, 1853 | "string-width": { 1854 | "version": "2.1.1", 1855 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1856 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1857 | "requires": { 1858 | "is-fullwidth-code-point": "^2.0.0", 1859 | "strip-ansi": "^4.0.0" 1860 | }, 1861 | "dependencies": { 1862 | "ansi-regex": { 1863 | "version": "3.0.0", 1864 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 1865 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 1866 | }, 1867 | "strip-ansi": { 1868 | "version": "4.0.0", 1869 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1870 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1871 | "requires": { 1872 | "ansi-regex": "^3.0.0" 1873 | } 1874 | } 1875 | } 1876 | }, 1877 | "stringstream": { 1878 | "version": "0.0.6", 1879 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", 1880 | "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" 1881 | }, 1882 | "strip-ansi": { 1883 | "version": "3.0.1", 1884 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1885 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1886 | "requires": { 1887 | "ansi-regex": "^2.0.0" 1888 | } 1889 | }, 1890 | "strip-eof": { 1891 | "version": "1.0.0", 1892 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 1893 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" 1894 | }, 1895 | "temp": { 1896 | "version": "0.8.3", 1897 | "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", 1898 | "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", 1899 | "requires": { 1900 | "os-tmpdir": "^1.0.0", 1901 | "rimraf": "~2.2.6" 1902 | } 1903 | }, 1904 | "throng": { 1905 | "version": "4.0.0", 1906 | "resolved": "https://registry.npmjs.org/throng/-/throng-4.0.0.tgz", 1907 | "integrity": "sha1-mDxroZk7WOroWZmKpof/6I34TBc=", 1908 | "requires": { 1909 | "lodash.defaults": "^4.0.1" 1910 | } 1911 | }, 1912 | "through": { 1913 | "version": "2.3.8", 1914 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1915 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 1916 | }, 1917 | "tough-cookie": { 1918 | "version": "2.3.4", 1919 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", 1920 | "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", 1921 | "requires": { 1922 | "punycode": "^1.4.1" 1923 | } 1924 | }, 1925 | "tunnel-agent": { 1926 | "version": "0.6.0", 1927 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1928 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1929 | "requires": { 1930 | "safe-buffer": "^5.0.1" 1931 | } 1932 | }, 1933 | "tweetnacl": { 1934 | "version": "0.14.5", 1935 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1936 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1937 | "optional": true 1938 | }, 1939 | "type-is": { 1940 | "version": "1.6.16", 1941 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 1942 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 1943 | "requires": { 1944 | "media-typer": "0.3.0", 1945 | "mime-types": "~2.1.18" 1946 | }, 1947 | "dependencies": { 1948 | "mime-db": { 1949 | "version": "1.35.0", 1950 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", 1951 | "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" 1952 | }, 1953 | "mime-types": { 1954 | "version": "2.1.19", 1955 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", 1956 | "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", 1957 | "requires": { 1958 | "mime-db": "~1.35.0" 1959 | } 1960 | } 1961 | } 1962 | }, 1963 | "typedarray": { 1964 | "version": "0.0.6", 1965 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1966 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 1967 | }, 1968 | "unpipe": { 1969 | "version": "1.0.0", 1970 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1971 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1972 | }, 1973 | "util-deprecate": { 1974 | "version": "1.0.2", 1975 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1976 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1977 | }, 1978 | "utils-merge": { 1979 | "version": "1.0.1", 1980 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1981 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1982 | }, 1983 | "uuid": { 1984 | "version": "3.1.0", 1985 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 1986 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 1987 | }, 1988 | "vary": { 1989 | "version": "1.1.1", 1990 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 1991 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 1992 | }, 1993 | "verror": { 1994 | "version": "1.10.0", 1995 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1996 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1997 | "requires": { 1998 | "assert-plus": "^1.0.0", 1999 | "core-util-is": "1.0.2", 2000 | "extsprintf": "^1.2.0" 2001 | }, 2002 | "dependencies": { 2003 | "assert-plus": { 2004 | "version": "1.0.0", 2005 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 2006 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 2007 | } 2008 | } 2009 | }, 2010 | "which": { 2011 | "version": "1.3.0", 2012 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", 2013 | "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", 2014 | "requires": { 2015 | "isexe": "^2.0.0" 2016 | } 2017 | }, 2018 | "which-module": { 2019 | "version": "2.0.0", 2020 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 2021 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 2022 | }, 2023 | "wrap-ansi": { 2024 | "version": "2.1.0", 2025 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 2026 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 2027 | "requires": { 2028 | "string-width": "^1.0.1", 2029 | "strip-ansi": "^3.0.1" 2030 | }, 2031 | "dependencies": { 2032 | "is-fullwidth-code-point": { 2033 | "version": "1.0.0", 2034 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 2035 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 2036 | "requires": { 2037 | "number-is-nan": "^1.0.0" 2038 | } 2039 | }, 2040 | "string-width": { 2041 | "version": "1.0.2", 2042 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 2043 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 2044 | "requires": { 2045 | "code-point-at": "^1.0.0", 2046 | "is-fullwidth-code-point": "^1.0.0", 2047 | "strip-ansi": "^3.0.0" 2048 | } 2049 | } 2050 | } 2051 | }, 2052 | "wrappy": { 2053 | "version": "1.0.2", 2054 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2055 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 2056 | "dev": true 2057 | }, 2058 | "xtend": { 2059 | "version": "4.0.1", 2060 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 2061 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 2062 | }, 2063 | "y18n": { 2064 | "version": "3.2.1", 2065 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", 2066 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" 2067 | }, 2068 | "yallist": { 2069 | "version": "2.1.2", 2070 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 2071 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 2072 | }, 2073 | "yargs": { 2074 | "version": "11.0.0", 2075 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", 2076 | "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", 2077 | "requires": { 2078 | "cliui": "^4.0.0", 2079 | "decamelize": "^1.1.1", 2080 | "find-up": "^2.1.0", 2081 | "get-caller-file": "^1.0.1", 2082 | "os-locale": "^2.0.0", 2083 | "require-directory": "^2.1.1", 2084 | "require-main-filename": "^1.0.1", 2085 | "set-blocking": "^2.0.0", 2086 | "string-width": "^2.0.0", 2087 | "which-module": "^2.0.0", 2088 | "y18n": "^3.2.1", 2089 | "yargs-parser": "^9.0.2" 2090 | } 2091 | }, 2092 | "yargs-parser": { 2093 | "version": "9.0.2", 2094 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", 2095 | "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", 2096 | "requires": { 2097 | "camelcase": "^4.1.0" 2098 | } 2099 | } 2100 | } 2101 | } 2102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@atom/teletype-server", 3 | "version": "0.18.2", 4 | "engines": { 5 | "node": "7.10.1", 6 | "npm": "5.4.2" 7 | }, 8 | "description": "", 9 | "main": "index.js", 10 | "scripts": { 11 | "migrate": "pg-migrate", 12 | "test": "mocha test/setup.js **/*.test.js --ui=tdd", 13 | "start": "script/server" 14 | }, 15 | "author": "", 16 | "license": "MIT", 17 | "dependencies": { 18 | "body-parser": "^1.18.3", 19 | "bugsnag": "^1.12.2", 20 | "cors": "^2.8.4", 21 | "dotenv": "^4.0.0", 22 | "express": "^4.15.5", 23 | "newrelic": "^4.6.0", 24 | "node-pg-migrate": "^2.25.0", 25 | "pg-promise": "^8.1.1", 26 | "pusher": "^2.0.1", 27 | "request": "^2.87.0", 28 | "request-promise-native": "^1.0.5", 29 | "server-destroy": "^1.0.1", 30 | "temp": "^0.8.3", 31 | "throng": "^4.0.0", 32 | "uuid": "^3.0.1" 33 | }, 34 | "devDependencies": { 35 | "deep-equal": "^1.0.1", 36 | "mocha": "^5.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /script/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {startServer} = require('../index') 4 | const throng = require('throng') 5 | throng({ 6 | workers: process.env.WEB_CONCURRENCY || 1, 7 | lifetime: Infinity 8 | }, startServer) 9 | -------------------------------------------------------------------------------- /test/controller-layer.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const {startTestServer} = require('..') 3 | const {get, post} = require('./helpers/request') 4 | const deepEqual = require('deep-equal') 5 | const condition = require('./helpers/condition') 6 | 7 | suite('Controller', () => { 8 | let server 9 | 10 | suiteSetup(async () => { 11 | server = await startTestServer({databaseURL: process.env.TEST_DATABASE_URL}) 12 | }) 13 | 14 | suiteTeardown(() => { 15 | return server.stop() 16 | }) 17 | 18 | setup(() => { 19 | return server.reset() 20 | }) 21 | 22 | suite('POST /peers/:id/signals', () => { 23 | test('sends authenticated signals to the peer with the given id', async () => { 24 | const peer1Identity = await server.identityProvider.identityForToken('peer-1-token') 25 | 26 | const signals = [] 27 | await server.pubSubGateway.subscribe('/peers/peer-2', 'signal', (signal) => signals.push(signal)) 28 | 29 | signals.length = 0 30 | await post(server, '/peers/peer-2/signals', { 31 | senderId: 'peer-1', 32 | signal: 'signal-1', 33 | sequenceNumber: 0 34 | }, {headers: {'GitHub-OAuth-token': 'peer-1-token'}}) 35 | await condition(() => deepEqual(signals, [{ 36 | senderId: 'peer-1', 37 | senderIdentity: peer1Identity, 38 | signal: 'signal-1', 39 | sequenceNumber: 0 40 | }])) 41 | 42 | signals.length = 0 43 | await post(server, '/peers/peer-2/signals', { 44 | senderId: 'peer-1', 45 | signal: 'signal-1', 46 | sequenceNumber: 1 47 | }, {headers: {'GitHub-OAuth-token': 'peer-1-token'}}) 48 | await condition(() => deepEqual(signals, [{ 49 | senderId: 'peer-1', 50 | signal: 'signal-1', 51 | sequenceNumber: 1 52 | }])) 53 | }) 54 | 55 | test('returns a 401 status code when authentication fails', async () => { 56 | const signals = [] 57 | await server.pubSubGateway.subscribe('/peers/peer-2', 'signal', (signal) => signals.push(signal)) 58 | 59 | try { 60 | server.identityProvider.identityForToken = simulateAuthenticationError 61 | 62 | let responseError 63 | try { 64 | await post(server, '/peers/peer-id/signals', { 65 | oauthToken: 'token', 66 | senderId: 'sender', 67 | signal: 'signal', 68 | sequenceNumber: 0 69 | }) 70 | } catch (e) { 71 | responseError = e 72 | } 73 | 74 | assert.equal(responseError.statusCode, 401) 75 | assert.deepEqual(signals, []) 76 | } finally { 77 | delete server.identityProvider.identityForToken 78 | } 79 | }) 80 | }) 81 | 82 | suite('GET /identity', () => { 83 | test('returns the identity associated with the given OAuth token', async () => { 84 | const identity = await get(server, '/identity', { 85 | headers: {'GitHub-OAuth-token': 'peer-1-token'} 86 | }) 87 | assert.deepEqual(identity, {id: 'user-with-token-peer-1-token-id', login: 'user-with-token-peer-1-token'}) 88 | }) 89 | 90 | test('returns a 401 status code when authentication fails', async () => { 91 | try { 92 | server.identityProvider.identityForToken = simulateAuthenticationError 93 | 94 | let responseError 95 | try { 96 | await get(server, '/identity', { 97 | headers: {'GitHub-OAuth-token': 'peer-1-token'} 98 | }) 99 | } catch (e) { 100 | responseError = e 101 | } 102 | 103 | assert.equal(responseError.statusCode, 401) 104 | assert.equal(responseError.error.message, 'Error resolving identity for token: an error') 105 | } finally { 106 | delete server.identityProvider.identityForToken 107 | } 108 | }) 109 | }) 110 | 111 | suite('events', () => { 112 | test('stores events on POST /portals and GET /portals/:id', async () => { 113 | const peer1Headers = {headers: {'GitHub-OAuth-token': 'peer-1-token'}} 114 | const peer2Headers = {headers: {'GitHub-OAuth-token': 'peer-2-token'}} 115 | 116 | const {id: portal1Id} = await post(server, '/portals', {hostPeerId: 'some-id'}, peer1Headers) 117 | await get(server, '/portals/' + portal1Id, peer2Headers) 118 | 119 | const {id: portal2Id} = await post(server, '/portals', {hostPeerId: 'some-id'}, peer2Headers) 120 | await get(server, '/portals/' + portal2Id, peer1Headers) 121 | 122 | const malformedPortalId = '123456' 123 | try { 124 | await get(server, '/portals/' + malformedPortalId, peer1Headers) 125 | } catch (e) {} 126 | 127 | await condition(async () => (await server.modelLayer.getEvents()).length === 5) 128 | const events = await server.modelLayer.getEvents() 129 | 130 | assert.equal(events[0].name, 'create-portal') 131 | assert.equal(events[0].portal_id, portal1Id) 132 | 133 | assert.equal(events[1].name, 'lookup-portal') 134 | assert.equal(events[1].portal_id, portal1Id) 135 | 136 | assert.equal(events[2].name, 'create-portal') 137 | assert.equal(events[2].portal_id, portal2Id) 138 | 139 | assert.equal(events[3].name, 'lookup-portal') 140 | assert.equal(events[3].portal_id, portal2Id) 141 | 142 | assert.equal(events[4].name, 'lookup-portal') 143 | assert.equal(events[4].portal_id, malformedPortalId) 144 | 145 | // Ensure user_id changes depending on the signed in user. 146 | assert.notEqual(events[0].user_id, events[1].user_id) 147 | assert.equal(events[0].user_id, events[3].user_id) 148 | assert.equal(events[1].user_id, events[2].user_id) 149 | assert.equal(events[3].user_id, events[4].user_id) 150 | 151 | // Ensure events are timestamped using the database clock. 152 | assert(events[0].created_at < events[1].created_at) 153 | assert(events[1].created_at < events[2].created_at) 154 | assert(events[2].created_at < events[3].created_at) 155 | assert(events[3].created_at < events[4].created_at) 156 | }) 157 | }) 158 | }) 159 | 160 | function simulateAuthenticationError (token) { 161 | const error = new Error('an error') 162 | error.statusCode = 499 163 | throw error 164 | } 165 | -------------------------------------------------------------------------------- /test/helpers/condition.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | function condition (fn) { 3 | const timeoutError = new Error('Condition timed out: ' + fn.toString()) 4 | Error.captureStackTrace(timeoutError, condition) 5 | 6 | return new Promise((resolve, reject) => { 7 | let verifyConditionTimeoutId 8 | 9 | const abortConditionTimeoutId = global.setTimeout(() => { 10 | global.clearTimeout(verifyConditionTimeoutId) 11 | reject(timeoutError) 12 | }, 500) 13 | 14 | const verifyCondition = async function () { 15 | if (await fn()) { 16 | global.clearTimeout(abortConditionTimeoutId) 17 | resolve() 18 | } else { 19 | verifyConditionTimeoutId = global.setTimeout(verifyCondition, 5) 20 | } 21 | } 22 | 23 | verifyCondition() 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /test/helpers/request.js: -------------------------------------------------------------------------------- 1 | const {URL} = require('url') 2 | const request = require('request-promise-native') 3 | 4 | exports.get = function (server, relativeURL, {headers}={}) { 5 | const url = new URL(relativeURL, server.address).toString() 6 | return request.get(url, {headers, json: true}) 7 | } 8 | 9 | exports.post = function (server, relativeURL, body, {headers}={}) { 10 | const url = new URL(relativeURL, server.address).toString() 11 | return request.post(url, {headers, body, json: true}) 12 | } 13 | -------------------------------------------------------------------------------- /test/identity-provider.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const {RequestError, StatusCodeError} = require('request-promise-core/lib/errors') 3 | const IdentityProvider = require('../lib/identity-provider') 4 | 5 | suite('IdentityProvider', () => { 6 | test('returns user associated with OAuth token', async () => { 7 | const request = { 8 | get: async function (url, {headers}) { 9 | const usersByOauthToken = { 10 | 'user-1-token': {id: 1, login: 'user-1'}, 11 | 'user-2-token': {id: 2, login: 'user-2'} 12 | } 13 | 14 | const authorizationHeader = headers['Authorization'] 15 | const token = authorizationHeader.split(' ').pop() 16 | const user = usersByOauthToken[token] 17 | 18 | return JSON.stringify(user) 19 | } 20 | } 21 | 22 | const provider = new IdentityProvider({request}) 23 | 24 | const user1 = await provider.identityForToken('user-1-token') 25 | assert.deepEqual(user1, {id: '1', login: 'user-1'}) 26 | 27 | const user2 = await provider.identityForToken('user-2-token') 28 | assert.deepEqual(user2, {id: '2', login: 'user-2'}) 29 | }) 30 | 31 | test('throws an error when given an invalid OAuth token', async () => { 32 | const request = { 33 | get: async function (url, {headers}) { 34 | const body = JSON.stringify({message: 'Bad credentials'}) 35 | throw new StatusCodeError(401, body) 36 | } 37 | } 38 | 39 | const provider = new IdentityProvider({request}) 40 | 41 | let error = null 42 | try { 43 | await provider.identityForToken('some-invalid-token') 44 | } catch (e) { 45 | error = e 46 | } 47 | 48 | assert(error.message.includes('401'), 'Expected error to include status code') 49 | }) 50 | 51 | test('throws an error when API rate limit is exceeded', async () => { 52 | const request = { 53 | get: async function (url, {headers}) { 54 | const body = JSON.stringify({message: 'API rate limit exceeded'}) 55 | throw new StatusCodeError(403, body) 56 | } 57 | } 58 | 59 | const provider = new IdentityProvider({request}) 60 | 61 | let error = null 62 | try { 63 | await provider.identityForToken('some-token') 64 | } catch (e) { 65 | error = e 66 | } 67 | 68 | assert(error.message.includes('403'), 'Expected error to include status code') 69 | }) 70 | 71 | test('throws an error when GitHub API is inaccessible', async () => { 72 | const request = { 73 | get: async function (url, {headers}) { 74 | throw new RequestError('a request error') 75 | } 76 | } 77 | 78 | const provider = new IdentityProvider({request}) 79 | 80 | let error = null 81 | try { 82 | await provider.identityForToken('some-token') 83 | } catch (e) { 84 | error = e 85 | } 86 | 87 | assert(error.message.includes('a request error'), 'Expected error to include request error message') 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /test/middleware.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const {authenticate, enforceProtocol} = require('../lib/middleware') 3 | 4 | suite('enforceProtocol', () => { 5 | test('requires HTTPS for production requests', () => { 6 | let httpRequestAllowed 7 | const httpRequest = { 8 | headers: { 'x-forwarded-proto': 'http' }, 9 | app: new Map([['env', 'production']]) 10 | } 11 | const httpResponse = new FakeResponse() 12 | 13 | enforceProtocol(httpRequest, httpResponse, function () { httpRequestAllowed = true }) 14 | assert(!httpRequestAllowed) 15 | assert.equal(httpResponse.code, 403) 16 | assert.deepEqual(httpResponse.body, { message: 'HTTPS required' }) 17 | 18 | let httpsRequestAllowed 19 | const httpsRequest = { 20 | headers: { 'x-forwarded-proto': 'https' }, 21 | app: new Map([['env', 'production']]) 22 | } 23 | const httpsResponse = new FakeResponse() 24 | 25 | enforceProtocol(httpsRequest, httpsResponse, () => { httpsRequestAllowed = true }) 26 | assert(httpsRequestAllowed) 27 | }) 28 | 29 | test('allows HTTP for non-production requests', () => { 30 | let httpRequestAllowed 31 | const httpRequest = { 32 | headers: { 'x-forwarded-proto': 'http' }, 33 | app: new Map([['env', 'development']]) 34 | } 35 | const httpResponse = new FakeResponse() 36 | 37 | enforceProtocol(httpRequest, httpResponse, () => { httpRequestAllowed = true }) 38 | assert(httpRequestAllowed) 39 | }) 40 | }) 41 | 42 | suite('authenticate', () => { 43 | test('creates a middleware that performs and enforces authentication', async () => { 44 | const identityProvider = { 45 | async identityForToken (token) { 46 | if (token === 'valid-token') { 47 | return {login: 'some-user'} 48 | } else { 49 | const error = new Error('an error') 50 | error.statusCode = 499 51 | throw error 52 | } 53 | } 54 | } 55 | const ignoredPaths = ['/ignored-path'] 56 | const authenticateMiddleware = authenticate({identityProvider, ignoredPaths}) 57 | 58 | // Make a request with a valid token. 59 | { 60 | let requestAllowed 61 | const request = { 62 | path: '/some-path', 63 | headers: {'github-oauth-token': 'valid-token'} 64 | } 65 | const response = new FakeResponse() 66 | await authenticateMiddleware(request, response, () => { requestAllowed = true }) 67 | 68 | assert(requestAllowed) 69 | assert.deepEqual(response.locals.identity, {login: 'some-user'}) 70 | } 71 | 72 | // Make a request with an invalid token. 73 | { 74 | let requestAllowed 75 | const request = { 76 | path: '/some-path', 77 | headers: {'github-oauth-token': 'invalid-token'} 78 | } 79 | const response = new FakeResponse() 80 | await authenticateMiddleware(request, response, () => { requestAllowed = true }) 81 | 82 | assert(!requestAllowed) 83 | assert.equal(response.code, 401) 84 | assert.equal(response.body.message, 'Error resolving identity for token: an error') 85 | } 86 | 87 | // Make a request with a missing token. 88 | { 89 | let requestAllowed 90 | const request = { 91 | path: '/some-path', 92 | headers: {} 93 | } 94 | const response = new FakeResponse() 95 | await authenticateMiddleware(request, response, () => { requestAllowed = true }) 96 | 97 | assert(!requestAllowed) 98 | assert.equal(response.code, 401) 99 | assert.equal(response.body.message, 'Authentication required') 100 | } 101 | 102 | // Make a request to an ignored path, and don't pass a token. 103 | { 104 | let requestAllowed 105 | const request = { 106 | path: '/ignored-path', 107 | headers: {} 108 | } 109 | const response = new FakeResponse() 110 | await authenticateMiddleware(request, response, () => { requestAllowed = true }) 111 | 112 | assert(requestAllowed) 113 | assert(!response.locals.identity) 114 | } 115 | }) 116 | }) 117 | 118 | class FakeResponse { 119 | constructor () { 120 | this.locals = {} 121 | } 122 | 123 | status (code) { 124 | this.code = code 125 | return this 126 | } 127 | 128 | send (body) { 129 | this.body = body 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | --------------------------------------------------------------------------------