add captcha, csrf and sessions
This commit is contained in:
parent
e8ca1fcaed
commit
d46eeacf41
|
@ -6,3 +6,6 @@ MAXFILES=128
|
|||
SHOW_SUBMITTED=true
|
||||
|
||||
ADMIN_EMAIL=john.doe@example.com
|
||||
|
||||
SESSION_SECRET=
|
||||
SESSION_MEMCACHE_HOST=memcache:11211
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
node_modules
|
||||
.env
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# code
|
||||
!*.js
|
||||
|
|
|
@ -10,5 +10,17 @@ services:
|
|||
volumes:
|
||||
- './usercontent:/opt/code/usercontent'
|
||||
# uncomment this for debug mode
|
||||
#- './:/opt/code'
|
||||
- './:/opt/code'
|
||||
restart: always
|
||||
networks:
|
||||
- bin
|
||||
memcache:
|
||||
image: memcached
|
||||
restart: always
|
||||
networks:
|
||||
- bin
|
||||
|
||||
networks:
|
||||
bin:
|
||||
driver: bridge
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
errcho() {
|
||||
echo $* >&2
|
||||
}
|
||||
|
||||
if [[ $1 != '-y' ]]; then
|
||||
errcho -e "\033[31mERROR: \033[0mThis will overwrite your current key."
|
||||
errcho -e "\033[31mERROR: \033[0mRun it again with -y as first argument to confirm."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KEY=$(cat /dev/urandom | tr -dc '[:alpha:]' | fold -w 32 | head -n 1)
|
||||
|
||||
sed -i "s/^SESSION_SECRET=[a-zA-Z0-9]*$/SESSION_SECRET=$KEY/g" .env
|
||||
|
||||
echo Your key is $KEY
|
|
@ -5,4 +5,22 @@ const bodyparse = require('body-parser');
|
|||
router.use(bodyparse.json());
|
||||
router.use(bodyparse.urlencoded({extended: true}));
|
||||
|
||||
const session = require('express-session');
|
||||
const memcache = require("connect-memcached")(session);
|
||||
const crypto = require('crypto');
|
||||
|
||||
router.use(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
secure: true,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
store: new memcache({
|
||||
hosts: [process.env.SESSION_MEMCACHE_HOST],
|
||||
secret: process.env.SESSION_SECRET +
|
||||
crypto.createHash('sha256', process.env.SESSION_SECRET).digest().toString('hex')
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
|
@ -12,9 +12,11 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"connect-memcached": "^2.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-session": "^1.17.3",
|
||||
"fancy-log": "^2.0.0",
|
||||
"glob": "^9.2.1",
|
||||
"pug": "^3.0.2"
|
||||
|
|
|
@ -47,3 +47,18 @@ input[type=submit] {
|
|||
input[type=submit]:hover {
|
||||
box-shadow: 0 2px 4px #60806040;
|
||||
}
|
||||
|
||||
div.captcha_box {
|
||||
margin:0 auto;
|
||||
width:100px;
|
||||
text-align:center;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
p.c_s {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
font-size:11pt;
|
||||
font-family: sans-serif
|
||||
}
|
|
@ -3,11 +3,25 @@ const router = express.Router();
|
|||
const handler = require('express-async-handler');
|
||||
const content = require('../helpers/content');
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
async function index(req, res) {
|
||||
|
||||
if (!req.session.captcha) {
|
||||
req.session.captcha = crypto.randomBytes(8).toString('base64').substring(0, 6);
|
||||
}
|
||||
|
||||
req.session.captcha_input = crypto.randomBytes(8).toString('base64').substring(0,10);
|
||||
if (!req.session.csrf)
|
||||
req.session.csrf = crypto.randomBytes(10).toString('base64');
|
||||
|
||||
res.render('main', {
|
||||
maxlen: process.env.MAXLEN,
|
||||
submitted: content.submitted()
|
||||
submitted: content.submitted(),
|
||||
req,
|
||||
crypto
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
router.get('/', handler(index));
|
||||
|
|
|
@ -2,11 +2,30 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
const handler = require('express-async-handler');
|
||||
const content = require('../helpers/content');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const { MAXFILES } = process.env;
|
||||
|
||||
async function upload(req, res) {
|
||||
|
||||
if (req.body['_csrf'] != req.session.csrf) {
|
||||
res.status(405).send('CSRF error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.body[req.session.captcha_input]) {
|
||||
res.status(405).send('Captcha error; please go back and refresh the page a few times.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.body[req.session.captcha_input] != req.session.captcha) {
|
||||
res.status(405).send('Bad captcha');
|
||||
return;
|
||||
}
|
||||
|
||||
req.session.captcha = crypto.randomBytes(8).toString('base64').substring(0,6);
|
||||
|
||||
|
||||
if (content.submitted() >= MAXFILES) {
|
||||
res.status(405).send('Not allowed');
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
mixin captcha(text)
|
||||
div(class='captcha_box')
|
||||
-
|
||||
const pyRange = (start, stop, step) =>
|
||||
Array.from(
|
||||
{ length: (stop - start) / step + 1 },
|
||||
(value, index) => start + index * step
|
||||
);
|
||||
var captcha = text
|
||||
var shuffled_indexes = pyRange(0, captcha.length - 1, 1);
|
||||
shuffled_indexes.sort(() => crypto.randomInt(0,10) > 5 ? 1 : -1);
|
||||
var current_index = -1;
|
||||
const rint = (m, mx) => crypto.randomInt(m, mx);
|
||||
|
||||
const blowfish = () => {
|
||||
const rules = [
|
||||
'font-weight:normal',
|
||||
'font-family:inherit',
|
||||
'font-weight:bold',
|
||||
'font-weight:normal',
|
||||
'display:block',
|
||||
'display:inline-block',
|
||||
'display:flex',
|
||||
'display:none',
|
||||
'display:none'
|
||||
];
|
||||
const n = rint(8, 16);
|
||||
let out = '';
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
out += rules[rint(0,rules.length - 1)] + ';'.repeat(rint(1,6))
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
each index in shuffled_indexes
|
||||
-
|
||||
current_index++
|
||||
var left_margin = 0;
|
||||
|
||||
|
||||
p(class='c_s' style=`transform:translate(${((index - current_index) * (rint(80,82) / 10))}px, ${rint(-3,3)}px);${blowfish()};font-weight:${rint(0,2) == 1 ? 'bold' : 'normal'};color:#220000;display:inline-block`)= captcha[index]
|
||||
each i in pyRange(0, rint(4,10), 1)
|
||||
p(class='c_s' style=`transform:translate(${((index - current_index) * (rint(80,82) / 10))}px, ${rint(-3,3)}px);${blowfish()};font-weight:bold;color:#220000;display:none`)= a
|
|
@ -1,13 +1,25 @@
|
|||
extends template/main.pug
|
||||
|
||||
block root
|
||||
include captcha.pug
|
||||
|
||||
block content
|
||||
- var exceeded = submitted >= process.env.MAXFILES
|
||||
form(action='/upload' method='POST')
|
||||
input(type='hidden' name='_csrf' value=req.session.csrf)
|
||||
p(align='center')
|
||||
textarea(name='text' class='data' placeholder='Put your text in here!' + (maxlen ? ` (Max length is ${maxlen} bytes)` : ''))
|
||||
br
|
||||
if (!exceeded)
|
||||
br
|
||||
| Captcha:
|
||||
br
|
||||
input(type='text' name=req.session.captcha_input)
|
||||
if (!exceeded)
|
||||
+captcha(req.session.captcha)
|
||||
p(align='center' style='padding-bottom:10px')
|
||||
input(type='submit' value='Upload!')
|
||||
|
||||
if (exceeded)
|
||||
p(style='color:darkred;font-weight:bold;font-size:9pt' align='center')
|
||||
| Max uploads limit exceeded. No more uploads would be accepted.
|
||||
|
|
Loading…
Reference in New Issue