From d7a403828803d5817e901d61c4dce5d29d9489cf Mon Sep 17 00:00:00 2001 From: joates Date: Sat, 20 Sep 2014 10:54:53 +0100 Subject: [PATCH 1/6] separate email client-side validation code --- .gitignore | 1 + package.json | 5 +-- server.js | 52 +++++++++++++++++++------------ src/{email.js => email-client.js} | 0 4 files changed, 36 insertions(+), 22 deletions(-) rename src/{email.js => email-client.js} (100%) diff --git a/.gitignore b/.gitignore index f4e3dcc..fb280fe 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lib-cov *.pid *.gz +db pids logs results diff --git a/package.json b/package.json index aee6ee8..0e35afc 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "server.js", "scripts": { "start": "echo -n 'building.. '; npm run build && node server.js", - "build": "browserify src/email.js -o html/assets/js/email.js", + "build": "browserify src/email-client.js -o html/assets/js/email.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -26,6 +26,7 @@ "browserify": "^5.11.2", "domready": "^1.0.7", "nodemailer": "^1.3.0", - "valid-email": "0.0.1" + "valid-email": "0.0.1", + "xss-escape": "0.0.5" } } diff --git a/server.js b/server.js index b20f6dd..0548b92 100644 --- a/server.js +++ b/server.js @@ -1,9 +1,7 @@ #!/usr/bin/env node -var nodemailer = require('nodemailer') - , transporter = nodemailer.createTransport() - , server = require('http').createServer(handler) - , email = require('./config.json').email +var server = require('http').createServer(handler) + , sanitize = require('xss-escape') , rn = require('./src/rng') , fs = require('fs') , re = new RegExp('\.js$', 'i') @@ -29,29 +27,42 @@ function handler(req, res) { if (/^\/email\?/.test(req.url)) { var params = require('url').parse(req.url, true) if (params && params.query.email) { + //console.log('got email:', params.query) - /* - var to_addr = params.query.email // @NOTE: - // Q: do we trust the user input ? - // A: FUCK NO !! + var obj = {} + , email = sanitize(params.query.email) + obj.token = rn() + obj.verified = false + obj.events = { paris: params.query.paris ? true : false } + obj.trace = (req.headers['x-forwarded-for'] || '').split(',') + || [ req.connection.remoteAddress ] + + var db = require('level')('./db/squatconf', { valueEncoding: 'json' }) + db.put(email, obj, function(err) { + if (err) cb(err) + // else.. db updated OK + }) + + var nodemailer = require('nodemailer') + , transporter = nodemailer.createTransport() + , config = require('./config.json') , url = 'http://squatconf.eu/confirm' - , link = url +'?email='+ to_addr +'&token='+ rn() +'\n\n' + , link = url +'?email='+ email +'&token='+ obj.token +'\n\n' var opts = { - from : email.from - , to : to_addr - , subject: email.subject - , text : email.bodyText.replace(/\%link\%/, link) + from : config.email.from + , to : email + , subject: config.email.subject + , text : config.email.bodyText.replace(/\%link\%/, link) } transporter.sendMail(opts, function(err, data) { - if (err) return console.error('email problem !', err) - console.log('email sent', data) + if (err) throw err + // validation email sent + console.log('email sent..', data) }) - */ - - console.log(' got email:', params.query) } + res.statusCode = 302 res.setHeader('Location', '/') return res.end() @@ -68,5 +79,6 @@ process.on('uncaughtException', function (err) { }) server.listen(port) -console.error('['+ process.pid +'] server started on port '+ port) -console.error('(use ctrl+c to stop server)') +console.log('['+ process.pid +'] server started on port '+ port) +console.log('(use Ctrl+c to stop the server)') + diff --git a/src/email.js b/src/email-client.js similarity index 100% rename from src/email.js rename to src/email-client.js From 5f52ab39bf1fde69baa55922b89e699a3fbfbafa Mon Sep 17 00:00:00 2001 From: joates Date: Sat, 20 Sep 2014 18:24:37 +0100 Subject: [PATCH 2/6] added db storage (match email addresses to tokens) --- server.js | 20 ++++++++++++++------ src/ip-trace.js | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 src/ip-trace.js diff --git a/server.js b/server.js index 0548b92..87ae80d 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,8 @@ var server = require('http').createServer(handler) , rn = require('./src/rng') , fs = require('fs') , re = new RegExp('\.js$', 'i') + , ip = require('./src/ip-trace') + , db = require('level')('./db/squatconf', { valueEncoding: 'json' }) , port = process.env.PORT || /*80*/ 8000 function handler(req, res) { @@ -34,13 +36,17 @@ function handler(req, res) { obj.token = rn() obj.verified = false obj.events = { paris: params.query.paris ? true : false } - obj.trace = (req.headers['x-forwarded-for'] || '').split(',') - || [ req.connection.remoteAddress ] + obj.trace = ip(req) - var db = require('level')('./db/squatconf', { valueEncoding: 'json' }) db.put(email, obj, function(err) { - if (err) cb(err) + if (err) throw err + // else.. db updated OK + db.get(email, function (err, value) { + if (err) return console.error('Ooops!', err) + + console.log('> '+ email, value) + }) }) var nodemailer = require('nodemailer') @@ -53,14 +59,16 @@ function handler(req, res) { from : config.email.from , to : email , subject: config.email.subject - , text : config.email.bodyText.replace(/\%link\%/, link) + //, text : config.email.bodyText.replace(/\%link\%/, link) } - +console.log('mockmail sent...', opts) + /* transporter.sendMail(opts, function(err, data) { if (err) throw err // validation email sent console.log('email sent..', data) }) + */ } res.statusCode = 302 diff --git a/src/ip-trace.js b/src/ip-trace.js new file mode 100644 index 0000000..f34bbe0 --- /dev/null +++ b/src/ip-trace.js @@ -0,0 +1,8 @@ +module.exports = function(req) { + if (req.headers['x-forwarded-for']) { + return req.headers['x-forwarded-for'].split(',') + } else { + return [ req.connection.remoteAddress ] + } +} + From 023d1b039a95d55afb2ba7efcccd56a74b3d4427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81ro=CC=82me=20Loi=CC=88?= Date: Sun, 21 Sep 2014 21:37:12 +0200 Subject: [PATCH 3/6] create the level db folder if it does not exists --- package.json | 1 + server.js | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/package.json b/package.json index 0e35afc..f9c265b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dependencies": { "browserify": "^5.11.2", "domready": "^1.0.7", + "level": "^0.18.0", "nodemailer": "^1.3.0", "valid-email": "0.0.1", "xss-escape": "0.0.5" diff --git a/server.js b/server.js index 87ae80d..c7d895f 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,15 @@ var server = require('http').createServer(handler) , db = require('level')('./db/squatconf', { valueEncoding: 'json' }) , port = process.env.PORT || /*80*/ 8000 +// create the level db folder if it does not exists +if(!fs.existsSync('./db/squatconf')){ + fs.mkdirSync('./db/squatconf', 0766, function(err){ + if(err){ + console.log(err); + } + }); + } + function handler(req, res) { // process incoming requests. From 756cf4ab72bfff705f7c18f9d4b5e34ad0532e2b Mon Sep 17 00:00:00 2001 From: joates Date: Mon, 22 Sep 2014 03:00:54 +0100 Subject: [PATCH 4/6] implement the 2-stage email verification process --- config.js | 16 +++++++ config.json | 13 ------ package.json | 4 ++ server.js | 100 ++++++++----------------------------------- src/email-confirm.js | 39 +++++++++++++++++ src/email-submit.js | 54 +++++++++++++++++++++++ 6 files changed, 131 insertions(+), 95 deletions(-) create mode 100644 config.js delete mode 100644 config.json create mode 100644 src/email-confirm.js create mode 100644 src/email-submit.js diff --git a/config.js b/config.js new file mode 100644 index 0000000..662be52 --- /dev/null +++ b/config.js @@ -0,0 +1,16 @@ +var join = require('path').join + , name = 'squatconf' + , cwd = process.cwd() + +module.exports = require('rc')(name, { + db_opts: { valueEncoding: 'json' } + , db_path: join(cwd, 'db', name) + , port: 8000 + , host: "squatconf.eu" + , email: { + from : "no-reply@squatconf.eu" + , subject : "Hello, everyone is welcome at SquatConf.." + , bodyText : "Please verify that you wish to signup by following this link\n%link%\nYou can ignore this message if you DID NOT request to signup at our website\nhttp://squatconf.eu\n\nThe next event is in Paris, we hope to see you there !!\n\nKind regards from the team,\nSquatConf Paris 2014" + } +}) + diff --git a/config.json b/config.json deleted file mode 100644 index 5f8ced6..0000000 --- a/config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "db": { - "path": "" - }, - - "email": { - "from" : "no-reply@squatconf.eu" - , "subject" : "Hello, everyone is welcome at SquatConf.." - , "bodyText": "Please verify that you wish to signup by following this link\n%link%\nYou can ignore this message if you DID NOT request to signup at our website\nhttp://squatconf.eu\n\nThe next event is in Paris, we hope to see you there !!\n\nKind regards from the team,\nSquatConf Paris 2014" - - } -} - diff --git a/package.json b/package.json index f9c265b..039fd1e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,11 @@ "browserify": "^5.11.2", "domready": "^1.0.7", "level": "^0.18.0", + "ecstatic": "^0.5.4", "nodemailer": "^1.3.0", + "rc": "^0.5.1", + "stack": "^0.1.0", + "tiny-route": "^2.1.1", "valid-email": "0.0.1", "xss-escape": "0.0.5" } diff --git a/server.js b/server.js index c7d895f..425747a 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,14 @@ #!/usr/bin/env node -var server = require('http').createServer(handler) - , sanitize = require('xss-escape') - , rn = require('./src/rng') - , fs = require('fs') - , re = new RegExp('\.js$', 'i') - , ip = require('./src/ip-trace') - , db = require('level')('./db/squatconf', { valueEncoding: 'json' }) - , port = process.env.PORT || /*80*/ 8000 +var fs = require('fs') + , http = require('http') + , stack = require('stack') + , route = require('tiny-route') + , assets = require('ecstatic') + , join = require('path').join + , config = require('./config') + , db = require('level')(config.db_path, config.db_opts) + , port = process.env.PORT || config.port // create the level db folder if it does not exists if(!fs.existsSync('./db/squatconf')){ @@ -18,84 +19,19 @@ if(!fs.existsSync('./db/squatconf')){ }); } -function handler(req, res) { - - // process incoming requests. - if (req.url == '/') req.url = '/index.html' - else if (re.test(req.url)) - res.setHeader('Content-Type', 'application/javascript') - - if (/^\/confirm\?/.test(req.url)) { - - // @TODO - // compare submitted token with the token stored in our database. - - res.statusCode = 302 - res.setHeader('Location', '/') - return res.end() - } - - if (/^\/email\?/.test(req.url)) { - var params = require('url').parse(req.url, true) - if (params && params.query.email) { - //console.log('got email:', params.query) - - var obj = {} - , email = sanitize(params.query.email) - obj.token = rn() - obj.verified = false - obj.events = { paris: params.query.paris ? true : false } - obj.trace = ip(req) - - db.put(email, obj, function(err) { - if (err) throw err - - // else.. db updated OK - db.get(email, function (err, value) { - if (err) return console.error('Ooops!', err) - - console.log('> '+ email, value) - }) - }) - - var nodemailer = require('nodemailer') - , transporter = nodemailer.createTransport() - , config = require('./config.json') - , url = 'http://squatconf.eu/confirm' - , link = url +'?email='+ email +'&token='+ obj.token +'\n\n' - - var opts = { - from : config.email.from - , to : email - , subject: config.email.subject - //, text : config.email.bodyText.replace(/\%link\%/, link) - } -console.log('mockmail sent...', opts) - /* - transporter.sendMail(opts, function(err, data) { - if (err) throw err - // validation email sent - console.log('email sent..', data) - }) - */ - } - - res.statusCode = 302 - res.setHeader('Location', '/') - return res.end() - } - - // serve static assets - var rs = fs.createReadStream(__dirname +'/html'+ req.url) - rs.pipe(res) -} +var app = stack( + route('/email', require('./src/email-submit')(db)) + , route('/confirm', require('./src/email-confirm')(db)) + , assets(join(__dirname, 'html')) +) process.on('uncaughtException', function (err) { console.error('Error at:', new Date) console.error(err.stack) }) -server.listen(port) -console.log('['+ process.pid +'] server started on port '+ port) -console.log('(use Ctrl+c to stop the server)') +http.createServer(app).listen(port, function() { + console.log('['+ process.pid +'] server started on port '+ port) + console.log('(use Ctrl+c to stop the server)') +}) diff --git a/src/email-confirm.js b/src/email-confirm.js new file mode 100644 index 0000000..a2e5df9 --- /dev/null +++ b/src/email-confirm.js @@ -0,0 +1,39 @@ +var sanitize = require('xss-escape') + , ip = require('./ip-trace') + +module.exports = function(db) { + return function (req, res, next) { + req.resume() + + var params = require('url').parse(req.url, true) + + if (params && params.query.email && params.query.token) { + //console.log('got token:', params.query) + + var email = sanitize(params.query.email) + , token = sanitize(params.query.token) + + db.get(email, function(err, obj) { + if (err) next(err) + + // db read OK.. + if (obj && obj.token === token) { + obj.verified = true + obj.trace = obj.trace.concat(ip(req)) + + db.put(email, obj, function(err) { + if (err) next(err) + + // db write OK.. + res.statusCode = 302 + res.setHeader('Location', '/verified.html') + return res.end() + }) + } + }) + + if (next) return next() + } + } +} + diff --git a/src/email-submit.js b/src/email-submit.js new file mode 100644 index 0000000..4b45b69 --- /dev/null +++ b/src/email-submit.js @@ -0,0 +1,54 @@ +var sanitize = require('xss-escape') + , rn = require('./rng') + , ip = require('./ip-trace') + , config = require('../config') + +module.exports = function(db) { + return function (req, res, next) { + req.resume() + + var params = require('url').parse(req.url, true) + + if (params && params.query.email) { + console.log('got email:', params.query) + + var obj = {} + , email = sanitize(params.query.email) + + obj.token = rn() + obj.verified = false + obj.events = { paris: params.query.paris ? true : false } + obj.trace = ip(req) + + db.put(email, obj, function(err) { + if (err) next(err) + + // db write OK.. + var nodemailer = require('nodemailer') + , transporter = nodemailer.createTransport() + , url = 'http://squatconf.eu/confirm' + , link = url +'?email='+ email +'&token='+ obj.token +'\n\n' + + var opts = { + from : config.email.from + , to : email + , subject: config.email.subject + , text : config.email.bodyText.replace(/\%link\%/, link) + } + + transporter.sendMail(opts, function(err, data) { + if (err) throw err + // validation email sent + console.log('email sent..', data) + }) + + res.statusCode = 302 + res.setHeader('Location', '/') + return res.end() + }) + } + + if (next) return next() + } +} + From 3fd96bf5c5a6cb19c3c3c84a8a1415a4e054349c Mon Sep 17 00:00:00 2001 From: joates Date: Mon, 22 Sep 2014 03:48:26 +0100 Subject: [PATCH 5/6] added email verified confirmation page --- html/verified.html | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 html/verified.html diff --git a/html/verified.html b/html/verified.html new file mode 100644 index 0000000..1af7948 --- /dev/null +++ b/html/verified.html @@ -0,0 +1,77 @@ + + + + + SquatConf.386 + + + + + + + + + + +
+
+ +
+ +
+ + + +
+ +
+

You're verified

+

+

Thanks for completing our signup process

+
+ Go back +
+
+
+
+ + + + + From 242a2e52776f9488b6a61873de38624a52f65822 Mon Sep 17 00:00:00 2001 From: joates Date: Mon, 22 Sep 2014 03:51:50 +0100 Subject: [PATCH 6/6] v0.4.0-rc2 (ready to deploy for testing) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 039fd1e..2aee1ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "squatconf-web", - "version": "0.3.0", + "version": "0.4.0", "description": "website for the squatConf conference", "main": "server.js", "scripts": {