mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-27 02:36:06 +01:00
Move token generation from dedicated class to TwoFAccount model class
This commit is contained in:
parent
acd1b2deca
commit
02798a05f3
@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Classes;
|
|
||||||
|
|
||||||
use OTPHP\TOTP;
|
|
||||||
use OTPHP\Factory;
|
|
||||||
use Assert\AssertionFailedException;
|
|
||||||
|
|
||||||
class OTP
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a TOTP
|
|
||||||
*
|
|
||||||
* @param \App\TwoFAccount $twofaccount
|
|
||||||
* @param Boolean $isPreview Prevent updating storage in case of HOTP preview
|
|
||||||
* @return an array that represent the totp code
|
|
||||||
*/
|
|
||||||
public static function generate($twofaccount, $isPreview = false)
|
|
||||||
{
|
|
||||||
|
|
||||||
if( $twofaccount->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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -114,31 +114,45 @@ public function reorder(Request $request)
|
|||||||
*/
|
*/
|
||||||
public function generateOTP(Request $request)
|
public function generateOTP(Request $request)
|
||||||
{
|
{
|
||||||
$isPreview = false;
|
|
||||||
|
|
||||||
if( $request->id ) {
|
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);
|
$twofaccount = TwoFAccount::FindOrFail($request->id);
|
||||||
}
|
}
|
||||||
else if( $request->otp['uri'] ) {
|
else if( $request->otp['uri'] ) {
|
||||||
|
|
||||||
// The request data contain an uri
|
// The request data contain an uri
|
||||||
$twofaccount = new TwoFAccount;
|
$twofaccount = new TwoFAccount;
|
||||||
$twofaccount->populateFromUri($request->otp['uri']);
|
$twofaccount->populateFromUri($request->otp['uri']);
|
||||||
|
|
||||||
$isPreview = true; // HOTP generated for preview (in the Create form) will not have its counter updated
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// The request data should contain all otp parameter
|
// The request data should contain all otp parameter
|
||||||
$twofaccount = new TwoFAccount;
|
$twofaccount = new TwoFAccount;
|
||||||
$twofaccount->populate($request->otp);
|
$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.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
|
@ -40,7 +40,7 @@ class TwoFAccount extends Model implements Sortable
|
|||||||
*
|
*
|
||||||
* @var array
|
* @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
|
* Update the uri attribute using the OTP object
|
||||||
* @return void
|
* @return void
|
||||||
@ -362,12 +385,13 @@ private function refreshUri() : void
|
|||||||
* Generate a token which is valid at the current time (now)
|
* Generate a token which is valid at the current time (now)
|
||||||
* @return string The generated token
|
* @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());
|
return $this->otpType === 'totp' ? $this->otp->now() : $this->otp->at($this->otp->getCounter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the hotp counter by 1
|
* Increment the hotp counter by 1
|
||||||
* @return string The generated token
|
* @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
|
* get OTP Type attribute
|
||||||
*
|
*
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: null,
|
id: null,
|
||||||
next_uri: '',
|
|
||||||
nextHotpCounter: null,
|
|
||||||
token : '',
|
token : '',
|
||||||
timerID: null,
|
timerID: null,
|
||||||
position: null,
|
position: null,
|
||||||
@ -127,7 +125,7 @@
|
|||||||
let spacePosition = Math.ceil(response.data.token.length / 2);
|
let spacePosition = Math.ceil(response.data.token.length / 2);
|
||||||
|
|
||||||
this.token = response.data.token.substr(0, spacePosition) + " " + response.data.token.substr(spacePosition);
|
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');
|
let dots = this.$el.querySelector('.dots');
|
||||||
|
|
||||||
@ -172,11 +170,9 @@
|
|||||||
let spacePosition = Math.ceil(response.data.token.length / 2);
|
let spacePosition = Math.ceil(response.data.token.length / 2);
|
||||||
|
|
||||||
this.token = response.data.token.substr(0, spacePosition) + " " + response.data.token.substr(spacePosition)
|
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 => {
|
.catch(error => {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<font-awesome-icon :icon="['fas', 'image']" size="2x" />
|
<font-awesome-icon :icon="['fas', 'image']" size="2x" />
|
||||||
</label>
|
</label>
|
||||||
<button class="delete delete-icon-button is-medium" v-if="tempIcon" @click.prevent="deleteIcon"></button>
|
<button class="delete delete-icon-button is-medium" v-if="tempIcon" @click.prevent="deleteIcon"></button>
|
||||||
<token-displayer ref="QuickFormTokenDisplayer" v-bind="form.data()">
|
<token-displayer ref="QuickFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||||
</token-displayer>
|
</token-displayer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -128,7 +128,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<!-- modal -->
|
<!-- modal -->
|
||||||
<modal v-model="ShowTwofaccountInModal">
|
<modal v-model="ShowTwofaccountInModal">
|
||||||
<token-displayer ref="AdvancedFormTokenDisplayer" v-bind="form.data()" @update-hotp-counter="updateHotpCounter">
|
<token-displayer ref="AdvancedFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||||
</token-displayer>
|
</token-displayer>
|
||||||
</modal>
|
</modal>
|
||||||
</form-wrapper>
|
</form-wrapper>
|
||||||
@ -205,17 +205,6 @@
|
|||||||
// set current temp icon as account icon
|
// set current temp icon as account icon
|
||||||
this.form.icon = this.tempIcon
|
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')
|
await this.form.post('/api/twofaccounts')
|
||||||
|
|
||||||
if( this.form.errors.any() === false ) {
|
if( this.form.errors.any() === false ) {
|
||||||
@ -253,7 +242,7 @@
|
|||||||
this.form.fill(data)
|
this.form.fill(data)
|
||||||
this.form.otpType = this.form.otpType.toUpperCase()
|
this.form.otpType = this.form.otpType.toUpperCase()
|
||||||
this.form.secretIsBase32Encoded = 1
|
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.hotpCounter = payload.nextHotpCounter
|
||||||
|
this.form.uri = payload.nextUri
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user