From 6224b124a81bfbb9184475b84e46a09472a5fe07 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 14 Mar 2025 09:21:59 +0200 Subject: [PATCH] add prod settings for backend and a bundle script --- .gitignore | 1 + backend/.gitignore | 2 + backend/package-lock.json | 124 ++++++++++++++++++++++++++++++++++++- backend/package.json | 11 +++- backend/public/.gitkeep | 0 backend/{ => src}/main.mjs | 77 +++++++++++++++-------- bundle.sh | 26 ++++++++ clean.sh | 9 +++ 8 files changed, 219 insertions(+), 31 deletions(-) create mode 100644 .gitignore create mode 100644 backend/public/.gitkeep rename backend/{ => src}/main.mjs (87%) create mode 100755 bundle.sh create mode 100755 clean.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c1fa35 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ucs-*.tar.gz diff --git a/backend/.gitignore b/backend/.gitignore index 656ad45..0dc5577 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -3,3 +3,5 @@ node_modules ucs.db ucs.db-shm ucs.db-wal +public/*.js +public/*.css diff --git a/backend/package-lock.json b/backend/package-lock.json index e92514c..05d3589 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,7 +11,11 @@ "dependencies": { "@blackglory/better-sqlite3-migrations": "^0.1.19", "better-sqlite3": "^11.8.1", - "express": "^4.21.2" + "compression": "^1.8.0", + "express": "^4.21.2", + "express-rate-limit": "^7.5.0", + "helmet": "^8.0.0", + "morgan": "^1.10.0" }, "devDependencies": { "nodemon": "^3.1.9" @@ -123,6 +127,24 @@ ], "license": "MIT" }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/better-sqlite3": { "version": "11.8.1", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.8.1.tgz", @@ -308,6 +330,45 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -550,6 +611,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/extra-lazy": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/extra-lazy/-/extra-lazy-1.3.1.tgz", @@ -743,6 +819,15 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", + "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -986,6 +1071,34 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1107,6 +1220,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/backend/package.json b/backend/package.json index bf02edb..f04945a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,7 +2,7 @@ "name": "ucs-backend", "version": "0.1.0", "description": "Backend for the Universal Comment System", - "main": "main.mjs", + "main": "src/main.mjs", "author": "alloca", "license": "MPL-2.0", "repository": { @@ -10,13 +10,18 @@ "url": "https://git.alloca.space/me/ucs.git" }, "scripts": { - "start": "nodemon main.mjs", + "start": "nodemon src/main.mjs", + "prod": "NODE_ENV=production node src/main.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "@blackglory/better-sqlite3-migrations": "^0.1.19", "better-sqlite3": "^11.8.1", - "express": "^4.21.2" + "compression": "^1.8.0", + "express": "^4.21.2", + "express-rate-limit": "^7.5.0", + "helmet": "^8.0.0", + "morgan": "^1.10.0" }, "devDependencies": { "nodemon": "^3.1.9" diff --git a/backend/public/.gitkeep b/backend/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend/main.mjs b/backend/src/main.mjs similarity index 87% rename from backend/main.mjs rename to backend/src/main.mjs index 8e05b8c..be29660 100644 --- a/backend/main.mjs +++ b/backend/src/main.mjs @@ -1,6 +1,10 @@ import express from 'express'; import Database from 'better-sqlite3'; import { migrate } from '@blackglory/better-sqlite3-migrations'; +import compression from 'compression'; +import helmet from 'helmet'; +import RateLimit from 'express-rate-limit'; +import morgan from 'morgan'; // Constants @@ -63,39 +67,26 @@ migrate(db, migrations, 1); const app = express(); const port = 8080; -app.use(express.static("public")); app.use(express.urlencoded({ extended: true })); +app.use(compression()); +app.use( + helmet.contentSecurityPolicy({ + directives: { + "script-src": ["'self'"] + }, + }) +); +app.use(morgan('combined')); -app.get('/', (req, res) => { - res.send(`

Hello, ${req.get('User-Agent')}!

`); -}); - -// GET -app.get('/url/:site/*', (req, res) => { - const site = req.params.site; - const path = req.params[0]; - const stmt = db.prepare(` -SELECT - c.id as id, - user, - user_website, - message, - published, - reply_to -FROM - (SELECT id from site where url = @site) s -JOIN (SELECT * FROM comment WHERE path = @path) c -ON c.site = s.id -ORDER BY c.id -;`); - const comments = stmt.all({ site, path }); - res.json(comments); +// POST +const limiter20 = RateLimit({ + windowMs: 1 * 60 * 1000, + max: 20, }); app.use(express.json()); -// POST -app.post('/url/:site/*', (req, res) => { +app.post('/url/:site/*', limiter20, (req, res) => { const site = req.params.site; const path = req.params[0]; @@ -156,6 +147,38 @@ RETURNING } }); + +// GET +const limiter500 = RateLimit({ + windowMs: 1 * 60 * 1000, + max: 500, +}); + +app.use(limiter500); + +app.use(express.static("public")); + +app.get('/url/:site/*', (req, res) => { + const site = req.params.site; + const path = req.params[0]; + const stmt = db.prepare(` +SELECT + c.id as id, + user, + user_website, + message, + published, + reply_to +FROM + (SELECT id from site where url = @site) s +JOIN (SELECT * FROM comment WHERE path = @path) c +ON c.site = s.id +ORDER BY c.id +;`); + const comments = stmt.all({ site, path }); + res.json(comments); +}); + app.listen(port, () => { console.log(`Listening on port ${port}`); }); diff --git a/bundle.sh b/bundle.sh new file mode 100755 index 0000000..4daa373 --- /dev/null +++ b/bundle.sh @@ -0,0 +1,26 @@ +#!/usr/bin/bash + +set -e -u -o pipefail + +# Build frontend + +cd frontend +npm install +npm run build +(cd dist/assets && mv *.js ucs.js && mv *.css ucs.css) +cd .. + +# Build backend + +cd backend +npm install +cp ../frontend/dist/assets/* public/ + +# Bundle + +PACKAGE="ucs-$(cat package.json | jq -r .version).tar.gz" + +tar czvf "$PACKAGE" package.json package-lock.json src/ node_modules/ public/ +cd .. +mv backend/"$PACKAGE" . +echo "$PACKAGE created." diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..8c38933 --- /dev/null +++ b/clean.sh @@ -0,0 +1,9 @@ +#!/usr/bin/bash + +set -e -u -o pipefail + +rm -rf frontend/node_modules/ +rm -rf backend/node_modules/ +rm -f ucs-*.tar.gz + +echo "Cleaned."