diff --git a/package-lock.json b/package-lock.json index c608ef4..8910a37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1974,31 +1974,12 @@ "dev": true }, "bcrypt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz", - "integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", "requires": { - "node-addon-api": "^2.0.0", - "node-pre-gyp": "0.14.0" - }, - "dependencies": { - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - } + "node-addon-api": "^3.0.0", + "node-pre-gyp": "0.15.0" } }, "big.js": { @@ -4767,9 +4748,9 @@ } }, "needle": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", - "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", + "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -4823,9 +4804,9 @@ "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" }, "node-addon-api": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.1.tgz", - "integrity": "sha512-2WVfwRfIr1AVn3dRq4yRc2Hn35ND+mPJH6inC6bjpYCZVrpXPB4j3T6i//OGVfqVsR1t/X/axRulDsheq4F0LQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", + "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" }, "node-libs-browser": { "version": "2.2.1", @@ -4866,6 +4847,23 @@ } } }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, "node-releases": { "version": "1.1.60", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", @@ -5385,6 +5383,11 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -6522,6 +6525,14 @@ "setimmediate": "^1.0.4" } }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -6787,6 +6798,31 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/package.json b/package.json index cb53435..9d8b8e8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "run-p --race dev cy:run" }, "dependencies": { - "bcrypt": "^4.0.1", + "bcrypt": "^5.0.0", "body-parser": "^1.19.0", "cl-editor": "^2.1.0", "compression": "^1.7.1", @@ -28,7 +28,8 @@ "passport": "^0.4.1", "passport-local": "^1.0.0", "rate-limiter-flexible": "^2.1.10", - "session-file-store": "^1.4.0" + "session-file-store": "^1.4.0", + "useragent": "^2.3.0" }, "devDependencies": { "@babel/core": "^7.11.4", diff --git a/src/routes/cms/register.js b/src/routes/cms/register.js new file mode 100644 index 0000000..87ed5c7 --- /dev/null +++ b/src/routes/cms/register.js @@ -0,0 +1,93 @@ +import User from '../../models/user.js'; + +export async function post(req, res) +{ + if (req.user) { + res.writeHead(401, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You are already logged in` + })); + return false; + } + let { username, password, password_confirm, realname } = req.body; + if (!username || !password || !password_confirm || !realname) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You need to supply a username, real name, password, and password confirmation.` + })); + return false; + } + if (password.length < 8) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `The password must be at least 8 characters long.` + })); + return false; + } + if (password !== password_confirm) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `The password does not match the confirmation.` + })); + return false; + } + if (!/^[a-z0-9.]+$/i.test(username)) { + res.writeHead(422, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `The username can only contain letters, numbers, and periods.` + })); + return false; + } + /* + try { + await registerRateLimiter.consume(); + } catch (err) { + res.writeHead(429, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `Too Many Requests` + })); + return false; + } + */ + try { + const user = await User.findOne({ username: req.body.username }); + if (user) { + res.writeHead(401, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `This username is taken.` + })); + return false; + } + // password gets automatically hashed + const newUser = await new User({ username, realname, password }); + await newUser.save(); + + req.login(newUser, err => { + if (err) throw err; + return res.redirect('/cms'); + }); + } catch (err) { + console.error(err); + res.writeHead(500, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `Internal server error` + })); + return false; + } +} diff --git a/src/routes/me/avatar.js b/src/routes/me/avatar.js new file mode 100644 index 0000000..1aae86f --- /dev/null +++ b/src/routes/me/avatar.js @@ -0,0 +1,26 @@ +import fs from 'fs'; +import User from '../../models/user.js'; + +export async function del(req, res) +{ + if (!req.user) { + res.writeHead(401, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You must be logged in to set an avatar.` + })); + return false; + } + const user = await User.findById(req.user._id); + const filename = 'default.jpg'; + if (user.avatar !== filename) { + fs.unlinkSync(`./static/u/${user.avatar}`); + } + req.user.avatar = user.avatar = filename; + await user.save(); + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ filename })); +} diff --git a/src/routes/me/index.js b/src/routes/me/index.js new file mode 100644 index 0000000..21265c7 --- /dev/null +++ b/src/routes/me/index.js @@ -0,0 +1,16 @@ +export function get(req, res, next) +{ + if (req.user) { + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify(req.user)); + } else { + res.writeHead(401, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ + message: `You are not logged in` + })); + } +} diff --git a/src/server.js b/src/server.js index 2ee74c6..cf11f3e 100644 --- a/src/server.js +++ b/src/server.js @@ -12,6 +12,7 @@ import fileUpload from 'express-fileupload'; import fs from 'fs'; import cors from 'cors'; import helmet from 'helmet'; +import useragent from 'useragent'; import crypto from 'crypto'; import Article from './models/article.js'; import Category from './models/category.js'; @@ -130,101 +131,6 @@ express() .use(passport.initialize()) .use(passport.session()) - .post('/cms/register', - function(req, res, next) { - if (!req.user) { - next(); - } else { - res.writeHead(401, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `You are already logged in` - })); - } - }, async (req, res) => { - let { username, password, password_confirm, realname } = req.body; - if (!username || !password || !password_confirm || !realname) { - res.writeHead(422, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `You need to supply a username, real name, password, and password confirmation.` - })); - return false; - } - if (password.length < 8) { - res.writeHead(422, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `The password must be at least 8 characters long.` - })); - return false; - } - if (password !== password_confirm) { - res.writeHead(422, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `The password does not match the confirmation.` - })); - return false; - } - if (!/^[a-z0-9.]+$/i.test(username)) { - res.writeHead(422, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `The username can only contain letters, numbers, and periods.` - })); - return false; - } - /* - try { - await registerRateLimiter.consume(); - } catch (err) { - res.writeHead(429, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `Too Many Requests` - })); - return false; - } - */ - try { - const user = await User.findOne({ username: req.body.username }); - if (user) { - res.writeHead(401, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `This username is taken.` - })); - return false; - } - // password gets automatically hashed - const newUser = await new User({ username, realname, password }); - await newUser.save(); - - req.login(newUser, err => { - if (err) throw err; - return res.redirect('/cms'); - }); - } catch (err) { - console.error(err); - res.writeHead(500, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `Internal server error` - })); - return false; - } - } - ) - .post('/cms/login', // rateLimiterMiddleware(loginAttemptRateLimiter), passport.authenticate('local', { failWithError: true }), @@ -433,22 +339,6 @@ express() } ) - .get('/me', function(req, res, next) { - if (req.user) { - res.writeHead(200, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify(req.user)); - } else { - res.writeHead(401, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `You are not logged in` - })); - } - }) - .post('/me/avatar', async function(req, res, next) { if (!req.user) { @@ -511,33 +401,12 @@ express() } ) - .delete('/me/avatar', - async function(req, res, next) { - if (!req.user) { - res.writeHead(401, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ - message: `You must be logged in to set an avatar.` - })); - return false; - } - const user = await User.findById(req.user._id); - const filename = 'default.jpg'; - if (user.avatar !== filename) { - fs.unlinkSync(`./static/u/${user.avatar}`); - } - req.user.avatar = user.avatar = filename; - await user.save(); - res.writeHead(200, { - 'Content-Type': 'application/json' - }); - res.end(JSON.stringify({ filename })); - } - ) - .use(compression({ threshold: 0 })) .use(express.static('./static')) + .use(async function (req, res, next) { + if (req.useragent.browser) { + } + }) .use(sapper.middleware({ session: req => ({ user: req.session.passport ? req.session.passport.user : null