fix: Passphrase getting generated correctly
This commit is contained in:
parent
40a13ecef8
commit
df52a0ef95
4 changed files with 214 additions and 5 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var cryptoUtil = require('../util/crypto');
|
var cryptoUtil = require('../util/crypto');
|
||||||
|
var buffers = require('buffer');
|
||||||
var querystring = require('querystring');
|
var querystring = require('querystring');
|
||||||
var Identity = require('../models/Identity');
|
var Identity = require('../models/Identity');
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ InsightStorage.prototype.setCredentials = function(email, password, opts) {
|
||||||
|
|
||||||
InsightStorage.prototype.createItem = function(name, value, callback) {
|
InsightStorage.prototype.createItem = function(name, value, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.getItem(name, function(err, retrieved) {
|
this.getItem(name, function(err, retrieved) {
|
||||||
if (err || !retrieved) {
|
if (err || !retrieved) {
|
||||||
return self.setItem(name, value, callback);
|
return self.setItem(name, value, callback);
|
||||||
|
|
@ -27,12 +29,35 @@ InsightStorage.prototype.createItem = function(name, value, callback) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mayBeOldPassword(password) {
|
||||||
|
// Test for base64
|
||||||
|
return /^[a-zA-Z0-9\/=\+]+$/.test(password);
|
||||||
|
}
|
||||||
|
|
||||||
InsightStorage.prototype.getItem = function(name, callback) {
|
InsightStorage.prototype.getItem = function(name, callback) {
|
||||||
var key = cryptoUtil.kdf(this.password + this.email);
|
var key = cryptoUtil.kdf(this.password + this.email);
|
||||||
var secret = cryptoUtil.kdf(key, this.password);
|
var secret = this.makeSecret(key);
|
||||||
var encodedEmail = encodeURIComponent(this.email);
|
var self = this;
|
||||||
var retrieveUrl = this.storeUrl + '/retrieve/' + encodedEmail;
|
|
||||||
this.request.get(retrieveUrl + '?' + querystring.encode({secret: secret, key: name}),
|
this._makeGetRequest(secret, name, function(err, body) {
|
||||||
|
if (err && err.indexOf('PNOTFOUND') !== -1 && mayBeOldPassword(self.password)) {
|
||||||
|
return self._brokenGetItem(key, name, callback);
|
||||||
|
}
|
||||||
|
return callback(err, body);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
InsightStorage.prototype.makeSecret = function(key) {
|
||||||
|
return cryptoUtil.kdf(key + this.password);
|
||||||
|
};
|
||||||
|
|
||||||
|
InsightStorage.prototype._makeGetRequest = function(secret, key, callback) {
|
||||||
|
var authHeader = new Buffer(this.email + ':' + secret).toString('base64');
|
||||||
|
var retrieveUrl = this.storeUrl + '/retrieve';
|
||||||
|
this.request.get({
|
||||||
|
url: retrieveUrl + '?' + querystring.encode({key: key}),
|
||||||
|
headers: {'Authorization': authHeader}
|
||||||
|
},
|
||||||
function(err, response, body) {
|
function(err, response, body) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback('Connection error');
|
return callback('Connection error');
|
||||||
|
|
@ -48,9 +73,56 @@ InsightStorage.prototype.getItem = function(name, callback) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
InsightStorage.prototype._brokenGetItem = function(key, name, callback) {
|
||||||
|
var secret = this._makeBrokenSecret(key);
|
||||||
|
var self = this;
|
||||||
|
this._makeGetRequest(secret, name, function(err, body) {
|
||||||
|
if (!err) {
|
||||||
|
return self._changePassword(function(err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null, body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
InsightStorage.prototype._makeBrokenSecret = function(key) {
|
||||||
|
return cryptoUtil.kdf(key, this.password);
|
||||||
|
};
|
||||||
|
|
||||||
|
InsightStorage.prototype._changePassword = function(callback) {
|
||||||
|
var key = cryptoUtil.kdf(this.password + this.email);
|
||||||
|
var secret = this._makeBrokenSecret(key);
|
||||||
|
var newSecret = this.makeSecret(key);
|
||||||
|
|
||||||
|
var url = this.storeUrl + '/change_passphrase';
|
||||||
|
this.request.post({
|
||||||
|
url: url,
|
||||||
|
body: querystring.encode({
|
||||||
|
email: this.email,
|
||||||
|
secret: secret,
|
||||||
|
newSecret: newSecret
|
||||||
|
})
|
||||||
|
}, function(err, response, body) {
|
||||||
|
if (err) {
|
||||||
|
return callback('Connection error');
|
||||||
|
}
|
||||||
|
if (response.statusCode === 409) {
|
||||||
|
return callback('BADCREDENTIALS: Invalid username or password');
|
||||||
|
}
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
return callback('Unable to store data on insight');
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
InsightStorage.prototype.setItem = function(name, value, callback) {
|
InsightStorage.prototype.setItem = function(name, value, callback) {
|
||||||
var key = cryptoUtil.kdf(this.password + this.email);
|
var key = cryptoUtil.kdf(this.password + this.email);
|
||||||
var secret = cryptoUtil.kdf(key, this.password);
|
var secret = this.makeSecret(key);
|
||||||
var registerUrl = this.storeUrl + '/register';
|
var registerUrl = this.storeUrl + '/register';
|
||||||
this.request.post({
|
this.request.post({
|
||||||
url: registerUrl,
|
url: registerUrl,
|
||||||
|
|
|
||||||
131
test/plugin.insight.js
Normal file
131
test/plugin.insight.js
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
var InsightStorage = require('../js/plugins/InsightStorage');
|
||||||
|
var assert = require('assert');
|
||||||
|
var querystring = require('querystring');
|
||||||
|
|
||||||
|
describe('insight storage plugin', function() {
|
||||||
|
|
||||||
|
var requestMock = sinon.stub();
|
||||||
|
var storage = new InsightStorage({request: requestMock});
|
||||||
|
var email = 'john@doe.com';
|
||||||
|
var password = '1234';
|
||||||
|
|
||||||
|
var data = '{"random": true}';
|
||||||
|
var namespace = 'profile::0000000000000000000000000000000000000000';
|
||||||
|
|
||||||
|
var oldSecret = 'rFA+F/N+ZvKXp717zBdfCKYQ5v9Fjry0W6tautj5etIH'
|
||||||
|
+ 'KLQliZBEYXA7AXjTJ9K3DglzGWJKost3QJUCMbhM/A=='
|
||||||
|
var newSecret = '2YTcmtkmC/WSVrfmjAEyOmDnmntVQ8A9wS/q1DbD6rkc'
|
||||||
|
+ 'si8LrIvS2Ru85Feb5Bvk2ziQbEN6PzlL1mIyQ3a+Vw=='
|
||||||
|
|
||||||
|
var setupStorageCredentials = function() {
|
||||||
|
storage.setCredentials(email, password);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
requestMock.reset();
|
||||||
|
requestMock.get = sinon.stub();
|
||||||
|
requestMock.post = sinon.stub();
|
||||||
|
setupStorageCredentials();
|
||||||
|
});
|
||||||
|
|
||||||
|
var setupForCreation = function() {
|
||||||
|
requestMock.get.onFirstCall().callsArgWith(1, 'Not found');
|
||||||
|
requestMock.post.onFirstCall().callsArgWith(1, null, {statusCode: 200});
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be able to create a namespace for storage', function(done) {
|
||||||
|
|
||||||
|
setupForCreation();
|
||||||
|
|
||||||
|
storage.createItem(namespace, data, function(err) {
|
||||||
|
assert(!err);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var setupForRetrieval = function() {
|
||||||
|
requestMock.get.onFirstCall().callsArgWith(1, null, {statusCode: 200}, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be able to retrieve data in a namespace', function(done) {
|
||||||
|
|
||||||
|
setupForRetrieval();
|
||||||
|
|
||||||
|
storage.getItem(namespace, function(err, retrieved) {
|
||||||
|
assert(!err);
|
||||||
|
assert(retrieved === data);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var setupForSave = function () {
|
||||||
|
requestMock.post.onFirstCall().callsArgWith(1, null, {statusCode: 200});
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should be able to overwrite data when using same password', function(done) {
|
||||||
|
setupForSave();
|
||||||
|
|
||||||
|
storage.setItem(namespace, data, function(err) {
|
||||||
|
assert(!err);
|
||||||
|
assert(requestMock.post.firstCall.args[0].url.indexOf('register') !== -1);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('won\'t make an unnecessary request if old password can\'t work', function(done) {
|
||||||
|
storage.setCredentials(email, '!');
|
||||||
|
setupForRetrieval();
|
||||||
|
|
||||||
|
storage.getItem(namespace, function(err, retrieved) {
|
||||||
|
assert(requestMock.get.firstCall);
|
||||||
|
assert(!requestMock.get.secondCall);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t be able to create a namespace twice', function(done) {
|
||||||
|
setupForRetrieval();
|
||||||
|
|
||||||
|
storage.createItem(namespace, data, function(err) {
|
||||||
|
assert(err);
|
||||||
|
assert(requestMock.get.firstCall.args[0].url.indexOf('retrieve') !== -1);
|
||||||
|
assert(!requestMock.post.firstCall);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var setupForOldData = function() {
|
||||||
|
requestMock.get = sinon.stub();
|
||||||
|
requestMock.get.onFirstCall().callsArgWith(1, null, {statusCode: 403});
|
||||||
|
requestMock.get.onSecondCall().callsArgWith(1, null, {statusCode: 200}, data);
|
||||||
|
requestMock.post = sinon.stub();
|
||||||
|
requestMock.post.onFirstCall().callsArgWith(1, null, {statusCode: 200});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should be able to restore 0.7.2 data', function(done) {
|
||||||
|
|
||||||
|
setupForOldData();
|
||||||
|
|
||||||
|
storage.getItem(namespace, function(error, dataReturned) {
|
||||||
|
assert(!error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change the remote passphrase if retrieved with 0.7.2 passphrase',
|
||||||
|
function(done) {
|
||||||
|
|
||||||
|
setupForOldData();
|
||||||
|
|
||||||
|
storage.getItem(namespace, function(error, dataReturned) {
|
||||||
|
var receivedArgs = requestMock.post.firstCall.args[0].body;
|
||||||
|
var url = requestMock.post.firstCall.args[0].url;
|
||||||
|
var args = querystring.decode(receivedArgs);
|
||||||
|
assert(url.indexOf('change_passphrase') !== -1);
|
||||||
|
assert(args.secret === oldSecret);
|
||||||
|
assert(args.newSecret === newSecret);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -48,6 +48,9 @@ describe('crypto utils', function() {
|
||||||
var phrase = cryptoUtils.kdf(t.word, t.salt, t.iterations);
|
var phrase = cryptoUtils.kdf(t.word, t.salt, t.iterations);
|
||||||
phrase.should.equal(t.phrase);
|
phrase.should.equal(t.phrase);
|
||||||
});
|
});
|
||||||
|
it('should generate a passphrase from weird chars', function() {
|
||||||
|
var phrase = cryptoUtils.kdf('Pwd123!@#$%^&*(){}[]\|/?.>,<=+-_`~åéþ䲤þçæ¶');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@ var createBundle = function(opts) {
|
||||||
b.require('./js/plugins/InsightStorage', {
|
b.require('./js/plugins/InsightStorage', {
|
||||||
expose: '../plugins/InsightStorage'
|
expose: '../plugins/InsightStorage'
|
||||||
});
|
});
|
||||||
|
b.require('./js/plugins/InsightStorage', {
|
||||||
|
expose: '../js/plugins/InsightStorage'
|
||||||
|
});
|
||||||
b.require('./js/plugins/LocalStorage', {
|
b.require('./js/plugins/LocalStorage', {
|
||||||
expose: '../plugins/LocalStorage'
|
expose: '../plugins/LocalStorage'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue