Last active
August 29, 2015 14:01
-
-
Save myndzi/49cde89af1ce37f11811 to your computer and use it in GitHub Desktop.
reforming https://github.com/AlexMax/charonauth/blob/master/authapp.js for promises
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint node: true */ | |
"use strict"; | |
var Promise = require('bluebird'); | |
var crypto = require('crypto'); | |
var dgram = require('dgram'); | |
var events = require('events'); | |
var fs = require('fs'); | |
var srp = require('srp'); | |
var util = require('util'); | |
require('date-utils'); | |
var DBConn = Promise.promisify(require('./dbconn')); | |
var proto = require('./proto'); | |
var readFile = Promise.promisify(fs.readFile, fs); | |
// AuthApp | |
// | |
// This object encompasses all functionality dealing with the UDP endpoint of | |
// charon. More specifically, the socket server lives here. | |
function AuthApp(config) { | |
// AuthApp is an EventEmitter | |
events.EventEmitter.call(this); | |
// AuthApp needs to be called from within a promise, so we should be throwing errors | |
if (!(this instanceof AuthApp)) | |
throw new Error('Constructor called as a function'); | |
['dbConnection', 'dbOptions', 'authPort'].forEach(function (prop) { | |
if (!(prop in config)) { | |
throw new Error('Missing ' + prop + ' in AuthApp configuration'); | |
} | |
}); | |
this.socket = null; | |
this.db = null; | |
} | |
util.inherits(AuthApp, EventEmitter); | |
// avoid using constructor | |
AuthApp.create = Promise.method(function (config) { | |
var app = new AuthApp(); | |
return app.init({ | |
dbConnection: config.dbConnection, | |
dbOptions: config.dbOptions | |
}, dbImport: config.dbImport) | |
.then(function () { | |
return app.listen(config.authPort); | |
}); | |
}); | |
// open the database and load any stuff you wanted to import | |
AuthApp.prototype.init = Promise.method(function (dbOpts, fileNames) { | |
var db = this.dbconn = new DBConn(dbOpts); | |
// DBConn.connect() should return a promise for the connected db object, | |
// i.e. the one you can call .query on, after it has been run through | |
// Promise.promisifyAll(db) | |
db.connect().then(function () { | |
if (!fileNames) { return; } | |
// load each file -- note that 'readFile' is a promisified function here | |
return Promise.map(fileNames, readFile).map(function (fileData) { | |
return data.split(';\n'); | |
}) | |
// get rid of empties and merge the statements from each file in order left to right | |
.reduce(function (allStatements, statement) { | |
if (statement.length) { allStatements.push(statement); } | |
return allStatements; | |
}, [ ]) | |
// run all the queries, failing at first failure (should this be in a transaction?) | |
.all(function (statement) { | |
retutrn db.queryAsync(statement); | |
}); | |
}).then(function () { | |
return this._listen(); | |
}); | |
}); | |
AuthApp.prototype.listen = Promise.method(function (authPort) { | |
// doing this instead of promisifying every socket we make | |
// prefer promisifying sockets if you use their callback methods elsewhere though | |
var deferred = Promise.defer(); | |
this.socket = dgram.createSocket('udp4'); | |
// this.socket = Promise.promisifyAll(dgram.createSocket('udp4')); | |
this.socket.on('message', self.router.bind(self)); | |
this.socket.bind(authPort, deferred.callback); | |
return deferred.promise; | |
}); | |
// it's event handlers below here, so I stop using Promise.method() | |
// Router. | |
// | |
// Routes incoming requests to the proper function. | |
AuthApp.prototype.router = function(msg, rinfo) { | |
if (msg.length < 4) { | |
this.emit('error', new Error('Message is too small')); | |
return; | |
} | |
var packetType = msg.readUInt32LE(0); | |
switch (packetType) { | |
case proto.SERVER_NEGOTIATE: | |
this.serverNegotiate(msg, rinfo); | |
break; | |
case proto.SERVER_EPHEMERAL: | |
this.serverEphemeral(msg, rinfo); | |
break; | |
case proto.SERVER_PROOF: | |
this.serverProof(msg, rinfo); | |
break; | |
default: | |
this.emit('error', new Error('Message has invalid packet type')); | |
break; | |
} | |
}; | |
// Server Negotiate Route | |
// | |
// This is the initial route that creates an authentication session. | |
AuthApp.prototype.serverNegotiate = function(msg, rinfo) { | |
var self = this; | |
// Unmarshall the server negotiation packet | |
var packet = proto.serverNegotiate.unmarshall(msg); | |
var username = packet.username; | |
// Create a new session for given user. | |
this.dbconn.newSession(username).then(function (data) { | |
// Write the response packet | |
return proto.authNegotiate.marshall({ | |
session: data.session, | |
salt: data.salt, | |
username: data.username | |
}); | |
}).catch(err) { | |
self.emit('error', err); | |
// FIXME: Specifically check for a "User does not exist" error. | |
return proto.userError.marshall({ | |
username: username, | |
error: proto.USER_NO_EXIST | |
}); | |
}).then(function (response) { | |
// Send the response packet to the sender | |
self.socket.send(response, 0, response.length, rinfo.port, rinfo.address); | |
}) | |
}; | |
// Server Ephemeral Route | |
// | |
// With the client ephemeral A value, generate an ephemeral number B to be sent | |
// back to the client. | |
AuthApp.prototype.serverEphemeral = function(msg, rinfo) { | |
var self = this; | |
// Unmarshall the server negotiation packet | |
var packet = proto.serverEphemeral.unmarshall(msg); | |
self.dbconn.findSession(packet.session, self.sessionTimeout) | |
.then(function (session) { | |
return srp.genKey(32).then(function () { | |
var srpServer = new srp.Server( | |
srp.params['2048'], | |
session.salt, | |
new Buffer(session.username, 'ascii'), | |
session.verifier, | |
secret | |
); | |
srpServer.setA(packet.ephemeral); | |
var serverEphemeral = srpServer.computeB(); | |
// setEphemeral needs to return a promise in our new promisy thing | |
return self.dbconn.setEphemeral( | |
packet.session, | |
packet.ephemeral, | |
secret | |
).then(function () { | |
var response = proto.authEphemeral.marshall({ | |
session: packet.session, | |
ephemeral: serverEphemeral | |
}); | |
// we don't send an error reply to the socket here but we do above? is this correct? | |
self.socket.send(response, 0, response.length, rinfo.port, rinfo.address); | |
}) | |
}); | |
}).catch(self.emit.bind(self, 'error')); | |
}; | |
// Server Proof Route | |
// | |
// Using the client M1, attempts to verify that the client is legitimate, and | |
// if so send the client an M2 that the client can use to verify that the auth | |
// server is who he says he is. | |
AuthApp.prototype.serverProof = function(msg, rinfo) { | |
var self = this; | |
// Unmarshall the server negotiation packet | |
var packet = proto.serverProof.unmarshall(msg); | |
self.dbconn.findSession(packet.session, self.sessionTimeout) | |
.then(function (session) { | |
// Recreate the necessary SRP state to check the client's proof. | |
var srpServer = new srp.Server( | |
srp.params['2048'], | |
session.salt, | |
new Buffer(session.username, 'ascii'), | |
session.verifier, | |
session.secret | |
); | |
var proof; | |
srpServer.setA(session.ephemeral); | |
proof = srpServer.checkM1(packet.proof); | |
return proto.authProof.marshall({ | |
session: packet.session, | |
proof: proof | |
}); | |
}).catch(function (err) { | |
return proto.sessionError.marshall({ | |
session: packet.session, | |
error: proto.SESSION_AUTH_FAILED | |
}); | |
}).then(function (response) { | |
self.socket.send(response, 0, response.length, rinfo.port, rinfo.address); | |
}); | |
}; | |
module.exports = AuthApp; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment