├── .dockerignore ├── .gitignore ├── .npmignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── bin └── www ├── config ├── default.json └── production.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── key-114-211406.png ├── key-120-211406.png ├── key-144-211406.png ├── key-152-211406.png ├── key-16-211406.png ├── key-24-211406.png ├── key-32-211406.png ├── key-48-211406.png ├── key-57-211406.png ├── key-64-211406.png └── key-72-211406.png ├── src ├── app.ts ├── bench.ts ├── logging.ts ├── middleware.ts ├── plugins │ ├── README.md │ ├── basicauth.ts │ ├── configusers.ts │ └── index.ts ├── routes │ ├── index.ts │ └── letsencrypt.ts ├── stopwatch.ts └── user.ts ├── test ├── Hash a Password.jmx └── hash.ts ├── tsconfig.json ├── tslint.json └── views ├── error.pug └── index.pug /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | LICENSE 4 | Procfile 5 | README.md 6 | npm-debug.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VS Code project 2 | .vscode 3 | 4 | # Transpiled code 5 | dist 6 | 7 | # Package files 8 | *.tgz 9 | 10 | # Types that are retrieved from typings 11 | typings 12 | 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules 43 | jspm_packages 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6" 5 | sudo: required 6 | dist: trusty 7 | env: 8 | - CXX=g++ 9 | before_install: 10 | - "npm install -g node-gyp" 11 | addons: 12 | apt: 13 | packages: 14 | - g++ 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6.11-alpine 2 | MAINTAINER JD Conley 3 | 4 | USER root 5 | ENV SERVICE_ROOT=/home/pwhaas 6 | 7 | # Dependencies for building native components, and our user 8 | # tini is used for proper node signal handling 9 | RUN apk add --update --virtual .build-deps build-base python \ 10 | && apk add --update tini \ 11 | && adduser -u 1001 -D -h $SERVICE_ROOT -s /bin/false pwhaas 12 | 13 | # Put everything in the home dir and give our user ownership 14 | WORKDIR $SERVICE_ROOT 15 | COPY package.json package.json 16 | 17 | RUN npm install 18 | 19 | COPY . $SERVICE_ROOT 20 | 21 | # Build the app 22 | # Temporarily pull in the dev dependencies to do the typescript build 23 | RUN npm run prep \ 24 | && rm -rf node_modules \ 25 | && npm install --production \ 26 | && npm cache clean \ 27 | && apk del .build-deps \ 28 | && rm -rf /var/cache/apk/* \ 29 | && rm -rf $SERVICE_ROOT/src \ 30 | && rm -rf $SERVICE_ROOT/bin \ 31 | && rm -f $SERVICE_ROOT/tsconfig.json \ 32 | && rm -f $SERVICE_ROOT/tslint.json \ 33 | && chown -R pwhaas:pwhaas $SERVICE_ROOT 34 | 35 | # Open our port and start the app 36 | EXPOSE 3000 37 | USER pwhaas 38 | ENTRYPOINT ["/sbin/tini", "--"] 39 | CMD [ "node", "dist/bin/www" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 JD Conley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jdconley/pwhaas.svg?branch=master)](https://travis-ci.org/jdconley/pwhaas) 2 | 3 | # What? 4 | A microservice. 5 | 6 | That can hash passwords. 7 | 8 | Pwhaas is a service that lets the good guys hash passwords with the same powerful hardware 9 | used by attackers. This makes the attacker's job 100's of times harder as it increases the 10 | amount of time they have to spend guessing the passwords. 11 | 12 | This service offloads CPU intensive password hashing from your application servers so they 13 | can do what they are good at and asynchronously wait on IO instead. 14 | 15 | It hashes passwords with the latest recommended salt generating and memory-hard 16 | algorithm optimized for x86: ([Argon2](https://github.com/P-H-C/phc-winner-argon2)). 17 | It is designed to hash in parallel on high CPU count systems with up to 4GB of memory 18 | utilized in order to make the resulting hashes difficult to crack with GPUs or ASIC 19 | processors. 20 | 21 | Use me at https://www.pwhaas.com and get acccess to some big iron if you don't 22 | want to host this yourself. 23 | 24 | ## Why? 25 | Typically we (as in software developers) run VM instances for general purpose web 26 | application servers that happen to also do password hashing. I've never used the 27 | largest systems available just for password hashing. Have you? 28 | 29 | Have you been paying attention to the news lately? Sites are hacked. Databases are stolen. 30 | Passwords are released. 31 | 32 | Experienced software engineers these days know they cannot store plain text passwords, so 33 | they use a one-way hash on the passwords. However, attackers don't just give up. When 34 | a new hack takes place, attackers get right to work utilizing GPU's and huge multi-core 35 | systems to try to crack those passwords. 36 | 37 | We (software engineers) should all be hashing passwords with the highest levels of 38 | security possible while still maintaining a great user experience. We owe it to our users. 39 | 40 | Do you run massively parallel, high memory systems dedicated to hashing passwords? No? 41 | Attackers that crack passwords sure do. And now you can, or you can use the ones we host. 42 | 43 | ## How? 44 | Pwhaas will automatically use as much of the available CPU and RAM on a system as it can. 45 | In the API you can specify how many milliseconds of compute time you want to use. The first 46 | time pwhaas starts up on a system it runs [argon2themax](https://github.com/jdconley/argon2themax) 47 | to determine how long it takes to run Argon2i various memory and iteration 48 | settings, while maxing out the CPU cores. The test algorithm favors using more memory 49 | over more iterations in order to fill a given amount of time but also always does at 50 | least 3 iterations. If the timings are well known, or you want to use different settings, 51 | you should put this file in place before the service starts. The HTTP server will not start 52 | listening until the test is complete. 53 | 54 | The argon2 algorithm is limited to 4GB RAM and, practically speaking, memory bandwidth 55 | of current systems limits things even further, so having more memory available than that 56 | will not be useful. For instance, if you have a 32 core server with 256GB of RAM and start 57 | up pwhaas, you'll have 252GB (minus overhead) of extra memory that you don't ever need 58 | and will not be utilized. However, pwhaas will always dedicate all CPU cores to the cause. 59 | 60 | Pwhaas also serializes requests and only hashes one password at a time per process. 61 | Argon2 supports up to 16,777,216 threads, so, you can throw as many CPU's as you want 62 | at it. However, memory bandwidth is almost always the bottleneck. 63 | 64 | ## Usage 65 | Pwhaas utilizes JSON for both requests and responses. This is for future expansion of the 66 | API and ease of consumption. 67 | 68 | ### Hash some data 69 | A 32 byte salt will be generated and `The password I want to hash!` will be hashed with 70 | Argon2i, utilizing up to 1,000ms of hash compute time. 71 | 72 | ```sh 73 | 74 | curl -X POST -H "Content-Type: application/json" -u "[Your API Key Here]:" -d '{"maxtime":1000, "plain":"The password I want to hash!"}' https://api.pwhaas.com/hash 75 | 76 | ``` 77 | 78 | In response you will receive a JSON document with either an error or the hash accompanied 79 | by some additional metadata. 80 | 81 | The "options" node contains the options that were sent to the 82 | [node-argon2](https://github.com/ranisalt/node-argon2/) hash function. 83 | 84 | The "hash" node contains the hashed data. Store this and use it in the call to `verify` later. 85 | 86 | The "timing" node contains the number of milliseconds pwhaas spent generating salt and 87 | hashing. If you have a paid pwhaas account, these values are used for metering/billing. 88 | 89 | The following data was returned in response to an actual call to `hash`. 90 | 91 | ```json 92 | { 93 | "hash": "$argon2i$v=19$m=4096,t=3,p=1$k3F2rWWXZ9MSTatHdd8Rgw$04F8gLV5HnwI8DdLDmB+2MPlPsSwkX0ETpVeuJzWX7o", 94 | "options": { 95 | "timeCost": 3, 96 | "memoryCost": 12, 97 | "parallelism": 1, 98 | "argon2d": false 99 | }, 100 | "timing": { 101 | "salt": 0.084359, 102 | "hash": 9.15277 103 | } 104 | } 105 | ``` 106 | 107 | ### Verify a hash 108 | When your users log back in you will need to verify their hashes. Pwhaas can do that for 109 | you. It uses a constant time comparison algorithm to mitigate timing attacks. 110 | 111 | ```sh 112 | 113 | curl -X POST -H "Content-Type: application/json" -u "[Your API Key Here]:" -d '{"hash":"$argon2i$v=19$m=4096,t=3,p=1$k3F2rWWXZ9MSTatHdd8Rgw$04F8gLV5HnwI8DdLDmB+2MPlPsSwkX0ETpVeuJzWX7o", "plain":"The password I want to hash!"}' https://api.pwhaas.com/verify 114 | 115 | ``` 116 | 117 | A call to `verify` will return a result JSON object indicating whether the plain and the 118 | hash are a match, as well as the time spent verifying the hash. If you have a paid pwhaas 119 | account, this is used for metering/billing. 120 | 121 | ```json 122 | 123 | { 124 | "match": true, 125 | "timing": { 126 | "verify": 9.596143 127 | } 128 | } 129 | 130 | ``` 131 | 132 | ## FAQ 133 | _I don't trust you, why would I send you my users' passwords?_ 134 | 135 | Fair enough. Then don't! Grab this code and run the service yourself, or hash passwords 136 | locally before you send them so pwhaas ends up just hashing a hash. 137 | Our [pwhaas Node.JS module](https://github.com/jdconley/pwhaas-js) hashes passwords with 138 | a fast configuration of Argon2 locally before sending the password to the pwhaas service. 139 | In production everything is transmitted over SSL. -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | //module dependencies. 5 | var ourApp = require("../src/app").server; 6 | var app = ourApp.app; 7 | var debug = require("debug")("express:server"); 8 | var http = require("http"); 9 | 10 | //get port from environment and store in Express. 11 | var port = normalizePort(process.env.PORT || 3000); 12 | app.set("port", port); 13 | 14 | //create http server 15 | var server = http.createServer(app); 16 | 17 | //listen on provided ports 18 | server.listen(port); 19 | 20 | //add error handler 21 | server.on("error", onError); 22 | 23 | //start listening on port 24 | server.on("listening", onListening); 25 | 26 | //asynchronously init the app, to do expensive startup things. 27 | ourApp.init(); 28 | 29 | //shutdown the app gracefully 30 | process.on('SIGINT', function() { 31 | server.close(); 32 | ourApp.shutdown(); 33 | process.exit(); 34 | }); 35 | 36 | /** 37 | * Normalize a port into a number, string, or false. 38 | */ 39 | function normalizePort(val) { 40 | var port = parseInt(val, 10); 41 | 42 | if (isNaN(port)) { 43 | // named pipe 44 | return val; 45 | } 46 | 47 | if (port >= 0) { 48 | // port number 49 | return port; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * Event listener for HTTP server "error" event. 57 | */ 58 | function onError(error) { 59 | if (error.syscall !== "listen") { 60 | throw error; 61 | } 62 | 63 | var bind = typeof port === "string" 64 | ? "Pipe " + port 65 | : "Port " + port; 66 | 67 | // handle specific listen errors with friendly messages 68 | switch (error.code) { 69 | case "EACCES": 70 | console.error(bind + " requires elevated privileges"); 71 | process.exit(1); 72 | break; 73 | case "EADDRINUSE": 74 | console.error(bind + " is already in use"); 75 | process.exit(1); 76 | break; 77 | default: 78 | throw error; 79 | } 80 | } 81 | 82 | /** 83 | * Event listener for HTTP server "listening" event. 84 | */ 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === "string" 88 | ? "pipe " + addr 89 | : "port " + addr.port; 90 | console.log("Listening on " + bind); 91 | } 92 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "bench": { 3 | "maxTimeMs": 100, 4 | "saltLength": 16, 5 | "osTmpCache": false 6 | }, 7 | "defaults": { 8 | "maxTimeMs": 50 9 | }, 10 | "plugins": [ 11 | { 12 | "name": "users", 13 | "module": "./configusers", 14 | "class": "ConfigUserProvider", 15 | "options": { 16 | "users": [ 17 | { 18 | "apiKey": "[Your API Key Here]", 19 | "hashMode": "default" 20 | }, 21 | { 22 | "apiKey": "secret", 23 | "hashMode": "all" 24 | } 25 | ] 26 | } 27 | }, 28 | { 29 | "name": "auth", 30 | "module": "./basicauth", 31 | "class": "Basic", 32 | "options": {} 33 | } 34 | ], 35 | "log": { 36 | "default": { 37 | "console": { 38 | "level": "debug", 39 | "colorize": true 40 | } 41 | }, 42 | "request": { 43 | "console": { 44 | "level": "info", 45 | "colorize": true 46 | } 47 | }, 48 | "requestError": { 49 | "console": { 50 | "level": "warn", 51 | "colorize": true 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "bench": { 3 | "maxTimeMs": 1000, 4 | "saltLength": 32, 5 | "osTmpCache": true 6 | }, 7 | "defaults": { 8 | "maxTimeMs": 1000 9 | }, 10 | "plugins": [ 11 | { 12 | "name": "users", 13 | "module": "./configusers", 14 | "class": "ConfigUserProvider", 15 | "options": { 16 | "users": [ 17 | { 18 | "apiKey": "[Your API Key Here]", 19 | "hashMode": "default" 20 | }, 21 | { 22 | "apiKey": "secret", 23 | "hashMode": "default" 24 | } 25 | ] 26 | } 27 | }, 28 | { 29 | "name": "auth", 30 | "module": "./basicauth", 31 | "class": "Basic", 32 | "options": {} 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pwhaas", 3 | "version": "1.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@phc/format": { 8 | "version": "0.5.0", 9 | "resolved": "https://registry.npmjs.org/@phc/format/-/format-0.5.0.tgz", 10 | "integrity": "sha512-JWtZ5P1bfXU0bAtTzCpOLYHDXuxSVdtL/oqz4+xa97h8w9E5IlVN333wugXVFv8vZ1hbXObKQf1ptXmFFcMByg==", 11 | "requires": { 12 | "safe-buffer": "^5.1.2" 13 | }, 14 | "dependencies": { 15 | "safe-buffer": { 16 | "version": "5.2.1", 17 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 18 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 19 | } 20 | } 21 | }, 22 | "@types/body-parser": { 23 | "version": "0.0.33", 24 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-0.0.33.tgz", 25 | "integrity": "sha1-M8oUmPw35Rxd8MgcrjRWnnBB4CU=", 26 | "dev": true, 27 | "requires": { 28 | "@types/express": "*" 29 | } 30 | }, 31 | "@types/chai": { 32 | "version": "3.5.2", 33 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-3.5.2.tgz", 34 | "integrity": "sha1-wRzSgX06QBt7oPWkIPNcVhObHB4=", 35 | "dev": true 36 | }, 37 | "@types/config": { 38 | "version": "0.0.30", 39 | "resolved": "https://registry.npmjs.org/@types/config/-/config-0.0.30.tgz", 40 | "integrity": "sha1-eEh5ofL2dPpSuA/EOVfkv/wkqt8=", 41 | "dev": true 42 | }, 43 | "@types/debug": { 44 | "version": "0.0.29", 45 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.29.tgz", 46 | "integrity": "sha1-oeUUrfvZLwOiJLpU1pMRHb8fN1Q=", 47 | "dev": true 48 | }, 49 | "@types/express": { 50 | "version": "4.0.36", 51 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz", 52 | "integrity": "sha1-FOtH3n7LEDGfCi+xz5caqGgHWMI=", 53 | "dev": true, 54 | "requires": { 55 | "@types/express-serve-static-core": "*", 56 | "@types/serve-static": "*" 57 | } 58 | }, 59 | "@types/express-serve-static-core": { 60 | "version": "4.0.49", 61 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.49.tgz", 62 | "integrity": "sha1-NDjWjSbjnbk0upQfGOOGKhvutyI=", 63 | "dev": true, 64 | "requires": { 65 | "@types/node": "*" 66 | } 67 | }, 68 | "@types/lodash": { 69 | "version": "4.14.68", 70 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.68.tgz", 71 | "integrity": "sha1-dU+6tovSu7aVR9yM51dPcBLu1/Y=", 72 | "dev": true 73 | }, 74 | "@types/mime": { 75 | "version": "1.3.1", 76 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.1.tgz", 77 | "integrity": "sha1-LPQpctCTHBBgx9X6Zif85r2Hby8=", 78 | "dev": true 79 | }, 80 | "@types/mocha": { 81 | "version": "2.2.41", 82 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.41.tgz", 83 | "integrity": "sha1-4nzwgXFT658nE7LT9saPHhw8pgg=", 84 | "dev": true 85 | }, 86 | "@types/node": { 87 | "version": "6.0.82", 88 | "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.82.tgz", 89 | "integrity": "sha1-jLrovQyVwwL/PWUiVfYA2JGty+s=", 90 | "dev": true 91 | }, 92 | "@types/serve-favicon": { 93 | "version": "2.2.28", 94 | "resolved": "https://registry.npmjs.org/@types/serve-favicon/-/serve-favicon-2.2.28.tgz", 95 | "integrity": "sha1-9ariTq/usgLft5g+J8FSqIEOqrU=", 96 | "dev": true, 97 | "requires": { 98 | "@types/express": "*" 99 | } 100 | }, 101 | "@types/serve-static": { 102 | "version": "1.7.31", 103 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz", 104 | "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=", 105 | "dev": true, 106 | "requires": { 107 | "@types/express-serve-static-core": "*", 108 | "@types/mime": "*" 109 | } 110 | }, 111 | "@types/winston": { 112 | "version": "0.0.28", 113 | "resolved": "https://registry.npmjs.org/@types/winston/-/winston-0.0.28.tgz", 114 | "integrity": "sha1-DKtbvLwU7wAPfKuiS/I3J+J7pjQ=", 115 | "dev": true, 116 | "requires": { 117 | "@types/node": "*" 118 | } 119 | }, 120 | "abbrev": { 121 | "version": "1.1.1", 122 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 123 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 124 | }, 125 | "accepts": { 126 | "version": "1.3.3", 127 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 128 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", 129 | "requires": { 130 | "mime-types": "~2.1.11", 131 | "negotiator": "0.6.1" 132 | } 133 | }, 134 | "acorn": { 135 | "version": "3.3.0", 136 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", 137 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" 138 | }, 139 | "acorn-globals": { 140 | "version": "3.1.0", 141 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", 142 | "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", 143 | "requires": { 144 | "acorn": "^4.0.4" 145 | }, 146 | "dependencies": { 147 | "acorn": { 148 | "version": "4.0.13", 149 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", 150 | "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" 151 | } 152 | } 153 | }, 154 | "align-text": { 155 | "version": "0.1.4", 156 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 157 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", 158 | "requires": { 159 | "kind-of": "^3.0.2", 160 | "longest": "^1.0.1", 161 | "repeat-string": "^1.5.2" 162 | } 163 | }, 164 | "amdefine": { 165 | "version": "1.0.1", 166 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 167 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" 168 | }, 169 | "ansi-regex": { 170 | "version": "2.1.1", 171 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 172 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 173 | }, 174 | "ansi-styles": { 175 | "version": "1.0.0", 176 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", 177 | "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=" 178 | }, 179 | "any-promise": { 180 | "version": "1.3.0", 181 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 182 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" 183 | }, 184 | "aproba": { 185 | "version": "1.2.0", 186 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 187 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 188 | }, 189 | "are-we-there-yet": { 190 | "version": "1.1.5", 191 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 192 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 193 | "requires": { 194 | "delegates": "^1.0.0", 195 | "readable-stream": "^2.0.6" 196 | } 197 | }, 198 | "argon2": { 199 | "version": "0.26.2", 200 | "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.26.2.tgz", 201 | "integrity": "sha512-Tk9I/r3KIHCIHU5x2UawKsPi+g7MByAYnUZghXztQDXRp/997P31wa4qvdvokTaFBpsu6jOZACd+2qkBGGssRA==", 202 | "requires": { 203 | "@phc/format": "^0.5.0", 204 | "node-addon-api": "^2.0.0", 205 | "node-pre-gyp": "^0.14.0" 206 | } 207 | }, 208 | "argon2themax": { 209 | "version": "1.3.0", 210 | "resolved": "https://registry.npmjs.org/argon2themax/-/argon2themax-1.3.0.tgz", 211 | "integrity": "sha512-OJkzAWfYO6jReo5k8Ax7Z1I1ONpxKQ2NFeHa3edP8XfqbGuIFqjaQgbeo19RKjuhM/Rz75BGPf6+iTI3+uoSJg==", 212 | "requires": { 213 | "argon2": "^0.26.2", 214 | "babel-runtime": "^6.11.6", 215 | "lodash": "^4.16.4" 216 | } 217 | }, 218 | "array-flatten": { 219 | "version": "1.1.1", 220 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 221 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 222 | }, 223 | "asap": { 224 | "version": "2.0.6", 225 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 226 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 227 | }, 228 | "assertion-error": { 229 | "version": "1.0.2", 230 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", 231 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", 232 | "dev": true 233 | }, 234 | "async": { 235 | "version": "1.0.0", 236 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 237 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 238 | }, 239 | "babel-runtime": { 240 | "version": "6.26.0", 241 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 242 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 243 | "requires": { 244 | "core-js": "^2.4.0", 245 | "regenerator-runtime": "^0.11.0" 246 | } 247 | }, 248 | "balanced-match": { 249 | "version": "1.0.0", 250 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 251 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 252 | }, 253 | "body-parser": { 254 | "version": "1.17.2", 255 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 256 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", 257 | "requires": { 258 | "bytes": "2.4.0", 259 | "content-type": "~1.0.2", 260 | "debug": "2.6.7", 261 | "depd": "~1.1.0", 262 | "http-errors": "~1.6.1", 263 | "iconv-lite": "0.4.15", 264 | "on-finished": "~2.3.0", 265 | "qs": "6.4.0", 266 | "raw-body": "~2.2.0", 267 | "type-is": "~1.6.15" 268 | } 269 | }, 270 | "brace-expansion": { 271 | "version": "1.1.8", 272 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 273 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 274 | "requires": { 275 | "balanced-match": "^1.0.0", 276 | "concat-map": "0.0.1" 277 | } 278 | }, 279 | "browser-stdout": { 280 | "version": "1.3.0", 281 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 282 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 283 | "dev": true 284 | }, 285 | "bytes": { 286 | "version": "2.4.0", 287 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 288 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 289 | }, 290 | "camelcase": { 291 | "version": "1.2.1", 292 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 293 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" 294 | }, 295 | "center-align": { 296 | "version": "0.1.3", 297 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 298 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", 299 | "requires": { 300 | "align-text": "^0.1.3", 301 | "lazy-cache": "^1.0.3" 302 | } 303 | }, 304 | "chai": { 305 | "version": "3.5.0", 306 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 307 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 308 | "dev": true, 309 | "requires": { 310 | "assertion-error": "^1.0.1", 311 | "deep-eql": "^0.1.3", 312 | "type-detect": "^1.0.0" 313 | } 314 | }, 315 | "chalk": { 316 | "version": "0.4.0", 317 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", 318 | "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", 319 | "requires": { 320 | "ansi-styles": "~1.0.0", 321 | "has-color": "~0.1.0", 322 | "strip-ansi": "~0.1.0" 323 | } 324 | }, 325 | "character-parser": { 326 | "version": "2.2.0", 327 | "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", 328 | "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", 329 | "requires": { 330 | "is-regex": "^1.0.3" 331 | } 332 | }, 333 | "chownr": { 334 | "version": "1.1.4", 335 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 336 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 337 | }, 338 | "clean-css": { 339 | "version": "3.4.27", 340 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", 341 | "integrity": "sha1-re91sxwWD/pdcvTeZ5ZuJmDBolU=", 342 | "requires": { 343 | "commander": "2.8.x", 344 | "source-map": "0.4.x" 345 | } 346 | }, 347 | "cliui": { 348 | "version": "2.1.0", 349 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 350 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", 351 | "requires": { 352 | "center-align": "^0.1.1", 353 | "right-align": "^0.1.1", 354 | "wordwrap": "0.0.2" 355 | } 356 | }, 357 | "code-point-at": { 358 | "version": "1.1.0", 359 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 360 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 361 | }, 362 | "colors": { 363 | "version": "1.0.3", 364 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 365 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 366 | }, 367 | "commander": { 368 | "version": "2.8.1", 369 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", 370 | "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", 371 | "requires": { 372 | "graceful-readlink": ">= 1.0.0" 373 | } 374 | }, 375 | "concat-map": { 376 | "version": "0.0.1", 377 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 378 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 379 | }, 380 | "config": { 381 | "version": "1.26.1", 382 | "resolved": "https://registry.npmjs.org/config/-/config-1.26.1.tgz", 383 | "integrity": "sha1-9kfOMsNF6AunOo6qeppLTlspDKE=", 384 | "requires": { 385 | "json5": "0.4.0", 386 | "os-homedir": "1.0.2" 387 | } 388 | }, 389 | "console-control-strings": { 390 | "version": "1.1.0", 391 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 392 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 393 | }, 394 | "constantinople": { 395 | "version": "3.1.0", 396 | "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.0.tgz", 397 | "integrity": "sha1-dWnKqKo/jVk11i4fqW+fcCzYHHk=", 398 | "requires": { 399 | "acorn": "^3.1.0", 400 | "is-expression": "^2.0.1" 401 | } 402 | }, 403 | "content-disposition": { 404 | "version": "0.5.2", 405 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 406 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 407 | }, 408 | "content-type": { 409 | "version": "1.0.2", 410 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 411 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 412 | }, 413 | "cookie": { 414 | "version": "0.3.1", 415 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 416 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 417 | }, 418 | "cookie-signature": { 419 | "version": "1.0.6", 420 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 421 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 422 | }, 423 | "core-js": { 424 | "version": "2.6.11", 425 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", 426 | "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" 427 | }, 428 | "core-util-is": { 429 | "version": "1.0.2", 430 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 431 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 432 | }, 433 | "cycle": { 434 | "version": "1.0.3", 435 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 436 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 437 | }, 438 | "debug": { 439 | "version": "2.6.7", 440 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 441 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", 442 | "requires": { 443 | "ms": "2.0.0" 444 | } 445 | }, 446 | "decamelize": { 447 | "version": "1.2.0", 448 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 449 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 450 | }, 451 | "deep-eql": { 452 | "version": "0.1.3", 453 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 454 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 455 | "dev": true, 456 | "requires": { 457 | "type-detect": "0.1.1" 458 | }, 459 | "dependencies": { 460 | "type-detect": { 461 | "version": "0.1.1", 462 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 463 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 464 | "dev": true 465 | } 466 | } 467 | }, 468 | "deep-extend": { 469 | "version": "0.6.0", 470 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 471 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 472 | }, 473 | "delegates": { 474 | "version": "1.0.0", 475 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 476 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 477 | }, 478 | "depd": { 479 | "version": "1.1.0", 480 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 481 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 482 | }, 483 | "destroy": { 484 | "version": "1.0.4", 485 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 486 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 487 | }, 488 | "detect-libc": { 489 | "version": "1.0.3", 490 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 491 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 492 | }, 493 | "diff": { 494 | "version": "3.2.0", 495 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 496 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 497 | "dev": true 498 | }, 499 | "doctypes": { 500 | "version": "1.1.0", 501 | "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", 502 | "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" 503 | }, 504 | "ee-first": { 505 | "version": "1.1.1", 506 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 507 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 508 | }, 509 | "encodeurl": { 510 | "version": "1.0.1", 511 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 512 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 513 | }, 514 | "escape-html": { 515 | "version": "1.0.3", 516 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 517 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 518 | }, 519 | "escape-string-regexp": { 520 | "version": "1.0.5", 521 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 522 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 523 | "dev": true 524 | }, 525 | "etag": { 526 | "version": "1.8.0", 527 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 528 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 529 | }, 530 | "express": { 531 | "version": "4.15.3", 532 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 533 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=", 534 | "requires": { 535 | "accepts": "~1.3.3", 536 | "array-flatten": "1.1.1", 537 | "content-disposition": "0.5.2", 538 | "content-type": "~1.0.2", 539 | "cookie": "0.3.1", 540 | "cookie-signature": "1.0.6", 541 | "debug": "2.6.7", 542 | "depd": "~1.1.0", 543 | "encodeurl": "~1.0.1", 544 | "escape-html": "~1.0.3", 545 | "etag": "~1.8.0", 546 | "finalhandler": "~1.0.3", 547 | "fresh": "0.5.0", 548 | "merge-descriptors": "1.0.1", 549 | "methods": "~1.1.2", 550 | "on-finished": "~2.3.0", 551 | "parseurl": "~1.3.1", 552 | "path-to-regexp": "0.1.7", 553 | "proxy-addr": "~1.1.4", 554 | "qs": "6.4.0", 555 | "range-parser": "~1.2.0", 556 | "send": "0.15.3", 557 | "serve-static": "1.12.3", 558 | "setprototypeof": "1.0.3", 559 | "statuses": "~1.3.1", 560 | "type-is": "~1.6.15", 561 | "utils-merge": "1.0.0", 562 | "vary": "~1.1.1" 563 | } 564 | }, 565 | "express-winston": { 566 | "version": "2.4.0", 567 | "resolved": "https://registry.npmjs.org/express-winston/-/express-winston-2.4.0.tgz", 568 | "integrity": "sha1-J6ts2TBT4t/cNbzuoUoHfcfVLkk=", 569 | "requires": { 570 | "chalk": "~0.4.0", 571 | "lodash": "~4.11.1" 572 | }, 573 | "dependencies": { 574 | "lodash": { 575 | "version": "4.11.2", 576 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.11.2.tgz", 577 | "integrity": "sha1-1rQzixEKWOIdrlzrz9u/0rxM2zs=" 578 | } 579 | } 580 | }, 581 | "eyes": { 582 | "version": "0.1.8", 583 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 584 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 585 | }, 586 | "finalhandler": { 587 | "version": "1.0.3", 588 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 589 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=", 590 | "requires": { 591 | "debug": "2.6.7", 592 | "encodeurl": "~1.0.1", 593 | "escape-html": "~1.0.3", 594 | "on-finished": "~2.3.0", 595 | "parseurl": "~1.3.1", 596 | "statuses": "~1.3.1", 597 | "unpipe": "~1.0.0" 598 | } 599 | }, 600 | "findup-sync": { 601 | "version": "0.3.0", 602 | "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", 603 | "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", 604 | "dev": true, 605 | "requires": { 606 | "glob": "~5.0.0" 607 | }, 608 | "dependencies": { 609 | "glob": { 610 | "version": "5.0.15", 611 | "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", 612 | "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", 613 | "dev": true, 614 | "requires": { 615 | "inflight": "^1.0.4", 616 | "inherits": "2", 617 | "minimatch": "2 || 3", 618 | "once": "^1.3.0", 619 | "path-is-absolute": "^1.0.0" 620 | } 621 | } 622 | } 623 | }, 624 | "forwarded": { 625 | "version": "0.1.0", 626 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 627 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 628 | }, 629 | "fresh": { 630 | "version": "0.5.0", 631 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 632 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 633 | }, 634 | "fs-extra": { 635 | "version": "0.26.7", 636 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", 637 | "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", 638 | "requires": { 639 | "graceful-fs": "^4.1.2", 640 | "jsonfile": "^2.1.0", 641 | "klaw": "^1.0.0", 642 | "path-is-absolute": "^1.0.0", 643 | "rimraf": "^2.2.8" 644 | } 645 | }, 646 | "fs-minipass": { 647 | "version": "1.2.7", 648 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 649 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 650 | "requires": { 651 | "minipass": "^2.6.0" 652 | } 653 | }, 654 | "fs-promise": { 655 | "version": "0.5.0", 656 | "resolved": "https://registry.npmjs.org/fs-promise/-/fs-promise-0.5.0.tgz", 657 | "integrity": "sha1-Q0fWv2JGVacGGkMZITw5MnatPvM=", 658 | "requires": { 659 | "any-promise": "^1.0.0", 660 | "fs-extra": "^0.26.5", 661 | "mz": "^2.3.1", 662 | "thenify-all": "^1.6.0" 663 | } 664 | }, 665 | "fs.realpath": { 666 | "version": "1.0.0", 667 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 668 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 669 | }, 670 | "function-bind": { 671 | "version": "1.1.0", 672 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", 673 | "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" 674 | }, 675 | "gauge": { 676 | "version": "2.7.4", 677 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 678 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 679 | "requires": { 680 | "aproba": "^1.0.3", 681 | "console-control-strings": "^1.0.0", 682 | "has-unicode": "^2.0.0", 683 | "object-assign": "^4.1.0", 684 | "signal-exit": "^3.0.0", 685 | "string-width": "^1.0.1", 686 | "strip-ansi": "^3.0.1", 687 | "wide-align": "^1.1.0" 688 | }, 689 | "dependencies": { 690 | "strip-ansi": { 691 | "version": "3.0.1", 692 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 693 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 694 | "requires": { 695 | "ansi-regex": "^2.0.0" 696 | } 697 | } 698 | } 699 | }, 700 | "glob": { 701 | "version": "7.1.2", 702 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 703 | "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", 704 | "requires": { 705 | "fs.realpath": "^1.0.0", 706 | "inflight": "^1.0.4", 707 | "inherits": "2", 708 | "minimatch": "^3.0.4", 709 | "once": "^1.3.0", 710 | "path-is-absolute": "^1.0.0" 711 | } 712 | }, 713 | "graceful-fs": { 714 | "version": "4.1.11", 715 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 716 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 717 | }, 718 | "graceful-readlink": { 719 | "version": "1.0.1", 720 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 721 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" 722 | }, 723 | "growl": { 724 | "version": "1.9.2", 725 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 726 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 727 | "dev": true 728 | }, 729 | "has": { 730 | "version": "1.0.1", 731 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", 732 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", 733 | "requires": { 734 | "function-bind": "^1.0.2" 735 | } 736 | }, 737 | "has-color": { 738 | "version": "0.1.7", 739 | "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", 740 | "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=" 741 | }, 742 | "has-flag": { 743 | "version": "1.0.0", 744 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 745 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 746 | "dev": true 747 | }, 748 | "has-unicode": { 749 | "version": "2.0.1", 750 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 751 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 752 | }, 753 | "http-errors": { 754 | "version": "1.6.1", 755 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 756 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", 757 | "requires": { 758 | "depd": "1.1.0", 759 | "inherits": "2.0.3", 760 | "setprototypeof": "1.0.3", 761 | "statuses": ">= 1.3.1 < 2" 762 | } 763 | }, 764 | "iconv-lite": { 765 | "version": "0.4.15", 766 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 767 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 768 | }, 769 | "ignore-walk": { 770 | "version": "3.0.3", 771 | "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", 772 | "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", 773 | "requires": { 774 | "minimatch": "^3.0.4" 775 | } 776 | }, 777 | "inflight": { 778 | "version": "1.0.6", 779 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 780 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 781 | "requires": { 782 | "once": "^1.3.0", 783 | "wrappy": "1" 784 | } 785 | }, 786 | "inherits": { 787 | "version": "2.0.3", 788 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 789 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 790 | }, 791 | "ini": { 792 | "version": "1.3.5", 793 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 794 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 795 | }, 796 | "ipaddr.js": { 797 | "version": "1.3.0", 798 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 799 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 800 | }, 801 | "is-buffer": { 802 | "version": "1.1.5", 803 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", 804 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" 805 | }, 806 | "is-expression": { 807 | "version": "2.1.0", 808 | "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-2.1.0.tgz", 809 | "integrity": "sha1-kb6dR968/vB3l36XIr5tz7RGXvA=", 810 | "requires": { 811 | "acorn": "~3.3.0", 812 | "object-assign": "^4.0.1" 813 | } 814 | }, 815 | "is-fullwidth-code-point": { 816 | "version": "1.0.0", 817 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 818 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 819 | "requires": { 820 | "number-is-nan": "^1.0.0" 821 | } 822 | }, 823 | "is-promise": { 824 | "version": "2.1.0", 825 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 826 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" 827 | }, 828 | "is-regex": { 829 | "version": "1.0.4", 830 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 831 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 832 | "requires": { 833 | "has": "^1.0.1" 834 | } 835 | }, 836 | "isarray": { 837 | "version": "1.0.0", 838 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 839 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 840 | }, 841 | "isstream": { 842 | "version": "0.1.2", 843 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 844 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 845 | }, 846 | "js-stringify": { 847 | "version": "1.0.2", 848 | "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", 849 | "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" 850 | }, 851 | "json3": { 852 | "version": "3.3.2", 853 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 854 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 855 | "dev": true 856 | }, 857 | "json5": { 858 | "version": "0.4.0", 859 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", 860 | "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" 861 | }, 862 | "jsonfile": { 863 | "version": "2.4.0", 864 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", 865 | "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", 866 | "requires": { 867 | "graceful-fs": "^4.1.6" 868 | } 869 | }, 870 | "jstransformer": { 871 | "version": "1.0.0", 872 | "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", 873 | "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", 874 | "requires": { 875 | "is-promise": "^2.0.0", 876 | "promise": "^7.0.1" 877 | } 878 | }, 879 | "kind-of": { 880 | "version": "3.2.2", 881 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 882 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 883 | "requires": { 884 | "is-buffer": "^1.1.5" 885 | } 886 | }, 887 | "klaw": { 888 | "version": "1.3.1", 889 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", 890 | "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", 891 | "requires": { 892 | "graceful-fs": "^4.1.9" 893 | } 894 | }, 895 | "lazy-cache": { 896 | "version": "1.0.4", 897 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 898 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" 899 | }, 900 | "lodash": { 901 | "version": "4.17.4", 902 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 903 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 904 | }, 905 | "lodash._baseassign": { 906 | "version": "3.2.0", 907 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 908 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 909 | "dev": true, 910 | "requires": { 911 | "lodash._basecopy": "^3.0.0", 912 | "lodash.keys": "^3.0.0" 913 | } 914 | }, 915 | "lodash._basecopy": { 916 | "version": "3.0.1", 917 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 918 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 919 | "dev": true 920 | }, 921 | "lodash._basecreate": { 922 | "version": "3.0.3", 923 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 924 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 925 | "dev": true 926 | }, 927 | "lodash._getnative": { 928 | "version": "3.9.1", 929 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 930 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 931 | "dev": true 932 | }, 933 | "lodash._isiterateecall": { 934 | "version": "3.0.9", 935 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 936 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 937 | "dev": true 938 | }, 939 | "lodash.create": { 940 | "version": "3.1.1", 941 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 942 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 943 | "dev": true, 944 | "requires": { 945 | "lodash._baseassign": "^3.0.0", 946 | "lodash._basecreate": "^3.0.0", 947 | "lodash._isiterateecall": "^3.0.0" 948 | } 949 | }, 950 | "lodash.isarguments": { 951 | "version": "3.1.0", 952 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 953 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 954 | "dev": true 955 | }, 956 | "lodash.isarray": { 957 | "version": "3.0.4", 958 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 959 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 960 | "dev": true 961 | }, 962 | "lodash.isempty": { 963 | "version": "4.4.0", 964 | "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", 965 | "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" 966 | }, 967 | "lodash.keys": { 968 | "version": "3.1.2", 969 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 970 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 971 | "dev": true, 972 | "requires": { 973 | "lodash._getnative": "^3.0.0", 974 | "lodash.isarguments": "^3.0.0", 975 | "lodash.isarray": "^3.0.0" 976 | } 977 | }, 978 | "longest": { 979 | "version": "1.0.1", 980 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 981 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" 982 | }, 983 | "media-typer": { 984 | "version": "0.3.0", 985 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 986 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 987 | }, 988 | "merge-descriptors": { 989 | "version": "1.0.1", 990 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 991 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 992 | }, 993 | "methods": { 994 | "version": "1.1.2", 995 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 996 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 997 | }, 998 | "mime": { 999 | "version": "1.3.4", 1000 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 1001 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 1002 | }, 1003 | "mime-db": { 1004 | "version": "1.27.0", 1005 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 1006 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 1007 | }, 1008 | "mime-types": { 1009 | "version": "2.1.15", 1010 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 1011 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", 1012 | "requires": { 1013 | "mime-db": "~1.27.0" 1014 | } 1015 | }, 1016 | "minimatch": { 1017 | "version": "3.0.4", 1018 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1019 | "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", 1020 | "requires": { 1021 | "brace-expansion": "^1.1.7" 1022 | } 1023 | }, 1024 | "minimist": { 1025 | "version": "0.0.8", 1026 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1027 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 1028 | }, 1029 | "minipass": { 1030 | "version": "2.9.0", 1031 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 1032 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 1033 | "requires": { 1034 | "safe-buffer": "^5.1.2", 1035 | "yallist": "^3.0.0" 1036 | }, 1037 | "dependencies": { 1038 | "safe-buffer": { 1039 | "version": "5.2.1", 1040 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1041 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1042 | } 1043 | } 1044 | }, 1045 | "minizlib": { 1046 | "version": "1.3.3", 1047 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 1048 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 1049 | "requires": { 1050 | "minipass": "^2.9.0" 1051 | } 1052 | }, 1053 | "mkdirp": { 1054 | "version": "0.5.1", 1055 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1056 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1057 | "requires": { 1058 | "minimist": "0.0.8" 1059 | } 1060 | }, 1061 | "mocha": { 1062 | "version": "3.4.2", 1063 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", 1064 | "integrity": "sha1-0O9NMyEm2/GNDWQMmzgt1IvpdZQ=", 1065 | "dev": true, 1066 | "requires": { 1067 | "browser-stdout": "1.3.0", 1068 | "commander": "2.9.0", 1069 | "debug": "2.6.0", 1070 | "diff": "3.2.0", 1071 | "escape-string-regexp": "1.0.5", 1072 | "glob": "7.1.1", 1073 | "growl": "1.9.2", 1074 | "json3": "3.3.2", 1075 | "lodash.create": "3.1.1", 1076 | "mkdirp": "0.5.1", 1077 | "supports-color": "3.1.2" 1078 | }, 1079 | "dependencies": { 1080 | "commander": { 1081 | "version": "2.9.0", 1082 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 1083 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 1084 | "dev": true, 1085 | "requires": { 1086 | "graceful-readlink": ">= 1.0.0" 1087 | } 1088 | }, 1089 | "debug": { 1090 | "version": "2.6.0", 1091 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", 1092 | "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", 1093 | "dev": true, 1094 | "requires": { 1095 | "ms": "0.7.2" 1096 | } 1097 | }, 1098 | "glob": { 1099 | "version": "7.1.1", 1100 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 1101 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 1102 | "dev": true, 1103 | "requires": { 1104 | "fs.realpath": "^1.0.0", 1105 | "inflight": "^1.0.4", 1106 | "inherits": "2", 1107 | "minimatch": "^3.0.2", 1108 | "once": "^1.3.0", 1109 | "path-is-absolute": "^1.0.0" 1110 | } 1111 | }, 1112 | "ms": { 1113 | "version": "0.7.2", 1114 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 1115 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 1116 | "dev": true 1117 | } 1118 | } 1119 | }, 1120 | "ms": { 1121 | "version": "2.0.0", 1122 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1123 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1124 | }, 1125 | "mz": { 1126 | "version": "2.6.0", 1127 | "resolved": "https://registry.npmjs.org/mz/-/mz-2.6.0.tgz", 1128 | "integrity": "sha1-yLhSHZWN8KTydoAl22nHGe5O8c4=", 1129 | "requires": { 1130 | "any-promise": "^1.0.0", 1131 | "object-assign": "^4.0.1", 1132 | "thenify-all": "^1.0.0" 1133 | } 1134 | }, 1135 | "needle": { 1136 | "version": "2.5.0", 1137 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", 1138 | "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", 1139 | "requires": { 1140 | "debug": "^3.2.6", 1141 | "iconv-lite": "^0.4.4", 1142 | "sax": "^1.2.4" 1143 | }, 1144 | "dependencies": { 1145 | "debug": { 1146 | "version": "3.2.6", 1147 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 1148 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 1149 | "requires": { 1150 | "ms": "^2.1.1" 1151 | } 1152 | }, 1153 | "ms": { 1154 | "version": "2.1.2", 1155 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1156 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1157 | } 1158 | } 1159 | }, 1160 | "negotiator": { 1161 | "version": "0.6.1", 1162 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 1163 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 1164 | }, 1165 | "node-addon-api": { 1166 | "version": "2.0.2", 1167 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", 1168 | "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" 1169 | }, 1170 | "node-pre-gyp": { 1171 | "version": "0.14.0", 1172 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", 1173 | "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", 1174 | "requires": { 1175 | "detect-libc": "^1.0.2", 1176 | "mkdirp": "^0.5.1", 1177 | "needle": "^2.2.1", 1178 | "nopt": "^4.0.1", 1179 | "npm-packlist": "^1.1.6", 1180 | "npmlog": "^4.0.2", 1181 | "rc": "^1.2.7", 1182 | "rimraf": "^2.6.1", 1183 | "semver": "^5.3.0", 1184 | "tar": "^4.4.2" 1185 | } 1186 | }, 1187 | "nopt": { 1188 | "version": "4.0.3", 1189 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", 1190 | "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", 1191 | "requires": { 1192 | "abbrev": "1", 1193 | "osenv": "^0.1.4" 1194 | } 1195 | }, 1196 | "npm-bundled": { 1197 | "version": "1.1.1", 1198 | "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", 1199 | "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", 1200 | "requires": { 1201 | "npm-normalize-package-bin": "^1.0.1" 1202 | } 1203 | }, 1204 | "npm-normalize-package-bin": { 1205 | "version": "1.0.1", 1206 | "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", 1207 | "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" 1208 | }, 1209 | "npm-packlist": { 1210 | "version": "1.4.8", 1211 | "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", 1212 | "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", 1213 | "requires": { 1214 | "ignore-walk": "^3.0.1", 1215 | "npm-bundled": "^1.0.1", 1216 | "npm-normalize-package-bin": "^1.0.1" 1217 | } 1218 | }, 1219 | "npmlog": { 1220 | "version": "4.1.2", 1221 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1222 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1223 | "requires": { 1224 | "are-we-there-yet": "~1.1.2", 1225 | "console-control-strings": "~1.1.0", 1226 | "gauge": "~2.7.3", 1227 | "set-blocking": "~2.0.0" 1228 | } 1229 | }, 1230 | "number-is-nan": { 1231 | "version": "1.0.1", 1232 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1233 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1234 | }, 1235 | "object-assign": { 1236 | "version": "4.1.1", 1237 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1238 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1239 | }, 1240 | "on-finished": { 1241 | "version": "2.3.0", 1242 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1243 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1244 | "requires": { 1245 | "ee-first": "1.1.1" 1246 | } 1247 | }, 1248 | "once": { 1249 | "version": "1.4.0", 1250 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1251 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1252 | "requires": { 1253 | "wrappy": "1" 1254 | } 1255 | }, 1256 | "optimist": { 1257 | "version": "0.6.1", 1258 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 1259 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 1260 | "dev": true, 1261 | "requires": { 1262 | "minimist": "~0.0.1", 1263 | "wordwrap": "~0.0.2" 1264 | } 1265 | }, 1266 | "os-homedir": { 1267 | "version": "1.0.2", 1268 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1269 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 1270 | }, 1271 | "os-tmpdir": { 1272 | "version": "1.0.2", 1273 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1274 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1275 | }, 1276 | "osenv": { 1277 | "version": "0.1.5", 1278 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 1279 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 1280 | "requires": { 1281 | "os-homedir": "^1.0.0", 1282 | "os-tmpdir": "^1.0.0" 1283 | } 1284 | }, 1285 | "parseurl": { 1286 | "version": "1.3.1", 1287 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 1288 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 1289 | }, 1290 | "path-is-absolute": { 1291 | "version": "1.0.1", 1292 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1293 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1294 | }, 1295 | "path-parse": { 1296 | "version": "1.0.5", 1297 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 1298 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" 1299 | }, 1300 | "path-to-regexp": { 1301 | "version": "0.1.7", 1302 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1303 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1304 | }, 1305 | "process-nextick-args": { 1306 | "version": "2.0.1", 1307 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1308 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1309 | }, 1310 | "promise": { 1311 | "version": "7.3.1", 1312 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 1313 | "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", 1314 | "requires": { 1315 | "asap": "~2.0.3" 1316 | } 1317 | }, 1318 | "proxy-addr": { 1319 | "version": "1.1.4", 1320 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 1321 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=", 1322 | "requires": { 1323 | "forwarded": "~0.1.0", 1324 | "ipaddr.js": "1.3.0" 1325 | } 1326 | }, 1327 | "pug": { 1328 | "version": "2.0.0-rc.2", 1329 | "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.0-rc.2.tgz", 1330 | "integrity": "sha1-B4RVJ3kKssa+Z9z16x8xgECB8Eo=", 1331 | "requires": { 1332 | "pug-code-gen": "^1.1.1", 1333 | "pug-filters": "^2.1.3", 1334 | "pug-lexer": "^3.1.0", 1335 | "pug-linker": "^3.0.1", 1336 | "pug-load": "^2.0.7", 1337 | "pug-parser": "^3.0.0", 1338 | "pug-runtime": "^2.0.3", 1339 | "pug-strip-comments": "^1.0.2" 1340 | } 1341 | }, 1342 | "pug-attrs": { 1343 | "version": "2.0.2", 1344 | "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.2.tgz", 1345 | "integrity": "sha1-i+KyIlVo/6ddG4Zpgr/59BEa/8s=", 1346 | "requires": { 1347 | "constantinople": "^3.0.1", 1348 | "js-stringify": "^1.0.1", 1349 | "pug-runtime": "^2.0.3" 1350 | } 1351 | }, 1352 | "pug-code-gen": { 1353 | "version": "1.1.1", 1354 | "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-1.1.1.tgz", 1355 | "integrity": "sha1-HPcnRO8qA56uajNAyqoRBYcSWOg=", 1356 | "requires": { 1357 | "constantinople": "^3.0.1", 1358 | "doctypes": "^1.1.0", 1359 | "js-stringify": "^1.0.1", 1360 | "pug-attrs": "^2.0.2", 1361 | "pug-error": "^1.3.2", 1362 | "pug-runtime": "^2.0.3", 1363 | "void-elements": "^2.0.1", 1364 | "with": "^5.0.0" 1365 | } 1366 | }, 1367 | "pug-error": { 1368 | "version": "1.3.2", 1369 | "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", 1370 | "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" 1371 | }, 1372 | "pug-filters": { 1373 | "version": "2.1.3", 1374 | "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-2.1.3.tgz", 1375 | "integrity": "sha1-1ZdnoiDeeX3XVUifZoNM+aqDqlQ=", 1376 | "requires": { 1377 | "clean-css": "^3.3.0", 1378 | "constantinople": "^3.0.1", 1379 | "jstransformer": "1.0.0", 1380 | "pug-error": "^1.3.2", 1381 | "pug-walk": "^1.1.3", 1382 | "resolve": "^1.1.6", 1383 | "uglify-js": "^2.6.1" 1384 | } 1385 | }, 1386 | "pug-lexer": { 1387 | "version": "3.1.0", 1388 | "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-3.1.0.tgz", 1389 | "integrity": "sha1-/QhzdtSmdbT1n4/vQiiDQ06VgaI=", 1390 | "requires": { 1391 | "character-parser": "^2.1.1", 1392 | "is-expression": "^3.0.0", 1393 | "pug-error": "^1.3.2" 1394 | }, 1395 | "dependencies": { 1396 | "acorn": { 1397 | "version": "4.0.13", 1398 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", 1399 | "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" 1400 | }, 1401 | "is-expression": { 1402 | "version": "3.0.0", 1403 | "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", 1404 | "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", 1405 | "requires": { 1406 | "acorn": "~4.0.2", 1407 | "object-assign": "^4.0.1" 1408 | } 1409 | } 1410 | } 1411 | }, 1412 | "pug-linker": { 1413 | "version": "3.0.1", 1414 | "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.1.tgz", 1415 | "integrity": "sha1-uj+P8hPKjzowSFm0T+0Tynud+hk=", 1416 | "requires": { 1417 | "pug-error": "^1.3.2", 1418 | "pug-walk": "^1.1.3" 1419 | } 1420 | }, 1421 | "pug-load": { 1422 | "version": "2.0.7", 1423 | "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.7.tgz", 1424 | "integrity": "sha1-Ux0MbhFUYBDphGMNA99AY2fS3nc=", 1425 | "requires": { 1426 | "object-assign": "^4.1.0", 1427 | "pug-walk": "^1.1.3" 1428 | } 1429 | }, 1430 | "pug-parser": { 1431 | "version": "3.0.0", 1432 | "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-3.0.0.tgz", 1433 | "integrity": "sha1-N8YZ3YAPZCGHzk1s4aFkzddUh6M=", 1434 | "requires": { 1435 | "pug-error": "^1.3.2", 1436 | "token-stream": "0.0.1" 1437 | } 1438 | }, 1439 | "pug-runtime": { 1440 | "version": "2.0.3", 1441 | "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.3.tgz", 1442 | "integrity": "sha1-mBYmB7D86eJU1CfzOYelrucWi9o=" 1443 | }, 1444 | "pug-strip-comments": { 1445 | "version": "1.0.2", 1446 | "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.2.tgz", 1447 | "integrity": "sha1-0xOvoBvMN0mA4TmeI+vy65vchRM=", 1448 | "requires": { 1449 | "pug-error": "^1.3.2" 1450 | } 1451 | }, 1452 | "pug-walk": { 1453 | "version": "1.1.3", 1454 | "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.3.tgz", 1455 | "integrity": "sha1-181bI9s8qHxjbIaglz+c2OAwQ2w=" 1456 | }, 1457 | "qs": { 1458 | "version": "6.4.0", 1459 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 1460 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 1461 | }, 1462 | "range-parser": { 1463 | "version": "1.2.0", 1464 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1465 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1466 | }, 1467 | "raw-body": { 1468 | "version": "2.2.0", 1469 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 1470 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", 1471 | "requires": { 1472 | "bytes": "2.4.0", 1473 | "iconv-lite": "0.4.15", 1474 | "unpipe": "1.0.0" 1475 | } 1476 | }, 1477 | "rc": { 1478 | "version": "1.2.8", 1479 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1480 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1481 | "requires": { 1482 | "deep-extend": "^0.6.0", 1483 | "ini": "~1.3.0", 1484 | "minimist": "^1.2.0", 1485 | "strip-json-comments": "~2.0.1" 1486 | }, 1487 | "dependencies": { 1488 | "minimist": { 1489 | "version": "1.2.5", 1490 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1491 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 1492 | } 1493 | } 1494 | }, 1495 | "readable-stream": { 1496 | "version": "2.3.7", 1497 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1498 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1499 | "requires": { 1500 | "core-util-is": "~1.0.0", 1501 | "inherits": "~2.0.3", 1502 | "isarray": "~1.0.0", 1503 | "process-nextick-args": "~2.0.0", 1504 | "safe-buffer": "~5.1.1", 1505 | "string_decoder": "~1.1.1", 1506 | "util-deprecate": "~1.0.1" 1507 | }, 1508 | "dependencies": { 1509 | "safe-buffer": { 1510 | "version": "5.1.2", 1511 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1512 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1513 | } 1514 | } 1515 | }, 1516 | "regenerator-runtime": { 1517 | "version": "0.11.1", 1518 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 1519 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 1520 | }, 1521 | "repeat-string": { 1522 | "version": "1.6.1", 1523 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 1524 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" 1525 | }, 1526 | "resolve": { 1527 | "version": "1.3.3", 1528 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", 1529 | "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", 1530 | "requires": { 1531 | "path-parse": "^1.0.5" 1532 | } 1533 | }, 1534 | "right-align": { 1535 | "version": "0.1.3", 1536 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 1537 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", 1538 | "requires": { 1539 | "align-text": "^0.1.1" 1540 | } 1541 | }, 1542 | "rimraf": { 1543 | "version": "2.6.1", 1544 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", 1545 | "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", 1546 | "requires": { 1547 | "glob": "^7.0.5" 1548 | } 1549 | }, 1550 | "safe-buffer": { 1551 | "version": "5.0.1", 1552 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", 1553 | "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" 1554 | }, 1555 | "sax": { 1556 | "version": "1.2.4", 1557 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1558 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1559 | }, 1560 | "semver": { 1561 | "version": "5.7.1", 1562 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1563 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1564 | }, 1565 | "send": { 1566 | "version": "0.15.3", 1567 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 1568 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=", 1569 | "requires": { 1570 | "debug": "2.6.7", 1571 | "depd": "~1.1.0", 1572 | "destroy": "~1.0.4", 1573 | "encodeurl": "~1.0.1", 1574 | "escape-html": "~1.0.3", 1575 | "etag": "~1.8.0", 1576 | "fresh": "0.5.0", 1577 | "http-errors": "~1.6.1", 1578 | "mime": "1.3.4", 1579 | "ms": "2.0.0", 1580 | "on-finished": "~2.3.0", 1581 | "range-parser": "~1.2.0", 1582 | "statuses": "~1.3.1" 1583 | } 1584 | }, 1585 | "serve-favicon": { 1586 | "version": "2.4.3", 1587 | "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.4.3.tgz", 1588 | "integrity": "sha1-WYaxewUCZCtkHCH4GLGszjICXSM=", 1589 | "requires": { 1590 | "etag": "~1.8.0", 1591 | "fresh": "0.5.0", 1592 | "ms": "2.0.0", 1593 | "parseurl": "~1.3.1", 1594 | "safe-buffer": "5.0.1" 1595 | } 1596 | }, 1597 | "serve-static": { 1598 | "version": "1.12.3", 1599 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 1600 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=", 1601 | "requires": { 1602 | "encodeurl": "~1.0.1", 1603 | "escape-html": "~1.0.3", 1604 | "parseurl": "~1.3.1", 1605 | "send": "0.15.3" 1606 | } 1607 | }, 1608 | "set-blocking": { 1609 | "version": "2.0.0", 1610 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1611 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1612 | }, 1613 | "setprototypeof": { 1614 | "version": "1.0.3", 1615 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1616 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 1617 | }, 1618 | "signal-exit": { 1619 | "version": "3.0.3", 1620 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1621 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 1622 | }, 1623 | "source-map": { 1624 | "version": "0.4.4", 1625 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", 1626 | "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", 1627 | "requires": { 1628 | "amdefine": ">=0.0.4" 1629 | } 1630 | }, 1631 | "sprintf-js": { 1632 | "version": "1.1.1", 1633 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", 1634 | "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=", 1635 | "dev": true 1636 | }, 1637 | "stack-trace": { 1638 | "version": "0.0.10", 1639 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 1640 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 1641 | }, 1642 | "statuses": { 1643 | "version": "1.3.1", 1644 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 1645 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 1646 | }, 1647 | "string-width": { 1648 | "version": "1.0.2", 1649 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1650 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1651 | "requires": { 1652 | "code-point-at": "^1.0.0", 1653 | "is-fullwidth-code-point": "^1.0.0", 1654 | "strip-ansi": "^3.0.0" 1655 | }, 1656 | "dependencies": { 1657 | "strip-ansi": { 1658 | "version": "3.0.1", 1659 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1660 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1661 | "requires": { 1662 | "ansi-regex": "^2.0.0" 1663 | } 1664 | } 1665 | } 1666 | }, 1667 | "string_decoder": { 1668 | "version": "1.1.1", 1669 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1670 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1671 | "requires": { 1672 | "safe-buffer": "~5.1.0" 1673 | }, 1674 | "dependencies": { 1675 | "safe-buffer": { 1676 | "version": "5.1.2", 1677 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1678 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1679 | } 1680 | } 1681 | }, 1682 | "strip-ansi": { 1683 | "version": "0.1.1", 1684 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", 1685 | "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=" 1686 | }, 1687 | "strip-json-comments": { 1688 | "version": "2.0.1", 1689 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1690 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 1691 | }, 1692 | "supports-color": { 1693 | "version": "3.1.2", 1694 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 1695 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 1696 | "dev": true, 1697 | "requires": { 1698 | "has-flag": "^1.0.0" 1699 | } 1700 | }, 1701 | "tar": { 1702 | "version": "4.4.13", 1703 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", 1704 | "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", 1705 | "requires": { 1706 | "chownr": "^1.1.1", 1707 | "fs-minipass": "^1.2.5", 1708 | "minipass": "^2.8.6", 1709 | "minizlib": "^1.2.1", 1710 | "mkdirp": "^0.5.0", 1711 | "safe-buffer": "^5.1.2", 1712 | "yallist": "^3.0.3" 1713 | }, 1714 | "dependencies": { 1715 | "safe-buffer": { 1716 | "version": "5.2.1", 1717 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1718 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1719 | } 1720 | } 1721 | }, 1722 | "thenify": { 1723 | "version": "3.3.0", 1724 | "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", 1725 | "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", 1726 | "requires": { 1727 | "any-promise": "^1.0.0" 1728 | } 1729 | }, 1730 | "thenify-all": { 1731 | "version": "1.6.0", 1732 | "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", 1733 | "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", 1734 | "requires": { 1735 | "thenify": ">= 3.1.0 < 4" 1736 | } 1737 | }, 1738 | "token-stream": { 1739 | "version": "0.0.1", 1740 | "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", 1741 | "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" 1742 | }, 1743 | "toobusy-js": { 1744 | "version": "0.5.1", 1745 | "resolved": "https://registry.npmjs.org/toobusy-js/-/toobusy-js-0.5.1.tgz", 1746 | "integrity": "sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk=" 1747 | }, 1748 | "tslint": { 1749 | "version": "3.15.1", 1750 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-3.15.1.tgz", 1751 | "integrity": "sha1-2hZcqT2P3CwIa1EWXuG6y0jJjqU=", 1752 | "dev": true, 1753 | "requires": { 1754 | "colors": "^1.1.2", 1755 | "diff": "^2.2.1", 1756 | "findup-sync": "~0.3.0", 1757 | "glob": "^7.0.3", 1758 | "optimist": "~0.6.0", 1759 | "resolve": "^1.1.7", 1760 | "underscore.string": "^3.3.4" 1761 | }, 1762 | "dependencies": { 1763 | "colors": { 1764 | "version": "1.1.2", 1765 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", 1766 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", 1767 | "dev": true 1768 | }, 1769 | "diff": { 1770 | "version": "2.2.3", 1771 | "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", 1772 | "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=", 1773 | "dev": true 1774 | } 1775 | } 1776 | }, 1777 | "type-detect": { 1778 | "version": "1.0.0", 1779 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 1780 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 1781 | "dev": true 1782 | }, 1783 | "type-is": { 1784 | "version": "1.6.15", 1785 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 1786 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 1787 | "requires": { 1788 | "media-typer": "0.3.0", 1789 | "mime-types": "~2.1.15" 1790 | } 1791 | }, 1792 | "typescript": { 1793 | "version": "2.4.1", 1794 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz", 1795 | "integrity": "sha1-w8yxbdqgsjFN4DHn5v7onlujRrw=", 1796 | "dev": true 1797 | }, 1798 | "uglify-js": { 1799 | "version": "2.8.29", 1800 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", 1801 | "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", 1802 | "requires": { 1803 | "source-map": "~0.5.1", 1804 | "uglify-to-browserify": "~1.0.0", 1805 | "yargs": "~3.10.0" 1806 | }, 1807 | "dependencies": { 1808 | "source-map": { 1809 | "version": "0.5.6", 1810 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 1811 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" 1812 | } 1813 | } 1814 | }, 1815 | "uglify-to-browserify": { 1816 | "version": "1.0.2", 1817 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 1818 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 1819 | "optional": true 1820 | }, 1821 | "underscore.string": { 1822 | "version": "3.3.4", 1823 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", 1824 | "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", 1825 | "dev": true, 1826 | "requires": { 1827 | "sprintf-js": "^1.0.3", 1828 | "util-deprecate": "^1.0.2" 1829 | } 1830 | }, 1831 | "unpipe": { 1832 | "version": "1.0.0", 1833 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1834 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1835 | }, 1836 | "util-deprecate": { 1837 | "version": "1.0.2", 1838 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1839 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1840 | }, 1841 | "utils-merge": { 1842 | "version": "1.0.0", 1843 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 1844 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 1845 | }, 1846 | "vary": { 1847 | "version": "1.1.1", 1848 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 1849 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 1850 | }, 1851 | "void-elements": { 1852 | "version": "2.0.1", 1853 | "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", 1854 | "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" 1855 | }, 1856 | "wide-align": { 1857 | "version": "1.1.3", 1858 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1859 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1860 | "requires": { 1861 | "string-width": "^1.0.2 || 2" 1862 | } 1863 | }, 1864 | "window-size": { 1865 | "version": "0.1.0", 1866 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 1867 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" 1868 | }, 1869 | "winston": { 1870 | "version": "2.3.1", 1871 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", 1872 | "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", 1873 | "requires": { 1874 | "async": "~1.0.0", 1875 | "colors": "1.0.x", 1876 | "cycle": "1.0.x", 1877 | "eyes": "0.1.x", 1878 | "isstream": "0.1.x", 1879 | "stack-trace": "0.0.x" 1880 | } 1881 | }, 1882 | "winston-config": { 1883 | "version": "0.5.1", 1884 | "resolved": "https://registry.npmjs.org/winston-config/-/winston-config-0.5.1.tgz", 1885 | "integrity": "sha1-ioYh5yKXQVFgLOmLBa+CA7G6Beo=", 1886 | "requires": { 1887 | "lodash.isempty": "^4.4.0" 1888 | } 1889 | }, 1890 | "with": { 1891 | "version": "5.1.1", 1892 | "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", 1893 | "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", 1894 | "requires": { 1895 | "acorn": "^3.1.0", 1896 | "acorn-globals": "^3.0.0" 1897 | } 1898 | }, 1899 | "wordwrap": { 1900 | "version": "0.0.2", 1901 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 1902 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" 1903 | }, 1904 | "wrappy": { 1905 | "version": "1.0.2", 1906 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1907 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1908 | }, 1909 | "yallist": { 1910 | "version": "3.1.1", 1911 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1912 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1913 | }, 1914 | "yargs": { 1915 | "version": "3.10.0", 1916 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 1917 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", 1918 | "requires": { 1919 | "camelcase": "^1.0.2", 1920 | "cliui": "^2.1.0", 1921 | "decamelize": "^1.0.0", 1922 | "window-size": "0.1.0" 1923 | } 1924 | } 1925 | } 1926 | } 1927 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pwhaas", 3 | "version": "1.0.1", 4 | "description": "Password Hash As A Service", 5 | "main": "dist/bin/www", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/jdconley/pwhaas.git" 9 | }, 10 | "private": true, 11 | "scripts": { 12 | "start": "npm run build && node dist/bin/www", 13 | "clean": "rm -rf dist", 14 | "build": "tsc -p . && cp -r bin views config public dist", 15 | "lint": "tslint --project tslint.json", 16 | "prep": "npm run clean && npm run lint && npm run build", 17 | "pretest": "npm run lint && npm run build", 18 | "test": "mocha dist/test/*.js" 19 | }, 20 | "keywords": [ 21 | "password", 22 | "hash", 23 | "argon2", 24 | "security", 25 | "microservice" 26 | ], 27 | "author": { 28 | "name": "JD Conley", 29 | "email": "jd.conley@gmail.com" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/jdconley/pwhaas/issues" 33 | }, 34 | "engines": { 35 | "node": "^6.0.0" 36 | }, 37 | "files": [ 38 | "dist" 39 | ], 40 | "license": "MIT", 41 | "dependencies": { 42 | "argon2themax": "^1.3.0", 43 | "body-parser": "^1.15.2", 44 | "config": "^1.21.0", 45 | "express": "^4.14.0", 46 | "express-winston": "^2.0.0", 47 | "fs-promise": "^0.5.0", 48 | "lodash": "^4.16.4", 49 | "pug": "^2.0.0-beta3", 50 | "serve-favicon": "^2.3.0", 51 | "toobusy-js": "^0.5.1", 52 | "winston": "^2.2.0", 53 | "winston-config": "^0.5.0" 54 | }, 55 | "devDependencies": { 56 | "typescript": "^2.0.3", 57 | "@types/body-parser": "0.0.33", 58 | "@types/chai": "^3.4.34", 59 | "@types/config": "0.0.30", 60 | "@types/debug": "0.0.29", 61 | "@types/express": "^4.0.33", 62 | "@types/lodash": "^4.14.37", 63 | "@types/mocha": "^2.2.32", 64 | "@types/node": "^6.0.45", 65 | "@types/serve-favicon": "^2.2.28", 66 | "@types/winston": "0.0.28", 67 | "tslint": "^3.15.1", 68 | "chai": "^3.5.0", 69 | "mocha": "^3.0.2" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/favicon.ico -------------------------------------------------------------------------------- /public/key-114-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-114-211406.png -------------------------------------------------------------------------------- /public/key-120-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-120-211406.png -------------------------------------------------------------------------------- /public/key-144-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-144-211406.png -------------------------------------------------------------------------------- /public/key-152-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-152-211406.png -------------------------------------------------------------------------------- /public/key-16-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-16-211406.png -------------------------------------------------------------------------------- /public/key-24-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-24-211406.png -------------------------------------------------------------------------------- /public/key-32-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-32-211406.png -------------------------------------------------------------------------------- /public/key-48-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-48-211406.png -------------------------------------------------------------------------------- /public/key-57-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-57-211406.png -------------------------------------------------------------------------------- /public/key-64-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-64-211406.png -------------------------------------------------------------------------------- /public/key-72-211406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdconley/pwhaas/84cff7006c199e9a47533820eb312116dd7eae5c/public/key-72-211406.png -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as bodyParser from "body-parser"; 4 | import * as express from "express"; 5 | import * as path from "path"; 6 | 7 | import * as indexRoute from "./routes/index"; 8 | import { Bench } from "./bench"; 9 | import { Middleware } from "./middleware"; 10 | import * as config from "config"; 11 | import * as Logger from "./logging"; 12 | import * as favicon from "serve-favicon"; 13 | import * as letsencrypt from "./routes/letsencrypt"; 14 | 15 | import * as plugins from "./plugins/index"; 16 | 17 | const expressWinston: any = require("express-winston"); 18 | 19 | export class Server implements plugins.DependencyResolver { 20 | getDependency(name: string): T { 21 | const plugin = plugins.getPlugin(name); 22 | if (plugin) { 23 | return plugin; 24 | } 25 | 26 | // Expose some well-known things that aren't in the plugin system 27 | if (name === "server") { 28 | return this as any; 29 | } 30 | 31 | if (name === "bench") { 32 | return this.bench as any; 33 | } 34 | 35 | if (name === "plugins") { 36 | return plugins as any; 37 | } 38 | 39 | return null; 40 | } 41 | 42 | app: express.Application; 43 | bench: Bench; 44 | private toobusy: any = require("toobusy-js"); 45 | private initialized: boolean = false; 46 | 47 | public static bootstrap(): Server { 48 | return new Server(); 49 | } 50 | 51 | public shutdown(): void { 52 | if (this.toobusy) { 53 | this.toobusy.shutdown(); 54 | } 55 | } 56 | 57 | constructor() { 58 | this.app = express(); 59 | this.bench = new Bench(); 60 | } 61 | 62 | public async init(): Promise { 63 | // Load plugins before config and routes. 64 | await plugins.init(this); 65 | 66 | // Config and routes may use certain types of plugins. 67 | this.config(); 68 | this.routes(); 69 | 70 | await this.bench.init(config.get("bench")); 71 | 72 | this.initialized = true; 73 | } 74 | 75 | private static nocache = (req: express.Request, res: express.Response, next: express.NextFunction) => { 76 | res.setHeader("Cache-Control", "private, max-age=0, no-cache, no-store, must-revalidate"); 77 | res.setHeader("Expires", "-1"); 78 | res.setHeader("Pragma", "no-cache"); 79 | next(); 80 | }; 81 | 82 | private static shortcache = (req: express.Request, res: express.Response, next: express.NextFunction) => { 83 | res.setHeader("Cache-Control", "public, max-age=60"); 84 | res.setHeader("Expires", new Date(Date.now() + 60000).toUTCString()); 85 | next(); 86 | }; 87 | 88 | private toobusyHandler = (req: express.Request, res: express.Response, next: express.NextFunction) => { 89 | if (this.toobusy()) { 90 | //TODO: json response if json in request 91 | res.status(503).send("I'm busy right now, sorry."); 92 | } else { 93 | next(); 94 | } 95 | } 96 | 97 | private initializedHandler = (req: express.Request, res: express.Response, next: express.NextFunction) => { 98 | if (this.initialized) { 99 | next(); 100 | } else { 101 | res.status(503).send("Not yet initialized. Try again in a minute."); 102 | } 103 | } 104 | 105 | private config() { 106 | this.app.set("views", path.join(__dirname, "../views")); 107 | this.app.set("view engine", "pug"); 108 | 109 | this.app.use(expressWinston.logger({ 110 | winstonInstance: Logger.getLogger("request") 111 | })); 112 | 113 | this.app.use(bodyParser.json()); 114 | this.app.use(bodyParser.urlencoded({ extended: true })); 115 | this.app.use(express.static(path.join(__dirname, "public"))); 116 | this.app.use(favicon(path.join(__dirname, "../public/favicon.ico"))); 117 | 118 | this.app.use(expressWinston.errorLogger({ 119 | winstonInstance: Logger.getLogger("requestError"), 120 | level: "error" 121 | })); 122 | 123 | // development error handler 124 | // will print stacktrace 125 | if (this.app.get("env") === "development") { 126 | this.app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { 127 | res.status(err.status || 500); 128 | // TODO: JSON response if requested in request 129 | res.render("error", { 130 | message: err.message, 131 | stack: JSON.stringify(err) 132 | }); 133 | }); 134 | } else { 135 | // production error handler 136 | // no stacktraces leaked to user 137 | this.app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { 138 | res.status(err.status || 500); 139 | // TODO: JSON response if requested in request 140 | res.render("error", { 141 | message: err.message, 142 | stack: JSON.stringify({"stacktrace": "Not available"}) 143 | }); 144 | }); 145 | } 146 | } 147 | 148 | private routes() { 149 | const router: express.Router = express.Router(); 150 | 151 | //home page 152 | const index: indexRoute.Index = new indexRoute.Index(config.get("defaults"), this.bench); 153 | router.get("/", Server.shortcache, index.index.bind(index.index)); 154 | 155 | //api endpoints 156 | const auth = this.getDependency("auth"); 157 | router.post( 158 | "/hash", 159 | Server.nocache, 160 | this.toobusyHandler.bind(this), 161 | this.initializedHandler.bind(this), 162 | auth.execute.bind(auth), 163 | index.hash.bind(index)); 164 | 165 | router.post( 166 | "/verify", 167 | Server.nocache, 168 | this.toobusyHandler.bind(this), 169 | this.initializedHandler.bind(this), 170 | auth.execute.bind(auth), 171 | index.verify.bind(index)); 172 | 173 | router.get( 174 | "/timings", 175 | Server.shortcache, 176 | this.toobusyHandler.bind(this), 177 | this.initializedHandler.bind(this), 178 | index.timings.bind(index)); 179 | 180 | router.get( 181 | "/.well-known/acme-challenge/:challenge", 182 | Server.nocache, 183 | letsencrypt.token.bind(letsencrypt)); 184 | 185 | this.app.use(router); 186 | 187 | this.app.use(function (req: express.Request, res: express.Response, next: express.NextFunction) { 188 | res.status(404); 189 | 190 | // TODO: JSON response if requested in request 191 | res.render("error", { 192 | message: "Hmmmm, couldn't find that resource.", 193 | stack: "N/A" 194 | }); 195 | }); 196 | } 197 | } 198 | 199 | export const server = Server.bootstrap(); -------------------------------------------------------------------------------- /src/bench.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as argon2 from "argon2themax"; 4 | import { Stopwatch } from "./stopwatch"; 5 | import * as _ from "lodash"; 6 | import * as logging from "./logging"; 7 | import * as path from "path"; 8 | import * as os from "os"; 9 | const fsp = require("fs-promise"); 10 | 11 | export interface BenchOptions { 12 | maxTimeMs?: number; 13 | saltLength?: number; 14 | osTmpCache?: boolean; 15 | timingsPath?: string; 16 | } 17 | 18 | export const defaults: BenchOptions = { 19 | maxTimeMs: 250, 20 | saltLength: 32, 21 | osTmpCache: true, 22 | timingsPath: path.join(__dirname, "timings.json") 23 | }; 24 | 25 | export class Bench { 26 | options: BenchOptions; 27 | selector: argon2.Selection.SelectionStrategy; 28 | private timingResult: argon2.Measurement.TimingResult; 29 | private logger = logging.getLogger("bench"); 30 | 31 | async tryLoadTimings(path: string): Promise { 32 | if (!path) { 33 | return false; 34 | } 35 | 36 | try { 37 | if (!await fsp.exists(path)) { 38 | return false; 39 | } 40 | 41 | this.timingResult = await fsp.readJson(path); 42 | 43 | if (this.timingResult && this.timingResult.timings) { 44 | this.logger.info(`Loaded ${this.timingResult.timings.length} timings from '${path}'`); 45 | return true; 46 | } 47 | 48 | } catch (err) { 49 | this.logger.error(`Unable to load timings from '${path}': `, err); 50 | } 51 | 52 | return false; 53 | } 54 | 55 | async init(options: BenchOptions = defaults): Promise { 56 | this.options = _.assignIn({}, defaults, options); 57 | 58 | // See if we should load timings from the options path 59 | await this.tryLoadTimings(this.options.timingsPath); 60 | 61 | // Timings not loaded yet, load them from temp path 62 | const timingTmpPath = path.join(os.tmpdir(), "pwhaas-timings.json"); 63 | if (this.options.osTmpCache && !this.timingResult) { 64 | await this.tryLoadTimings(timingTmpPath); 65 | } 66 | 67 | // No cached timings found 68 | if (!this.timingResult) { 69 | const timingStrategy = argon2.Measurement.getTimingStrategy(argon2.Measurement.TimingStrategyType.ClosestMatch); 70 | 71 | const sw = new Stopwatch(); 72 | this.timingResult = await argon2.Measurement.generateTimings( 73 | this.options, timingStrategy); 74 | sw.stop(); 75 | 76 | this.logger.info(`Found ${this.timingResult.timings.length} timings in ${sw.elapsed}ms`); 77 | 78 | // Store found timings 79 | if (this.options.osTmpCache && this.timingResult) { 80 | try { 81 | await fsp.writeJson(timingTmpPath, this.timingResult); 82 | this.logger.info(`OsTmpCache enabled. Wrote ${this.timingResult.timings.length} timings to '${timingTmpPath}'.`); 83 | } catch (err) { 84 | this.logger.error(`Unable to write timings to '${timingTmpPath}': `, err); 85 | } 86 | } 87 | } 88 | 89 | this.selector = argon2.Selection.getSelectionStrategy( 90 | argon2.Selection.SelectionStrategyType.MaxCost); 91 | 92 | this.selector.initialize(this.timingResult); 93 | 94 | // Lookup common values to warmup the cache 95 | for (let ms = 50; ms <= 1000; ms += 50) { 96 | try { 97 | this.selector.select(ms); 98 | } catch (err) { 99 | this.logger.warn(`Error performing selector warmup for ${ms}ms. It's probably because this system is slow.`, err); 100 | } 101 | } 102 | } 103 | 104 | getTimings(): argon2.Measurement.Timing[] { 105 | if (!this.timingResult || !this.timingResult.timings) { 106 | return []; 107 | } 108 | 109 | return _.cloneDeep(this.timingResult.timings); 110 | } 111 | 112 | getMaxTiming(maxTimeMs: number): argon2.Measurement.Timing { 113 | if (!this.selector) { 114 | return null; 115 | } 116 | 117 | return this.selector.select(maxTimeMs); 118 | } 119 | } -------------------------------------------------------------------------------- /src/logging.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as winston from "winston"; 4 | import * as config from "config"; 5 | 6 | const winstonConf = require("winston-config"); 7 | let initialized = false; 8 | 9 | export function getLogger(category: string): winston.LoggerInstance { 10 | if (!initialized) { 11 | winstonConf.fromJson(config.get("log"), c => { console.log("Winston Configured"); }); 12 | initialized = true; 13 | } 14 | 15 | return winston.loggers.get(category); 16 | } -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as express from "express"; 4 | 5 | export interface Middleware { 6 | execute(req: express.Request, res: express.Response, next: express.NextFunction): Promise; 7 | } -------------------------------------------------------------------------------- /src/plugins/README.md: -------------------------------------------------------------------------------- 1 | # You found the plugins directory 2 | From here you can customize much of the behavior of the service. This is how we do 3 | custom things on [pwhaas.com](https://www.pwhaas.com) like persist accounts outside 4 | of config, account management, more routes for views, etc. 5 | 6 | This plugins directory is automatically compiled and included in the build that happens 7 | during `npm start` so you can just drop your TypeScript modules in here and customize 8 | the config files to use your plugins where appropriate instead of the defaults. Easy as pie! 9 | 10 | So, fork this repo, or add it as a [subtree](https://developer.atlassian.com/blog/2015/05/the-power-of-git-subtree/), 11 | and drop TypeScript modules in here. Alternatively, you can implement plugins in separate 12 | repos using whatever tooling you want and use those in the configuration. Just make sure 13 | you expose the full interface for the type of plugin you are implementing. -------------------------------------------------------------------------------- /src/plugins/basicauth.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as express from "express"; 4 | import * as logging from "../logging"; 5 | import { UserProvider } from "../user"; 6 | import { Middleware } from "../middleware"; 7 | import { PwhaasPlugin, DependencyResolver } from "."; 8 | 9 | export class Basic implements Middleware, PwhaasPlugin { 10 | public async execute(req: express.Request, res: express.Response, next: express.NextFunction): Promise { 11 | const failAuth = (code?: number) => { 12 | res.statusCode = code || 401; 13 | res.end(); 14 | return false; 15 | }; 16 | 17 | // Has authorization header? 18 | const authorization = req.header("authorization"); 19 | if (!authorization) { 20 | this.logger.debug("No authorization header. Sending authenticate challenge to client.", req.headers); 21 | res.setHeader("WWW-Authenticate", "Basic realm=\"setTimeout API\""); 22 | return failAuth(); 23 | } 24 | 25 | // Proper authorization header? 26 | const parts = authorization.split(" "); 27 | if (parts.length < 2) { 28 | this.logger.debug("Invalid authorization header '%s'. Sending bad request.", authorization); 29 | return failAuth(400); 30 | } 31 | 32 | // Only support Basic auth 33 | const scheme = parts[0]; 34 | if (!/Basic/i.test(scheme)) { 35 | this.logger.debug("Authorization scheme '%s' is not supported. Sending bad request. We only support 'Basic' auth.", scheme); 36 | return failAuth(400); 37 | } 38 | 39 | const decodedAuth = new Buffer(parts[1], "base64").toString(); 40 | const separatorIndex = decodedAuth.indexOf(":"); 41 | 42 | if (separatorIndex < 0) { 43 | this.logger.debug("Invalid authorization data '%s'. Sending bad request.", decodedAuth); 44 | return failAuth(400); 45 | } 46 | 47 | const userid = decodedAuth.substr(0, separatorIndex); 48 | try { 49 | const user = await this.userProvider.getUser(userid); 50 | // User not found. Auth failed. 51 | if (!user) { 52 | this.logger.debug("Invalid Api Key '%s' received. Authentication failed.", userid); 53 | res.setHeader("WWW-Authenticate", "Basic realm=\"pwhaas API\""); 54 | return failAuth(401); 55 | } 56 | 57 | // Slap the user on the request so the pipeline can do authorization 58 | (req as any).user = user; 59 | this.logger.debug("Api Key '%s' is making a request.", userid, user); 60 | 61 | next(); 62 | } catch (err) { 63 | this.logger.error("Error authenticating user with id '%s'", userid, err); 64 | next(err); 65 | } 66 | 67 | return false; 68 | } 69 | 70 | private logger = logging.getLogger("auth"); 71 | private userProvider: UserProvider; 72 | 73 | init(options: any): Promise { 74 | return Promise.resolve(); 75 | } 76 | 77 | resolveDependencies(ioc: DependencyResolver): void { 78 | this.userProvider = ioc.getDependency("users"); 79 | } 80 | } -------------------------------------------------------------------------------- /src/plugins/configusers.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as config from "config"; 4 | import * as _ from "lodash"; 5 | import { User, UserProvider } from "../user"; 6 | import { PwhaasPlugin, DependencyResolver } from "."; 7 | 8 | interface ConfigUserProviderConfig { 9 | users: User[]; 10 | } 11 | 12 | export class ConfigUserProvider implements UserProvider, PwhaasPlugin { 13 | private usersByApiKey: _.Dictionary; 14 | 15 | init(options: any): Promise { 16 | const users = (options as ConfigUserProviderConfig).users; 17 | 18 | this.usersByApiKey = _.keyBy(users, "apiKey"); 19 | 20 | return Promise.resolve(); 21 | } 22 | 23 | getUser(apiKey: string): Promise { 24 | return Promise.resolve(this.usersByApiKey[apiKey]); 25 | } 26 | 27 | resolveDependencies(ioc: DependencyResolver): void { 28 | // No plugin deps 29 | } 30 | } -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as config from "config"; 4 | import * as logging from "../logging"; 5 | 6 | const logger = logging.getLogger("plugins"); 7 | 8 | interface PwhaasPluginConfig { 9 | name: string; 10 | module: string; 11 | class?: string; 12 | options?: any; 13 | } 14 | 15 | export interface DependencyResolver { 16 | getDependency(name: string): T; 17 | } 18 | 19 | export interface PwhaasPlugin { 20 | init(options: any): Promise; 21 | resolveDependencies(ioc: DependencyResolver): void; 22 | } 23 | 24 | function createPlugin(config: PwhaasPluginConfig): PwhaasPlugin { 25 | let moduleInstance: any; 26 | 27 | try { 28 | logger.info(`Creating module: "${config.module}".`); 29 | moduleInstance = require(config.module); 30 | } catch (err) { 31 | logger.error(`Unable to create module: "${config.module}".`, err); 32 | return null; 33 | } 34 | 35 | let classInstance: PwhaasPlugin; 36 | 37 | if (config.class) { 38 | try { 39 | logger.info(`Creating class: "${config.class}". Module "${config.module}".`); 40 | classInstance = new moduleInstance[config.class](); 41 | } catch (err) { 42 | logger.error(`Unable to create object from class: "${config.class}". Module "${config.module}".`, err); 43 | return null; 44 | } 45 | } else { 46 | logger.info(`No class found in configuration. Using module directly: "${config.module}".`); 47 | classInstance = moduleInstance; 48 | } 49 | 50 | return classInstance; 51 | } 52 | 53 | const plugins: {[name: string]: PwhaasPlugin} = {}; 54 | 55 | export function getPlugin(name: string): TPlugin { 56 | let pluginInstance: PwhaasPlugin = plugins[name]; 57 | 58 | if (!pluginInstance) { 59 | logger.error(`Plugin not available: "${name}". Make sure it is in your configuration file.`); 60 | return null; 61 | } 62 | 63 | return (pluginInstance as any); 64 | } 65 | 66 | export async function init(ioc: DependencyResolver): Promise { 67 | const allPluginConfigs = config.get("plugins"); 68 | 69 | // In the order of the configs listed in the file: 70 | // - Create plugins 71 | // - Resolve dependencies 72 | // - Initialize plugins 73 | 74 | for (let i = 0; i < allPluginConfigs.length; i++) { 75 | const pConfig = allPluginConfigs[i]; 76 | plugins[pConfig.name] = createPlugin(pConfig); 77 | if (!plugins[pConfig.name]) { 78 | logger.error(`Unable to load plugin: "${name}". Couldn't create instance.`); 79 | return null; 80 | } 81 | } 82 | 83 | for (let i = 0; i < allPluginConfigs.length; i++) { 84 | const pConfig = allPluginConfigs[i]; 85 | try { 86 | logger.info(`Resolving dependencies for plugin: "${pConfig.name}".`); 87 | plugins[pConfig.name].resolveDependencies(ioc); 88 | } catch (err) { 89 | logger.error(`Unable to resolve dependencies for plugin: "${pConfig.name}".`, err); 90 | } 91 | } 92 | 93 | for (let i = 0; i < allPluginConfigs.length; i++) { 94 | const pConfig = allPluginConfigs[i]; 95 | try { 96 | logger.info(`Initializing plugin: "${pConfig.name}".`); 97 | await plugins[pConfig.name].init(pConfig.options); 98 | } catch (err) { 99 | logger.error(`Unable to initialize plugin: "${pConfig.name}".`, err); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | import * as express from "express"; 5 | import * as argon2 from "argon2themax"; 6 | 7 | import { Stopwatch } from "../stopwatch"; 8 | import { Bench } from "../bench"; 9 | import { User, HASH_MODE_ALL, HASH_MODE_DEFAULT } from "../user"; 10 | import * as logging from "../logging"; 11 | 12 | module Route { 13 | export interface Options { 14 | maxTimeMs?: number; 15 | } 16 | 17 | export class Index { 18 | logger = logging.getLogger("api"); 19 | 20 | constructor(private defaults: Options, private bench: Bench) { 21 | } 22 | 23 | public index(req: express.Request, res: express.Response, next: express.NextFunction) { 24 | res.render("index"); 25 | } 26 | 27 | getOptions(user: User, req: express.Request): argon2.Options { 28 | if (user && user.hashMode === HASH_MODE_ALL) { 29 | const maxtime = Number(req.body.maxtime || this.defaults.maxTimeMs); 30 | const timing = this.bench.getMaxTiming(maxtime); 31 | 32 | this.logger.info(`User has all access. Maxtime Requested: ${maxtime}. Timing chosen: `, timing); 33 | return timing.options; 34 | } 35 | 36 | // Give the user up to the default maxTime if they are a "default" user 37 | if (user && user.hashMode === HASH_MODE_DEFAULT) { 38 | const maxtime = Math.min(Number(req.body.maxtime || this.defaults.maxTimeMs), this.defaults.maxTimeMs); 39 | const timing = this.bench.getMaxTiming(maxtime); 40 | 41 | this.logger.info(`User has default access. Maxtime Requested: ${maxtime}. Timing chosen: `, timing); 42 | return timing.options; 43 | } 44 | 45 | this.logger.info("User has no access. Maxtime Requested. Using Argon2 defaults:", argon2.defaults); 46 | return argon2.defaults; 47 | } 48 | 49 | public async hash(req: express.Request, res: express.Response, next: express.NextFunction): Promise { 50 | const plain = req.body.plain; 51 | const options = this.getOptions((req as any).user, req); 52 | 53 | const sw = new Stopwatch(); 54 | const salt = await argon2.generateSalt(this.bench.options.saltLength); 55 | const saltTiming = sw.stop(); 56 | 57 | sw.start(); 58 | const hash = await argon2.hash(plain, salt, options); 59 | const hashTiming = sw.stop(); 60 | 61 | res.json({hash, options, timing: {salt: saltTiming, hash: hashTiming}}); 62 | } 63 | 64 | public async verify(req: express.Request, res: express.Response, next: express.NextFunction): Promise { 65 | const hash = req.body.hash; 66 | const plain = req.body.plain; 67 | 68 | const sw = new Stopwatch(); 69 | const match = await argon2.verify(hash, plain); 70 | const verifyTiming = sw.stop(); 71 | 72 | res.json({match, timing: {verify: verifyTiming}}); 73 | } 74 | 75 | public timings(req: express.Request, res: express.Response, next: express.NextFunction) { 76 | res.json(this.bench.getTimings()); 77 | } 78 | } 79 | } 80 | 81 | export = Route; -------------------------------------------------------------------------------- /src/routes/letsencrypt.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as express from "express"; 4 | 5 | export function token(req: express.Request, res: express.Response, next: express.NextFunction) { 6 | if (process.env.LETSENCRYPT_TOKEN && 7 | req.params.challenge === process.env.LETSENCRYPT_CHALLENGE) { 8 | 9 | res.type("text/plain"); 10 | res.send(process.env.LETSENCRYPT_TOKEN); 11 | 12 | } else { 13 | next(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/stopwatch.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export class Stopwatch { 4 | private started: [number, number]; 5 | elapsed: number; 6 | 7 | constructor() { 8 | this.start(); 9 | } 10 | 11 | start() { 12 | this.started = process.hrtime(); 13 | this.elapsed = 0; 14 | } 15 | 16 | stop(): number { 17 | const elapsedHr = process.hrtime(this.started); 18 | return this.elapsed = elapsedHr[0] * 1e3 + elapsedHr[1] / 1e6; 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/user.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export const HASH_MODE_ALL = "all"; 4 | export const HASH_MODE_DEFAULT = "default"; 5 | 6 | export interface User { 7 | hashMode: string; 8 | apiKey: string; 9 | } 10 | 11 | export interface UserProvider { 12 | getUser(apiKey: string): Promise; 13 | } -------------------------------------------------------------------------------- /test/Hash a Password.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | SERVICE_ROOT 12 | https://api.pwhaas.com 13 | = 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | continue 22 | 23 | false 24 | 5 25 | 26 | 2 27 | 1 28 | 1476374400000 29 | 1476388800000 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Authorization 39 | Basic c2VjcmV0Og== 40 | 41 | 42 | Content-Type 43 | application/json 44 | 45 | 46 | Accepts 47 | application/json 48 | 49 | 50 | 51 | 52 | 53 | true 54 | 55 | 56 | 57 | false 58 | {"maxtime":1000, "plain":"The password I want to hash!"} 59 | = 60 | 61 | 62 | 63 | api.pwhaas.com 64 | 443 65 | 1000 66 | 5000 67 | https 68 | 69 | /hash 70 | POST 71 | false 72 | false 73 | false 74 | false 75 | false 76 | 77 | 78 | 79 | 80 | 81 | 200 82 | 83 | Assertion.response_code 84 | false 85 | 8 86 | 87 | 88 | 89 | 90 | true 91 | 92 | 93 | 94 | false 95 | {"hash":"$argon2i$v=19$m=4096,t=37,p=16$RhyzpQfcGCrD5PnWPGCSPDN/i1i+XP1cBGaTCiCYr5s$atXmBlq1Z9uu6jdAUeGp8X1vCed5+tBG/SEJm5SBdD0", "plain":"The password I want to hash!"} 96 | = 97 | 98 | 99 | 100 | api.pwhaas.com 101 | 443 102 | 1000 103 | 5000 104 | https 105 | 106 | /verify 107 | POST 108 | false 109 | false 110 | false 111 | false 112 | false 113 | 114 | 115 | 116 | 117 | 118 | 200 119 | 120 | Assertion.response_code 121 | false 122 | 8 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /test/hash.ts: -------------------------------------------------------------------------------- 1 | import {Index} from "../src/routes/index"; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "sourceMap": false, 6 | "rootDir": ".", 7 | "outDir": "dist" 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "views", 12 | "bin" 13 | ], 14 | "types": [ 15 | "node" 16 | ] 17 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "eofline": false, 6 | "forin": true, 7 | "indent": false, 8 | "label-position": true, 9 | "label-undefined": true, 10 | "max-line-length": [ 11 | true, 12 | 150 13 | ], 14 | "no-arg": true, 15 | "no-bitwise": true, 16 | "no-console": false, 17 | "no-construct": true, 18 | "no-constructor-vars": false, 19 | "no-debugger": true, 20 | "no-duplicate-key": true, 21 | "no-duplicate-variable": true, 22 | "no-empty": true, 23 | "no-eval": true, 24 | "no-string-literal": true, 25 | "no-switch-case-fall-through": true, 26 | "no-trailing-whitespace": true, 27 | "no-unused-expression": true, 28 | "no-unused-variable": false, 29 | "no-unreachable": true, 30 | "no-use-before-declare": true, 31 | "no-var-requires": false, 32 | "one-line": [ 33 | true, 34 | "check-open-brace", 35 | "check-catch", 36 | "check-else", 37 | "check-whitespace" 38 | ], 39 | "quotemark": [ 40 | true, 41 | "double" 42 | ], 43 | "semicolon": true, 44 | "triple-equals": [ 45 | true, 46 | "allow-null-check" 47 | ], 48 | "typedef": [ 49 | true, 50 | "callSignature", 51 | "indexSignature", 52 | "parameter", 53 | "propertySignature", 54 | "variableDeclarator", 55 | "memberVariableDeclarator" 56 | ], 57 | "use-strict": false, 58 | "variable-name": [ 59 | true, 60 | "allow-leading-underscore" 61 | ], 62 | "whitespace": [ 63 | true, 64 | "check-branch", 65 | "check-decl", 66 | "check-operator", 67 | "check-separator", 68 | "check-type" 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /views/error.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title An Error Has Occurred 4 | body 5 | h1 Bummer, something is broken 6 | p Error: #{message} 7 | p Stack: #{stack} -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title Pwhaas - Password Hash as a Service 4 | link(rel="shortcut-icon" href="favicon.ico") 5 | style(type="text/css"). 6 | body { 7 | font-family: Arial, sans-serif; 8 | font-size: larger; 9 | } 10 | body 11 | div(style="max-width: 666px; margin: 100px auto;") 12 | h1 Password Hash As A Service 13 | p 14 | strong Pwhaas is a service that lets the good guys hash passwords with the same powerful hardware used by attackers. 15 | p This makes the attacker's job 100's of times harder as it increases the amount of time they have to spend guessing passwords obtained in a data breach. 16 | p It is a RESTful API that fully utilizes current generation fast hardware to hash passwords. We're talking 10's of CPU cores, up to 4GB of RAM, and up to two seconds of CPU time for each password. 17 | 18 | h2 Why do I care? 19 | p 20 | span Glad you asked! Well, have you been paying attention to the news lately? Sites are hacked. Databases are stolen. Passwords are released. There are even 21 | a(href="https://haveibeenpwned.com/") web services 22 | span dedicated to keeping consumers informed about leaks. 23 | p Every software engineer these days knows they cannot store plain text passwords, so they use a one-way hash on the passwords. However, attackers don't just give up. When a new hack takes place, attackers get right to work utilizing GPU's and huge multi-core systems to try to crack those passwords. 24 | p If you're unfamiliar with the term "one-way hash", it just means to convert the password to an unreadable mumbo jumbo of information in a repeatable way using lots of fancy math. When properly designed, this math insures that there is no way you can retrieve someone's password once it is hashed. All you can do is repeat the same hash algorithm on a plain text password and see if it matches the original one. Hashing is not encryption, because with encryption you can retrieve the information. That's why websites and apps cannot send you your password when you forget it. They do not know the password either. 25 | p So, even if someone steals your database, if you have hashed passwords the attacker would have to try to guess each person's password individually by repeating the hashing algorithm that was used to protect the passwords. That's good. The problem is that attackers use very powerful hardware that is usually 100x or more powerful than the typical server used to create the hashes, and they can often guess at a password thousands or millions of times per second. That's where we come in. 26 | 27 | h2 Usage 28 | p 29 | span See 30 | a(href="https://github.com/jdconley/pwhaas") pwhaas on Github 31 | span for usage information. You can install the 32 | a(href="https://github.com/jdconley/pwhaas-js") pwhaas Node.JS module 33 | span to get going quickly if you use Node.JS 4.0+. 34 | 35 | h2 Pricing 36 | p We have special introductory pricing! Lock in savings of 50% or more, for life! 37 | p 38 | span We are currently in a private beta period for paid accounts and availability is limited. 39 | a(href="mailto:info@pwhaas.com") Send us an email 40 | span with your requirements if you want to participate or otherwise chat about password hashing. 41 | 42 | div 43 | h3 Free 44 | p To access the free tier just use the default API Key in our libraries, or set it to "secret". 45 | ul 46 | li 1,000ms hash time limit 47 | li 8 VM CPU cores 48 | li Shared VM's, requests can overlap 49 | li 50 | span Hash cost of 7,000 51 | sup 52 | small 1 53 | li 100 hashes/day 54 | div 55 | h3 Hobby - $20/month 56 | p Moderate protection for hobbyists and startups with real users. 57 | ul 58 | li 1,000ms hash time limit 59 | li 16 VM CPU cores 60 | li Dedicated VM instance per request 61 | li 62 | span Hash cost of 15,000 63 | sup 64 | small 1 65 | li 100 hashes/day, $0.01 per hash second thereafter 66 | div 67 | h3 Paranoid - $100/mo 68 | p For sensitive applications where having the best possible hash is desired. 69 | ul 70 | li 2,000ms hash time limit 71 | li 48 CPU cores on metal (no Virtual Machines) 72 | li Dedicated hardware instance per request 73 | li 74 | span Hash cost of 50,000 75 | sup 76 | small 1 77 | li 1,000 hashes/day, $0.05 per hash second thereafter 78 | 79 | h3 Enterprise - Get Quote 80 | p 81 | span Need something more? Want dedicated instances to call your own? Desire VM's or metal in certain data centers? 82 | a(href="mailto:info@pwhaas.com") Let us know! 83 | 84 | h2 FAQ 85 | h3 Hesitant to send your users' passwords to a new service? 86 | p Fair enough. Then don't! Grab the code and run the service yourself, or hash passwords locally before you send them so pwhaas ends up just hashing a hash. The pwhaas libraries do this with a fast configuration of Argon2 locally before sending the password to the pwhaas service. Everything is also transmitted over SSL/TLS. 87 | 88 | h3 What if Pwhaas is down? 89 | p No problem! The pwhaas libraries automatically fall back to local hashing and verifying. The local hashes will be much less secure than the ones in the cloud, but are probably about 10x as secure as the default Argon2 options. Verifying will take significantly longer than our API if your hardware is not similar to ours. 90 | 91 | p(style="margin-top: 400px") 92 | sup 93 | small 1 94 | small Hash costs are estimates based on measurements taken from representative systems and may vary depending on the actual hardware, concurrent requests, and operating system overhead. Costs are based on the formula: [parallelism] x [memory cost] x [time cost]. The default hash cost is 36, based on the formula 1 x 12 x 3 = 36. 95 | 96 | p 97 | span © Copyright 2016, 98 | a(href="https://www.linkedin.com/in/jdconley") JD Conley. 99 | --------------------------------------------------------------------------------