mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-20 11:47:53 +02:00
Fix #11 : Set TOTP loop duration on remaining time instead of remaining dots
This commit is contained in:
parent
02798a05f3
commit
8253d28102
@ -40,7 +40,7 @@ class TwoFAccount extends Model implements Sortable
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $appends = ['token', 'isConsistent', 'otpType', 'secret', 'algorithm', 'digits', 'totpPeriod', 'totpPosition', 'hotpCounter', 'imageLink'];
|
||||
protected $appends = ['token', 'isConsistent', 'otpType', 'secret', 'algorithm', 'digits', 'totpPeriod', 'totpTimestamp', 'hotpCounter', 'imageLink'];
|
||||
|
||||
|
||||
/**
|
||||
@ -56,7 +56,7 @@ class TwoFAccount extends Model implements Sortable
|
||||
*
|
||||
* @var OTPHP/TOTP || OTPHP/HOTP
|
||||
*/
|
||||
protected $otp;
|
||||
protected $otp, $timestamp;
|
||||
|
||||
|
||||
/**
|
||||
@ -348,29 +348,6 @@ class TwoFAccount extends Model implements Sortable
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -382,12 +359,15 @@ class TwoFAccount extends Model implements Sortable
|
||||
|
||||
|
||||
/**
|
||||
* Generate a token which is valid at the current time (now)
|
||||
* Generate a token which is valid at the current time
|
||||
* @return string The generated token
|
||||
*/
|
||||
public function generateToken() : string
|
||||
{
|
||||
return $this->otpType === 'totp' ? $this->otp->now() : $this->otp->at($this->otp->getCounter());
|
||||
$this->timestamp = time();
|
||||
$token = $this->otpType === 'totp' ? $this->otp->at($this->timestamp) : $this->otp->at($this->otp->getCounter());
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
|
||||
@ -405,6 +385,17 @@ class TwoFAccount extends Model implements Sortable
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get totpTimestamp attribute
|
||||
*
|
||||
* @return int The timestamp
|
||||
*/
|
||||
public function getTotpTimestampAttribute()
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get token attribute
|
||||
*
|
||||
@ -416,17 +407,6 @@ class TwoFAccount extends Model implements Sortable
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get totpPosition attribute
|
||||
*
|
||||
* @return int The position
|
||||
*/
|
||||
public function getTotpPositionAttribute()
|
||||
{
|
||||
return $this->getTotpPosition();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get OTP Type attribute
|
||||
*
|
||||
|
@ -7,7 +7,7 @@
|
||||
<p class="is-size-6 has-text-grey has-ellipsis">{{ internal_account }}</p>
|
||||
<p class="is-size-1 has-text-white is-clickable" :title="$t('commons.copy_to_clipboard')" v-clipboard="() => token.replace(/ /g, '')" v-clipboard:success="clipboardSuccessHandler">{{ displayedToken }}</p>
|
||||
<ul class="dots" v-if="internal_otpType === 'totp'">
|
||||
<li v-for="n in 30"></li>
|
||||
<li v-for="n in 10"></li>
|
||||
</ul>
|
||||
<ul v-else-if="internal_otpType === 'hotp'">
|
||||
<li>counter: {{ internal_hotpCounter }}</li>
|
||||
@ -23,13 +23,18 @@
|
||||
return {
|
||||
id: null,
|
||||
token : '',
|
||||
timerID: null,
|
||||
remainingTimeout: null,
|
||||
firstDotToNextOneTimeout: null,
|
||||
dotToDotInterval: null,
|
||||
position: null,
|
||||
totpTimestamp: null,
|
||||
internal_otpType: '',
|
||||
internal_account: '',
|
||||
internal_service: '',
|
||||
internal_icon: '',
|
||||
internal_hotpCounter: null,
|
||||
lastActiveDot: null,
|
||||
dotToDotCounter: null,
|
||||
}
|
||||
},
|
||||
|
||||
@ -121,42 +126,62 @@
|
||||
|
||||
getTOTP: function() {
|
||||
|
||||
this.dotToDotCounter = 0
|
||||
|
||||
this.axios.post('/api/twofaccounts/otp', { id: this.id, otp: this.$props }).then(response => {
|
||||
|
||||
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.totpPosition;
|
||||
this.totpTimestamp = response.data.totpTimestamp; // the timestamp used to generate the token
|
||||
this.position = this.totpTimestamp % response.data.totpPeriod // The position of the totp timestamp in the current period
|
||||
|
||||
// Hide all dots
|
||||
let dots = this.$el.querySelector('.dots');
|
||||
|
||||
// clear active dots
|
||||
while (dots.querySelector('[data-is-active]')) {
|
||||
dots.querySelector('[data-is-active]').removeAttribute('data-is-active');
|
||||
}
|
||||
|
||||
// set dot at given position as the active one
|
||||
let active = dots.querySelector('li:nth-child(' + (this.position + 1 ) + ')');
|
||||
active.setAttribute('data-is-active', true);
|
||||
// Activate the dot at the totp position
|
||||
let relativePosition = (this.position * 10) / response.data.totpPeriod
|
||||
let dotNumber = (Math.floor(relativePosition) +1)
|
||||
|
||||
let self = this;
|
||||
this.lastActiveDot = dots.querySelector('li:nth-child(' + dotNumber + ')');
|
||||
this.lastActiveDot.setAttribute('data-is-active', true);
|
||||
|
||||
this.timerID = setInterval(function() {
|
||||
// Main timeout which run all over the totpPeriod.
|
||||
|
||||
let sibling = active.nextSibling;
|
||||
let remainingTimeBeforeEndOfPeriod = response.data.totpPeriod - this.position
|
||||
let self = this; // because of the setInterval/setTimeout closures
|
||||
|
||||
if(active.nextSibling === null) {
|
||||
console.log('no more sibling to activate, we refresh the OTP')
|
||||
self.stopLoop()
|
||||
self.getTOTP();
|
||||
}
|
||||
else
|
||||
{
|
||||
active.removeAttribute('data-is-active');
|
||||
sibling.setAttribute('data-is-active', true);
|
||||
active = sibling
|
||||
}
|
||||
this.remainingTimeout = setTimeout(function() {
|
||||
self.stopLoop()
|
||||
self.getTOTP();
|
||||
}, remainingTimeBeforeEndOfPeriod*1000);
|
||||
|
||||
}, 1000);
|
||||
// During the remainingTimeout countdown we have to show a next dot every durationBetweenTwoDots seconds
|
||||
// except for the first next dot
|
||||
|
||||
let durationBetweenTwoDots = response.data.totpPeriod / 10 // we have 10 dots
|
||||
let firstDotTimeout = (Math.ceil(this.position / durationBetweenTwoDots) * durationBetweenTwoDots) - this.position
|
||||
|
||||
this.firstDotToNextOneTimeout = setTimeout(function() {
|
||||
|
||||
if( firstDotTimeout > 0 ) {
|
||||
self.activeNextDot()
|
||||
dotNumber += 1
|
||||
}
|
||||
|
||||
self.dotToDotInterval = setInterval(function() {
|
||||
|
||||
self.dotToDotCounter += 1
|
||||
self.activeNextDot()
|
||||
dotNumber += 1
|
||||
|
||||
}, durationBetweenTwoDots*1000)
|
||||
|
||||
}, firstDotTimeout*1000)
|
||||
})
|
||||
.catch(error => {
|
||||
this.$router.push({ name: 'genericError', params: { err: error.response } });
|
||||
@ -183,7 +208,7 @@
|
||||
|
||||
clearOTP: function() {
|
||||
this.stopLoop()
|
||||
this.id = this.timerID = this.position = this.internal_hotpCounter = null
|
||||
this.id = this.remainingTimeout = this.dotToDotInterval = this.firstDotToNextOneTimeout = this.position = this.internal_hotpCounter = null
|
||||
this.internal_service = this.internal_account = this.internal_icon = this.internal_otpType = ''
|
||||
this.token = '... ...'
|
||||
|
||||
@ -199,7 +224,19 @@
|
||||
|
||||
stopLoop: function() {
|
||||
if( this.internal_otpType === 'totp' ) {
|
||||
clearInterval(this.timerID)
|
||||
clearTimeout(this.remainingTimeout)
|
||||
clearTimeout(this.firstDotToNextOneTimeout)
|
||||
clearInterval(this.dotToDotInterval)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
activeNextDot: function() {
|
||||
if(this.lastActiveDot.nextSibling !== null) {
|
||||
|
||||
this.lastActiveDot.removeAttribute('data-is-active')
|
||||
this.lastActiveDot.nextSibling.setAttribute('data-is-active', true)
|
||||
this.lastActiveDot = this.lastActiveDot.nextSibling
|
||||
}
|
||||
},
|
||||
|
||||
|
16
resources/sass/app.scss
vendored
16
resources/sass/app.scss
vendored
@ -310,23 +310,17 @@ figure.no-icon {
|
||||
background: hsl(0, 0%, 7%); /* grey */
|
||||
}
|
||||
|
||||
.dots li:nth-child(-n+27) {
|
||||
.dots li:nth-child(-n+9) {
|
||||
background: hsl(48, 100%, 67%); /* yellow */
|
||||
}
|
||||
|
||||
.dots li:nth-child(-n+18) {
|
||||
.dots li:nth-child(-n+6) {
|
||||
background: hsl(141, 71%, 48%); /* green */
|
||||
}
|
||||
|
||||
.dots li:nth-child(3n+2), .dots li:nth-child(3n+3) {
|
||||
//background-color: black;
|
||||
display:none;
|
||||
}
|
||||
|
||||
.dots li:last-child() {
|
||||
// background-color: black;
|
||||
//display:none;
|
||||
}
|
||||
// .dots li:nth-child(3n+2), .dots li:nth-child(3n+3) {
|
||||
// display:none;
|
||||
// }
|
||||
|
||||
.input, .select select, .textarea {
|
||||
background-color: hsl(0, 0%, 21%);
|
||||
|
Loading…
x
Reference in New Issue
Block a user