diff --git a/src/app/components/login/login.js b/src/app/components/login/login.js
index 3f7a87213..84b61f269 100644
--- a/src/app/components/login/login.js
+++ b/src/app/components/login/login.js
@@ -1,6 +1,3 @@
-import crypto from 'crypto';
-import mnemonic from 'bitcore-mnemonic';
-
import './login.less';
import './save.less';
@@ -11,17 +8,20 @@ app.component('login', {
onLogin: '&',
},
controller: class login {
- constructor($scope, $rootScope, $timeout, $document, $mdDialog, $mdMedia, $cookies, $peers) {
+
+ /* eslint no-param-reassign: ["error", { "props": false }] */
+ constructor($scope, $rootScope, $timeout, $document, $mdMedia, $cookies, $peers, Passphrase) {
this.$scope = $scope;
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.$document = $document;
- this.$mdDialog = $mdDialog;
this.$mdMedia = $mdMedia;
this.$cookies = $cookies;
this.$peers = $peers;
+ this.Passphrase = Passphrase;
+ this.generatingNewPassphrase = false;
- this.$scope.$watch('$ctrl.input_passphrase', this.isValidPassphrase.bind(this));
+ this.$scope.$watch('$ctrl.input_passphrase', val => this.valid = this.Passphrase.isValidPassphrase(val));
this.$timeout(this.devTestAccount.bind(this), 200);
this.$scope.$watch(() => this.$mdMedia('xs') || this.$mdMedia('sm'), (wantsFullScreen) => {
@@ -35,180 +35,32 @@ app.component('login', {
this.$peers.setActive($peers.stack.official[0]);
}
});
- }
- reset() {
- this.input_passphrase = '';
- this.progress = 0;
- this.seed = login.emptyBytes().map(() => '00');
- }
-
- stopNewPassphraseGeneration() {
- this.generatingNewPassphrase = false;
- this.$document.unbind('mousemove', this.listener);
+ this.$scope.$on('onAfterSignup', (ev, args) => {
+ if (args.target === 'primary-pass') {
+ this.passConfirmSubmit(args.passphrase);
+ }
+ });
}
- doTheLogin() {
- if (this.isValidPassphrase(this.input_passphrase) === 1) {
- this.passphrase = login.fixCaseAndWhitespace(this.input_passphrase);
+ passConfirmSubmit(_passphrase = this.input_passphrase) {
+ if (this.Passphrase.normalize.constructor === Function) {
+ this.passphrase = this.Passphrase.normalize(_passphrase);
- this.reset();
this.$timeout(this.onLogin);
}
}
- isValidPassphrase(value) {
- const fixedValue = login.fixCaseAndWhitespace(value);
-
- if (fixedValue === '') {
- this.valid = 2;
- } else if (fixedValue.split(' ').length < 12 || !mnemonic.isValid(fixedValue)) {
- this.valid = 0;
- } else {
- this.valid = 1;
- }
- return this.valid;
- }
-
- startGenratingNewPassphrase() {
- this.reset();
-
+ generatePassphrase() {
this.generatingNewPassphrase = true;
-
- let last = [0, 0];
- let used = login.emptyBytes();
-
- const turns = 10 + parseInt(Math.random() * 10, 10);
- const steps = 2;
- const total = turns * used.length;
- let count = 0;
-
- this.listener = (ev) => {
- const distance = Math.sqrt(Math.pow(ev.pageX - last[0], 2) +
- (Math.pow(ev.pageY - last[1]), 2));
-
- if (distance > 60 || ev.isTrigger) {
- for (let p = 0; p < steps; p++) {
- if (count >= total) {
- this.stopNewPassphraseGeneration();
- this.setNewPassphrase(this.seed);
- return;
- }
-
- count++;
-
- if (!ev.isTrigger) {
- last = [ev.pageX, ev.pageY];
- }
-
- used = this.updateSeedAndProgress(used, count / total);
- }
- }
- };
-
- this.$timeout(() => this.$document.mousemove(this.listener), 300);
- }
-
- updateSeedAndProgress(_used, progress) {
- let pos;
- let used = _used;
- const available = used.map((u, i) => (!u ? i : null)).filter(u => u !== null);
-
- if (!available.length) {
- used = used.map(() => 0);
- pos = parseInt(Math.random() * used.length, 10);
- } else {
- pos = available[parseInt(Math.random() * available.length, 10)];
- }
-
- this.seed[pos] = login.lpad(crypto.randomBytes(1)[0].toString(16), '0', 2);
- this.progress = parseInt((progress) * 100, 10);
-
- if (this.$scope.$root.$$phase !== '$apply' && this.$scope.$root.$$phase !== '$digest') {
- this.$scope.$apply();
- }
-
- used[pos] = 1;
- return used;
- }
-
- simulateMousemove() {
- this.$document.mousemove();
- }
-
- setNewPassphrase(seed) {
- const passphrase = (new mnemonic(new Buffer(seed.join(''), 'hex'))).toString();
- const ok = () => {
- this.input_passphrase = passphrase;
- this.$timeout(this.doTheLogin.bind(this), 100);
- };
-
- this.$mdDialog.show({
- controllerAs: '$ctrl',
- controller: /* @ngInject*/ class save {
- constructor($scope, $mdDialog) {
- this.$mdDialog = $mdDialog;
- this.passphrase = passphrase;
-
- $scope.$watch('$ctrl.missing_input', () => {
- this.missing_ok = this.missing_input && this.missing_input === this.missing_word;
- });
- }
-
- next() {
- this.enter = true;
-
- const words = this.passphrase.split(' ');
- const missingNumber = parseInt(Math.random() * words.length, 10);
-
- this.missing_word = words[missingNumber];
- this.pre = words.slice(0, missingNumber).join(' ');
- this.pos = words.slice(missingNumber + 1).join(' ');
- }
-
- ok() {
- ok();
- this.close();
- }
-
- close() {
- this.$mdDialog.hide();
- }
- },
-
- template: require('./save.pug')(),
- fullscreen: (this.$mdMedia('sm') || this.$mdMedia('xs')) && this.$scope.customFullscreen,
- });
}
devTestAccount() {
const passphrase = this.$cookies.get('passphrase');
if (passphrase) {
this.input_passphrase = passphrase;
- this.$timeout(this.doTheLogin.bind(this), 10);
+ this.$timeout(this.passConfirmSubmit.bind(this), 10);
}
}
-
- static fixCaseAndWhitespace(v) {
- return (v || '').replace(/ +/g, ' ').trim().toLowerCase();
- }
-
- static lpad(str, pad, length) {
- let result = str;
- while (result.length < length) result = pad + str;
- return result;
- }
-
- static emptyBytes() {
- return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
- }
-
- // istanbul ignore next
- // Let's consider this to be third party code that we don't want to test
- static mobileAndTabletcheck() {
- let check = false
- ;(function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd(m|p|t)|hei|hi(pt|ta)|hp( i|ip)|hsc|ht(c(| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i(20|go|ma)|i230|iac( ||\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|[a-w])|libw|lynx|m1w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|mcr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|([1-8]|c))|phil|pire|pl(ay|uc)|pn2|po(ck|rt|se)|prox|psio|ptg|qaa|qc(07|12|21|32|60|[2-7]|i)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h|oo|p)|sdk\/|se(c(|0|1)|47|mc|nd|ri)|sgh|shar|sie(|m)|sk0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h|v|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl|tdg|tel(i|m)|tim|tmo|to(pl|sh)|ts(70|m|m3|m5)|tx9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas|your|zeto|zte/i.test(a.substr(0, 4))) check = true; }(navigator.userAgent || navigator.vendor || window.opera));
- return check;
- }
},
});
diff --git a/src/app/components/login/login.less b/src/app/components/login/login.less
index 0e1cf775f..471636faf 100644
--- a/src/app/components/login/login.less
+++ b/src/app/components/login/login.less
@@ -11,24 +11,4 @@ login {
.random-input {
margin: 0 0 25px 0;
}
-
- .byte {
- display: inline-block;
- text-align: center;
- font-size: 140%;
- margin: 5px;
- font-family: monospace;
-
- &.change-add, &.change-remove {
- transition: all .15s ease;
- }
-
- &.change, &.change-add-active, &.change-remove {
- transform: scale(1.3);
- }
-
- &.change-remove-active {
- transform: none;
- }
- }
}
diff --git a/src/app/components/login/login.pug b/src/app/components/login/login.pug
index ed28135bb..e0ef11a15 100644
--- a/src/app/components/login/login.pug
+++ b/src/app/components/login/login.pug
@@ -3,7 +3,7 @@ md-card
md-card-title-text
span.md-title Sign In
md-card-content(flex='100', flex-gt-sm='70', flex-offset-gt-sm='15')
- form(ng-submit='$ctrl.doTheLogin()')
+ form(ng-submit='$ctrl.passConfirmSubmit()')
md-input-container.md-block(md-is-error='$ctrl.valid === 0')
label.select Choose a peer
md-select(ng-model='$ctrl.$peers.currentPeerConfig', aria-label='Peer')
@@ -14,14 +14,8 @@ md-card
input(type="{{ $ctrl.show_passphrase ? 'text' : 'password' }}", ng-model='$ctrl.input_passphrase', ng-disabled='$ctrl.generatingNewPassphrase', autofocus)
md-input-container.md-block
md-checkbox.md-primary(ng-model="$ctrl.show_passphrase", aria-label="Show passphrase") Show passphrase
- md-content(layout='row', layout-align='center center')
- // md-button(ng-disabled='$ctrl.generatingNewPassphrase', ng-click='$ctrl.devTestAccount()') Dev Test Account
- md-button.md-primary(ng-disabled='$ctrl.random || $ctrl.generatingNewPassphrase', ng-click='$ctrl.startGenratingNewPassphrase()') NEW ACCOUNT
- md-button.md-raised.md-primary(md-autofocus, ng-disabled='$ctrl.valid !== 1', ng-click='$ctrl.doTheLogin()') Login
- md-content(layout-padding, layout='column', layout-align='center center', ng-show='$ctrl.generatingNewPassphrase')
- h4.move(ng-show='$ctrl.mobileAndTabletcheck()') Enter text below to generate random bytes
- h4.move(ng-hide='$ctrl.mobileAndTabletcheck()') Move your mouse to generate random bytes
- input.random-input(type="text", ng-keydown='$ctrl.simulateMousemove()', ng-show='$ctrl.mobileAndTabletcheck()')
- md-progress-linear(md-mode='determinate', value='{{ $ctrl.progress }}')
- md-content.bytes
- span.byte(ng-repeat='byte in $ctrl.seed track by $index', ng-bind='byte', animate-on-change='byte')
+ md-content(layout='row', layout-align='center center')
+ // md-button(ng-disabled='$ctrl.generatingNewPassphrase', ng-click='$ctrl.devTestAccount()') Dev Test Account
+ md-button.md-primary(ng-disabled='$ctrl.random || $ctrl.generatingNewPassphrase', ng-click='$ctrl.generatePassphrase()') NEW ACCOUNT
+ md-button.md-raised.md-primary(md-autofocus, ng-disabled='$ctrl.valid != undefined && $ctrl.valid !== 1', type='submit') Login
+ passphrase(ng-if='$ctrl.generatingNewPassphrase', data-on-login='$ctrl.onLogin', data-target='primary-pass')
\ No newline at end of file
diff --git a/src/app/components/login/passphrase.js b/src/app/components/login/passphrase.js
new file mode 100644
index 000000000..747818fdf
--- /dev/null
+++ b/src/app/components/login/passphrase.js
@@ -0,0 +1,95 @@
+import './passphrase.less';
+
+app.directive('passphrase', ($rootScope, $document, Passphrase, $mdDialog, $mdMedia, $timeout) => {
+ /* eslint no-param-reassign: ["error", { "props": false }] */
+ const PassphraseLink = function (scope, element, attrs) {
+ const bindEvents = (listener) => {
+ $document.bind('mousemove', listener);
+ };
+
+ const unbindEvents = (listener) => {
+ $document.unbind('mousemove', listener);
+ };
+
+ const generateAndDoubleCheck = (seed) => {
+ const passphrase = Passphrase.generatePassPhrase(seed);
+
+ const ok = () => {
+ // this.input_passphrase = passphrase;
+ $timeout(() => {
+ $rootScope.$broadcast('onAfterSignup', {
+ passphrase,
+ target: attrs.target,
+ });
+ }, 100);
+ };
+
+ $mdDialog.show({
+ controllerAs: '$ctrl',
+ controller: /* @ngInject*/ class save {
+ constructor($scope) {
+ this.$mdDialog = $mdDialog;
+ this.passphrase = passphrase;
+
+ $scope.$watch('$ctrl.missing_input', () => {
+ this.missing_ok = this.missing_input && this.missing_input === this.missing_word;
+ });
+ }
+
+ next() {
+ this.enter = true;
+
+ const words = this.passphrase.split(' ');
+ const missingNumber = parseInt(Math.random() * words.length, 10);
+
+ this.missing_word = words[missingNumber];
+ this.pre = words.slice(0, missingNumber).join(' ');
+ this.pos = words.slice(missingNumber + 1).join(' ');
+ }
+
+ ok() {
+ ok();
+ this.close();
+ }
+
+ close() {
+ this.$mdDialog.hide();
+ }
+ },
+
+ template: require('./save.pug')(),
+ fullscreen: ($mdMedia('sm') || $mdMedia('xs')) && this.scope.customFullscreen,
+ });
+ };
+
+ const terminate = (seed) => {
+ unbindEvents(Passphrase.listene);
+ generateAndDoubleCheck(seed);
+ };
+
+ scope.simulateMousemove = () => {
+ $document.mousemove();
+ };
+
+ scope.mobileAndTabletcheck = (agent) => {
+ let check = false;
+ if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(agent || navigator.userAgent || navigator.vendor || window.opera)) {
+ check = true;
+ }
+ return check;
+ };
+
+ Passphrase.init();
+ bindEvents(e => Passphrase.listener(e, terminate));
+ scope.progress = Passphrase.progress;
+ };
+
+ return {
+ link: PassphraseLink,
+ restrict: 'E',
+ scope: {
+ onLogin: '=',
+ },
+ template: require('./passphrase.pug')(),
+ };
+});
diff --git a/src/app/components/login/passphrase.less b/src/app/components/login/passphrase.less
new file mode 100644
index 000000000..d1aeb5f80
--- /dev/null
+++ b/src/app/components/login/passphrase.less
@@ -0,0 +1,19 @@
+md-content.bytes .byte {
+ display: inline-block;
+ text-align: center;
+ font-size: 140%;
+ margin: 5px;
+ font-family: monospace;
+
+ &.change-add, &.change-remove {
+ transition: all .15s ease;
+ }
+
+ &.change, &.change-add-active, &.change-remove {
+ transform: scale(1.3);
+ }
+
+ &.change-remove-active {
+ transform: none;
+ }
+ }
\ No newline at end of file
diff --git a/src/app/components/login/passphrase.pug b/src/app/components/login/passphrase.pug
new file mode 100644
index 000000000..5692044bc
--- /dev/null
+++ b/src/app/components/login/passphrase.pug
@@ -0,0 +1,7 @@
+md-content(layout-padding, layout='column', layout-align='center center')
+ h4.move(ng-show='mobileAndTabletcheck()') Enter text below to generate random bytes
+ h4.move(ng-hide='mobileAndTabletcheck()') Move your mouse to generate random bytes
+ input.random-input(type="text", ng-keydown='simulateMousemove()', ng-show='mobileAndTabletcheck()')
+ md-progress-linear(md-mode='determinate', value='{{ progress.percentage }}')
+ md-content.bytes
+ span.byte(ng-repeat='byte in progress.seed track by $index', ng-bind='byte', animate-on-change='byte')
diff --git a/src/app/components/main/main.pug b/src/app/components/main/main.pug
index 65e4fa927..91e4b3718 100644
--- a/src/app/components/main/main.pug
+++ b/src/app/components/main/main.pug
@@ -3,6 +3,8 @@ md-content(layout='row', id="main")
md-content.header(layout='row', layout-align='center center', layout-padding)
img.logo(src=require('./images/LISK-nano.png'))
div(flex)
+ md-button.md-raised.md-secondary.set-2nd(data-set-second-pass, ng-if='$ctrl.logged && !$ctrl.account.secondSignature',
+ data-account='{{$ctrl.account.publicKey}}', data-passphrase='{{$ctrl.passphrase}}') Set 2nd passphrase
md-button.md-raised.md-primary.send(data-show-send-modal, ng-if='$ctrl.logged') Send
md-button.md-raised.md-secondary.logout(ng-click='$ctrl.logout()', ng-if='$ctrl.logged') Logout
md-menu.top-menu(ng-if='$ctrl.logged', md-position-mode='target-right target', md-offset='14 0')
diff --git a/src/app/components/main/secondPass.less b/src/app/components/main/secondPass.less
new file mode 100644
index 000000000..600f9ceed
--- /dev/null
+++ b/src/app/components/main/secondPass.less
@@ -0,0 +1,4 @@
+md-dialog.dialog-second {
+ width: 80%;
+ max-width: 1000px;
+}
\ No newline at end of file
diff --git a/src/app/components/main/secondPass.pug b/src/app/components/main/secondPass.pug
new file mode 100644
index 000000000..9402e8ee9
--- /dev/null
+++ b/src/app/components/main/secondPass.pug
@@ -0,0 +1,6 @@
+md-dialog.dialog-second(aria-label='Generate a second passphrase for your account')
+ form
+ md-toolbar
+ .md-toolbar-tools
+ h2 Generate a second passphrase of your account
+ passphrase(data-on-login='md.onLogin', data-target='second-pass')
\ No newline at end of file
diff --git a/src/app/components/main/setSecondPassDirective.js b/src/app/components/main/setSecondPassDirective.js
new file mode 100644
index 000000000..39001cc5f
--- /dev/null
+++ b/src/app/components/main/setSecondPassDirective.js
@@ -0,0 +1,38 @@
+import './secondPass.less';
+
+app.directive('setSecondPass', (setSecondPass, $peers, $rootScope, success, error) => {
+ /* eslint no-param-reassign: ["error", { "props": false }] */
+ const SetSecondPassLink = function (scope, element, attrs) {
+ element.bind('click', () => {
+ setSecondPass.show();
+ });
+
+ scope.passConfirmSubmit = (secondsecret) => {
+ $peers.active.setSignature(secondsecret, attrs.publicKey, attrs.passphrase)
+ .then(() => {
+ success.dialog('Your second passphrase was successfully registered.');
+ })
+ .catch((err) => {
+ let text = '';
+ if (err.message === 'Missing sender second signature') {
+ text = 'You already have a second passphrase.';
+ } else if (/^(Account does not have enough LSK)/.test(err.message)) {
+ text = 'You have insuffcient funds to register a second passphrase.';
+ } else {
+ text = 'An error occurred while registering your second passphrase. Please try again.';
+ }
+ error.dialog({ text });
+ });
+ };
+
+ scope.$on('onAfterSignup', (ev, args) => {
+ if (args.target === 'second-pass') {
+ scope.passConfirmSubmit(args.passphrase);
+ }
+ });
+ };
+ return {
+ restrict: 'A',
+ link: SetSecondPassLink,
+ };
+});
diff --git a/src/app/components/main/setSecondPassService.js b/src/app/components/main/setSecondPassService.js
new file mode 100644
index 000000000..94ae3d715
--- /dev/null
+++ b/src/app/components/main/setSecondPassService.js
@@ -0,0 +1,27 @@
+const setSecondPassConstructor = function ($mdDialog) {
+ this.ok = () => {
+ $mdDialog.hide();
+ };
+
+ this.cancel = () => {
+ $mdDialog.hide();
+ };
+
+ this.show = () => {
+ $mdDialog.show({
+ template: require('./secondPass.pug')(),
+ parent: angular.element('#main'),
+ bindToController: true,
+ locals: {
+ ok: this.ok,
+ cancel: this.cancel,
+ },
+ controller: () => {},
+ controllerAs: 'md',
+ });
+ };
+
+ return this;
+};
+
+app.factory('setSecondPass', setSecondPassConstructor);
diff --git a/src/app/lisk-nano.js b/src/app/lisk-nano.js
index 3ec1afbd1..71c429dd2 100644
--- a/src/app/lisk-nano.js
+++ b/src/app/lisk-nano.js
@@ -3,7 +3,10 @@ import './index.less';
import './theme/theme';
import './util/animateOnChange/animateOnChange';
import './components/main/main';
+import './components/main/setSecondPassService';
+import './components/main/setSecondPassDirective';
import './components/login/login';
+import './components/login/passphrase';
import './components/top/top';
import './components/send/send';
import './components/send/sendModalService';
@@ -21,6 +24,7 @@ import './services/peers/peers';
import './services/lsk';
import './services/success';
import './services/error';
+import './services/passphrase';
import './services/sign-verify';
import './filters/lsk';
diff --git a/src/app/services/passphrase.js b/src/app/services/passphrase.js
new file mode 100644
index 000000000..29ece1db1
--- /dev/null
+++ b/src/app/services/passphrase.js
@@ -0,0 +1,104 @@
+import crypto from 'crypto';
+import mnemonic from 'bitcore-mnemonic';
+
+/* eslint no-param-reassign: ["error", { "props": false }] */
+
+app.factory('Passphrase', function ($rootScope) {
+ this.progress = {
+ seed: null,
+ percentage: 0,
+ step: 0,
+ };
+ const lastCaptured = {
+ coordination: {
+ x: 0,
+ y: 0,
+ },
+ time: 0,
+ };
+ let byte = null;
+
+ const emptyBytes = () => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+ this.normalize = (v = '') => v.replace(/ +/g, ' ').trim().toLowerCase();
+
+ this.reset = () => {
+ this.progress.percentage = 0;
+ this.progress.seed = emptyBytes().map(() => '00');
+ };
+
+ /**
+ * fills the left side of str with a given padding string to meet the required length
+ */
+ const leftPadd = (str, pad, length) => {
+ let paddedStr = str;
+ while (paddedStr.length < length) paddedStr = pad + paddedStr;
+ return paddedStr;
+ };
+
+ this.isValidPassphrase = (value) => {
+ const normalizedValue = this.normalize(value);
+
+ if (normalizedValue === '') {
+ return 2;
+ } else if (normalizedValue.split(' ').length < 12 || !mnemonic.isValid(normalizedValue)) {
+ return 0;
+ }
+ return 1;
+ };
+
+ this.init = () => {
+ this.reset();
+ byte = emptyBytes();
+ this.progress.step = (160 + Math.floor(Math.random() * 160)) / 100;
+ };
+
+ const updateSeedAndProgress = () => {
+ let pos;
+ const available = byte.map((bit, index) => (!bit ? index : null)).filter(bit => (bit !== null));
+ if (!available.length) {
+ byte = byte.map(() => 0);
+ pos = parseInt(Math.random() * byte.length, 10);
+ } else {
+ pos = available[parseInt(Math.random() * available.length, 10)];
+ }
+
+ this.progress.seed[pos] = leftPadd(crypto.randomBytes(1)[0].toString(16), '0', 2);
+
+ /**
+ * @todo why it's not working without manual digestion
+ */
+ if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') {
+ $rootScope.$apply();
+ }
+
+ byte[pos] = 1;
+ return byte;
+ };
+
+ this.generatePassPhrase = seed => (new mnemonic(new Buffer(seed.join(''), 'hex'))).toString();
+
+ this.listener = (ev, callback) => {
+ const distance = Math.sqrt(Math.pow(ev.pageX - lastCaptured.coordination.x, 2) +
+ (Math.pow(ev.pageY - lastCaptured.coordination.y), 2));
+
+ if (distance > 120 || ev.isTrigger) {
+ for (let p = 0; p < 2; p++) {
+ if (this.progress.percentage >= 100) {
+ callback(this.progress.seed);
+ return;
+ }
+
+ if (!ev.isTrigger) {
+ lastCaptured.coordination.x = ev.pageX;
+ lastCaptured.coordination.y = ev.pageY;
+ }
+
+ this.progress.percentage += this.progress.step;
+ byte = updateSeedAndProgress(byte, this.progress.percentage);
+ }
+ }
+ };
+
+ return this;
+});
diff --git a/src/app/services/peers/peers.js b/src/app/services/peers/peers.js
index 106eeac75..a2572f39b 100644
--- a/src/app/services/peers/peers.js
+++ b/src/app/services/peers/peers.js
@@ -115,6 +115,28 @@ app.factory('$peers', ($timeout, $cookies, $location, $q) => {
});
return deferred.promise;
};
+
+ this.active.listTransactionsPromise = (address, limit, offset) => {
+ const deferred = $q.defer();
+ this.active.listTransactions(address, limit, offset, (data) => {
+ if (data.success) {
+ return deferred.resolve(data);
+ }
+ return deferred.reject(data);
+ });
+ return deferred.promise;
+ };
+
+ this.active.setSignature = (secondSecret, publicKey, secret) => {
+ const deferred = $q.defer();
+ this.active.sendRequest('signatures', { secondSecret, publicKey, secret }, (res) => {
+ if (res.success) {
+ deferred.resolve(res);
+ }
+ deferred.reject(res);
+ });
+ return deferred.promise;
+ };
}
check() {
diff --git a/src/test/components/login/login.spec.js b/src/test/components/login/login.spec.js
index 7721e3a2e..6069e3519 100644
--- a/src/test/components/login/login.spec.js
+++ b/src/test/components/login/login.spec.js
@@ -4,6 +4,8 @@ const sinonChai = require('sinon-chai');
const expect = chai.expect;
chai.use(sinonChai);
+const VALID_PASSPHRASE = 'illegal symbol search tree deposit youth mixture craft amazing tool soon unit';
+const INVALID_PASSPHRASE = 'INVALID_PASSPHRASE';
describe('Login component', () => {
let $compile;
@@ -59,11 +61,22 @@ describe('Login controller', () => {
let $scope;
let controller;
let $componentController;
+ let Passphrase;
let testPassphrase;
+ let $cookies;
+ /* eslint-disable no-unused-vars */
+ let $timeout;
+ /* eslint-enable no-unused-vars */
- beforeEach(inject((_$componentController_, _$rootScope_) => {
+ beforeEach(inject((_$componentController_, _$rootScope_,
+ _Passphrase_, _$cookies_, _$timeout_) => {
$componentController = _$componentController_;
$rootScope = _$rootScope_;
+ Passphrase = _Passphrase_;
+ $cookies = _$cookies_;
+ /* eslint-disable no-unused-vars */
+ $timeout = _$timeout_;
+ /* eslint-enable no-unused-vars */
}));
beforeEach(() => {
@@ -95,179 +108,63 @@ describe('Login controller', () => {
$scope.$apply();
expect(controller.$peers.currentPeerConfig).to.equal(controller.$peers.stack.official[0]);
});
- });
- describe('$scope.reset()', () => {
- it('makes input_passphrase empty', () => {
- const passphrase = 'TEST';
- controller.input_passphrase = passphrase;
- expect(controller.input_passphrase).to.equal(passphrase);
- controller.reset();
- expect(controller.input_passphrase).to.equal('');
- });
- });
-
- describe('$scope.stopNewPassphraseGeneration()', () => {
- it('sets this.generatingNewPassphrase = false', () => {
- controller.generatingNewPassphrase = true;
- controller.stopNewPassphraseGeneration();
- expect(controller.generatingNewPassphrase).to.equal(false);
+ it('should define a watcher for $ctrl.input_passphrase', () => {
+ $scope.$apply();
+ const spy = sinon.spy(Passphrase, 'isValidPassphrase');
+ controller.input_passphrase = INVALID_PASSPHRASE;
+ $scope.$apply();
+ expect(controller.valid).to.not.equal(1);
+ controller.input_passphrase = VALID_PASSPHRASE;
+ $scope.$apply();
+ expect(controller.valid).to.equal(1);
+ expect(spy).to.have.been.calledWith();
});
- it('unbinds mousemove listener', () => {
- const unbindSpy = sinon.spy(controller.$document, 'unbind');
- controller.stopNewPassphraseGeneration();
- expect(unbindSpy).to.have.been.calledWith('mousemove', controller.listener);
+ it('listens for an onAfterSignup event', () => {
+ const spy = sinon.spy(controller, 'passConfirmSubmit');
+ $rootScope.$broadcast('onAfterSignup', {
+ passphrase: 'TEST_VALUE',
+ target: 'primary-pass',
+ });
+ expect(spy).to.have.been.calledWith('TEST_VALUE');
});
});
- describe('$scope.startGenratingNewPassphrase()', () => {
+ describe('generatePassphrase()', () => {
it('sets this.generatingNewPassphrase = true', () => {
- controller.startGenratingNewPassphrase();
+ controller.generatePassphrase();
expect(controller.generatingNewPassphrase).to.equal(true);
});
-
- it('unbinds mousemove listener', () => {
- const spy = sinon.spy(controller, 'reset');
- controller.startGenratingNewPassphrase();
- expect(spy).to.have.been.calledWith();
- });
-
- it('creates this.listener(ev) which if called repeatedly will generate a random this.seed', () => {
- controller.startGenratingNewPassphrase();
- expect(controller.seed).to.deep.equal(['00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00']);
- expect(controller.progress).to.equal(0);
-
- for (let j = 0; j < 300; j++) {
- const ev = {
- pageX: Math.random() * 1000,
- pageY: Math.random() * 1000,
- };
- controller.listener(ev);
- }
-
- expect(controller.seed).not.to.deep.equal(['00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00']);
- expect(controller.progress).to.equal(100);
- });
});
- describe('$scope.doTheLogin()', () => {
- it('sets this.phassphrase as this.input_passphrase processed by fixCaseAndWhitespace', () => {
- controller.input_passphrase = '\tGLOW two GliMpse camp aware tip brief confirm similar code float defense ';
- controller.doTheLogin();
- expect(controller.passphrase).to.equal('glow two glimpse camp aware tip brief confirm similar code float defense');
+ describe('passConfirmSubmit()', () => {
+ it('sets this.phassphrase as this.input_passphrase processed by normalizer', () => {
+ controller.input_passphrase = '\tTEST PassPHrASe ';
+ controller.passConfirmSubmit();
+ expect(controller.passphrase).to.equal('test passphrase');
});
- it('calls this.reset()', () => {
- controller.input_passphrase = testPassphrase;
- const spy = sinon.spy(controller, 'reset');
- controller.doTheLogin();
+ it('calls Passphrase.normalize()', () => {
+ const spy = sinon.spy(Passphrase, 'normalize');
+ controller.passConfirmSubmit();
expect(spy).to.have.been.calledWith();
});
it('sets timeout with this.onLogin', () => {
controller.input_passphrase = testPassphrase;
const spy = sinon.spy(controller, '$timeout');
- controller.doTheLogin();
+ controller.passConfirmSubmit();
expect(spy).to.have.been.calledWith(controller.onLogin);
});
});
- describe('$scope.constructor()', () => {
- it.skip('sets $watch on $ctrl.input_passphrase to keep validating it', () => {
- // Skipped because it doesn't work
- const spy = sinon.spy(controller.$scope, '$watch');
- controller.constructor();
- expect(spy).to.have.been.calledWith('$ctrl.input_passphrase', controller.isValidPassphrase);
- });
-
- it.skip('sets $watch that sets customFullscreen on small screens', () => {
- });
- });
-
- describe('$scope.simulateMousemove()', () => {
- it('calls this.$document.mousemove()', () => {
- const spy = sinon.spy(controller.$document, 'mousemove');
- controller.simulateMousemove();
- expect(spy).to.have.been.calledWith();
- });
- });
-
- describe('$scope.setNewPassphrase()', () => {
- it('opens a material design dialog', () => {
- const seed = ['23', '34', '34', '34', '34', '34', '34', '34'];
- const dialogSpy = sinon.spy(controller.$mdDialog, 'show');
- controller.setNewPassphrase(seed);
- expect(dialogSpy).to.have.been.calledWith();
- });
- });
-
- describe('$scope.devTestAccount()', () => {
- it('sets input_passphrase from cookie called passphrase if present', () => {
- const mock = sinon.mock(controller.$cookies);
- mock.expects('get').returns(testPassphrase);
- controller.devTestAccount();
- expect(controller.input_passphrase).to.equal(testPassphrase);
- });
-
- it('does nothing if cooke called passphrase not present', () => {
- controller.input_passphrase = testPassphrase;
- const mock = sinon.mock(controller.$cookies);
-
- mock.expects('get').returns(undefined);
+ describe('devTestAccount()', () => {
+ it('calls passConfirmSubmit with timeout if a passphrase is set in the cookies', () => {
+ $cookies.put('passphrase', testPassphrase);
+ const spy = sinon.spy(controller, '$timeout');
controller.devTestAccount();
- expect(controller.input_passphrase).to.equal(testPassphrase);
- });
- });
-
- describe('$scope.isValidPassphrase(value)', () => {
- it('sets $scope.valid = 2 if value is empty', () => {
- controller.isValidPassphrase('');
- expect(controller.valid).to.equal(2);
- });
-
- it('sets $scope.valid = 1 if value is valid', () => {
- controller.isValidPassphrase('ability theme abandon abandon abandon abandon abandon abandon abandon abandon abandon absorb');
- expect(controller.valid).to.equal(1);
- });
-
- it('sets $scope.valid = 0 if value is invalid', () => {
- controller.isValidPassphrase('INVALID VALUE');
- expect(controller.valid).to.equal(0);
- });
- });
-});
-
-describe('save $mdDialog controller', () => {
- describe('constructor()', () => {
- it.skip('sets $watch on $ctrl.missing_input', () => {
- });
- });
-
- describe('next()', () => {
- it.skip('sets this.enter=true', () => {
- });
-
- it.skip('sets this.missing_word to a random word of passphrase', () => {
- });
-
- it.skip('sets this.pre to part of the passphrase before this.missing_word', () => {
- });
-
- it.skip('sets this.pos to part of the passphrase after this.missing_word', () => {
- });
- });
-
- describe('ok()', () => {
- it.skip('calls ok()', () => {
- });
-
- it.skip('calls this.close()', () => {
- });
- });
-
- describe('close()', () => {
- it.skip('calls this.$mdDialog.hide()', () => {
+ expect(spy).to.have.been.calledWith();
});
});
});
diff --git a/src/test/components/login/passphrase.spec.js b/src/test/components/login/passphrase.spec.js
new file mode 100644
index 000000000..ca83a045b
--- /dev/null
+++ b/src/test/components/login/passphrase.spec.js
@@ -0,0 +1,61 @@
+const chai = require('chai');
+const sinon = require('sinon');
+const sinonChai = require('sinon-chai');
+
+const expect = chai.expect;
+chai.use(sinonChai);
+
+describe('Passphrase Directive', () => {
+ let $compile;
+ let $rootScope;
+ let $document;
+ let Passphrase;
+ let $isolateScope;
+
+ beforeEach(() => {
+ // Load the myApp module, which contains the directive
+ angular.mock.module('app');
+
+ // Store references to $rootScope and $compile
+ // so they are available to all tests in this describe block
+ inject((_$compile_, _$rootScope_, _$document_, _Passphrase_) => {
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ $document = _$document_;
+ Passphrase = _Passphrase_;
+ });
+
+ // Compile a piece of HTML containing the directive
+ const element = angular.element('');
+ const e = $compile(element)($rootScope);
+ e.scope().$digest();
+ $isolateScope = e.isolateScope();
+ });
+
+ describe('PassphraseLink', () => {
+ it('should assign progress to its own $scope', () => {
+ expect($isolateScope.progress).to.not.equal(undefined);
+ expect($isolateScope.progress).to.equal(Passphrase.progress);
+ });
+ });
+
+ describe('$scope.simulateMousemove()', () => {
+ it('calls $document.mousemove()', () => {
+ const spy = sinon.spy($document, 'mousemove');
+ $isolateScope.simulateMousemove();
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+
+ describe('$scope.mobileAndTabletcheck()', () => {
+ it('checks if the useAgent is a device', () => {
+ const agents = [
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25',
+ 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25',
+ ];
+ let isDevice = true;
+ agents.forEach(agent => isDevice = isDevice && $isolateScope.mobileAndTabletcheck(agent));
+ expect(isDevice).to.equal(true);
+ });
+ });
+});
diff --git a/src/test/components/main/setSecondPassDirective.spec.js b/src/test/components/main/setSecondPassDirective.spec.js
new file mode 100644
index 000000000..5f2697567
--- /dev/null
+++ b/src/test/components/main/setSecondPassDirective.spec.js
@@ -0,0 +1,137 @@
+const chai = require('chai');
+const sinon = require('sinon');
+const sinonChai = require('sinon-chai');
+
+const expect = chai.expect;
+chai.use(sinonChai);
+
+describe('setSecondPass Directive', () => {
+ let $compile;
+ let $scope;
+ let $rootScope;
+ let element;
+ let $peers;
+ let setSecondPass;
+ let $q;
+ let success;
+ let error;
+
+ beforeEach(() => {
+ // Load the myApp module, which contains the directive
+ angular.mock.module('app');
+
+ // Store references to $rootScope and $compile
+ // so they are available to all tests in this describe block
+ inject((_$compile_, _$rootScope_, _setSecondPass_, _$peers_, _$q_, _success_, _error_) => {
+ // The injector unwraps the underscores (_) from around the parameter names when matching
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ setSecondPass = _setSecondPass_;
+ $peers = _$peers_;
+ $q = _$q_;
+ success = _success_;
+ error = _error_;
+ $scope = $rootScope.$new();
+ });
+
+ // Compile a piece of HTML containing the directive
+ element = $compile('')($scope);
+ $scope.$digest();
+ });
+
+ describe('SetSecondPassLink', () => {
+ it('listens for an onAfterSignup event', () => {
+ $peers.active = { setSignature() {} };
+ const mock = sinon.mock($peers.active);
+ const deffered = $q.defer();
+ mock.expects('setSignature').returns(deffered.promise);
+
+ const spy = sinon.spy(success, 'dialog');
+
+ $scope.$broadcast('onAfterSignup', {
+ passphrase: 'TEST_VALUE',
+ target: 'second-pass',
+ });
+
+ deffered.resolve({});
+ $scope.$apply();
+
+ expect(spy).to.have.been.calledWith();
+ });
+
+ it('binds click listener to call setSecondPass.show()', () => {
+ const spy = sinon.spy(setSecondPass, 'show');
+ element.triggerHandler('click');
+ $scope.$digest();
+
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+
+ describe('scope.passConfirmSubmit', () => {
+ it('should call $peers.active.setSignature', () => {
+ $peers.active = { setSignature() {} };
+ const mock = sinon.mock($peers.active);
+ const deffered = $q.defer();
+ mock.expects('setSignature').returns(deffered.promise);
+
+ const spy = sinon.spy(success, 'dialog');
+ $scope.passConfirmSubmit();
+
+ deffered.resolve({});
+ $scope.$apply();
+
+ expect(spy).to.have.been.calledWith();
+ });
+
+ it('should show error dialog if trying to set second passphrase mulpiple times', () => {
+ $peers.active = { setSignature() {} };
+ const mock = sinon.mock($peers.active);
+ const deffered = $q.defer();
+ mock.expects('setSignature').returns(deffered.promise);
+
+ const spy = sinon.spy(error, 'dialog');
+ $scope.passConfirmSubmit();
+
+ deffered.reject({ message: 'Missing sender second signature' });
+ $scope.$apply();
+ expect(spy).to.have.been.calledWith();
+
+ deffered.reject({ message: 'Account does not have enough LSK : TEST_ADDRESS' });
+ $scope.$apply();
+ expect(spy).to.have.been.calledWith();
+
+ deffered.reject({ message: 'OTHER MESSAGE' });
+ $scope.$apply();
+ expect(spy).to.have.been.calledWith();
+ });
+
+ it('should show error dialog if account does not have enough LSK', () => {
+ $peers.active = { setSignature() {} };
+ const mock = sinon.mock($peers.active);
+ const deffered = $q.defer();
+ mock.expects('setSignature').returns(deffered.promise);
+
+ const spy = sinon.spy(error, 'dialog');
+ $scope.passConfirmSubmit();
+
+ deffered.reject({ message: 'Missing sender second signature' });
+ $scope.$apply();
+ expect(spy).to.have.been.calledWith();
+ });
+
+ it('should show error dialog for all the other errors', () => {
+ $peers.active = { setSignature() {} };
+ const mock = sinon.mock($peers.active);
+ const deffered = $q.defer();
+ mock.expects('setSignature').returns(deffered.promise);
+
+ const spy = sinon.spy(error, 'dialog');
+ $scope.passConfirmSubmit();
+
+ deffered.reject({ message: 'Other messages' });
+ $scope.$apply();
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+});
diff --git a/src/test/components/main/setSecondPassService.spec.js b/src/test/components/main/setSecondPassService.spec.js
new file mode 100644
index 000000000..4190f7788
--- /dev/null
+++ b/src/test/components/main/setSecondPassService.spec.js
@@ -0,0 +1,46 @@
+const chai = require('chai');
+const sinon = require('sinon');
+const sinonChai = require('sinon-chai');
+
+const expect = chai.expect;
+chai.use(sinonChai);
+
+describe('Passphrase factory', () => {
+ let $mdDialog;
+ let setSecondPass;
+
+ // Load the myApp module, which contains the directive
+ beforeEach(angular.mock.module('app'));
+
+ // Store references to $rootScope and $compile
+ // so they are available to all tests in this describe block
+ beforeEach(inject((_setSecondPass_, _$mdDialog_) => {
+ // The injector unwraps the underscores (_) from around the parameter names when matching
+ $mdDialog = _$mdDialog_;
+ setSecondPass = _setSecondPass_;
+ }));
+
+ describe('ok', () => {
+ it('should call $mdDialog.hide()', () => {
+ const spy = sinon.spy($mdDialog, 'hide');
+ setSecondPass.ok();
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+
+ describe('cancel', () => {
+ it('should call $mdDialog.hide()', () => {
+ const spy = sinon.spy($mdDialog, 'hide');
+ setSecondPass.cancel();
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+
+ describe('show', () => {
+ it('should call $mdDialog.show()', () => {
+ const spy = sinon.spy($mdDialog, 'show');
+ setSecondPass.show();
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+});
diff --git a/src/test/services/passphrase.spec.js b/src/test/services/passphrase.spec.js
new file mode 100644
index 000000000..72ea2df78
--- /dev/null
+++ b/src/test/services/passphrase.spec.js
@@ -0,0 +1,131 @@
+const chai = require('chai');
+const sinon = require('sinon');
+const sinonChai = require('sinon-chai');
+
+const expect = chai.expect;
+chai.use(sinonChai);
+const TEST_SEED = ['12', '12', '12', '12', '12', '12', '12', '12',
+ '12', '12', '12', '12', '12', '12', '12', '12'];
+const INVALID_PASSPHRASE = 'INVALID_PASSPHRASE';
+
+describe('Factory: Passphrase', () => {
+ let Passphrase;
+
+ beforeEach(angular.mock.module('app'));
+
+ beforeEach(inject((_Passphrase_) => {
+ Passphrase = _Passphrase_;
+ }));
+
+ describe('Passphrase.reset()', () => {
+ it('resets percentage of progress and seed', () => {
+ Passphrase.init();
+ Passphrase.progress = {
+ percentage: 50,
+ seed: TEST_SEED,
+ };
+ Passphrase.reset();
+ expect(Passphrase.progress.percentage).to.equal(0);
+ let allZero = true;
+ Passphrase.progress.seed.forEach(member => (allZero = (allZero && member === '00')));
+ expect(allZero).to.equal(true);
+ });
+ });
+
+ describe('Passphrase.init()', () => {
+ it('should define progress.steps as a number above 1.6', () => {
+ Passphrase.init();
+ expect(Passphrase.progress.step).to.be.above(1.6);
+ });
+
+ it('should call Passphrase.reset()', () => {
+ const spy = sinon.spy(Passphrase, 'reset');
+ Passphrase.init();
+ expect(spy).to.have.been.calledWith();
+ });
+ });
+
+ describe('Passphrase.progress', () => {
+ it('should define progress object', () => {
+ Passphrase.init();
+ expect(Passphrase.progress).to.not.equal(undefined);
+ });
+ });
+
+ describe('Passphrase.generatePassPhrase', () => {
+ it('should generate a valid passphrase out of a given valid seed array', () => {
+ const passphrase = Passphrase.generatePassPhrase(TEST_SEED);
+ const isValid = Passphrase.isValidPassphrase(passphrase);
+ expect(isValid).to.equal(1);
+ });
+ });
+
+ describe('Passphrase.isValidPassphrase', () => {
+ it('should return 1 for a valid passphrase', () => {
+ const passphrase = Passphrase.generatePassPhrase(TEST_SEED);
+ const isValid = Passphrase.isValidPassphrase(passphrase);
+ expect(isValid).to.equal(1);
+ });
+
+ it('should return 0 for an invalid passphrase', () => {
+ const isValid = Passphrase.isValidPassphrase(INVALID_PASSPHRASE);
+ expect(isValid).to.equal(0);
+ });
+
+ it('should return 2 for an empty passphrase', () => {
+ const isValid = Passphrase.isValidPassphrase('');
+ expect(isValid).to.equal(2);
+ });
+ });
+
+ describe('Passphrase.normalize', () => {
+ it('should trim multiple spaces globally and lowercase the string', () => {
+ const rawString = ' FIRST second Third ';
+ const fixedString = 'first second third';
+ const result = Passphrase.normalize(rawString);
+ expect(result).to.equal(fixedString);
+ });
+ });
+
+ describe('Passphrase.listener', () => {
+ it('should update progress percentage and seed if called with proper event', () => {
+ Passphrase.init();
+ const event = {
+ pageY: 0,
+ pageX: 0,
+ };
+ let percentage = -1;
+ let isProgressIncreasing = true;
+
+ // √(2 * 90^2) > 120
+ for (let i = 0; i < 100; i++) {
+ event.pageX = i * 90;
+ event.pageY = i * 90;
+ Passphrase.listener(event, () => {});
+ isProgressIncreasing = isProgressIncreasing &&
+ (percentage <= Passphrase.progress.percentage ||
+ Math.floor(Passphrase.progress.percentage) === 100);
+ percentage = Passphrase.progress.percentage;
+ }
+ expect(isProgressIncreasing).to.equal(true);
+ });
+
+ it('should call callback if progress percentage is equal to 100', () => {
+ Passphrase.init();
+ const event = {
+ pageY: 0,
+ pageX: 0,
+ };
+ let seed = null;
+ const callback = param => seed = param;
+
+ // √(2 * 90^2) > 120
+ for (let i = 0; i < 100; i++) {
+ event.pageX = i * 90;
+ event.pageY = i * 90;
+ Passphrase.listener(event, callback);
+ }
+ expect(seed).to.not.equal(undefined);
+ });
+ });
+});
diff --git a/src/test/test.js b/src/test/test.js
index 30ad452fe..00960449c 100644
--- a/src/test/test.js
+++ b/src/test/test.js
@@ -2,7 +2,10 @@ require('./components/forging/forging.spec');
require('./components/delegates/delegates.spec');
require('./components/delegates/vote.spec');
require('./components/login/login.spec');
+require('./components/login/passphrase.spec');
require('./components/main/main.spec');
+require('./components/main/setSecondPassDirective.spec');
+require('./components/main/setSecondPassService.spec');
require('./components/send/send.spec');
require('./components/send/sendModalDirective.spec');
require('./components/top/top.spec');
@@ -12,6 +15,7 @@ require('./components/sign-verify/sign-message.spec');
require('./components/sign-verify/verify-message.spec');
require('./services/peers/peers.spec');
+require('./services/passphrase.spec');
require('./services/sign-verify.spec');
require('./services/lsk.spec');