diff --git a/captcha.js b/captcha.js index 50e952b..2273517 100644 --- a/captcha.js +++ b/captcha.js @@ -1,73 +1,126 @@ -'use strict' +'use strict'; -const Canvas = require('canvas') -const crypto = require('crypto') +const Canvas = require('canvas'); +const sha256 = require('js-sha256'); + +function randomFromTo(a, b) { + return () => { + return Math.floor(Math.random() * (b - a)) + a + } +} + +const randomTextColor = randomFromTo(0, 100); +const randomBgColor = randomFromTo(101, 255); +const random10t30 = randomFromTo(10, 30); +const random2t6 = randomFromTo(2, 6); +const random1t3 = randomFromTo(1, 3); +const random0t3 = randomFromTo(0, 3); +const random0t57 = randomFromTo(0, 57); + +// Removed o,O,0,I,l for simplicity for humans +const chars = ['a','b','c','d','e','f','g','h','i','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z', + 'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z', + '0','1','2','3','4','5','6','7','8','9', +]; + +const getRandomChar = () => { + return chars[random0t57()]; +}; class Captcha { - constructor(params) { - params.cookie = params.cookie || 'captcha' - params.cryptoAlg = params.cryptoAlg || 'sha1' - params.cryptoPass = params.cryptoPass || crypto.randomBytes(32) - params.color = params.color || 'rgb(0,100,100)' - params.background = params.background || 'rgb(255,200,150)' - params.lineWidth = params.lineWidth || 8 - params.fontSize = params.fontSize || 80 - params.codeLength = params.codeLength || 6 - params.canvasWidth = params.canvasWidth || 250 - params.canvasHeight = params.canvasHeight || 150 - - this.params = params - } + constructor(params) { + params.cookie = params.cookie || 'captcha'; + params.color = params.color || 'rgb(0,100,100)'; + params.background = params.background || 'rgb(255,200,150)'; + params.lineWidth = params.lineWidth || 6; + params.fontSize = params.fontSize || 70; + params.codeLength = params.codeLength || 6; + params.canvasHeight = params.canvasHeight || 120; + + this.randomHeightInCanvas = randomFromTo(0, params.canvasHeight); + + this.params = params + } + + randomHeightInCanvas() {} + randomWidthInCanvas() {} + + image() { + return (req, res, next) => { + const codeLength = this.params.codeLength + random0t3(); + this.params.canvasWidth = 40 * codeLength; + this.randomWidthInCanvas = randomFromTo(0, this.params.canvasWidth); + const canvas = new Canvas(this.params.canvasWidth, this.params.canvasHeight); + const ctx = canvas.getContext('2d'); + + ctx.antialias = 'gray'; + ctx.fillStyle = `rgb(${randomBgColor()},${randomBgColor()},${randomBgColor()})`; + ctx.fillRect(0, 0, this.params.canvasWidth, this.params.canvasHeight); + ctx.strokeStyle = this.params.color; + ctx.font = `${this.params.fontSize }px sans`; + + // draw text: + // draw two curve lines: + for (let i = 0; i < 6; i++) { + ctx.strokeStyle = `rgb(${randomTextColor()},${randomTextColor()},${randomTextColor()})`; + ctx.beginPath(); + ctx.lineWidth = random2t6(); + ctx.moveTo(Math.floor(0.08 * this.params.canvasWidth), Math.random() * this.params.canvasHeight); + ctx.bezierCurveTo(Math.floor(0.32 * this.params.canvasWidth), Math.random() * this.params.canvasHeight, Math.floor(1.07 * this.params.canvasHeight), Math.random() * this.params.canvasHeight, Math.floor(0.92 * this.params.canvasWidth), Math.random() * this.params.canvasHeight) + ctx.stroke(); + } + + for (let i = 0; i < 20; i++) { + ctx.strokeStyle = `rgb(${randomTextColor()},${randomTextColor()},${randomTextColor()})`; + ctx.beginPath(); + ctx.lineWidth = random1t3(); + ctx.arc(this.randomWidthInCanvas(), this.randomHeightInCanvas(), random10t30(), 0, 2 * Math.PI); + ctx.stroke(); + } + + for (let i = 0; i < 30; i++) { + ctx.strokeStyle = `rgb(${randomTextColor()},${randomTextColor()},${randomTextColor()})`; + ctx.beginPath(); + ctx.lineWidth = random1t3(); + ctx.rect(this.randomWidthInCanvas(), this.randomHeightInCanvas(), random10t30(), random10t30()); + ctx.stroke(); + } + + let text = ''; + for (let i = 0; i < codeLength; i++) { + const char = getRandomChar(); + text += char; + ctx.fillStyle = `rgb(${randomTextColor()},${randomTextColor()},${randomTextColor()})`; + ctx.setTransform(Math.random() * 0.5 + 1, Math.random() * 0.4, Math.random() * 0.4, Math.random() * 0.5 + 1, Math.floor(0.375 * this.params.fontSize) * i + Math.floor(0.25 * this.params.fontSize), Math.floor(1.25 * this.params.fontSize)); + ctx.fillText(char, 0, 0); + } + + // save text: + if (req.session === undefined) { + throw Error('node-captcha requires express-session!') + } + req.session[this.params.cookie] = text.toLowerCase(); - image() { - return (req, res, next) => { - const canvas = new Canvas(this.params.canvasWidth, this.params.canvasHeight) - const ctx = canvas.getContext('2d') - - ctx.antialias = 'gray' - ctx.fillStyle = this.params.background - ctx.fillRect(0, 0, this.params.canvasWidth, this.params.canvasHeight) - ctx.fillStyle = this.params.color - ctx.lineWidth = this.params.lineWidth - ctx.strokeStyle = this.params.color - ctx.font = `${this.params.fontSize }px sans` - - // draw two curve lines: - for (var i = 0; i < 2; i++) { - ctx.moveTo(Math.floor(0.08 * this.params.canvasWidth), Math.random() * this.params.canvasHeight) - ctx.bezierCurveTo(Math.floor(0.32 * this.params.canvasWidth), Math.random() * this.params.canvasHeight, Math.floor(1.07 * this.params.canvasHeight), Math.random() * this.params.canvasHeight, Math.floor(0.92 * this.params.canvasWidth), Math.random() * this.params.canvasHeight) - ctx.stroke() - } - - // draw text: - const text = ('' + crypto.randomBytes(4).readUIntBE(0, 4)).substr(2, this.params.codeLength) - text.split('').forEach((char, i) => { - ctx.setTransform(Math.random() * 0.5 + 1, Math.random() * 0.4, Math.random() * 0.4, Math.random() * 0.5 + 1, Math.floor(0.375 * this.params.fontSize) * i + Math.floor(0.25 * this.params.fontSize), Math.floor(1.25 * this.params.fontSize)) - ctx.fillText(char, 0, 0) - }) - - // save text: - if (req.session === undefined) { - throw Error('node-captcha requires express-session!') - } - req.session[this.params.cookie] = text - - // send image: - res.type('jpg') - res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0') - res.header('Expires', 'Sun, 19 May 1984 02:00:00 GMT') - canvas.jpegStream().pipe(res) - } + // send image: + if (typeof res.type !== 'undefined') { + res.type('jpg'); + } else { + res.setHeader('content-type', 'image/jpeg') + } + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + res.header('Expires', 'Sun, 19 May 1984 02:00:00 GMT'); + canvas.jpegStream().pipe(res) } + } - check(req, text) { - if (req.session === undefined) { - throw Error('node-captcha requires express-session!') - } - const res = req.session[this.params.cookie] === text - req.session[this.params.cookie] = null - return res + check(req, text) { + if (req.session === undefined) { + throw Error('node-captcha requires express-session!') } + const res = req.session[this.params.cookie] === text.toLowerCase(); + req.session[this.params.cookie] = null; + return res + } } -module.exports.create = params => new Captcha(params) +module.exports.create = params => new Captcha(params); diff --git a/package.json b/package.json index 1e054a1..4ea728f 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { - "name": "captcha", - "description": "Simple captcha middleware for Express", - "version": "0.1.3", + "name": "@visualead/captcha", + "description": "Simple captcha middleware for Express or Restify", + "version": "2.0.0", "author": "napa3um ", "dependencies": { - "canvas": "^1.6.5" + "canvas": "^1.6.5", + "js-sha256": "^0.6.0" }, "main": "./captcha.js", "repository": { "type": "git", - "url": "git@github.com:napa3um/node-captcha.git" + "url": "git@github.com:Visualead/node-captcha.git" }, "license": "MIT", "readmeFilename": "Readme.md", diff --git a/test-server.js b/test-server.js index 6f1ea35..e2e59aa 100644 --- a/test-server.js +++ b/test-server.js @@ -39,5 +39,5 @@ app.post('/login', (req, res) => { }) app.listen(8080, () => { - console.log('server started') + console.log('server started at http://localhost:8080') }) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..2d9a742 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,393 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" + dependencies: + mime-types "~2.1.11" + negotiator "0.6.1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +body-parser@^1.17.1: + version "1.17.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" + dependencies: + bytes "2.4.0" + content-type "~1.0.2" + debug "2.6.7" + depd "~1.1.0" + http-errors "~1.6.1" + iconv-lite "0.4.15" + on-finished "~2.3.0" + qs "6.4.0" + raw-body "~2.2.0" + type-is "~1.6.15" + +bytes@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" + +canvas@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-1.6.5.tgz#557f9988f5d2c95fdc247c61a5ee43de52f6717c" + dependencies: + nan "^2.4.0" + parse-css-font "^2.0.2" + units-css "^0.4.0" + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +crc@3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" + +css-font-size-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz#854875ace9aca6a8d2ee0d345a44aae9bb6db6cb" + +css-font-stretch-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz#50cee9b9ba031fb5c952d4723139f1e107b54b10" + +css-font-style-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz#5c3532813f63b4a1de954d13cea86ab4333409e4" + +css-font-weight-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz#9bc04671ac85bc724b574ef5d3ac96b0d604fd97" + +css-global-keywords@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69" + +css-list-helpers@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-1.0.1.tgz#fff57192202db83240c41686f919e449a7024f7d" + dependencies: + tcomb "^2.5.0" + +css-system-font-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" + +debug@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" + dependencies: + ms "2.0.0" + +depd@1.1.0, depd@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +etag@~1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" + +express-session@^1.15.2: + version "1.15.3" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.3.tgz#db545f0435a7b1b228ae02da8197f65141735c67" + dependencies: + cookie "0.3.1" + cookie-signature "1.0.6" + crc "3.4.4" + debug "2.6.7" + depd "~1.1.0" + on-headers "~1.0.1" + parseurl "~1.3.1" + uid-safe "~2.1.4" + utils-merge "1.0.0" + +express@^4.15.2: + version "4.15.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662" + dependencies: + accepts "~1.3.3" + array-flatten "1.1.1" + content-disposition "0.5.2" + content-type "~1.0.2" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.7" + depd "~1.1.0" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.0" + finalhandler "~1.0.3" + fresh "0.5.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.1" + path-to-regexp "0.1.7" + proxy-addr "~1.1.4" + qs "6.4.0" + range-parser "~1.2.0" + send "0.15.3" + serve-static "1.12.3" + setprototypeof "1.0.3" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.0" + vary "~1.1.1" + +finalhandler@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" + dependencies: + debug "2.6.7" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.1" + statuses "~1.3.1" + unpipe "~1.0.0" + +forwarded@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" + +fresh@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" + +http-errors@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" + dependencies: + depd "1.1.0" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +iconv-lite@0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ipaddr.js@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" + +isnumeric@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64" + +js-sha256@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.6.0.tgz#7fc3693526c53d39db9daaf63537ed38416aba96" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + +mime-types@~2.1.11, mime-types@~2.1.15: + version "2.1.15" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + dependencies: + mime-db "~1.27.0" + +mime@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +nan@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +parse-css-font@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/parse-css-font/-/parse-css-font-2.0.2.tgz#7b60b060705a25a9b90b7f0ed493e5823248a652" + dependencies: + css-font-size-keywords "^1.0.0" + css-font-stretch-keywords "^1.0.1" + css-font-style-keywords "^1.0.1" + css-font-weight-keywords "^1.0.0" + css-global-keywords "^1.0.1" + css-list-helpers "^1.0.1" + css-system-font-keywords "^1.0.0" + tcomb "^2.5.0" + unquote "^1.1.0" + +parseurl@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +proxy-addr@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" + dependencies: + forwarded "~0.1.0" + ipaddr.js "1.3.0" + +qs@6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" + dependencies: + bytes "2.4.0" + iconv-lite "0.4.15" + unpipe "1.0.0" + +send@0.15.3: + version "0.15.3" + resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309" + dependencies: + debug "2.6.7" + depd "~1.1.0" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.0" + fresh "0.5.0" + http-errors "~1.6.1" + mime "1.3.4" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serve-static@1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.1" + send "0.15.3" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +tcomb@^2.5.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" + +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + +uid-safe@~2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.4.tgz#3ad6f38368c6d4c8c75ec17623fb79aa1d071d81" + dependencies: + random-bytes "~1.0.0" + +units-css@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07" + dependencies: + isnumeric "^0.2.0" + viewport-dimensions "^0.2.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +unquote@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.0.tgz#98e1fc608b6b854c75afb1b95afc099ba69d942f" + +utils-merge@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" + +vary@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" + +viewport-dimensions@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c"