Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Support second passphrase - Closes #21 #145

Merged
merged 32 commits into from
Apr 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c9ce16b
remove dependencies as they're not used in login component anymore
reyraa Apr 23, 2017
543d101
create Passphrase service only handle passphrase generation for given…
reyraa Apr 23, 2017
4963639
remove mdDialog since we'll use it in a separated directive, add Pass…
reyraa Apr 23, 2017
0e142e4
remove all the functions generating passphrase since Passphrase servi…
reyraa Apr 23, 2017
e5be5ea
import passphrase service to project
reyraa Apr 23, 2017
23890ca
import directives and factories of setting primary and second passphrase
reyraa Apr 23, 2017
ec9603a
create a passphrase directive to use passphrase service and manage re…
reyraa Apr 23, 2017
d07ffea
remove styles of mdDialogs
reyraa Apr 23, 2017
f04eb07
replace markups concerning passphrase with equivalent directives
reyraa Apr 23, 2017
4805526
create setSecondPass directive: - uses instanse of passphrase directi…
reyraa Apr 23, 2017
20aab5f
addapt tests according to changes in login.js
reyraa Apr 23, 2017
4eca9a8
create setSecondPass service: uses mdDialog to show/hide modals of se…
reyraa Apr 23, 2017
5a64ed0
create unit tests for passphrase service
reyraa Apr 23, 2017
41edf41
unit test coverage for passphrase directive used in login page
reyraa Apr 23, 2017
87408d8
remove unnecessary comments
reyraa Apr 23, 2017
f776080
import setSecondPass service to test modules
reyraa Apr 23, 2017
f1fa111
fix method names
reyraa Apr 23, 2017
c21af08
increased test coverage in setSecondPass directive
reyraa Apr 24, 2017
9d18e38
minor fixing
reyraa Apr 24, 2017
0b5c832
remove unused controller, fix the name of like function
reyraa Apr 24, 2017
d9981cc
restructure passphrase directive unit tests
reyraa Apr 24, 2017
403928b
hide second passphrase button if already set
reyraa Apr 25, 2017
396cb1a
add API call to set second passphrase, handle error messages
reyraa Apr 25, 2017
d0c529e
adapt tests accordingly
reyraa Apr 25, 2017
0b94620
minor fixins before digestion
reyraa Apr 25, 2017
0ba7b65
increate test converage
reyraa Apr 25, 2017
919c727
Merge branch 'development' into 21-support-second-passphrase
reyraa Apr 25, 2017
e602f9e
Merge branch 'development' into 21-support-second-passphrase
reyraa Apr 26, 2017
913d3af
tyding up a condition block
reyraa Apr 26, 2017
37d7c80
increased test coverage
reyraa Apr 26, 2017
c3ec49e
disable eslint for vriable usage as object parameter
reyraa Apr 26, 2017
d114646
fix typos
reyraa Apr 26, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 16 additions & 164 deletions src/app/components/login/login.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import crypto from 'crypto';
import mnemonic from 'bitcore-mnemonic';

import './login.less';
import './save.less';

Expand All @@ -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) => {
Expand All @@ -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;
}
},
});
20 changes: 0 additions & 20 deletions src/app/components/login/login.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
18 changes: 6 additions & 12 deletions src/app/components/login/login.pug
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
95 changes: 95 additions & 0 deletions src/app/components/login/passphrase.js
Original file line number Diff line number Diff line change
@@ -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')(),
};
});
19 changes: 19 additions & 0 deletions src/app/components/login/passphrase.less
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading