Skip to content

Instantly share code, notes, and snippets.

@tyage

tyage/blog.js Secret

Created June 25, 2017 16:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tyage/cac08c8e17b90b840fb22cb434cff127 to your computer and use it in GitHub Desktop.
Save tyage/cac08c8e17b90b840fb22cb434cff127 to your computer and use it in GitHub Desktop.
const datastore = require('@google-cloud/datastore')();
const htmlparser = require('htmlparser2');
const uuidV4 = require('uuid/v4');
const http = require('http');
const KIND = 'Comment';
const allowedTags = ['p', 'a', 'b', 'img', 'br', 'i'];
const allowedTypes = ['tag', 'text']
function insert_comment(content) {
var id = uuidV4();
var key = datastore.key([KIND, id]);
return datastore.save({
key: key,
data: { content: content, viewed: false },
}).then(() => {
return id;
});
}
function get_comment(id) {
var key = datastore.key([KIND, id]);
return datastore.get(key).then((result) => {
if (!result[0])
throw new Exception();
return result[0];
});
}
function delete_comment(id) {
var key = datastore.key([KIND, id]);
return datastore.delete(key);
}
function set_comment_viewed(id) {
var key = datastore.key([KIND, id]);
return datastore.get(key).then((result) => {
if (!result[0])
return;
var comment = result[0];
comment.viewed = true;
return datastore.save({
key: key,
data: comment
});
});
}
function check_comment(comment) {
function validate(node)
{
if (allowedTypes.indexOf(node.type) == -1)
throw 'Invalid type';
else if (node.type == 'tag' && allowedTags.indexOf(node.name) === -1)
throw 'Invalid tag';
for (var name in node.attribs) {
var value = node.attribs[name];
if (/^on.+/.test(name) && value !== '')
throw 'Invalid event';
console.log(value)
if (name == 'href' && /^(https?:)/.test(value) === false)
throw 'Invalid link';
}
for (var i in node.children) {
validate(node.children[i]);
}
}
var handler = new htmlparser.DomHandler(function (error, dom) {
if (error) {
throw error;
} else {
for (var i in dom)
validate(dom[i]);
}
});
try {
var parser = new htmlparser.Parser(handler);
parser.write(comment);
parser.end();
} catch (err) {
console.log(err);
return false;
}
return true;
}
module.exports = {
check_comment: check_comment,
insert_comment: insert_comment,
get_comment: get_comment,
delete_comment: delete_comment,
set_comment_viewed: set_comment_viewed
}
//Lets require/import the HTTP module
const pubsub = require('@google-cloud/pubsub')();
const http = require('http');
const dispatcher = require('httpdispatcher');
const fs = require('fs');
const recaptcha = require('express-recaptcha');
const blog = require('./blog');
//Lets define a port we want to listen to
const PORT = process.env.PORT || 8080;
//Lets use our dispatcher
function handleRequest(request, response){
try {
//Disptach
dispatcher.dispatch(request, response);
} catch(err) {
console.log(err);
throw err;
}
}
recaptcha.init('unused', process.env.RECAPTCHA_PRIVKEY);
process.env.RECAPTCHA_PRIVKEY= 'deleted'
//For all your static (js/css/images/etc.) set the directory name (relative path).
dispatcher.setStatic('static');
dispatcher.setStaticDirname('.');
//A sample GET request
dispatcher.onGet("/", function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
fs.readFile('static/pages/index.html', function(err, data) {
if (err) throw err;
res.end(data);
});
});
dispatcher.beforeFilter("/reply", function(req, res, chain) {
req.body = req.params;
recaptcha.verify(req, function(error) {
if(!error) {
chain.next(req, res, chain);
} else {
res.writeHead(403);
res.end('Wrong captcha.');
}
});
});
//A sample POST request
dispatcher.onPost("/reply", function(req, res) {
if (req.params.comment && blog.check_comment(req.params.comment)) {
console.log(req.params.comment);
blog.insert_comment(req.params.comment).then((id) => {
res.writeHead(200, {'Content-Type': 'text/html', 'X-XSS-Protection': '0'});
fs.readFile('static/pages/valid.html', 'utf-8', function(err, data) {
if (err) throw err;
res.end(data.replace('$comment$', id)); // Templates not invented yet...
var topic = pubsub.topic('xss');
var comment_url = `https://${process.env.GAE_SERVICE}-dot-${process.env.GCLOUD_PROJECT}.appspot.com/comment?id=${id}`;
topic.publish({service: 'geokitties',
url: comment_url});
});
});
} else {
res.writeHead(403, {'Content-Type': 'text/html'});
fs.readFile('static/pages/invalid.html', function(err, data) {
if (err) throw err;
res.end(data);
});
}
});
dispatcher.onGet("/comment", function(req, res) {
var post_id = req.params.id || '';
if (!post_id) {
res.writeHead(404);
res.end();
} else {
blog.get_comment(post_id).then((result) => {
res.writeHead(200, {'Content-Type': 'text/html'});
if (result.viewed)
res.end('Comment already validated.');
else
res.end(result.content);
if (req.headers['user-agent'] && req.headers['user-agent'].indexOf('Headless') !== -1)
blog.set_comment_viewed(post_id);
}, () => {
res.writeHead(404);
res.end();
}).catch((err) => {
console.log(err);
res.writeHead(500);
res.end();
});
}
});
dispatcher.beforeFilter(/\//, function(req, res, chain) { //any url
var filter = /dog|\.\.\/|passwd|select|union|insert|update|script|from|where|"|'/ig
if (filter.test(req.url))
{
req.url = req.url.replace(filter, '');
res.setHeader('WAF-Debug', new Buffer(req.url).toString('base64'));
} else {
res.setHeader('WAF-Debug', 'OK');
}
chain.next(req, res, chain);
});
//Create a server
var server = http.createServer(handleRequest);
//Lets start our server
server.listen(PORT, function() {
//Callback triggered when server is successfully listening. Hurray!
console.log("Server listening...");
});
{
"name": "frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node main.js"
},
"engines" : {
"node" : ">=7.9.0"
},
"dependencies" : {
"httpdispatcher": "0.4.0",
"htmlparser2": "3.9.2",
"uuid": "latest",
"express-recaptcha": "~2.1.0",
"@google-cloud/datastore": "~1.0.0",
"@google-cloud/pubsub": "~0.11.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment