Skip to content
This repository has been archived by the owner on Oct 21, 2020. It is now read-only.

Commit

Permalink
feat(wallet): Add wallet authentication using ember-simple-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
devinus committed Jan 11, 2018
1 parent e61e1b2 commit 89edf29
Show file tree
Hide file tree
Showing 40 changed files with 615 additions and 64 deletions.
4 changes: 3 additions & 1 deletion app/application/route.js
@@ -1,11 +1,13 @@
import Route from '@ember/routing/route';

import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';

import nprogress from 'nprogress';

import { service } from 'ember-decorators/service';
import { action } from 'ember-decorators/object';

export default Route.extend({
export default Route.extend(ApplicationRouteMixin, {
@service intl: null,

beforeModel() {
Expand Down
33 changes: 33 additions & 0 deletions app/authenticators/wallet.js
@@ -0,0 +1,33 @@
import Base from 'ember-simple-auth/authenticators/base';

import { service } from 'ember-decorators/service';

import { defineError } from 'ember-exex/error';

export const AuthenticationError = defineError({
name: 'AuthenticationError',
message: 'Authentication error',
});

export default Base.extend({
@service rpc: null,
@service store: null,

async restore({ wallet }) {
if (!wallet) {
throw new AuthenticationError('Unable to restore session');
}

return { wallet };
},

async authenticate({ wallet, password }) {
try {
await this.get('rpc').passwordEnter(wallet, password);
} catch (err) {
throw new AuthenticationError(err);
}

return { wallet };
},
});
2 changes: 1 addition & 1 deletion app/components/account-send/template.hbs
@@ -1,4 +1,4 @@
{{#bs-form model=changeset onSubmit=onSubmit as |form|}}
{{#bs-form model=changeset onSubmit=(action onSubmit) as |form|}}
{{#form.element controlType="power-select" label=(t 'wallets.accounts.send.source') property="source" required=true options=accounts onChange=(action onChange) as |el|}}
{{#el.control searchField="id" searchPlaceholder=(t 'wallets.accounts.send.search') as |item|}}
<b>{{item.id}}</b>
Expand Down
4 changes: 0 additions & 4 deletions app/components/wallet-overview/template.hbs
Expand Up @@ -2,10 +2,6 @@
{{t 'wallets.overview.balance' htmlSafe=true balance=(format-amount wallet.balance)}}
</p>

<p class="small text-muted">
{{t 'wallet'}}: {{wallet.id}}
</p>

{{page-numbers content=content currentPage=page totalPages=totalPages}}

{{#light-table table tableClassNames='table table-striped' height='auto' as |t|}}
Expand Down
11 changes: 11 additions & 0 deletions app/components/wallet-password/component.js
@@ -0,0 +1,11 @@
import Component from '@ember/component';

import ChangePasswordValidations from '../../validations/change-password';

export default Component.extend({
ChangePasswordValidations,

wallet: null,
password: null,
onSubmit: null,
});
16 changes: 16 additions & 0 deletions app/components/wallet-password/template.hbs
@@ -0,0 +1,16 @@
{{#bs-modal as |modal|}}
{{#modal.header}}
<h4 class="modal-title">{{t 'wallets.password.title'}}</h4>
{{/modal.header}}
{{#modal.body}}
{{#bs-form model=(changeset (hash password=password) ChangePasswordValidations)
onSubmit=(action onSubmit wallet) as |form|}}
{{form.element controlType="password" label=(t 'password') property='password' required=true minlength=8}}
{{/bs-form}}
{{/modal.body}}
{{#modal.footer}}
{{#bs-button onClick=(action modal.submit) type="primary"}}
{{t 'wallets.password.submit'}}
{{/bs-button}}
{{/modal.footer}}
{{/bs-modal}}
12 changes: 6 additions & 6 deletions app/index/route.js
Expand Up @@ -4,7 +4,7 @@ import { get } from '@ember/object';
import { service } from 'ember-decorators/service';

export default Route.extend({
@service settings: null,
@service session: null,
@service electron: null,

// eslint-disable-next-line consistent-return
Expand All @@ -19,14 +19,14 @@ export default Route.extend({
}
},

afterModel(model, transaction) {
const settings = this.get('settings');
const wallet = settings.get('wallet');
if (!wallet) {
transaction.abort();
afterModel() {
const session = this.get('session');
const isAuthenticated = get(session, 'isAuthenticated');
if (!isAuthenticated) {
return this.transitionTo('setup');
}

const wallet = get(session, 'data.authenticated.wallet');
return this.transitionTo('wallets', wallet);
},
});
9 changes: 9 additions & 0 deletions app/login/route.js
@@ -0,0 +1,9 @@
import Route from '@ember/routing/route';

import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin';

export default Route.extend(UnauthenticatedRouteMixin, {
beforeModel() {
return this.transitionTo('setup');
},
});
1 change: 1 addition & 0 deletions app/login/template.hbs
@@ -0,0 +1 @@
{{outlet}}
11 changes: 11 additions & 0 deletions app/logout/route.js
@@ -0,0 +1,11 @@
import Route from '@ember/routing/route';

import { service } from 'ember-decorators/service';

export default Route.extend({
@service session: null,

beforeModel() {
return this.get('session').invalidate();
},
});
1 change: 1 addition & 0 deletions app/logout/template.hbs
@@ -0,0 +1 @@
{{outlet}}
9 changes: 7 additions & 2 deletions app/router.js
Expand Up @@ -11,10 +11,11 @@ Router.map(function routerMap() {
this.route('start');

this.route('setup', function setupRoute() {
this.route('import');
this.route('backup');
this.route('download');
this.route('start');
this.route('import');
this.route('backup');
this.route('password', { path: '/:wallet_id' });
});

this.route('wallets', { path: '/:wallet_id' }, function walletsRoute() {
Expand All @@ -24,7 +25,11 @@ Router.map(function routerMap() {
this.route('send');
this.route('history');
});
this.route('logout');
});
this.route('login');
this.route('logout');
this.route('error');
});

export default Router;
36 changes: 36 additions & 0 deletions app/rpc/service.js
Expand Up @@ -19,13 +19,22 @@ export const actions = {
SEND: 'send',
PEERS: 'peers',
BLOCK_COUNT: 'block_count',
PASSWORD_CHANGE: 'password_change',
PASSWORD_ENTER: 'password_enter',
PASSWORD_VALID: 'password_valid',
};

const RPCError = defineError({
name: 'RPCError',
message: 'RPC error',
});

const InvalidPasswordError = defineError({
name: 'InvalidPasswordError',
message: 'Invalid password',
extends: RPCError,
});

export default Service.extend({
@service ajax: null,

Expand Down Expand Up @@ -119,4 +128,31 @@ export default Service.extend({
blockCount() {
return this.call(actions.BLOCK_COUNT);
},

async passwordChange(wallet, password) {
const { changed } = await this.call(actions.PASSWORD_CHANGE, { wallet, password });
if (changed !== '1') {
throw new RPCError('Password change failed');
}

return true;
},

async passwordEnter(wallet, password) {
const { valid } = await this.call(actions.PASSWORD_ENTER, { wallet, password });
if (valid !== '1') {
throw new InvalidPasswordError();
}

return true;
},

async passwordValid(wallet, password) {
const { valid } = await this.call(actions.PASSWORD_VALID, { wallet, password });
if (valid !== '1') {
throw new InvalidPasswordError();
}

return true;
},
});
27 changes: 3 additions & 24 deletions app/settings/service.js
@@ -1,39 +1,18 @@
import Service from '@ember/service';
import {
get,
set,
getProperties,
setProperties,
getWithDefault,
} from '@ember/object';
import { get, set } from '@ember/object';

import { storageFor } from 'ember-local-storage';

export default Service.extend({
settings: storageFor('settings'),

get(keyName) {
unknownProperty(keyName) {
const settings = get(this, 'settings');
return get(settings, keyName);
},

set(keyName, value) {
setUnknownProperty(keyName, value) {
const settings = get(this, 'settings');
return set(settings, keyName, value);
},

getProperties(...args) {
const settings = get(this, 'settings');
return getProperties(settings, ...args);
},

setProperties(hash) {
const settings = get(this, 'settings');
return setProperties(settings, hash);
},

getWithDefault(keyName, defaultValue) {
const settings = get(this, 'settings');
return getWithDefault(settings, keyName, defaultValue);
},
});
5 changes: 1 addition & 4 deletions app/settings/storage.js
Expand Up @@ -4,10 +4,7 @@ const Storage = StorageObject.extend();

Storage.reopenClass({
initialState() {
return {
wallet: null,
account: null,
};
return {};
},
});

Expand Down
2 changes: 1 addition & 1 deletion app/setup/backup/route.js
Expand Up @@ -25,6 +25,6 @@ export default Route.extend({
@action
done(wallet, seed) {
set(wallet, 'seed', seed);
return this.transitionTo('wallets', wallet.save());
return this.transitionTo('setup.password', wallet.save());
},
});
2 changes: 1 addition & 1 deletion app/setup/import/route.js
Expand Up @@ -18,6 +18,6 @@ export default Route.extend({

@action
cancel() {
return this.transitionTo('setup');
return this.transitionTo('setup.password');
},
});
18 changes: 18 additions & 0 deletions app/setup/password/route.js
@@ -0,0 +1,18 @@
import Route from '@ember/routing/route';
import { get } from '@ember/object';

import { service } from 'ember-decorators/service';
import { action } from 'ember-decorators/object';

export default Route.extend({
@service rpc: null,
@service session: null,

@action
async changePassword(wallet, changeset) {
const walletId = get(wallet, 'id');
const password = get(changeset, 'password');
await this.get('rpc').passwordChange(walletId, password);
return this.get('session').authenticate('authenticator:wallet', { password, wallet: walletId });
},
});
1 change: 1 addition & 0 deletions app/setup/password/template.hbs
@@ -0,0 +1 @@
{{wallet-password wallet=model onSubmit=(route-action 'changePassword')}}
2 changes: 1 addition & 1 deletion app/status/service.js
Expand Up @@ -7,7 +7,7 @@ import { on } from 'ember-decorators/object/evented';

import { hash } from 'rsvp';

const DEFAULT_INTERVAL = 5000; // 5s
const DEFAULT_INTERVAL = 10000; // 10s

const Service = ObjectProxy.extend(PromiseProxyMixin, {
@service pollboy: null,
Expand Down
5 changes: 5 additions & 0 deletions app/validations/change-password.js
@@ -0,0 +1,5 @@
import validatePassword from '../validators/password';

export default {
password: validatePassword(),
};
5 changes: 5 additions & 0 deletions app/validators/password.js
@@ -0,0 +1,5 @@
import { validateLength } from 'ember-changeset-validations/validators';

export default function validatePassword() {
return validateLength({ min: 8 });
}
22 changes: 22 additions & 0 deletions app/wallets/logout/route.js
@@ -0,0 +1,22 @@
import Route from '@ember/routing/route';
import { get } from '@ember/object';
import { tryInvoke } from '@ember/utils';

import { service } from 'ember-decorators/service';
import { action } from 'ember-decorators/object';

export default Route.extend({
@service window: null,

@action
cancel() {
const window = this.get('window');
const history = get(window, 'history');
return tryInvoke(history, 'back');
},

@action
logout() {
return this.transitionTo('logout');
},
});
21 changes: 21 additions & 0 deletions app/wallets/logout/template.hbs
@@ -0,0 +1,21 @@
{{#bs-modal onHide=(route-action 'cancel') onSubmit=(route-action 'logout') as |modal|}}
{{#modal.header}}
<h4 class="modal-title">
{{fa-icon 'exclamation-triangle' class="text-danger"}}
{{t 'wallets.logout.title'}}
</h4>
{{/modal.header}}
{{#modal.body}}
<p class="lead">{{t 'wallets.logout.lead'}}</p>
<p>{{t 'wallets.logout.warning' htmlSafe=true}}</p>
{{/modal.body}}
{{#modal.footer}}
{{#bs-button onClick=(action modal.close) type="secondary"}}
{{t 'cancel'}}
{{/bs-button}}
{{#bs-button onClick=(action modal.submit) type="danger"}}
{{fa-icon 'sign-out'}}
{{t 'logout'}}
{{/bs-button}}
{{/modal.footer}}
{{/bs-modal}}

0 comments on commit 89edf29

Please sign in to comment.