diff --git a/client/app.js b/client/app.js index 540136d..d167d03 100644 --- a/client/app.js +++ b/client/app.js @@ -79,6 +79,7 @@ app.addSetter('load.user', async (data) => { data.user = await res.json(); window.uid = data.user.id; + window.csrfToken = data.user.csrf; dispatchSocketConnect(); store.do("loader.hide"); @@ -183,7 +184,10 @@ async function multipartUpload(file, boardId, newBoardName, siteUrl, description let res = await fetch("./multiup", { method: "POST", - body: formData + body: formData, + headers: { + "x-csrf-token": window.csrfToken + } }); if ( res.status == 200 ){ @@ -449,6 +453,11 @@ window.ondrop = async (evt) => { }; +function getCookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(';').shift(); +} diff --git a/client/components/addpin.js b/client/components/addpin.js index d329b33..1b13a3b 100644 --- a/client/components/addpin.js +++ b/client/components/addpin.js @@ -94,7 +94,8 @@ app.addSetter('addPinModal.save', async (data) => { let res = await fetch('api/pins', { method: 'POST', headers: { - 'Content-Type': "application/json" + 'Content-Type': "application/json", + "x-csrf-token" : window.csrfToken }, body: JSON.stringify(postData) }); diff --git a/client/components/brickwall.js b/client/components/brickwall.js index 6d941db..cc45efd 100644 --- a/client/components/brickwall.js +++ b/client/components/brickwall.js @@ -32,7 +32,10 @@ app.addSetter("brickwall.deletePin", async (data) => { closeLightGallery(); let res = await fetch(`api/pins/${pinId}`, { - method: 'DELETE' + method: 'DELETE', + headers: { + 'x-csrf-token': window.csrfToken + } }); if ( res.status == 200 ){ @@ -63,7 +66,7 @@ async function iosShare(){ let index = getLightGalleryIndex(); let pin = data.board.pins[index]; - let result = await fetch("/api/pins/" + pin.id + "/otl", {method: 'POST'}); + let result = await fetch("/api/pins/" + pin.id + "/otl", {method: 'POST', headers: {'x-csrf-token':window.csrfToken}}); let obj = await result.json(); let t = obj.t; diff --git a/client/components/editboard.js b/client/components/editboard.js index b238be7..9531a90 100644 --- a/client/components/editboard.js +++ b/client/components/editboard.js @@ -33,6 +33,7 @@ app.addSetter('editBoardModal.save', async (data) => { method: 'POST', headers: { 'Content-Type': 'application/json', + 'x-csrf-token': window.csrfToken }, body: JSON.stringify({ name: name, @@ -70,7 +71,10 @@ app.addSetter('editBoardModal.delete', async (data) => { let res = await fetch(`/api/boards/${boardId}`, { - method: 'DELETE' + method: 'DELETE', + headers: { + 'x-csrf-token':window.csrfToken + } }); if ( res.status == 200 ){ diff --git a/client/components/editpin.js b/client/components/editpin.js index f8b4e85..9ff1df8 100644 --- a/client/components/editpin.js +++ b/client/components/editpin.js @@ -56,7 +56,10 @@ app.addSetter("editPinModal.save", async (data) => { // TODO: make a helper method let res = await fetch("/api/boards", { method: 'POST', - headers: {'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json' + 'x-csrf-token': window.csrfToken + }, body: JSON.stringify({ "name": data.editPinModal.newBoardName }) @@ -79,7 +82,8 @@ app.addSetter("editPinModal.save", async (data) => { let res = await fetch('api/pins/' + pin.id, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'x-csrf-token': window.csrfToken }, body: JSON.stringify(postData) }); diff --git a/package-lock.json b/package-lock.json index 6079e68..d9bf50e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,14 @@ "better-sqlite3": "^7.4.3", "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", + "csurf": "^1.11.0", "eta": "^1.12.3", "express": "^4.17.1", + "express-rate-limit": "^5.4.0", "express-ws": "^5.0.2", "multer": "^1.4.3", "node-fetch": "^2.6.1", + "sanitize-filename": "^1.6.3", "sharp": "^0.29.1", "yargs": "^17.2.1" } @@ -388,6 +391,48 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "node_modules/csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "dependencies": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/csurf": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", + "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", + "dependencies": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/csurf/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -583,6 +628,11 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.4.0.tgz", + "integrity": "sha512-sT+rk1wvj06+0MpEiij7y3kGdB4hoMyQ+a5zcESUpDMLhbLXoYIQI6JfsvLBz1wOhmfF//ALG/Q59FKMI0x2Eg==" + }, "node_modules/express-ws": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", @@ -1071,6 +1121,14 @@ "node": ">=0.6" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1129,6 +1187,11 @@ "node": ">=0.10.0" } }, + "node_modules/rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1139,6 +1202,14 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1415,6 +1486,22 @@ "node": ">=0.6" } }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1443,6 +1530,17 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1451,6 +1549,11 @@ "node": ">= 0.8" } }, + "node_modules/utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1937,6 +2040,41 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + } + }, + "csurf": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", + "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.3" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2102,6 +2240,11 @@ } } }, + "express-rate-limit": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.4.0.tgz", + "integrity": "sha512-sT+rk1wvj06+0MpEiij7y3kGdB4hoMyQ+a5zcESUpDMLhbLXoYIQI6JfsvLBz1wOhmfF//ALG/Q59FKMI0x2Eg==" + }, "express-ws": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz", @@ -2471,6 +2614,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2517,6 +2665,11 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -2527,6 +2680,14 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -2739,6 +2900,19 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2761,11 +2935,24 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index b8236a4..63c6cac 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,14 @@ "better-sqlite3": "^7.4.3", "body-parser": "^1.19.0", "cookie-parser": "^1.4.5", + "csurf": "^1.11.0", "eta": "^1.12.3", "express": "^4.17.1", + "express-rate-limit": "^5.4.0", "express-ws": "^5.0.2", "multer": "^1.4.3", "node-fetch": "^2.6.1", + "sanitize-filename": "^1.6.3", "sharp": "^0.29.1", "yargs": "^17.2.1" } diff --git a/server/auth.js b/server/auth.js index 109bb67..8bb8728 100644 --- a/server/auth.js +++ b/server/auth.js @@ -99,7 +99,7 @@ module.exports = async (req, res, next) => { console.log("login"); // res.type("html").sendFile(path.resolve('./templates/login.html')); - res.render("login", { registerEnabled: dao.getProperty("registerEnabled") }); + res.render("login", { registerEnabled: dao.getProperty("registerEnabled"), csrfToken: req.csrfToken() }); return; } else if ( req.method == "POST" && req.originalUrl == "/login" ){ let username = req.body.username; @@ -136,7 +136,7 @@ module.exports = async (req, res, next) => { return; } - res.render("register", {}); + res.render("register", { csrfToken: req.csrfToken() }); return; } else if ( req.method == "POST" && req.originalUrl == "/register" ){ diff --git a/server/server.js b/server/server.js index fc8b8df..d26e7b5 100644 --- a/server/server.js +++ b/server/server.js @@ -2,6 +2,7 @@ const yargs = require('yargs'); const express = require('express'); const bodyParser = require('body-parser'); const multer = require("multer") +const RateLimit = require("express-rate-limit"); const path = require('path'); const cookieParser = require('cookie-parser'); const tokenUtil = require('./token-utils.js'); @@ -10,10 +11,21 @@ const conf = require("./conf.js"); const imageUtils = require('./image-utils.js'); var eta = require("eta"); const tokenUtils = require('./token-utils.js'); +const csrf = require("csurf"); +const sanitizeFilename = require("sanitize-filename"); // consider using temp files, but we're going to limit the size so should be ok const upload = multer({storage:multer.memoryStorage(), limits: {fileSize: 26214400, files: 1}}); // 1 - 25MB file +// enable a rate limit so we can't swamp the server/filesystem -- 100 per second here. Pretty fast because we're likely +// running locally and want to load images fast. +// GitHub CodeQL - js/missing-rate-limiting +const rateLimiter = new RateLimit({ + windowMs: 1000, + max: 100 +}); + + module.exports = async () => { process.on('SIGINT', () => { @@ -88,6 +100,8 @@ module.exports = async () => { app.set("views", "./templates") const expressWs = require('express-ws')(app); + + app.use(rateLimiter); // rate limiting app.use(bodyParser.raw({type: 'image/jpeg', limit: '25mb'})); // accept image/jpeg files only app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()); @@ -96,6 +110,44 @@ module.exports = async () => { + // api method that are not subject to CSRF checks + // handle raw uploads for pin creation + app.post("/up", async (req, res) => { + + try { + require("fs").writeFileSync("up.jpg", req.body); + + // try to parse the image first... if this blows up we'll stop early + let image = await imageUtils.processImage(req.body); + + let boardName = req.headers['board-name'].trim(); + + // get the board + let board = dao.findBoardByUserAndName(req.user.id, boardName); + + if ( !board ){ + board = dao.createBoard(req.user.id, boardName, 0); + } + + let pin = dao.createPin(req.user.id, board.id, null, null, null, null, image.original.height, image.original.width, image.thumbnail.height, image.thumbnail.height); + + await imageUtils.saveImage(req.user.id, pin.id, image); + + broadcast(req.user.id, {updateBoard:board.id}); + res.status(200).send(pin); + + } catch (err){ + console.log(`Error uploading pin`, err); + res.status(500).send(SERVER_ERROR); + } + }); + + + // all other endpoints require csrf + app.use(csrf({cookie:true})); + + + // accept websocket connections. currently are parsing the userid from the path to // map the connections to only notify on changes from the same user. // this simple mapping of holding all connections in memory here won't really scale beyond @@ -145,7 +197,7 @@ module.exports = async () => { res.sendStatus(403); return; } - res.send({name: user.username, id: user.id, admin: user.admin, version: VERSION}); + res.send({name: user.username, id: user.id, admin: user.admin, version: VERSION, csrf: req.csrfToken()}); }); // list boards @@ -227,7 +279,7 @@ module.exports = async () => { res.status(404).send(NOT_FOUND); } } catch (err) { - console.log(`Error deleting board#${req.params.boardId}:`, err); + console.log('Error deleting board# %s', req.params.boardId, err); res.status(500).send(SERVER_ERROR); } }); @@ -242,7 +294,7 @@ module.exports = async () => { res.status(404).send(NOT_FOUND); } } catch (err){ - console.error(`Error getting pin#${req.params.pinId}: ${err.message}`, err); + console.error('Error getting pin# %s', req.params.pinId, err); res.status(500).send(SERVER_ERROR); } }); @@ -295,7 +347,7 @@ module.exports = async () => { res.status(404).send(NOT_FOUND); } } catch (err) { - console.log(`Error updating pin#${req.params.pinId}`, err); + console.log('Error updating pin# %s', req.params.pinId, err); res.status(500).send(SERVER_ERROR); } @@ -325,7 +377,7 @@ module.exports = async () => { } } catch (err){ - console.log(`Error deleting pin#${req.params.pinId}`, err); + console.log('Error deleting pin# %s', req.params.pinId, err); res.status(500).send(SERVER_ERROR); } }); @@ -344,37 +396,7 @@ module.exports = async () => { res.status(200).send({t: token}); }); - // handle raw uploads for pin creation - app.post("/up", async (req, res) => { - try { - - require("fs").writeFileSync("up.jpg", req.body); - - // try to parse the image first... if this blows up we'll stop early - let image = await imageUtils.processImage(req.body); - - let boardName = req.headers['board-name'].trim(); - - // get the board - let board = dao.findBoardByUserAndName(req.user.id, boardName); - - if ( !board ){ - board = dao.createBoard(req.user.id, boardName, 0); - } - - let pin = dao.createPin(req.user.id, board.id, null, null, null, null, image.original.height, image.original.width, image.thumbnail.height, image.thumbnail.height); - - await imageUtils.saveImage(req.user.id, pin.id, image); - - broadcast(req.user.id, {updateBoard:board.id}); - res.status(200).send(pin); - - } catch (err){ - console.log(`Error uploading pin`, err); - res.status(500).send(SERVER_ERROR); - } - }); // handle multipart uploads for pin creation @@ -431,6 +453,7 @@ module.exports = async () => { } res.render("settings", { + csrfToken: req.csrfToken(), registerEnabled: registerEnabled, users: users, userId: req.user.id @@ -478,8 +501,6 @@ module.exports = async () => { let password = req.body.password; let repeatPassword = req.body.repeatPassword; - console.log(`username: ${username} password: ${password} rp: ${repeatPassword}`); - if ( password != repeatPassword ){ res.redirect("./settings#password-match") return; @@ -491,7 +512,7 @@ module.exports = async () => { try{ dao.createUser(username, 0, key, salt); } catch (err){ - console.log("error creating user " + username, err); + console.log("error creating user %s", username, err); res.redirect("./settings#create-user-error"); return; } @@ -503,11 +524,17 @@ module.exports = async () => { let uid = req.body.uid; + // uids must ONLY be integer numbers, to ensure that this is safe to use in the path below + if ( !uid.match(/^[0-9]+$/) ){ + console.log("Invalid uid: %s", uid); + res.redirect("./settings#delete-user-error"); + } + try { dao.deleteUser(uid); require("fs").rmdirSync(conf.getImagePath() + "/" + uid , { recursive: true }); } catch (err){ - console.log("error deleting user " + uid, err); + console.log("error deleting user %s", uid, err); res.redirect("./settings#delete-user-error"); return; } diff --git a/templates/login.eta b/templates/login.eta index 175499e..0d52a4e 100644 --- a/templates/login.eta +++ b/templates/login.eta @@ -26,6 +26,7 @@
tinypin » log in