diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a865156 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +npm-debug.log +node_modules +*.swp +*.swo +data +*.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd53efc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +FROM node:14.8.0-stretch + +RUN mkdir -p /usr/src/app && \ + chown node:node /usr/src/app + +USER node:node + +WORKDIR /usr/src/app + +COPY --chown=node:node . . + +RUN npm install && \ + npm install redis@0.8.1 && \ + npm install pg@4.1.1 && \ + npm install memcached@2.2.2 && \ + npm install aws-sdk@2.738.0 && \ + npm install rethinkdbdash@2.3.31 + +ENV STORAGE_TYPE=memcached \ + STORAGE_HOST=127.0.0.1 \ + STORAGE_PORT=11211\ + STORAGE_EXPIRE_SECONDS=2592000\ + STORAGE_DB=2 \ + STORAGE_AWS_BUCKET= \ + STORAGE_AWS_REGION= \ + STORAGE_USENAMER= \ + STORAGE_PASSWORD= \ + STORAGE_FILEPATH= + +ENV LOGGING_LEVEL=verbose \ + LOGGING_TYPE=Console \ + LOGGING_COLORIZE=true + +ENV HOST=0.0.0.0\ + PORT=7777\ + KEY_LENGTH=10\ + MAX_LENGTH=400000\ + STATIC_MAX_AGE=86400\ + RECOMPRESS_STATIC_ASSETS=true + +ENV KEYGENERATOR_TYPE=phonetic \ + KEYGENERATOR_KEYSPACE= + +ENV RATELIMITS_NORMAL_TOTAL_REQUESTS=500\ + RATELIMITS_NORMAL_EVERY_MILLISECONDS=60000 \ + RATELIMITS_WHITELIST_TOTAL_REQUESTS= \ + RATELIMITS_WHITELIST_EVERY_MILLISECONDS= \ + # comma separated list for the whitelisted \ + RATELIMITS_WHITELIST=example1.whitelist,example2.whitelist \ + \ + RATELIMITS_BLACKLIST_TOTAL_REQUESTS= \ + RATELIMITS_BLACKLIST_EVERY_MILLISECONDS= \ + # comma separated list for the blacklisted \ + RATELIMITS_BLACKLIST=example1.blacklist,example2.blacklist +ENV DOCUMENTS=about=./about.md + +EXPOSE ${PORT} +STOPSIGNAL SIGINT +ENTRYPOINT [ "bash", "docker-entrypoint.sh" ] + +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s \ + --retries=3 CMD [ "curl" , "-f" "localhost:${PORT}", "||", "exit", "1"] +CMD ["npm", "start"] diff --git a/README.md b/README.md index ba2bfc2..1cb06f7 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,126 @@ Also, you must create an `uploads` table, which will store all the data for uplo You can optionally add the `user` and `password` properties to use a user system. +### Amazon S3 + +To use [Amazon S3](https://aws.amazon.com/s3/) as a storage system, you must +install the `aws-sdk` package via npm: + +`npm install aws-sdk` + +Once you've done that, your config section should look like this: + +```json +{ + "type": "amazon-s3", + "bucket": "your-bucket-name", + "region": "us-east-1" +} +``` + +Authentication is handled automatically by the client. Check +[Amazon's documentation](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html) +for more information. You will need to grant your role these permissions to +your bucket: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::your-bucket-name-goes-here/*" + } + ] +} +``` + +## Docker + +### Build image + +```bash +docker build --tag haste-server . +``` + +### Run container + +For this example we will run haste-server, and connect it to a redis server + +```bash +docker run --name haste-server-container --env STORAGE_TYPE=redis --env STORAGE_HOST=redis-server --env STORAGE_PORT=6379 haste-server +``` + +### Use docker-compose example + +There is an example `docker-compose.yml` which runs haste-server together with memcached + +```bash +docker-compose up +``` + +### Configuration + +The docker image is configured using environmental variables as you can see in the example above. + +Here is a list of all the environment variables + +### Storage + +| Name | Default value | Description | +| :--------------------: | :-----------: | :-----------------------------------------------------------------------------------------------------------: | +| STORAGE_TYPE | memcached | Type of storage . Accepted values: "memcached","redis","postgres","rethinkdb", "amazon-s3", and "file" | +| STORAGE_HOST | 127.0.0.1 | Storage host. Applicable for types: memcached, redis, postgres, and rethinkdb | +| STORAGE_PORT | 11211 | Port on the storage host. Applicable for types: memcached, redis, postgres, and rethinkdb | +| STORAGE_EXPIRE_SECONDS | 2592000 | Number of seconds to expire keys in. Applicable for types. Redis, postgres, memcached. `expire` option to the | +| STORAGE_DB | 2 | The name of the database. Applicable for redis, postgres, and rethinkdb | +| STORAGE_PASSWORD | | Password for database. Applicable for redis, postges, rethinkdb . | +| STORAGE_USERNAME | | Database username. Applicable for postgres, and rethinkdb | +| STORAGE_AWS_BUCKET | | Applicable for amazon-s3. This is the name of the S3 bucket | +| STORAGE_AWS_REGION | | Applicable for amazon-s3. The region in which the bucket is located | +| STORAGE_FILEPATH | | Path to file to save data to. Applicable for type file | + +### Logging + +| Name | Default value | Description | +| :---------------: | :-----------: | :---------: | +| LOGGING_LEVEL | verbose | | +| LOGGING_TYPE= | Console | +| LOGGING_COLORIZE= | true | + +### Basics + +| Name | Default value | Description | +| :----------------------: | :--------------: | :---------------------------------------------------------------------------------------: | +| HOST | 0.0.0.0 | The hostname which the server answers on | +| PORT | 7777 | The port on which the server is running | +| KEY_LENGTH | 10 | the length of the keys to user | +| MAX_LENGTH | 400000 | maximum length of a paste | +| STATIC_MAX_AGE | 86400 | max age for static assets | +| RECOMPRESS_STATIC_ASSETS | true | whether or not to compile static js assets | +| KEYGENERATOR_TYPE | phonetic | Type of key generator. Acceptable values: "phonetic", or "random" | +| KEYGENERATOR_KEYSPACE | | keySpace argument is a string of acceptable characters | +| DOCUMENTS | about=./about.md | Comma separated list of static documents to serve. ex: \n about=./about.md,home=./home.md | + +### Rate limits + +| Name | Default value | Description | +| :----------------------------------: | :-----------------------------------: | :--------------------------------------------------------------------------------------: | +| RATELIMITS_NORMAL_TOTAL_REQUESTS | 500 | By default anyone uncategorized will be subject to 500 requests in the defined timespan. | +| RATELIMITS_NORMAL_EVERY_MILLISECONDS | 60000 | The timespan to allow the total requests for uncategorized users | +| RATELIMITS_WHITELIST_TOTAL_REQUESTS | | By default client names in the whitelist will not have their requests limited. | +| RATELIMITS_WHITELIST_EVERY_SECONDS | | By default client names in the whitelist will not have their requests limited. | +| RATELIMITS_WHITELIST | example1.whitelist,example2.whitelist | Comma separated list of the clients which are in the whitelist pool | +| RATELIMITS_BLACKLIST_TOTAL_REQUESTS | | By default client names in the blacklist will be subject to 0 requests per hours. | +| RATELIMITS_BLACKLIST_EVERY_SECONDS | | By default client names in the blacklist will be subject to 0 requests per hours | +| RATELIMITS_BLACKLIST | example1.blacklist,example2.blacklist | Comma separated list of the clients which are in the blacklistpool. | + + + ## Author John Crepezzi diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..8365c5d --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,19 @@ +version: '3.0' +services: + haste-server: + build: . + networks: + - db-network + environment: + - STORAGE_TYPE=memcached + - STORAGE_HOST=memcached + - STORAGE_PORT=11211 + ports: + - 7777:7777 + memcached: + image: memcached:latest + networks: + - db-network + +networks: + db-network: diff --git a/docker-entrypoint.js b/docker-entrypoint.js new file mode 100644 index 0000000..5afff14 --- /dev/null +++ b/docker-entrypoint.js @@ -0,0 +1,108 @@ +const { + HOST, + PORT, + KEY_LENGTH, + MAX_LENGTH, + STATIC_MAX_AGE, + RECOMPRESS_STATIC_ASSETS, + STORAGE_TYPE, + STORAGE_HOST, + STORAGE_PORT, + STORAGE_EXPIRE_SECONDS, + STORAGE_DB, + STORAGE_AWS_BUCKET, + STORAGE_AWS_REGION, + STORAGE_PASSWORD, + STORAGE_USERNAME, + STORAGE_FILEPATH, + LOGGING_LEVEL, + LOGGING_TYPE, + LOGGING_COLORIZE, + KEYGENERATOR_TYPE, + KEY_GENERATOR_KEYSPACE, + RATE_LIMITS_NORMAL_TOTAL_REQUESTS, + RATE_LIMITS_NORMAL_EVERY_MILLISECONDS, + RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, + RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS, + RATE_LIMITS_WHITELIST, + RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS, + RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, + RATE_LIMITS_BLACKLIST, + DOCUMENTS, +} = process.env; + +const config = { + host: HOST, + port: PORT, + + keyLength: KEY_LENGTH, + + maxLength: MAX_LENGTH, + + staticMaxAge: STATIC_MAX_AGE, + + recompressStaticAssets: RECOMPRESS_STATIC_ASSETS, + + logging: [ + { + level: LOGGING_LEVEL, + type: LOGGING_TYPE, + colorize: LOGGING_COLORIZE, + }, + ], + + keyGenerator: { + type: KEYGENERATOR_TYPE, + keyspace: KEY_GENERATOR_KEYSPACE, + }, + + rateLimits: { + whitelist: RATE_LIMITS_WHITELIST ? RATE_LIMITS_WHITELIST.split(",") : [], + blacklist: RATE_LIMITS_BLACKLIST ? RATE_LIMITS_BLACKLIST.split(",") : [], + categories: { + normal: { + totalRequests: RATE_LIMITS_NORMAL_TOTAL_REQUESTS, + every: RATE_LIMITS_NORMAL_EVERY_MILLISECONDS, + }, + whitelist: + RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS || + RATE_LIMITS_WHITELIST_TOTAL_REQUESTS + ? { + totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, + every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS, + } + : null, + blacklist: + RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS || + RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS + ? { + totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS, + every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS, + } + : null, + }, + }, + + storage: { + type: STORAGE_TYPE, + host: STORAGE_HOST, + port: STORAGE_PORT, + expire: STORAGE_EXPIRE_SECONDS, + bucket: STORAGE_AWS_BUCKET, + region: STORAGE_AWS_REGION, + connectionUrl: `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`, + db: STORAGE_DB, + user: STORAGE_USERNAME, + password: STORAGE_PASSWORD, + path: STORAGE_FILEPATH, + }, + + documents: DOCUMENTS + ? DOCUMENTS.split(",").reduce((acc, item) => { + const keyAndValueArray = item.replace(/\s/g, "").split("="); + return { ...acc, [keyAndValueArray[0]]: keyAndValueArray[1] }; + }, {}) + : null, +}; + +console.log(JSON.stringify(config)); diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..0b089d8 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# We use this file to translate environmental variables to .env files used by the application + +set -e + +node ./docker-entrypoint.js > ./config.js + +exec "$@" diff --git a/lib/document_stores/amazon-s3.js b/lib/document_stores/amazon-s3.js new file mode 100644 index 0000000..11dd85d --- /dev/null +++ b/lib/document_stores/amazon-s3.js @@ -0,0 +1,56 @@ +/*global require,module,process*/ + +var AWS = require('aws-sdk'); +var winston = require('winston'); + +var AmazonS3DocumentStore = function(options) { + this.expire = options.expire; + this.bucket = options.bucket; + this.client = new AWS.S3({region: options.region}); +}; + +AmazonS3DocumentStore.prototype.get = function(key, callback, skipExpire) { + var _this = this; + + var req = { + Bucket: _this.bucket, + Key: key + }; + + _this.client.getObject(req, function(err, data) { + if(err) { + callback(false); + } + else { + callback(data.Body.toString('utf-8')); + if (_this.expire && !skipExpire) { + winston.warn('amazon s3 store cannot set expirations on keys'); + } + } + }); +} + +AmazonS3DocumentStore.prototype.set = function(key, data, callback, skipExpire) { + var _this = this; + + var req = { + Bucket: _this.bucket, + Key: key, + Body: data, + ContentType: 'text/plain' + }; + + _this.client.putObject(req, function(err, data) { + if (err) { + callback(false); + } + else { + callback(true); + if (_this.expire && !skipExpire) { + winston.warn('amazon s3 store cannot set expirations on keys'); + } + } + }); +} + +module.exports = AmazonS3DocumentStore; diff --git a/package-lock.json b/package-lock.json index 60815b5..669aab7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -275,21 +275,6 @@ "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "connect-ratelimit": { @@ -313,12 +298,11 @@ "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, "decamelize": { @@ -344,12 +328,6 @@ "streamsearch": "0.1.2" } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -394,7 +372,6 @@ "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", "is-regex": "^1.1.1", "object-inspect": "^1.8.0", "object-keys": "^1.1.1", @@ -409,30 +386,8 @@ "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } } } } @@ -524,21 +479,6 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "find-up": { @@ -584,20 +524,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", @@ -613,12 +539,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "optional": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -722,11 +642,6 @@ "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", "dev": true }, - "is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -881,13 +796,83 @@ "yargs": "13.3.2", "yargs-parser": "13.1.2", "yargs-unparser": "1.6.1" + }, + "dependencies": { + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "negotiator": { "version": "0.6.2", @@ -1256,9 +1241,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" diff --git a/package.json b/package.json index 7fe8725..cbe07b3 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,6 @@ "mocha": "^8.1.3" }, "bundledDependencies": [], - "engines": { - "node": "8.1.4", - "npm": "5.2.0" - }, "bin": { "haste-server": "./server.js" }, diff --git a/static/application.css b/static/application.css index 28087e3..15a06e9 100644 --- a/static/application.css +++ b/static/application.css @@ -17,6 +17,8 @@ textarea { outline: none; resize: none; font-size: 13px; + margin-top: 0; + margin-bottom: 0; } /* the line numbers */