early captchas

merge-requests/208/head
fatchan 5 years ago
parent e7dc699cbc
commit 8a0160a924
  1. 2
      .gitignore
  2. 20
      controllers/forms.js
  3. 14
      controllers/pages.js
  4. 25
      db/captchas.js
  5. 26
      gulp/res/css/style.css
  6. 8
      helpers/captchagenerate.js
  7. 47
      helpers/captchaverify.js
  8. 0
      helpers/paramconverter.js
  9. 27
      models/pages/banners.js
  10. 32
      models/pages/captcha.js
  11. 2
      server.js
  12. 4
      views/includes/actionfooter.pug
  13. 2
      views/includes/boardheader.pug
  14. 4
      views/includes/postform.pug
  15. 2
      views/mixins/catalogtile.pug
  16. 6
      views/pages/message.pug
  17. 5
      wipe.js

2
.gitignore vendored

@ -1,4 +1,6 @@
node_modules/
configs/*.json
uploads/img/*
uploads/captcha/*
gulp/dist/
tmp/

@ -4,6 +4,7 @@ const express = require('express')
, router = express.Router()
, Boards = require(__dirname+'/../db/boards.js')
, Posts = require(__dirname+'/../db/posts.js')
, Captchas = require(__dirname+'/../db/captchas.js')
, Trips = require(__dirname+'/../db/trips.js')
, Bans = require(__dirname+'/../db/bans.js')
, Mongo = require(__dirname+'/../db/db.js')
@ -27,8 +28,9 @@ const express = require('express')
, registerAccount = require(__dirname+'/../models/forms/register.js')
, checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js')
, checkPerms = require(__dirname+'/../helpers/hasperms.js')
, numberConverter = require(__dirname+'/../helpers/number-converter.js')
, paramConverter = require(__dirname+'/../helpers/paramconverter.js')
, banCheck = require(__dirname+'/../helpers/bancheck.js')
, verifyCaptcha = require(__dirname+'/../helpers/captchaverify.js')
, actionChecker = require(__dirname+'/../helpers/actionchecker.js');
// login to account
@ -117,7 +119,7 @@ router.post('/changepassword', async (req, res, next) => {
});
//register account
router.post('/register', (req, res, next) => {
router.post('/register', verifyCaptcha, (req, res, next) => {
const errors = [];
@ -159,7 +161,7 @@ router.post('/register', (req, res, next) => {
});
// make new post
router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, async (req, res, next) => {
router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verifyCaptcha, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@ -208,7 +210,7 @@ router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, asyn
});
//upload banners
router.post('/board/:board/addbanners', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => {
router.post('/board/:board/addbanners', Boards.exists, banCheck, checkPermsMiddleware, paramConverter, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@ -244,7 +246,7 @@ router.post('/board/:board/addbanners', Boards.exists, banCheck, checkPermsMiddl
});
//delete banners
router.post('/board/:board/deletebanners', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => {
router.post('/board/:board/deletebanners', Boards.exists, banCheck, checkPermsMiddleware, paramConverter, async (req, res, next) => {
const errors = [];
@ -280,7 +282,7 @@ router.post('/board/:board/deletebanners', Boards.exists, banCheck, checkPermsMi
});
//report/delete/spoiler/ban
router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, async (req, res, next) => {
router.post('/board/:board/actions', Boards.exists, banCheck, paramConverter, verifyCaptcha, async (req, res, next) => {
const errors = [];
@ -494,7 +496,7 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a
});
//unban
router.post('/board/:board/unban', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => {
router.post('/board/:board/unban', Boards.exists, banCheck, checkPermsMiddleware, paramConverter, async (req, res, next) => {
//keep this for later in case i add other options to unbans
const errors = [];
@ -526,7 +528,7 @@ router.post('/board/:board/unban', Boards.exists, banCheck, checkPermsMiddleware
});
router.post('/global/actions', checkPermsMiddleware, numberConverter, async(req, res, next) => {
router.post('/global/actions', checkPermsMiddleware, paramConverter, async(req, res, next) => {
const errors = [];
@ -640,7 +642,7 @@ router.post('/global/actions', checkPermsMiddleware, numberConverter, async(req,
});
router.post('/global/unban', checkPermsMiddleware, numberConverter, async(req, res, next) => {
router.post('/global/unban', checkPermsMiddleware, paramConverter, async(req, res, next) => {
const errors = [];

@ -5,7 +5,7 @@ const express = require('express')
, Boards = require(__dirname+'/../db/boards.js')
, hasPerms = require(__dirname+'/../helpers/haspermsmiddleware.js')
, isLoggedIn = require(__dirname+'/../helpers/isloggedin.js')
, numberConverter = require(__dirname+'/../helpers/number-converter.js')
, paramConverter = require(__dirname+'/../helpers/paramconverter.js')
//page models
, home = require(__dirname+'/../models/pages/home.js')
, register = require(__dirname+'/../models/pages/register.js')
@ -15,6 +15,8 @@ const express = require('express')
, login = require(__dirname+'/../models/pages/login.js')
, board = require(__dirname+'/../models/pages/board.js')
, catalog = require(__dirname+'/../models/pages/catalog.js')
, banners = require(__dirname+'/../models/pages/banners.js')
, captcha = require(__dirname+'/../models/pages/captcha.js')
, thread = require(__dirname+'/../models/pages/thread.js');
//homepage with board list
@ -42,6 +44,12 @@ router.get('/logout', isLoggedIn, (req, res, next) => {
});
// get captcha
router.get('/captcha', captcha);
// random board banner
router.get('/banners', banners);
//board manage page
router.get('/:board/manage', Boards.exists, isLoggedIn, hasPerms, manage);
@ -49,10 +57,10 @@ router.get('/:board/manage', Boards.exists, isLoggedIn, hasPerms, manage);
router.get('/globalmanage', isLoggedIn, hasPerms, globalManage);
// board page/recents
router.get('/:board', Boards.exists, numberConverter, board);
router.get('/:board', Boards.exists, paramConverter, board);
// thread view page
router.get('/:board/thread/:id(\\d+)', Boards.exists, numberConverter, thread);
router.get('/:board/thread/:id(\\d+)', Boards.exists, paramConverter, thread);
// board catalog page
router.get('/:board/catalog', Boards.exists, catalog);

@ -0,0 +1,25 @@
'use strict';
const Mongo = require(__dirname+'/db.js')
, db = Mongo.client.db('jschan').collection('captchas');
module.exports = {
db,
findOne: (id) => {
return db.findOne({ '_id': id });
},
insertOne: (text) => {
return db.insertOne({
'text': text,
'expireAt': new Date((new Date).getTime() + (5*1000*60)) //5 minute expiration
});
},
deleteAll: () => {
return db.deleteMany({});
},
}

@ -41,6 +41,8 @@ object {
align-items: center;
}
.catalog-tile-button {
width: 100%;
line-height: 30px;
@ -150,9 +152,7 @@ span {
.post-container, .pages, .toggle-label {
background: #D6DAF0;
border-color: #B7C5D9;
border-width: 0 1px 1px 0;
border-style: none solid solid none;
border: 1px solid #B7C5D9;
}
.actions {
@ -392,7 +392,10 @@ input[type="text"], input[type="submit"], input[type="password"], input[type="fi
margin: 0;
flex-grow: 1;
border-radius: 0px;
min-height: 29px;
}
input[type="submit"] {
min-height: 30px;
}
input[type="file"] {
@ -406,6 +409,17 @@ input[type="file"] {
max-width: 100%;
}
.postform-data {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.captcha {
margin: auto;
margin-bottom: 1px;
}
.postform-label {
padding: 3px;
border: 1px solid black;
@ -424,8 +438,6 @@ input[type="file"] {
hr {
color: lightgray;
/*border-top: 1px solid black;
background: lightgray;*/
}
@media only screen and (max-width: 800px) {
@ -454,7 +466,6 @@ hr {
.post-container {
width: 100%;
border: none;
}
.catalog-tile {
@ -467,7 +478,6 @@ hr {
.post-info {
background-color: #B7C5D9;
/*width: 100%;*/
}
}

@ -1,6 +1,4 @@
const gm = require('@tohru/gm')
, crypto = require('crypto')
, fs = require('fs')
, rr = (min, max) => Math.floor(Math.random() * (max-min + 1) + min)
, width = 200
, height = 80;
@ -14,9 +12,9 @@ function getShape() {
return { x1, x2, y1, y2 };
}
module.exports = () => {
module.exports = (text, captchaId) => {
return new Promise((resolve, reject) => {
const text = crypto.randomBytes(20).toString('hex').substring(0,6).split('')
text = text.split(''); //array of chars
const x = gm(200, 80, '#fff')
.fill('#000')
.fontSize(80)
@ -31,7 +29,7 @@ module.exports = () => {
x.wave(10, rr(50,80))
.blur(1, 2)
.crop(200, 80, 0, 0)
.write('./static/img/captcha.jpg', (err) => {
.write(`./uploads/captcha/${captchaId}.png`, (err) => {
if (err) {
return reject();
}

@ -0,0 +1,47 @@
'use strict';
const Captchas = require(__dirname+'/../db/captchas.js')
, Mongo = require(__dirname+'/../db/db.js');
module.exports = async (req, res, next) => {
//check if captcha field in form is valid
const input = req.body.captcha;
if (!input || input.length !== 6) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect captcha'
});
}
//make sure they have captcha cookie and its 24 chars
const captchaId = req.cookies.captchaid;
if (!captchaId || captchaId.length !== 24) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Captcha expired'
});
}
// try to get the captcha from the DB
let captcha;
try {
const captchaMongoId = Mongo.ObjectId(captchaId);
captcha = await Captchas.findOne(captchaMongoId);
} catch (err) {
return next(err);
}
//check that it exists and matches captcha in DB
if (!captcha || captcha.text !== input) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect captcha'
});
}
//it was correct, so continue
res.clearCookie('captchaid');
return next();
}

@ -0,0 +1,27 @@
'use strict';
const Boards = require(__dirname+'/../../db/boards.js');
module.exports = async (req, res, next) => {
if (!req.query.board) {
return next();
}
// get all threads
let board;
try {
board = await Boards.findOne(req.query.board);
} catch (err) {
return next(err);
}
if (!board) {
return next();
}
const randomBanner = board.banners[Math.floor(Math.random()*board.banners.length)];
return res.redirect(`/img/${randomBanner}`);
}

@ -0,0 +1,32 @@
'use strict';
const crypto = require('crypto')
, Captchas = require(__dirname+'/../../db/captchas.js')
, generateCaptcha = require(__dirname+'/../../helpers/captchagenerate.js');
module.exports = async (req, res, next) => {
//will move captcha cookie check to nginx at some point
if (req.cookies.captchaid) {
return res.redirect(`/captcha/${req.cookies.captchaid}.png`);
}
// if we got here, they dont have a cookie so we need to
// gen a captcha, set their cookie and redirect to the captcha
const text = crypto.randomBytes(20).toString('hex').substring(0,6);
let captchaId;
try {
captchaId = await Captchas.insertOne(text).then(r => r.insertedId); //get id of document as filename and captchaid
await generateCaptcha(text, captchaId);
} catch (err) {
return next(err);
}
return res
.cookie('captchaid', captchaId, {
'maxAge': 5*60*1000, //5 minute cookie
'httpOnly': true
})
.redirect(`/captcha/${captchaId}.png`);
}

@ -53,7 +53,7 @@ const express = require('express')
// use pug view engine
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views/pages'));
app.enable('view cache');
// app.enable('view cache');
// routes
app.use('/forms', require(__dirname+'/controllers/forms.js'))

@ -44,4 +44,8 @@ label.toggle-label Toggle Post Actions
| Show Post In Ban
label
input#report(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
.actions
h4.no-m-p Captcha:
img.captcha(src='/captcha' width=200 height=80)
input#captcha(type='text', name='captcha', autocomplete='off' placeholder='captcha text' maxlength='6')
input(type='submit', value='submit')

@ -1,6 +1,6 @@
section.board-header
if board.banners.length > 0
object.board-banner(data=`/img/${board.banners[Math.floor(Math.random()*board.banners.length)]}` width='300' height='100')
object.board-banner(data=`/banners?board=${board._id}` width='300' height='100')
a.no-decoration(href=`/${board._id}`)
h1.board-title /#{board._id}/ - #{board.name}
h4.board-description #{board.description}

@ -26,7 +26,9 @@ section.form-wrapper
| Spoiler
section.postform-section
.postform-label Captcha
input#captcha(type='text', name='captcha', autocomplete='off' placeholder='under construction' maxlength='6')
span.postform-data
img.captcha(src='/captcha' width=200 height=80)
input#captcha(type='text', name='captcha', autocomplete='off' placeholder='captcha text' maxlength='6')
input(type='submit', value='submit')

@ -14,7 +14,9 @@ mixin catalogtile(board, post, truncate)
object.catalog-thumb(data=`/img/thumb-${post.files[0].filename.split('.')[0]}.png` width='64' height='64')
header.post-info
span: a(href=postURL) ##{post.postId}
|
span Replies: #{post.replyposts}
|
span Images: #{post.replyfiles}
br
if post.message

@ -1,7 +1,8 @@
extends ../layout.pug
block head
meta(http-equiv="refresh" content=`3;url=${redirect}`)
if redirect
meta(http-equiv="refresh" content=`3;url=${redirect}`)
block content
h1 #{title}
@ -16,4 +17,5 @@ block content
if errors
each error in errors
li #{error}
p You will be redirected shortly. If you are not redirected automatically, you can #[a(href=redirect) click here].
if redirect
p You will be redirected shortly. If you are not redirected automatically, you can #[a(href=redirect) click here].

@ -14,7 +14,10 @@ const Mongo = require(__dirname+'/db/db.js')
, Posts = require(__dirname+'/db/posts.js')
, Bans = require(__dirname+'/db/bans.js')
, Trips = require(__dirname+'/db/trips.js')
, Captchas = require(__dirname+'/db/captchas.js')
, Accounts = require(__dirname+'/db/accounts.js');
console.log('deleting captchas')
await Captchas.deleteAll();
console.log('deleting accounts')
await Accounts.deleteAll();
console.log('deleting posts')
@ -47,6 +50,8 @@ const Mongo = require(__dirname+'/db/db.js')
console.log('creating indexes')
await Bans.db.dropIndexes();
await Bans.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 });
await Captchas.db.dropIndexes();
await Captchas.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 });
await Posts.db.dropIndexes();
//these are fucked
await Posts.db.createIndex({

Loading…
Cancel
Save