From 02798a05f31efe04820bf16c7179885c5b4ff95c Mon Sep 17 00:00:00 2001
From: Bubka <858858+Bubka@users.noreply.github.com>
Date: Sat, 14 Nov 2020 18:55:10 +0100
Subject: [PATCH] Move token generation from dedicated class to TwoFAccount
model class
---
app/Classes/OTP.php | 60 -------------------
.../Controllers/TwoFAccountController.php | 28 ++++++---
app/TwoFAccount.php | 50 +++++++++++++++-
resources/js/components/TokenDisplayer.vue | 10 +---
resources/js/views/twofaccounts/Create.vue | 24 +++-----
5 files changed, 81 insertions(+), 91 deletions(-)
delete mode 100644 app/Classes/OTP.php
diff --git a/app/Classes/OTP.php b/app/Classes/OTP.php
deleted file mode 100644
index 0c6010cf..00000000
--- a/app/Classes/OTP.php
+++ /dev/null
@@ -1,60 +0,0 @@
-otpType === 'totp' ) {
-
- $currentPosition = time();
- $PeriodCount = floor($currentPosition / $twofaccount->totpPeriod); //nombre de période de x s depuis T0 (x=30 par défaut)
- $currentPeriodStartAt = $PeriodCount * $twofaccount->totpPeriod;
- $positionInCurrentPeriod = $currentPosition - $currentPeriodStartAt;
-
- // For memo :
- // $nextOtpAt = ($PeriodCount+1)*$period
- // $remainingTime = $nextOtpAt - time()
-
- return $totp = [
- 'token' => $twofaccount->token(),
- 'position' => $positionInCurrentPeriod
- ];
- }
- else {
- // It's a HOTP
- $hotp = [
- 'token' => $twofaccount->token(),
- 'hotpCounter' => $twofaccount->hotpCounter
- ];
-
- // now we update the counter for the next OTP generation
- $twofaccount->increaseHotpCounter();
-
- $hotp['nextHotpCounter'] = $twofaccount->hotpCounter;
- $hotp['nextUri'] = $twofaccount->uri;
-
- if( !$isPreview ) {
- $twofaccount->save();
- }
-
- return $hotp;
- }
-
- }
-
-}
diff --git a/app/Http/Controllers/TwoFAccountController.php b/app/Http/Controllers/TwoFAccountController.php
index 011a9c90..502801b9 100644
--- a/app/Http/Controllers/TwoFAccountController.php
+++ b/app/Http/Controllers/TwoFAccountController.php
@@ -114,31 +114,45 @@ public function reorder(Request $request)
*/
public function generateOTP(Request $request)
{
- $isPreview = false;
if( $request->id ) {
- // The request data is the Id of the account
+
+ // The request data is the Id of an existing account
$twofaccount = TwoFAccount::FindOrFail($request->id);
}
else if( $request->otp['uri'] ) {
+
// The request data contain an uri
$twofaccount = new TwoFAccount;
$twofaccount->populateFromUri($request->otp['uri']);
-
- $isPreview = true; // HOTP generated for preview (in the Create form) will not have its counter updated
}
else {
+
// The request data should contain all otp parameter
$twofaccount = new TwoFAccount;
$twofaccount->populate($request->otp);
-
- $isPreview = true; // HOTP generated for preview (in the Create form) will not have its counter updated
}
- return response()->json(OTP::generate($twofaccount, $isPreview ? true : false), 200);
+ if( $twofaccount->otpType === 'hotp' ) {
+
+ // returned counter & uri will be updated
+ $twofaccount->increaseHotpCounter();
+
+ // and the db too
+ if( $request->id ) {
+ $twofaccount->save();
+ }
+ }
+
+ if( $request->id ) {
+ return response()->json($twofaccount, 200);
+ }
+
+ return response()->json($twofaccount->makeVisible(['uri', 'secret', 'algorithm']), 200);
}
+
/**
* Update the specified resource in storage.
*
diff --git a/app/TwoFAccount.php b/app/TwoFAccount.php
index 1df82e8f..c617b45a 100644
--- a/app/TwoFAccount.php
+++ b/app/TwoFAccount.php
@@ -40,7 +40,7 @@ class TwoFAccount extends Model implements Sortable
*
* @var array
*/
- protected $appends = ['isConsistent', 'otpType', 'secret', 'algorithm', 'digits', 'totpPeriod', 'hotpCounter', 'imageLink'];
+ protected $appends = ['token', 'isConsistent', 'otpType', 'secret', 'algorithm', 'digits', 'totpPeriod', 'totpPosition', 'hotpCounter', 'imageLink'];
/**
@@ -348,6 +348,29 @@ public function populate(Array $attrib = [])
}
+ /**
+ * Calculate where is now() in the totp current period
+ * @return mixed The position
+ */
+ private function getTotpPosition()
+ {
+ // For memo :
+ // $nextOtpAt = ($PeriodCount+1)*$period
+ // $remainingTime = $nextOtpAt - time()
+ if( $this->otpType === 'totp' ) {
+
+ $currentPosition = time();
+ $PeriodCount = floor($currentPosition / $this->totpPeriod); //nombre de période de x s depuis T0 (x=30 par défaut)
+ $currentPeriodStartAt = $PeriodCount * $this->totpPeriod;
+ $positionInCurrentPeriod = $currentPosition - $currentPeriodStartAt;
+
+ return $positionInCurrentPeriod;
+ }
+
+ return null;
+ }
+
+
/**
* Update the uri attribute using the OTP object
* @return void
@@ -362,12 +385,13 @@ private function refreshUri() : void
* Generate a token which is valid at the current time (now)
* @return string The generated token
*/
- public function token() : string
+ public function generateToken() : string
{
return $this->otpType === 'totp' ? $this->otp->now() : $this->otp->at($this->otp->getCounter());
}
+
/**
* Increment the hotp counter by 1
* @return string The generated token
@@ -381,6 +405,28 @@ public function increaseHotpCounter() : void
}
+ /**
+ * get token attribute
+ *
+ * @return string The token
+ */
+ public function getTokenAttribute() : string
+ {
+ return $this->generateToken();
+ }
+
+
+ /**
+ * get totpPosition attribute
+ *
+ * @return int The position
+ */
+ public function getTotpPositionAttribute()
+ {
+ return $this->getTotpPosition();
+ }
+
+
/**
* get OTP Type attribute
*
diff --git a/resources/js/components/TokenDisplayer.vue b/resources/js/components/TokenDisplayer.vue
index 6cec9e76..83640f45 100644
--- a/resources/js/components/TokenDisplayer.vue
+++ b/resources/js/components/TokenDisplayer.vue
@@ -22,8 +22,6 @@
data() {
return {
id: null,
- next_uri: '',
- nextHotpCounter: null,
token : '',
timerID: null,
position: null,
@@ -127,7 +125,7 @@
let spacePosition = Math.ceil(response.data.token.length / 2);
this.token = response.data.token.substr(0, spacePosition) + " " + response.data.token.substr(spacePosition);
- this.position = response.data.position;
+ this.position = response.data.totpPosition;
let dots = this.$el.querySelector('.dots');
@@ -172,11 +170,9 @@
let spacePosition = Math.ceil(response.data.token.length / 2);
this.token = response.data.token.substr(0, spacePosition) + " " + response.data.token.substr(spacePosition)
- this.internal_hotpCounter = response.data.hotpCounter
- this.nextHotpCounter = response.data.nextHotpCounter
- this.next_uri = response.data.nextUri
- this.$emit('update-hotp-counter', { nextHotpCounter: this.nextHotpCounter })
+ // returned counter & uri are incremented
+ this.$emit('increment-hotp', { nextHotpCounter: response.data.hotpCounter, nextUri: response.data.uri })
})
.catch(error => {
diff --git a/resources/js/views/twofaccounts/Create.vue b/resources/js/views/twofaccounts/Create.vue
index 04ef9211..bccdeac9 100644
--- a/resources/js/views/twofaccounts/Create.vue
+++ b/resources/js/views/twofaccounts/Create.vue
@@ -9,7 +9,7 @@
-
+
@@ -128,7 +128,7 @@
-
+
@@ -205,17 +205,6 @@
// set current temp icon as account icon
this.form.icon = this.tempIcon
- // The quick form or the preview feature has incremented the HOTP counter so the next_uri property
- // must be used as the uri to store.
- // This could desynchronized the HOTP verification server and our local counter if the user never verified the HOTP but this
- // is acceptable (and HOTP counter can be edited by the way)
- if( this.isQuickForm && this.$refs.QuickFormTokenDisplayer.next_uri ) {
- this.form.uri = this.$refs.QuickFormTokenDisplayer.next_uri
- }
- else if( this.$refs.AdvancedFormTokenDisplayer && this.$refs.AdvancedFormTokenDisplayer.next_uri ) {
- this.form.uri = this.$refs.AdvancedFormTokenDisplayer.next_uri
- }
-
await this.form.post('/api/twofaccounts')
if( this.form.errors.any() === false ) {
@@ -253,7 +242,7 @@
this.form.fill(data)
this.form.otpType = this.form.otpType.toUpperCase()
this.form.secretIsBase32Encoded = 1
- this.form.uri = '' // we don't want an uri now because the user can change any otp parameter in the form
+ this.form.uri = '' // we don't want the uri because the user can change any otp parameter in the form
},
@@ -278,8 +267,13 @@
}
},
- updateHotpCounter(payload) {
+ incrementHotp(payload) {
+ // The quick form or the preview feature has incremented the HOTP counter so we get the new value from
+ // the component.
+ // This could desynchronized the HOTP verification server and our local counter if the user never verified the HOTP but this
+ // is acceptable (and HOTP counter can be edited by the way)
this.form.hotpCounter = payload.nextHotpCounter
+ this.form.uri = payload.nextUri
},
},