login/logout & authenticate with session token
This commit is contained in:
parent
3ee12d3c0f
commit
4fd990772c
|
@ -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;
|
||||
|
|
111
lib/server.js
111
lib/server.js
|
@ -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.
|
||||
|
|
|
@ -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() {};
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue