UNPKG

doushio

Version:
163 lines (148 loc) 4.57 kB
var _ = require('../lib/underscore'), config = require('../config'), crypto = require('crypto'), formidable = require('formidable'), querystring = require('querystring'), RES = require('./state').resources, request = require('request'), winston = require('winston'); function connect() { return global.redis; } exports.login = function (req, resp) { try { var form = new formidable.IncomingForm(); form.maxFieldsSize = 50 * 1024; form.type = 'urlencoded'; form.parse(req, verify_persona.bind(null, resp)); } catch (e) { winston.error('formidable threw ' + e); respond_error(resp, "Bad request."); } }; function verify_persona(resp, err, fields) { if (err) { winston.error("Bad POST: " + err); return respond_error(resp, 'POST error.'); } if (!fields.assertion || typeof fields.assertion != 'string') return respond_error(resp, 'Bad Persona assertion.'); var payload = { assertion: fields.assertion, audience: config.PERSONA_AUDIENCE, }; var opts = { url: 'https://verifier.login.persona.org/verify', body: payload, json: true, }; request.post(opts, function (err, verResp, packet) { if (err) { winston.error("Bad persona request: " + err); respond_error(resp, "Couldn't contact persona.org."); return; } if (verResp.statusCode != 200) { winston.error('Code ' + verResp.statusCode); return respond_error(resp, 'Persona.org error.'); } verify_auth(resp, packet); }); } function verify_auth(resp, packet) { if (!packet || packet.status != 'okay') return respond_error(resp, 'Bad Persona.'); delete packet.status; if (packet.audience != config.PERSONA_AUDIENCE) { winston.error("Wrong audience: " + packet.audience); return respond_error(resp, 'Bad Persona audience.'); } delete packet.audience; if (packet.expires && packet.expires < new Date().getTime()) return respond_error(resp, 'Login attempt expired.'); var email = packet.email; var admin = config.ADMIN_PERSONAS.indexOf(email) >= 0; var mod = config.MODERATOR_PERSONAS.indexOf(email) >= 0; if (!(admin || mod)) { winston.error("Login attempt by " + email); return respond_error(resp, 'Wrong Persona.'); } if (admin) packet.auth = 'Admin'; else if (mod) packet.auth = 'Moderator'; else delete packet.auth; exports.set_cookie(resp, packet); }; exports.set_cookie = function (resp, info) { var pass = random_str(); info.csrf = random_str(); var m = connect().multi(); m.hmset('session:'+pass, info); m.expire('session:'+pass, config.LOGIN_SESSION_TIME); m.exec(function (err) { if (err) return oauth_error(resp, err); respond_ok(resp, make_cookie('a', pass, info.expires)); }); }; function extract_login_cookie(chunks) { if (!chunks || !chunks.a) return false; return /^[a-zA-Z0-9+\/]{20}$/.test(chunks.a) ? chunks.a : false; } exports.extract_login_cookie = extract_login_cookie; exports.check_cookie = function (cookie, callback) { var r = connect(); r.hgetall('session:' + cookie, function (err, session) { if (err) return callback(err); else if (_.isEmpty(session)) return callback('Not logged in.'); callback(null, session); }); }; exports.logout = function (req, resp) { var r = connect(); var chunks = require('./web').parse_cookie(req.headers.cookie); var cookie = extract_login_cookie(chunks); if (!cookie) return respond_error(resp, "No login cookie for logout."); r.hgetall('session:' + cookie, function (err, session) { if (err) return respond_error(resp, "Logout error."); r.del('session:' + chunks.a); respond_ok(resp, 'a=; expires=Thu, 01 Jan 1970 00:00:00 GMT'); }); }; function respond_error(resp, message) { resp.writeHead(200, {'Content-Type': 'application/json'}); resp.end(JSON.stringify({status: 'error', message: message})); } function respond_ok(resp, cookie) { var headers = { 'Content-Type': 'application/json', 'Set-Cookie': cookie, }; resp.writeHead(200, headers); resp.end(JSON.stringify({status: 'okay'})); } function make_expiry() { var expiry = new Date(new Date().getTime() + config.LOGIN_SESSION_TIME*1000).toUTCString(); /* Change it to the expected dash-separated format */ var m = expiry.match(/^(\w+,\s+\d+)\s+(\w+)\s+(\d+\s+[\d:]+\s+\w+)$/); return m ? m[1] + '-' + m[2] + '-' + m[3] : expiry; } function make_cookie(key, val) { var header = key + '=' + val + '; Expires=' + make_expiry(); var domain = config.LOGIN_COOKIE_DOMAIN; if (domain) header += '; Domain=' + domain; return header; } function random_str() { return crypto.randomBytes(15).toString('base64'); }