login/logout & authenticate with session token

This commit is contained in:
Ivan Socolsky 2016-08-09 16:45:48 -03:00
parent 3ee12d3c0f
commit 4fd990772c
No known key found for this signature in database
GPG Key ID: FAECE6A05FAA4F56
4 changed files with 215 additions and 19 deletions

View File

@ -88,4 +88,6 @@ Defaults.BLOCKHEIGHT_CACHE_TIME = 10 * 60;
Defaults.MAX_NOTIFICATIONS_TIMESPAN = 60 * 60 * 24 * 14; // ~ 2 weeks
Defaults.NOTIFICATIONS_TIMESPAN = 60;
Defaults.SESSION_EXPIRATION = 1 * 60 * 60; // 1 hour to session expiration
module.exports = Defaults;

View File

@ -196,32 +196,65 @@ WalletService.getInstance = function(opts) {
* Gets an instance of the server after authenticating the copayer.
* @param {Object} opts
* @param {string} opts.copayerId - The copayer id making the request.
* @param {string} opts.message - The contents of the request to be signed.
* @param {string} opts.signature - Signature of message to be verified using one of the copayer's requestPubKeys
* @param {string} opts.message - (Optional) The contents of the request to be signed. Only needed if no session token is provided.
* @param {string} opts.signature - (Optional) Signature of message to be verified using one of the copayer's requestPubKeys. Only needed if no session token is provided.
* @param {string} opts.session - (Optional) A valid session token previously obtained using the #login method
* @param {string} opts.clientVersion - A string that identifies the client issuing the request
*/
WalletService.getInstanceWithAuth = function(opts, cb) {
if (!checkRequired(opts, ['copayerId', 'message', 'signature'], cb)) return;
function withSignature(cb) {
if (!checkRequired(opts, ['copayerId', 'message', 'signature'], cb)) return;
var server;
try {
server = WalletService.getInstance(opts);
} catch (ex) {
return cb(ex);
}
var server;
try {
server = WalletService.getInstance(opts);
} catch (ex) {
return cb(ex);
}
server.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) {
if (err) return cb(err);
if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found'));
server.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) {
if (err) return cb(err);
if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found'));
var isValid = !!server._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys);
if (!isValid)
return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature'));
var isValid = !!server._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys);
if (!isValid)
return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature'));
server.copayerId = opts.copayerId;
server.walletId = copayer.walletId;
return cb(null, server);
});
server.copayerId = opts.copayerId;
server.walletId = copayer.walletId;
return cb(null, server);
});
};
function withSession(cb) {
if (!checkRequired(opts, ['copayerId', 'session'], cb)) return;
var server;
try {
server = WalletService.getInstance(opts);
} catch (ex) {
return cb(ex);
}
server.storage.getSession(opts.copayerId, function(err, s) {
if (err) return cb(err);
var isValid = s && s.id == opts.session && s.isValid();
if (!isValid) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Session expired'));
server.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) {
if (err) return cb(err);
if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found'));
server.copayerId = opts.copayerId;
server.walletId = copayer.walletId;
return cb(null, server);
});
});
};
var authFn = opts.session ? withSession : withSignature;
return authFn(cb);
};
WalletService.prototype._runLocked = function(cb, task) {
@ -229,6 +262,46 @@ WalletService.prototype._runLocked = function(cb, task) {
this.lock.runLocked(this.walletId, cb, task);
};
WalletService.prototype.login = function(opts, cb) {
var self = this;
var session;
async.series([
function(next) {
self.storage.getSession(self.copayerId, function(err, s) {
if (err) return next(err);
session = s;
next();
});
},
function(next) {
if (!session || !session.isValid()) {
session = Model.Session.create({
copayerId: self.copayerId,
walletId: self.walletId,
});
} else {
session.touch();
}
next();
},
function(next) {
self.storage.storeSession(session, next);
},
], function(err) {
if (err) return cb(err);
if (!session) return cb(new Error('Could not get current session for this copayer'));
return cb(null, session.id);
});
};
WalletService.prototype.logout = function(opts, cb) {
var self = this;
self.storage.removeSession(self.copayerId, cb);
};
/**
* Creates a new wallet.

View File

@ -23,6 +23,7 @@ var collections = {
CACHE: 'cache',
FIAT_RATES: 'fiat_rates',
TX_NOTES: 'tx_notes',
SESSIONS: 'sessions',
};
var Storage = function(opts) {
@ -868,6 +869,27 @@ Storage.prototype.storeTxNote = function(txNote, cb) {
}, cb);
};
Storage.prototype.getSession = function(copayerId, cb) {
var self = this;
self.db.collection(collections.SESSIONS).findOne({
copayerId: copayerId,
},
function(err, result) {
if (err || !result) return cb(err);
return cb(null, Model.Session.fromObj(result));
});
};
Storage.prototype.storeSession = function(session, cb) {
this.db.collection(collections.SESSIONS).update({
copayerId: session.copayerId,
}, session.toObject(), {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log;
cb = cb || function() {};

View File

@ -142,6 +142,105 @@ describe('Wallet service', function() {
});
});
describe('Session management (#login, #logout, #authenticate)', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 2, function(s, w) {
server = s;
wallet = w;
done();
});
});
it('should get a new session & authenticate', function(done) {
WalletService.getInstanceWithAuth({
copayerId: server.copayerId,
session: 'dummy',
}, function(err, server2) {
should.exist(err);
err.code.should.equal('NOT_AUTHORIZED');
err.message.toLowerCase().should.contain('session');
should.not.exist(server2);
server.login({}, function(err, token) {
should.not.exist(err);
should.exist(token);
WalletService.getInstanceWithAuth({
copayerId: server.copayerId,
session: token,
}, function(err, server2) {
should.not.exist(err);
should.exist(server2);
server2.copayerId.should.equal(server.copayerId);
server2.walletId.should.equal(server.walletId);
done();
});
});
});
});
it('should get the same session token for two requests in a row', function(done) {
server.login({}, function(err, token) {
should.not.exist(err);
should.exist(token);
server.login({}, function(err, token2) {
should.not.exist(err);
token2.should.equal(token);
done();
});
});
});
it('should create a new session if the previous one has expired', function(done) {
var timer = sinon.useFakeTimers('Date');
var token;
async.series([
function(next) {
server.login({}, function(err, t) {
should.not.exist(err);
should.exist(t);
token = t;
next();
});
},
function(next) {
WalletService.getInstanceWithAuth({
copayerId: server.copayerId,
session: token,
}, function(err, server2) {
should.not.exist(err);
should.exist(server2);
next();
});
},
function(next) {
timer.tick((Defaults.SESSION_EXPIRATION + 1) * 1000);
next();
},
function(next) {
server.login({}, function(err, t) {
should.not.exist(err);
t.should.not.equal(token);
next();
});
},
function(next) {
WalletService.getInstanceWithAuth({
copayerId: server.copayerId,
session: token,
}, function(err, server2) {
should.exist(err);
err.code.should.equal('NOT_AUTHORIZED');
err.message.should.contain('expired');
next();
});
},
], function(err) {
should.not.exist(err);
timer.restore();
done();
});
});
});
describe('#createWallet', function() {
var server;
beforeEach(function() {