mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 17:27:59 +00:00
[FEATURE] recover blob and change password
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
var crypt = require('./crypt').Crypt;
|
||||
var SignedRequest = require('./signedrequest').SignedRequest;
|
||||
var request = require('superagent');
|
||||
var extend = require("extend");
|
||||
var async = require("async");
|
||||
var request = require('superagent');
|
||||
var extend = require("extend");
|
||||
var async = require("async");
|
||||
|
||||
var BlobClient = {};
|
||||
|
||||
@@ -261,11 +261,12 @@ BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) {
|
||||
* Decrypt recovery key
|
||||
*
|
||||
* @param {string} secret
|
||||
* @param {string} encryptedKey
|
||||
*/
|
||||
|
||||
BlobObj.prototype.decryptBlobCrypt = function(secret) {
|
||||
function decryptBlobCrypt (secret, encryptedKey) {
|
||||
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
|
||||
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
|
||||
return crypt.decrypt(recoveryEncryptionKey, encryptedKey);
|
||||
};
|
||||
|
||||
/**** Blob updating functions ****/
|
||||
@@ -843,6 +844,7 @@ BlobClient.verify = function(url, username, token, fn) {
|
||||
* ResendEmail
|
||||
* resend verification email
|
||||
*/
|
||||
|
||||
BlobClient.resendEmail = function (opts, fn) {
|
||||
var config = {
|
||||
method : 'POST',
|
||||
@@ -875,6 +877,145 @@ BlobClient.resendEmail = function (opts, fn) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* RecoverBlob
|
||||
* recover a blob using the account secret
|
||||
* @param {object} opts
|
||||
* @param {string} opts.url
|
||||
* @param {string} opts.username
|
||||
* @param {string} opts.masterkey
|
||||
* @param {function} fn
|
||||
*/
|
||||
|
||||
BlobClient.recoverBlob = function (opts, fn) {
|
||||
var username = String(opts.username).trim();
|
||||
var config = {
|
||||
method : 'GET',
|
||||
url : opts.url + '/v1/user/recov/' + username,
|
||||
};
|
||||
|
||||
var signedRequest = new SignedRequest(config);
|
||||
var signed = signedRequest.signAsymmetricRecovery(opts.masterkey, username);
|
||||
|
||||
request.get(signed.url)
|
||||
.end(function(err, resp) {
|
||||
if (err) {
|
||||
fn(err);
|
||||
} else if (resp.body && resp.body.result === 'success') {
|
||||
handleRecovery(resp);
|
||||
} else if (resp.body && resp.body.result === 'error') {
|
||||
fn(new Error(resp.body.message));
|
||||
} else {
|
||||
fn(new Error('Could not recover blob'));
|
||||
}
|
||||
});
|
||||
|
||||
function handleRecovery (resp) {
|
||||
//decrypt crypt key
|
||||
var crypt = decryptBlobCrypt(opts.masterkey, resp.body.encrypted_blobdecrypt_key);
|
||||
var blob = new BlobObj(opts.url, resp.body.blob_id, crypt);
|
||||
|
||||
blob.revision = resp.body.revision;
|
||||
blob.encrypted_secret = resp.body.encrypted_secret;
|
||||
|
||||
if (!blob.decrypt(resp.body.blob)) {
|
||||
return fn(new Error('Error while decrypting blob'));
|
||||
}
|
||||
|
||||
//Apply patches
|
||||
if (resp.body.patches && resp.body.patches.length) {
|
||||
var successful = true;
|
||||
resp.body.patches.forEach(function(patch) {
|
||||
successful = successful && blob.applyEncryptedPatch(patch);
|
||||
});
|
||||
|
||||
if (successful) {
|
||||
blob.consolidate();
|
||||
}
|
||||
}
|
||||
|
||||
//return with newly decrypted blob
|
||||
fn(null, blob);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* updateKeys
|
||||
* @param {object} opts
|
||||
* @param {string} opts.username
|
||||
* @param {string} opts.password
|
||||
* @param {string} opts.masterkey
|
||||
* @param {object} opts.pakdf
|
||||
*/
|
||||
|
||||
BlobObj.prototype.updateKeys = function (opts, fn) {
|
||||
var self = this;
|
||||
var username = String(opts.username).trim();
|
||||
var password = String(opts.password).trim();
|
||||
|
||||
function deriveKeys(callback) {
|
||||
// derive unlock and login keys
|
||||
var keys = { };
|
||||
|
||||
function deriveKey(keyType, callback) {
|
||||
crypt.derive(opts.pakdf, keyType, username.toLowerCase(), password, function(err, key) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
keys[keyType] = key;
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async.eachSeries([ 'login', 'unlock' ], deriveKey, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, keys);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function updateBlob (keys, callback) {
|
||||
var old_id = self.id;
|
||||
|
||||
self.id = keys.login.id;
|
||||
self.key = keys.login.crypt;
|
||||
self.encrypted_secret = self.encryptSecret(keys.unlock.unlock, opts.masterkey);
|
||||
|
||||
//post to the blob vault to create
|
||||
var config = {
|
||||
method : 'POST',
|
||||
url : self.url + '/v1/user/' + username,
|
||||
data : {
|
||||
blob_id : self.id,
|
||||
data : self.encrypt(),
|
||||
revision : self.revision,
|
||||
encrypted_blobdecrypt_key : self.encryptBlobCrypt(opts.masterkey, self.key),
|
||||
encrypted_secret : self.encrypted_secret
|
||||
}
|
||||
};
|
||||
|
||||
var signedRequest = new SignedRequest(config);
|
||||
var signed = signedRequest.signAsymmetric(opts.masterkey, self.data.account_id, old_id);
|
||||
|
||||
request.post(signed.url)
|
||||
.send(signed.data)
|
||||
.end(function(err, resp) {
|
||||
if (err) {
|
||||
fn(new Error('Updated blob could not be saved - XHR error'));
|
||||
} else if (!resp.body || resp.body.result !== 'success') {
|
||||
fn(new Error('Updated blob could not be saved - bad result'));
|
||||
} else {
|
||||
fn(null, resp.body);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async.waterfall([ deriveKeys, updateBlob ], fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename a ripple account
|
||||
*
|
||||
@@ -894,6 +1035,7 @@ BlobClient.rename = function (opts, fn) {
|
||||
username: opts.new_username,
|
||||
data: opts.blob.encrypt(),
|
||||
encrypted_secret: opts.blob.encryptedSecret,
|
||||
encrypted_blobdecrypt_key: opts.blob.encrypted_blobdecrypt_key,
|
||||
revision: opts.blob.revision
|
||||
}
|
||||
};
|
||||
@@ -978,7 +1120,7 @@ BlobClient.create = function(options, fn) {
|
||||
if (err) {
|
||||
fn(err);
|
||||
} else if (resp.body && resp.body.result === 'success') {
|
||||
fn(null, blob,resp.body);
|
||||
fn(null, blob, resp.body);
|
||||
} else if (resp.body && resp.body.result === 'error') {
|
||||
fn(new Error(resp.body.message));
|
||||
} else {
|
||||
@@ -988,3 +1130,7 @@ BlobClient.create = function(options, fn) {
|
||||
};
|
||||
|
||||
exports.BlobClient = BlobClient;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ var SignedRequest = function (config) {
|
||||
// XXX Constructor should be generalized and constructing from an Angular.js
|
||||
// $http config should be a SignedRequest.from... utility method.
|
||||
this.config = extend(true, {}, config);
|
||||
if (!this.config.data) this.config.data = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -150,6 +151,34 @@ SignedRequest.prototype.signAsymmetric = function (secretKey, account, blob_id)
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Asymmetric signed request for vault recovery
|
||||
* @param {Object} config
|
||||
* @param {Object} secretKey
|
||||
* @param {Object} username
|
||||
*/
|
||||
SignedRequest.prototype.signAsymmetricRecovery = function (secretKey, username) {
|
||||
var config = extend(true, {}, this.config);
|
||||
|
||||
// Parse URL
|
||||
var parsed = parser.parse(config.url);
|
||||
var date = dateAsIso8601();
|
||||
var signatureType = 'RIPPLE1-ECDSA-SHA512';
|
||||
var stringToSign = this.getStringToSign(parsed, date, signatureType);
|
||||
var signature = Message.signMessage(stringToSign, secretKey);
|
||||
|
||||
var query = querystring.stringify({
|
||||
signature: Crypt.base64ToBase64Url(signature),
|
||||
signature_date: date,
|
||||
signature_username: username,
|
||||
signature_type: signatureType
|
||||
});
|
||||
|
||||
config.url += (parsed.search ? '&' : '?') + query;
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
var dateAsIso8601 = (function () {
|
||||
function pad(n) {
|
||||
return (n < 0 || n > 9 ? "" : "0") + n;
|
||||
|
||||
@@ -90,9 +90,9 @@ VaultClient.prototype.login = function(username, password, callback) {
|
||||
self.infos[keys.id] = authInfo;
|
||||
|
||||
callback(null, {
|
||||
blob: blob,
|
||||
username: authInfo.username,
|
||||
verified: authInfo.emailVerified
|
||||
blob : blob,
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -203,11 +203,11 @@ VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
blob : blob,
|
||||
unlock : keys.unlock,
|
||||
secret : secret,
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
blob : blob,
|
||||
unlock : keys.unlock,
|
||||
secret : secret,
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -231,7 +231,7 @@ VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
|
||||
if (!authInfo) {
|
||||
return callback(new Error('Unable to find authInfo'));
|
||||
}
|
||||
|
||||
|
||||
deriveUnlockKey(authInfo, resp.blob, callback);
|
||||
});
|
||||
};
|
||||
@@ -281,10 +281,117 @@ VaultClient.prototype.verify = function(username, token, callback) {
|
||||
* resendEmail
|
||||
* send a new verification email
|
||||
* @param {object} options
|
||||
* @param {function} fn - Callback
|
||||
*/
|
||||
VaultClient.prototype.resendEmail = function (options, fn) {
|
||||
blobClient.resendEmail(options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* recoverBlob
|
||||
* recover blob with account secret
|
||||
* @param {object} options
|
||||
* @param {string} options.url
|
||||
* @param {string} options.username
|
||||
* @param {string} options.masterkey
|
||||
* @param {function}
|
||||
*/
|
||||
|
||||
VaultClient.prototype.recoverBlob = function (options, fn) {
|
||||
blobClient.recoverBlob(options, fn);
|
||||
};
|
||||
|
||||
VaultClient.prototype.updateBlobKeys = function (options, fn) {
|
||||
var username = String(options.username).trim();
|
||||
|
||||
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
options.pakdf = authInfo.pakdf;
|
||||
options.blob.updateKeys(options, fn);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* rename
|
||||
* rename a ripple account
|
||||
* @param {object} options
|
||||
* @param {function} callback
|
||||
*/
|
||||
VaultClient.prototype.resendEmail = function (options, callback) {
|
||||
blobClient.resendEmail(options, callback);
|
||||
VaultClient.prototype.rename = function (options, callback) {
|
||||
var self = this;
|
||||
var new_username = options.new_username;
|
||||
var password = options.password;
|
||||
|
||||
// TODO duplicate function
|
||||
function getAuthInfo(callback) {
|
||||
self.authInfo.get(self.domain, new_username, function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (authInfo.version !== 3) {
|
||||
return callback(new Error('This wallet is incompatible with this version of the vault-client.'));
|
||||
}
|
||||
|
||||
if (!authInfo.pakdf) {
|
||||
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||
}
|
||||
|
||||
/*if (!authInfo.exists) {
|
||||
return callback(new Error('User does not exist.'));
|
||||
}*/
|
||||
|
||||
if (typeof authInfo.blobvault !== 'string') {
|
||||
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||
}
|
||||
|
||||
callback(null, authInfo);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO duplicate function
|
||||
function deriveLoginKeys(authInfo, callback) {
|
||||
//derive login keys
|
||||
crypt.derive(authInfo.pakdf, 'login', new_username.toLowerCase(), password, function(err, keys) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, authInfo, keys);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO duplicate function
|
||||
function deriveUnlockKey(authInfo, callback) {
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', new_username.toLowerCase(), password, function(err, keys) {
|
||||
if (err) {
|
||||
console.log('Error',err);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, keys.unlock);
|
||||
});
|
||||
}
|
||||
|
||||
getAuthInfo(function(err, authInfo){
|
||||
deriveLoginKeys(authInfo, function(err, authInfo, loginKeys){
|
||||
deriveUnlockKey(authInfo, function(err, unlockKeys){
|
||||
if (err) {
|
||||
console.log('Error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
options.crypt = loginKeys.crypt;
|
||||
options.new_blob_id = loginKeys.id;
|
||||
options.unlock = unlockKeys;
|
||||
|
||||
blobClient.rename(options, callback);
|
||||
})
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user