diff --git a/src/pages/pin/pin.scss b/src/pages/pin/pin.scss
index bb4ee9289..f429419f1 100644
--- a/src/pages/pin/pin.scss
+++ b/src/pages/pin/pin.scss
@@ -5,7 +5,7 @@ page-pin {
max-width: 300px;
margin: auto;
}
- .block-buttons{
+ .block-buttons {
.row {
font-size: 1.7rem;
cursor: pointer;
diff --git a/src/pages/pin/pin.ts b/src/pages/pin/pin.ts
index a7e1191e8..3241edaa6 100644
--- a/src/pages/pin/pin.ts
+++ b/src/pages/pin/pin.ts
@@ -3,26 +3,29 @@ import { NavController, NavParams, ViewController } from 'ionic-angular';
import { ConfigProvider } from '../../providers/config/config';
import { Logger } from '@nsalaun/ng-logger';
-import * as _ from 'lodash';
-
@Component({
selector: 'page-pin',
templateUrl: 'pin.html',
})
export class PinModalPage {
+ private ATTEMPT_LIMIT: number = 3;
+ private ATTEMPT_LOCK_OUT_TIME: number = 5 * 60;
+ public currentAttempts: number = 0;
public currentPin: string = '';
public firstPinEntered: string = '';
public confirmingPin: boolean = false;
public action: string = '';
public appName: string = 'copay';
+ public disableButtons: boolean = false;
+ public expires: string = '';
constructor(
- public navCtrl: NavController,
- public navParams: NavParams,
+ private navCtrl: NavController,
+ private navParams: NavParams,
private config: ConfigProvider,
private logger: Logger,
- public viewCtrl: ViewController
+ private viewCtrl: ViewController
) {
switch (this.navParams.get('action')) {
@@ -36,6 +39,18 @@ export class PinModalPage {
this.action = 'removeLock'
}
+ if (this.action === 'checkPin' || this.action === 'removeLock') {
+ let config = this.config.get();
+ let bannedUntil = config['lock']['bannedUntil'];
+ if (bannedUntil) {
+ let now = Math.floor(Date.now() / 1000);
+ if (now < bannedUntil) {
+ this.disableButtons = true;
+ this.lockTimeControl(bannedUntil);
+ }
+ }
+ }
+
}
goBack(): void {
@@ -43,6 +58,7 @@ export class PinModalPage {
}
newEntry(value: string): void {
+ if (this.disableButtons) return;
this.currentPin = this.currentPin + value;
if (!this.isComplete()) return;
if (this.action === 'checkPin' || this.action === 'removeLock') this.checkIfCorrect();
@@ -53,20 +69,56 @@ export class PinModalPage {
this.currentPin = '';
}
else if (this.firstPinEntered === this.currentPin) this.save();
- else {
- this.firstPinEntered = this.currentPin = '';
- }
+ else this.firstPinEntered = this.currentPin = '';
}
}
+ checkAttempts(): void {
+ this.currentAttempts += 1;
+ this.logger.info('Attempts to unlock:', this.currentAttempts);
+ if (this.currentAttempts == this.ATTEMPT_LIMIT) {
+ this.currentAttempts = 0;
+ let bannedUntil = Math.floor(Date.now() / 1000) + this.ATTEMPT_LOCK_OUT_TIME;
+ this.saveFailedAttempt(bannedUntil);
+ this.lockTimeControl(bannedUntil);
+ }
+ }
+
+ lockTimeControl(bannedUntil): void {
+ this.setExpirationTime(bannedUntil, null);
+ var countDown = setInterval(() => {
+ this.setExpirationTime(bannedUntil, countDown);
+ }, 1000);
+ }
+
+ setExpirationTime(bannedUntil, countDown) {
+ let now = Math.floor(Date.now() / 1000);
+ if (now > bannedUntil) {
+ if (countDown) this.reset(countDown);
+ } else {
+ this.disableButtons = true;
+ let totalSecs = bannedUntil - now;
+ let m = Math.floor(totalSecs / 60);
+ let s = totalSecs % 60;
+ this.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
+ }
+ }
+
+ reset(countDown) {
+ this.expires = this.disableButtons = null;
+ this.currentPin = this.firstPinEntered = '';
+ clearInterval(countDown);
+ }
+
delete(): void {
+ if (this.disableButtons) return;
this.currentPin = this.currentPin.substring(0, this.currentPin.length - 1);
}
isComplete(): boolean {
if (this.currentPin.length < 4) return false;
else return true;
- };
+ }
save(): void {
let lock = { method: 'PIN', value: this.currentPin, bannedUntil: null };
@@ -85,11 +137,19 @@ export class PinModalPage {
}
if (this.action === 'checkPin') this.viewCtrl.dismiss();
}
- else this.currentPin = '';
+ else {
+ this.currentPin = '';
+ this.checkAttempts();
+ }
}
getFilledClass(limit): string {
return this.currentPin.length >= limit ? 'filled-' + this.appName : null;
}
+ saveFailedAttempt(bannedUntil) {
+ let lock = { bannedUntil: bannedUntil };
+ this.config.set({ lock });
+ }
+
}
diff --git a/src/pages/settings/lock/lock.html b/src/pages/settings/lock/lock.html
index 59198d87c..837276acc 100644
--- a/src/pages/settings/lock/lock.html
+++ b/src/pages/settings/lock/lock.html
@@ -14,7 +14,7 @@
{{opt.method}}
-
+
\ No newline at end of file
diff --git a/src/pages/settings/lock/lock.ts b/src/pages/settings/lock/lock.ts
index ee4082aad..f90b9b9a4 100644
--- a/src/pages/settings/lock/lock.ts
+++ b/src/pages/settings/lock/lock.ts
@@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { NavController, NavParams, ModalController } from 'ionic-angular';
import { ConfigProvider } from '../../../providers/config/config';
-
+import { TouchIdProvider } from '../../../providers/touchid/touchid';
import { PinModalPage } from '../../pin/pin';
@Component({
@@ -9,27 +9,37 @@ import { PinModalPage } from '../../pin/pin';
templateUrl: 'lock.html',
})
export class LockPage {
- public options: Array<{ method: string, enabled: boolean }> = [];
+ public options: Array<{ method: string, enabled: boolean, disabled: boolean }> = [];
public lockOptions: Object;
constructor(
private modalCtrl: ModalController,
- private config: ConfigProvider
+ private config: ConfigProvider,
+ private touchid: TouchIdProvider,
) {
this.lockOptions = this.config.get()['lock'];
this.options = [
{
method: 'Disabled',
- enabled: this.lockOptions['method'] == 'Disabled' ? true : false
+ enabled: this.lockOptions['method'] == 'Disabled' ? true : false,
+ disabled: false
},
{
method: 'PIN',
- enabled: this.lockOptions['method'] == 'PIN' ? true : false
+ enabled: this.lockOptions['method'] == 'PIN' ? true : false,
+ disabled: false
},
+ {
+ method: 'Fingerprint',
+ enabled: this.lockOptions['method'] == 'Fingerprint' ? true : false,
+ disabled: !this.touchid.isAvailable() ? true : false
+ }
];
}
+
+
select(method): void {
switch (method) {
case 'PIN':
@@ -37,6 +47,10 @@ export class LockPage {
break;
case 'Disabled':
this.openPinModal('removeLock');
+ break;
+ case 'Fingerprint':
+ this.lockByFingerprint();
+ break;
}
}
@@ -44,4 +58,9 @@ export class LockPage {
let modal = this.modalCtrl.create(PinModalPage, { action });
modal.present();
}
+
+ lockByFingerprint() {
+ let lock = { method: 'Fingerprint', value: null, bannedUntil: null };
+ this.config.set({ lock });
+ }
}
diff --git a/src/providers/app/app.ts b/src/providers/app/app.ts
index d3d8b527b..a95404b39 100644
--- a/src/providers/app/app.ts
+++ b/src/providers/app/app.ts
@@ -5,6 +5,7 @@ import 'rxjs/add/operator/map';
import { LanguageProvider } from '../../providers/language/language';
import { ConfigProvider } from '../../providers/config/config';
+import { TouchIdProvider } from '../../providers/touchid/touchid';
interface App {
WindowsStoreDisplayName: string;
@@ -51,7 +52,8 @@ export class AppProvider {
public http: Http,
private logger: Logger,
private language: LanguageProvider,
- private config: ConfigProvider
+ private config: ConfigProvider,
+ private touchid: TouchIdProvider
) {
this.logger.info('AppProvider initialized.');
}
@@ -62,6 +64,7 @@ export class AppProvider {
// storage -> config -> language -> unit -> app
// Everything ok
this.language.init(config);
+ this.touchid.init();
this.getInfo().subscribe((info) => {
this.info = info;
resolve(true);
diff --git a/src/providers/touchid/touchid.ts b/src/providers/touchid/touchid.ts
index e0f31a851..9c265b338 100644
--- a/src/providers/touchid/touchid.ts
+++ b/src/providers/touchid/touchid.ts
@@ -7,13 +7,15 @@ import { AndroidFingerprintAuth } from '@ionic-native/android-fingerprint-auth';
@Injectable()
export class TouchIdProvider {
- private _isAvailable: any = false;
+ private _isAvailable: boolean = false;
- constructor(private touchId: TouchID, private androidFingerprintAuth: AndroidFingerprintAuth, private platform: PlatformProvider) {
- this.checkDeviceFingerprint();
- }
+ constructor(
+ private touchId: TouchID,
+ private androidFingerprintAuth: AndroidFingerprintAuth,
+ private platform: PlatformProvider
+ ) { }
- checkDeviceFingerprint() {
+ init() {
if (this.platform.isAndroid) this.checkAndroid();
if (this.platform.isIOS) this.checkIOS();
}
@@ -21,7 +23,7 @@ export class TouchIdProvider {
checkIOS() {
this.touchId.isAvailable()
.then(
- res => this._isAvailable = 'IOS',
+ res => this._isAvailable = true,
err => console.log("Fingerprint is not available")
);
}
@@ -30,7 +32,7 @@ export class TouchIdProvider {
this.androidFingerprintAuth.isAvailable()
.then(
res => {
- if (res.isAvailable) this._isAvailable = 'ANDROID'
+ if (res.isAvailable) this._isAvailable = true
else console.log("Fingerprint is not available")
});
}
@@ -39,8 +41,8 @@ export class TouchIdProvider {
return new Promise((resolve, reject) => {
this.touchId.verifyFingerprint('Scan your fingerprint please')
.then(
- res => resolve(true),
- err => reject(false)
+ res => resolve(),
+ err => reject()
);
});
}
@@ -51,19 +53,19 @@ export class TouchIdProvider {
.then(result => {
if (result.withFingerprint) {
console.log('Successfully authenticated with fingerprint.');
- resolve(true);
+ resolve();
} else if (result.withBackup) {
console.log('Successfully authenticated with backup password!');
- resolve(true);
+ resolve();
} else console.log('Didn\'t authenticate!');
})
.catch(error => {
if (error === this.androidFingerprintAuth.ERRORS.FINGERPRINT_CANCELLED) {
console.log('Fingerprint authentication cancelled');
- reject(false);
+ reject();
} else {
console.error(error);
- resolve(false);
+ resolve();
}
});
});
@@ -76,22 +78,22 @@ export class TouchIdProvider {
check(): Promise
{
return new Promise((resolve, reject) => {
if (!this.isAvailable()) reject();
- if (this.isAvailable() == 'IOS') {
+ if (this.platform.isIOS) {
this.verifyIOSFingerprint()
- .then((success) => {
- resolve(success);
+ .then(() => {
+ resolve();
})
- .catch(err => {
- reject(err);
+ .catch(() => {
+ reject();
});
}
- if (this.isAvailable() == 'ANDROID') {
+ if (this.platform.isAndroid) {
this.verifyAndroidFingerprint()
- .then((success) => {
- resolve(success);
+ .then(() => {
+ resolve();
})
- .catch(err => {
- reject(err);
+ .catch(() => {
+ reject();
});
}
});