diff --git a/.jshintrc b/.jshintrc
index 57512cb3..9cb6ee00 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -20,7 +20,8 @@
"jquery": true,
"predef": [
"_",
- "angular",
+ "angular",
+ "bs58",
"BigNumber",
"iso4217",
"Modernizr",
diff --git a/app/index.html b/app/index.html
index 93ed45e2..48844d01 100644
--- a/app/index.html
+++ b/app/index.html
@@ -133,6 +133,7 @@
Loading...
+
diff --git a/app/scripts/controllers/recovery-v2-controller.js b/app/scripts/controllers/recovery-v2-controller.js
index ef14a5df..408ac11b 100644
--- a/app/scripts/controllers/recovery-v2-controller.js
+++ b/app/scripts/controllers/recovery-v2-controller.js
@@ -36,6 +36,7 @@ angular.module('stellarClient').controller('RecoveryV2Ctrl', function($scope, $s
return $q.when(params)
.then(validate)
+ .then(getServerRecoveryCode)
.then(recover)
.then(function (params) {
$state.go('change_password_v2', {
@@ -61,19 +62,63 @@ angular.module('stellarClient').controller('RecoveryV2Ctrl', function($scope, $s
return $q.reject();
}
- // Append suffix
- params.username = params.username+'@stellar.org';
-
return params;
}
+ function getServerRecoveryCode(params) {
+ var deferred = $q.defer();
+
+ var data = {
+ username: params.username,
+ userRecoveryCode: params.recoveryCode
+ };
+
+ $http.post(Options.API_SERVER + '/user/recover', data)
+ .success(function(body) {
+ if (body.data && body.data.serverRecoveryCode) {
+ params.serverRecoveryCode = body.data.serverRecoveryCode;
+ deferred.resolve(params);
+ } else {
+ $scope.recoveryError = 'An error occurred.';
+ deferred.reject();
+ }
+ })
+ .error(function(body, status) {
+ switch(status) {
+ case 400:
+ if (body.code === 'invalid') {
+ $scope.recoveryError = 'Invalid username or recovery code.';
+ } else if (body.code === 'disabled') {
+ $scope.recoveryError = 'Recovery has been disabled for this account.';
+ }
+ break;
+ case 0:
+ $scope.recoveryError = 'Unable to contact the server.';
+ break;
+ default:
+ $scope.recoveryError = 'An error occurred.';
+ }
+ deferred.reject();
+ });
+
+ return deferred.promise;
+ }
+
function recover(params) {
var deferred = $q.defer();
+ // Append domain
+ params.username += '@stellar.org';
+
+ var userPartBytes = bs58.decode(params.recoveryCode);
+ var serverPartBytes = bs58.decode(params.serverRecoveryCode);
+ var fullRecoveryCodeBytes = userPartBytes.concat(serverPartBytes);
+ var fullRecoveryCode = bs58.encode(fullRecoveryCodeBytes);
+
var data = {
server: Options.WALLET_SERVER+'/v2',
username: params.username,
- recoveryCode: params.recoveryCode
+ recoveryCode: fullRecoveryCode
};
if ($scope.totpRequired) {
diff --git a/app/scripts/controllers/settings-controller.js b/app/scripts/controllers/settings-controller.js
index 10c4bdf5..4f0f4ab4 100644
--- a/app/scripts/controllers/settings-controller.js
+++ b/app/scripts/controllers/settings-controller.js
@@ -14,7 +14,7 @@ angular.module('stellarClient').controller('SettingsCtrl', function($scope, $htt
$scope.handleServerError = function (element) {
return function (error) {
- var message = error.status === 'fail' ? error.message : 'Server error';
+ var message = error.data.status === 'fail' ? error.data.message : 'Server error';
Util.showTooltip(element, message, 'error', 'top');
};
};
diff --git a/app/scripts/controllers/settings-email-controller.js b/app/scripts/controllers/settings-email-controller.js
index b90886d9..860cc127 100644
--- a/app/scripts/controllers/settings-email-controller.js
+++ b/app/scripts/controllers/settings-email-controller.js
@@ -1,8 +1,9 @@
-angular.module('stellarClient').controller('SettingsEmailCtrl', function($scope, $http, session, singletonPromise) {
+angular.module('stellarClient').controller('SettingsEmailCtrl', function($scope, $http, $q, session, singletonPromise) {
$scope.$on('settings-refresh', function () {
$scope.email = session.getUser().getEmailAddress();
$scope.emailVerified = session.getUser().isEmailVerified();
+ $scope.verifyToken = null;
$scope.resetEmailState();
});
@@ -27,36 +28,52 @@ angular.module('stellarClient').controller('SettingsEmailCtrl', function($scope,
return changeEmail();
} else if ($scope.emailState === 'verify') {
return verifyEmail();
- } else {
- return;
}
});
function verifyEmail () {
var verifyToken = $scope.verifyToken;
return session.getUser().verifyEmail(verifyToken)
- .then(function (response) {
- if (response.data.data && response.data.data.serverRecoveryCode) {
- return session.get('wallet').storeRecoveryData(verifyToken, response.data.data.serverRecoveryCode);
- }
- })
- .then(function () {
- return $scope.refreshAndInitialize();
- })
- .then(function () {
- $scope.verifyToken = null;
- })
- .catch($scope.handleServerError($('#email-input')));
+ .then(function (response) {
+ if (response.data.data && response.data.data.serverRecoveryCode) {
+ var userPartBytes = bs58.decode(verifyToken);
+ var serverPartBytes = bs58.decode(response.data.data.serverRecoveryCode);
+ var fullRecoveryCodeBytes = userPartBytes.concat(serverPartBytes);
+ return bs58.encode(fullRecoveryCodeBytes);
+ } else {
+ return $q.reject();
+ }
+ })
+ .then(function(fullRecoveryCode) {
+ var wallet = session.get('wallet');
+ return wallet.walletV2.enableRecovery({
+ recoveryCode: fullRecoveryCode,
+ secretKey: wallet.keychainData.signingKeys.secretKey
+ });
+ })
+ .then(function () {
+ return $scope.$parent.refreshAndInitialize();
+ })
+ .then(function () {
+ $scope.verifyToken = null;
+ })
+ .catch(StellarWallet.errors.ConnectionError, function(e) {
+ Util.showTooltip($('#verify-input'), 'Error connecting wallet server.', 'error', 'top');
+ })
+ .catch($scope.$parent.handleServerError($('#verify-input')))
+ .finally(function() {
+ $scope.$apply();
+ });
}
function changeEmail () {
return session.getUser().changeEmail($scope.newEmail)
- .then(function () {
- return $scope.refreshAndInitialize();
- })
- .then(function () {
- $scope.newEmail = null;
- })
- .catch($scope.handleServerError($('#verify-input')));
+ .then(function () {
+ return $scope.$parent.refreshAndInitialize();
+ })
+ .then(function () {
+ $scope.newEmail = null;
+ })
+ .catch($scope.$parent.handleServerError($('#email-input')));
}
});
diff --git a/app/scripts/controllers/settings-recovery-controller.js b/app/scripts/controllers/settings-recovery-controller.js
index ed003972..81f38810 100644
--- a/app/scripts/controllers/settings-recovery-controller.js
+++ b/app/scripts/controllers/settings-recovery-controller.js
@@ -1,58 +1,133 @@
'use strict';
-angular.module('stellarClient').controller('SettingsRecoveryCtrl', function($scope, session) {
- var wallet = session.get('wallet').walletV2;
+angular.module('stellarClient').controller('SettingsRecoveryCtrl', function($scope, $http, session, stellarApi, UserPrivateInfo, FlashMessages) {
+ var wallet = session.get('wallet');
+ var params = {
+ username: session.get('username'),
+ updateToken: wallet.keychainData.updateToken
+ };
+
+ var userRecoveryCode = null;
+ var serverRecoveryCode = null;
$scope.reset = function () {
$scope.error = null;
- $scope.enabling = false;
+ $scope.resetting = false;
+ $scope.sendingRecoveryCode = false;
$scope.code = null;
+
+ stellarApi.User.getNewRecoveryCode(params)
+ .then(function(response) {
+ if (response.data.status === 'success') {
+ $scope.resetting = true;
+ userRecoveryCode = response.data.userRecoveryCode;
+ serverRecoveryCode = response.data.serverRecoveryCode;
+ } else {
+ $scope.resetting = false;
+ }
+ });
};
$scope.reset();
$scope.$on('settings-refresh', $scope.reset);
$scope.$on('settings-recovery-clicked', function($event, toggle) {
- if (toggle.on) {
- disableRecovery();
- } else {
- enableRecovery();
- }
+ toggleRecovery(!toggle.on);
});
- function disableRecovery() {
- $scope.enabling = false;
- $scope.$emit('settings-recovery-toggled', false);
+ function toggleRecovery(value) {
+ $http.post(Options.API_SERVER+'/user/setrecover', _.extend(params, {
+ recover: value
+ })).success(function () {
+ $scope.$emit('settings-recovery-toggled', value);
+ }).error(function () {
+ FlashMessages.add({
+ title: 'Server error',
+ info: 'There was an error contacting server. Please try again later.',
+ type: 'error'
+ });
+ });
}
- var recoveryCode = null;
- $scope.recoveryCode = null; // Temp
+ $scope.resetRecovery = function($event) {
+ $event.preventDefault();
+ $scope.sendingRecoveryCode = true;
- function enableRecovery() {
- $scope.enabling = true;
- recoveryCode = StellarWallet.util.generateRandomRecoveryCode();
- $scope.recoveryCode = recoveryCode; // temp
- }
+ stellarApi.User.changeRecoveryToken(params)
+ .success(function() {
+ stellarApi.User.getNewRecoveryCode(params)
+ .then(function(response) {
+ if (response.data.status === 'success') {
+ $scope.resetting = true;
+ userRecoveryCode = response.data.userRecoveryCode;
+ serverRecoveryCode = response.data.serverRecoveryCode;
+ }
+ });
+ }).error(function (response){
+ $scope.resetting = false;
+ if (response.code === 'no_email') {
+ FlashMessages.add({
+ title: 'No email added',
+ info: 'Add and verify your email first. Recovery token will be sent to your email inbox.'
+ });
+ } else {
+ FlashMessages.add({
+ title: 'Recovery Error',
+ info: 'There was error trying to generate recovery token. Please try again later.',
+ type: 'error'
+ });
+ }
+ }).finally(function() {
+ $scope.sendingRecoveryCode = false;
+ });
+ };
- $scope.confirmEnableRecovery = function($event) {
+ $scope.confirmResetRecovery = function($event) {
$event.preventDefault();
- if ($scope.code === recoveryCode) {
- wallet.enableRecovery({
- secretKey: session.get('wallet').keychainData.signingKeys.secretKey,
- recoveryCode: $scope.code
- }).then(function() {
- recoveryCode = null;
- $scope.code = null;
- $scope.recoveryCode = null; // Temp
- $scope.enabling = false;
- $scope.$emit('settings-recovery-toggled', true);
- if (session.isPersistent()) {
- session.get('wallet').saveLocal(); // We need to rewrite wallet object because lockVersion has changed
- }
- }).finally(function() {
- $scope.$apply();
- }); // TODO handle errors
- } else {
+
+ if ($scope.code !== userRecoveryCode) {
$scope.error = 'Incorrect recovery code. Please try again.';
+ return;
}
+
+ var userPartBytes = bs58.decode($scope.code);
+ var serverPartBytes = bs58.decode(serverRecoveryCode);
+ var fullRecoveryCodeBytes = userPartBytes.concat(serverPartBytes);
+ var fullRecoveryCode = bs58.encode(fullRecoveryCodeBytes);
+ wallet.walletV2.enableRecovery({
+ recoveryCode: fullRecoveryCode,
+ secretKey: wallet.keychainData.signingKeys.secretKey
+ }).then(function() {
+ return stellarApi.User.finishChangeRecoveryToken(_.extend(params, {
+ userRecoveryCode: $scope.code
+ }));
+ }).then(function() {
+ $scope.code = null;
+ $scope.resetting = false;
+ FlashMessages.add({
+ title: 'Success',
+ info: 'Your recovery token has been reset!'
+ });
+ if (session.isPersistent()) {
+ session.get('wallet').saveLocal(); // We need to rewrite wallet object because lockVersion has changed
+ }
+ }).catch(StellarWallet.errors.ConnectionError, function(e) {
+ $scope.error = 'Connection error. Please try again.';
+ }).catch(function(e) {
+ $scope.error = 'Unknown error. Please try again.';
+ // TODO add logging
+ }).finally(function() {
+ $scope.$apply();
+ });
+ };
+
+ $scope.cancelResetRecovery = function($event) {
+ $event.preventDefault();
+
+ stellarApi.User.cancelChangingRecoveryCode(params)
+ .success(function() {
+ $scope.resetting = false;
+ }).error(function() {
+ $scope.error = 'Cannot cancel changing recovery code now. Please try again.';
+ });
};
});
\ No newline at end of file
diff --git a/app/scripts/libraries/bs58.js b/app/scripts/libraries/bs58.js
new file mode 100644
index 00000000..1bc0505f
--- /dev/null
+++ b/app/scripts/libraries/bs58.js
@@ -0,0 +1,82 @@
+// Base58 encoding/decoding
+// Originally written by Mike Hearn for BitcoinJ
+// Copyright (c) 2011 Google Inc
+// Ported to JavaScript by Stefan Thomas
+// Merged Buffer refactorings from base58-native by Stephen Pair
+// Copyright (c) 2013 BitPay Inc
+
+var bs58 = function() {
+var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+var ALPHABET_MAP = {}
+for(var i = 0; i < ALPHABET.length; i++) {
+ ALPHABET_MAP[ALPHABET.charAt(i)] = i
+}
+var BASE = 58
+
+function encode(buffer) {
+ if (buffer.length === 0) return ''
+
+ var i, j, digits = [0]
+ for (i = 0; i < buffer.length; i++) {
+ for (j = 0; j < digits.length; j++) digits[j] <<= 8
+
+ digits[0] += buffer[i]
+
+ var carry = 0
+ for (j = 0; j < digits.length; ++j) {
+ digits[j] += carry
+
+ carry = (digits[j] / BASE) | 0
+ digits[j] %= BASE
+ }
+
+ while (carry) {
+ digits.push(carry % BASE)
+
+ carry = (carry / BASE) | 0
+ }
+ }
+
+ // deal with leading zeros
+ for (i = 0; buffer[i] === 0 && i < buffer.length - 1; i++) digits.push(0)
+
+ return digits.reverse().map(function(digit) { return ALPHABET[digit] }).join('')
+}
+
+function decode(string) {
+ if (string.length === 0) return []
+
+ var i, j, bytes = [0]
+ for (i = 0; i < string.length; i++) {
+ var c = string[i]
+ if (!(c in ALPHABET_MAP)) throw new Error('Non-base58 character')
+
+ for (j = 0; j < bytes.length; j++) bytes[j] *= BASE
+ bytes[0] += ALPHABET_MAP[c]
+
+ var carry = 0
+ for (j = 0; j < bytes.length; ++j) {
+ bytes[j] += carry
+
+ carry = bytes[j] >> 8
+ bytes[j] &= 0xff
+ }
+
+ while (carry) {
+ bytes.push(carry & 0xff)
+
+ carry >>= 8
+ }
+ }
+
+ // deal with leading zeros
+ for (i = 0; string[i] === '1' && i < string.length - 1; i++) bytes.push(0)
+
+ return bytes.reverse()
+}
+
+return {
+ encode: encode,
+ decode: decode
+};
+}();
diff --git a/app/scripts/modules/stellar-api/user.js b/app/scripts/modules/stellar-api/user.js
index f9cb9a3e..43a68489 100644
--- a/app/scripts/modules/stellar-api/user.js
+++ b/app/scripts/modules/stellar-api/user.js
@@ -2,7 +2,23 @@ var api = angular.module('stellarApi');
api.service('User', function(http) {
var User = {};
-
+
+ User.changeRecoveryToken = function(params) {
+ return http.post('/user/changeRecoveryToken', {username: params.username, updateToken: params.updateToken});
+ };
+
+ User.getNewRecoveryCode = function(params) {
+ return http.get('/user/getNewRecoveryCode', {params: {username: params.username, updateToken: params.updateToken}});
+ };
+
+ User.cancelChangingRecoveryCode = function(params) {
+ return http.post('/user/cancelChangingRecoveryToken', {username: params.username, updateToken: params.updateToken});
+ };
+
+ User.finishChangeRecoveryToken = function(params) {
+ return http.post('/user/finishChangeRecoveryToken', {username: params.username, updateToken: params.updateToken, userRecoveryCode: params.userRecoveryCode});
+ };
+
User.validateUsername = function(username) {
return http.post('/user/validname', {username: username});
};
diff --git a/app/scripts/utilities/wallet.js b/app/scripts/utilities/wallet.js
index c120e257..75825b30 100644
--- a/app/scripts/utilities/wallet.js
+++ b/app/scripts/utilities/wallet.js
@@ -226,35 +226,6 @@ angular.module('stellarClient').factory('Wallet', function($q, $http, ipCookie)
return this;
};
- Wallet.prototype.storeRecoveryData = function (userRecoveryCode, serverRecoveryCode) {
- var recoveryId = Wallet.deriveId(userRecoveryCode, serverRecoveryCode);
- var recoveryKey = Wallet.deriveKey(recoveryId, userRecoveryCode, serverRecoveryCode);
-
- var data = this.createRecoveryData(recoveryId, recoveryKey);
-
- return $http.post(Options.WALLET_SERVER + '/wallets/create_recovery_data', data);
- };
-
- /**
- * Encrypts the wallet's id and key into the recoveryData and sets its the recoveryId.
- *
- * @param {string} recoveryId
- * @param {string} recoveryKey
- * @memberOf Wallet
- */
- Wallet.prototype.createRecoveryData = function(recoveryId, recoveryKey){
- var rawRecoveryKey = sjcl.codec.hex.toBits(recoveryKey);
- var recoveryData = Wallet.encryptData({id: this.id, key: this.key}, rawRecoveryKey);
-
- return {
- id: this.id,
- authToken: this.keychainData.authToken,
- recoveryId: recoveryId,
- recoveryData: recoveryData,
- recoveryDataHash: sjcl.codec.hex.fromBits(sjcl.hash.sha1.hash(recoveryData))
- };
- };
-
/**
* Encrypts the wallet data into a generic object.
*
diff --git a/app/templates/settings-recovery.html b/app/templates/settings-recovery.html
index b37b331a..8bf4b77f 100644
--- a/app/templates/settings-recovery.html
+++ b/app/templates/settings-recovery.html
@@ -1,20 +1,33 @@
-
+
+ Sending recovery code to your email...
+
+
+
+
+
- We've just sent a new code to your email. Enter your code below: ({{ recoveryCode }})
+ We've sent a code to your email. Enter your code below:
-