UNPKG

litejs

Version:

Single-page application framework

344 lines (305 loc) 9.61 kB
// AAAAAAAABBBBBBBBCCCCCCCC // aaaaaabbbbbbccccccdddddd // INSERT INTO device(ua, hash) WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x<1000000) SELECT 123,randomblob(18) FROM cnt; // .timer on // Run Time: real 0.001 user 0.000069 sys 0.000079 var crypto = require("crypto") , log = require("../lib/log")("app:hello") , util = require("../lib/util") , digests = [ "sha1", "sha256" ] , defaults = { challenges: 'Bearer realm="app"', hello: "/hello", authType: "S", devCookie: { name: "a", ttl: 5 * 365 * 24 * 60 * 60, httpOnly: true }, sesCookie: { name: "b", httpOnly: true }, pepper: Buffer.from("Lcv9WvIPieSVuEYS", "base64"), cluster: 0, digest: "sha256", bytes: 21, iterations: 2 } module.exports = function createHello(_opts) { var hello = require(".")(Object.assign({}, defaults, _opts)) , opts = hello.options opts.digestId = digests.indexOf(opts.digest) opts.byteTimes = 0|(opts.bytes/3) if (opts.digestId < 0) { log.error("Invalid digest %s, should be one of: %s", opts.digest, digests) throw Error("Invalid digest " + opts.digest) } opts.bytes = (1 + opts.byteTimes) * 3 opts.hashType = (opts.digestId << 4) | opts.byteTimes hello.findHash = findHash opts.devCookie.path = opts.sesCookie.path = opts.hello return hello .get(opts.hello, helloMw) .put(opts.hello, helloMw) .use(authMw) .del(opts.hello, delMw) function findHash(source, next) { getByHash("device", source, opts, function(err, map, row) { if (err) return next(true) if (map.window) { opts.db.get( "SELECT l.id,l.user,w.session " + "FROM window AS w,session AS s " + "LEFT JOIN login AS l ON l.deleted IS NULL AND l.window=w.id " + "WHERE s.id=w.session AND w.id=? AND s.device=?", [map.window, map.device], function(err, row) { if (row) { map.login = row.id map.user = row.user map.session = row.session } else { map.window = null } next(null, map) } ) } else { next(null, map, row) } }) } } function helloMw(req, res, next, opts) { var cookie , hello = this , body = req.body || {} , time = req.date.getTime() if (!body.user && body.email) { body.user = body.email } // opts.db.text(".schema window_log_", function(err, text) { console.log("SCHEMA", text) }) hello.findHash(req.cookie(opts.devCookie), function(err, map) { if (err) createDevice({}) else testSession(map) }) function testSession(map) { getById("session", testWindow, createSession, req.cookie(opts.sesCookie), map) } function testWindow(map, sess) { if (sess.device !== map.device) return createSession(map) if (body.a !== void 0) { getById("window", testLogin, createWindow, body.a, map) } else if (body.b !== void 0) { createWindow(map) } else { opts.db.get( "SELECT id FROM window WHERE session=? AND deleted IS NULL ORDER BY id DESC LIMIT 1", [sess.id], function(err, row) { if (!row) return createWindow(map) map.window = row.id testLogin(map, row) } ) } } function testLogin(map, win) { if (win.session !== map.session) return createWindow(map) if (body.user != null) { return hello.emit("auth", req, res, map, function() { createLogin(map) }) } if (!map.window) { return respond(map) } if (body.b !== void 0) { } opts.db.get("SELECT id,user FROM login WHERE window=? AND deleted IS NULL", [map.window], function(err, row) { if (row) { map.login = row.id map.user = row.user return respond(map) } opts.db.all("SELECT user FROM remember WHERE device=?", [map.device], function(err, rows) { if (rows.length === 1 && rows[0]) { map.user = rows[0].user map.loginType = "remember" return createLogin(map) } if (rows.length > 1) { map.remember = rows } respond(map) }) }) } function createDevice(map) { sub("device_ua", req.headers["user-agent"], function setHash(ua, errCount) { errCount = errCount > 0 ? errCount + 1 : 1 if (errCount > 6) throw "to many errors" crypto.randomBytes(opts.bytes, function(err, buf) { if (err) { log.error(err) return setHash(ua, errCount) } buf[1] = opts.cluster buf[4] = opts.hashType buf[7] = opts.iterations // TODO:2019-02-11:lauri:Make it random above conf crypto.pbkdf2(buf, opts.pepper, buf[7], opts.bytes, opts.digest, function(err, buf2) { if (err) { log.error(err) return setHash(ua, errCount) } opts.db.insert("device(ua,hash)", [ua, buf2], function(err, row) { if (err) { log.error(err) return setHash(ua, errCount) } log.debug("device created: %s", buf.toString("base64")) res.cookie(opts.devCookie, cookie = buf.toString("base64")) map.device = row.lastId map.buf = buf createSession(map) }) }) }) }) } function createSession(map) { // opts.db.run("DELETE FROM session WHERE device=?", [map.device]) opts.db.run( "UPDATE login SET deleted=? WHERE deleted IS NULL AND window IN " + "(SELECT w.id FROM window AS w,session AS s WHERE s.id=w.session AND s.device=?)", [time, map.device] ) opts.db.run( "UPDATE window SET deleted=? WHERE deleted IS NULL AND session IN (SELECT id FROM session WHERE device=?)", [time, map.device] ) opts.db.insert("session(device,ip)", [map.device, util.ip2buf(req.ip)], function(err, row) { if (err) throw err log.debug("session created: %s", row.lastId) res.cookie(opts.sesCookie, row.lastId.toString(32)) map.session = row.lastId createWindow(map) }) } function createWindow(map) { var opener = ( body.b && parseInt(body.b, 32) || body.c !== req.headers.referer && body.c || null ) opts.db.insert("window(created,session,opener)", [time, map.session, opener], function(err, row) { if (err) return log.error("createWindow", err) log.debug("window created: %s from %s", row.lastId, opener) map.window = row.lastId testLogin(map, {id: map.window, session: map.session}) }) } function createLogin(map) { opts.db.run("UPDATE login SET deleted=? WHERE window=? AND deleted IS NULL", [time, map.window]) if (!map.user) return respond(map) sub("login_type", map.loginType, function(type) { opts.db.insert("login(created,window,type,user)", [time, map.window, type, map.user], function(err, row) { log.debug("login created: %s, %s", row.lastId, !!body.remember) map.login = row.lastId if (body.remember) { opts.db.insert("remember(device,user,login)", [map.device, map.user, map.login]) } respond(map) }) }) } function respond(map) { var out = {} , buf = Buffer.alloc(map.buf.length + 6, map.buf) buf.writeUIntLE(map.window, map.buf.length, 6) out.authorization = opts.authType + " " + buf.toString("base64").replace(/(?:AA)+$/g, "") if (!body.a || parseInt(body.a, 32) !== map.window) { out.a = map.window.toString(32) } hello.emit("result", req, res, out, map) } function sub(table, text, next) { if (!text) return next(null) opts.db.get("SELECT id FROM ? WHERE text=?", [table, text], function(err, row) { if (row) return next(row.id) opts.db.insert(table + "(text)", [text], function(err, row) { next(row.lastId) }) }) } function getById(table, resolve, reject, source, map) { var id = typeof source === "string" && parseInt(source, 32) if (!id) return reject(map) opts.db.get("SELECT * FROM ? WHERE id=?", [table, id], function(err, row) { if (!row) return reject(map) map[table] = row.id resolve(map, row) }) } } /* * Authentication - login + password (who you are) * Authorization - permissions (what you are allowed to do) * Accounting - consumed resources (session statistics and usage for * authorization control, billing, trend analysis, * resource utilization, and capacity planning activities) */ function authMw(req, res, next, opts) { var hello = this , auth = req.headers["authorization"] req.session = {} if (auth !== void 0) { // Authorization: <type> <credentials> // Authorization: OAuth realm="Example", oauth_token="ad180jjd733klru7", oauth_version="1.0" auth = auth.split(/\s+/) if (auth[0] === "Basic") { auth[1] = Buffer.from(auth[1], "base64").toString() } if (auth.length !== 2 || hello.emit("auth:" + auth[0], req, res, next, auth[1]) === 0) { res.setHeader("WWW-Authenticate", opts.challenges) res.sendStatus(401) } } else { next() } } function delMw(req, res, next, opts) { var hello = this , map = req.authMap , time = req.date.getTime() if (map && map.login) { log.debug("logout %s", map.login) hello.emit("logout", map) opts.db.run("UPDATE login SET deleted=? WHERE id=?", [time, map.login]) opts.db.run("DELETE FROM remember WHERE device=? AND user=?", [map.device, map.user]) } res.sendStatus(204) } function getByHash(table, source, opts, next) { if (!source) return next(true) var map = {} , buf = map.buf = Buffer.from(source, "base64") , digest = map.buf[4] >>> 4 , bytes = 3 * (1 + (map.buf[4] & 0xf)) , extra = map.buf.length - bytes if (extra < 0 || extra > 6) { return next(true) } if (extra > 0) { map.window = buf.readUIntLE(bytes, extra) buf = map.buf = map.buf.slice(0, bytes) } crypto.pbkdf2(buf, opts.pepper, buf[7], bytes, digests[digest], function(err, buf) { opts.db.get("SELECT id FROM ? WHERE hash=?", [table, buf], function(err, row) { if (!row) return next(true) map[table] = row.id next(null, map, row) }) }) }