mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-02-18 11:20:49 +01:00
Fix Always On OTPs & Dots display and looping
This commit is contained in:
parent
a75e3c13f7
commit
e8a3c441be
@ -3,12 +3,12 @@ import { httpClientFactory } from '@/services/httpClientFactory'
|
|||||||
const apiClient = httpClientFactory('api')
|
const apiClient = httpClientFactory('api')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAll(withOtp = false) {
|
getAll(withOtp = false, config = {}) {
|
||||||
return apiClient.get('/twofaccounts' + (withOtp ? '?withOtp=1' : ''))
|
return apiClient.get('/twofaccounts' + (withOtp ? '?withOtp=1' : ''), { ...config })
|
||||||
},
|
},
|
||||||
|
|
||||||
getByIds(ids, withOtp = false) {
|
getByIds(ids, withOtp = false, config = {}) {
|
||||||
return apiClient.get('/twofaccounts?ids=' + ids + (withOtp ? '&withOtp=1' : ''))
|
return apiClient.get('/twofaccounts?ids=' + ids + (withOtp ? '&withOtp=1' : ''), { ...config })
|
||||||
},
|
},
|
||||||
|
|
||||||
get(id, config = {}) {
|
get(id, config = {}) {
|
||||||
|
32
resources/js_vue3/stores/twofaccounts.js
vendored
32
resources/js_vue3/stores/twofaccounts.js
vendored
@ -79,31 +79,33 @@ export const useTwofaccounts = defineStore({
|
|||||||
/**
|
/**
|
||||||
* Refreshes the accounts collection using the backend
|
* Refreshes the accounts collection using the backend
|
||||||
*/
|
*/
|
||||||
async fetch() {
|
async fetch(force = false) {
|
||||||
// We do not want to fetch fresh data multiple times in the same 2s timespan
|
// We do not want to fetch fresh data multiple times in the same 2s timespan
|
||||||
const age = Math.floor(Date.now() - this.fetchedOn)
|
const age = Math.floor(Date.now() - this.fetchedOn)
|
||||||
const isNotFresh = age > 2000
|
const isOutOfAge = age > 2000
|
||||||
|
|
||||||
if (isNotFresh) {
|
if (isOutOfAge || force) {
|
||||||
this.fetchedOn = Date.now()
|
this.fetchedOn = Date.now()
|
||||||
|
|
||||||
await twofaccountService.getAll(! useUserStore().preferences.getOtpOnRequest).then(response => {
|
await twofaccountService.getAll(! useUserStore().preferences.getOtpOnRequest).then(response => {
|
||||||
// Defines if the store was up-to-date with the backend
|
// Defines if the store was up-to-date with the backend
|
||||||
this.backendWasNewer = response.data.length !== this.items.length
|
if (force) {
|
||||||
|
this.backendWasNewer = response.data.length !== this.items.length
|
||||||
this.items.forEach((item) => {
|
|
||||||
let matchingBackendItem = response.data.find(e => e.id === item.id)
|
this.items.forEach((item) => {
|
||||||
if (matchingBackendItem == undefined) {
|
let matchingBackendItem = response.data.find(e => e.id === item.id)
|
||||||
this.backendWasNewer = true
|
if (matchingBackendItem == undefined) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const field in item) {
|
|
||||||
if (field !== 'otp' && item[field] != matchingBackendItem[field]) {
|
|
||||||
this.backendWasNewer = true
|
this.backendWasNewer = true
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
for (const field in item) {
|
||||||
})
|
if (field !== 'otp' && item[field] != matchingBackendItem[field]) {
|
||||||
|
this.backendWasNewer = true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Updates the state
|
// Updates the state
|
||||||
this.items = response.data
|
this.items = response.data
|
||||||
|
@ -32,9 +32,8 @@
|
|||||||
const showGroupSwitch = ref(false)
|
const showGroupSwitch = ref(false)
|
||||||
const showDestinationGroupSelector = ref(false)
|
const showDestinationGroupSelector = ref(false)
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const stepIndexesCache = ref({})
|
|
||||||
const isRenewingOTPs = ref(false)
|
const isRenewingOTPs = ref(false)
|
||||||
const renewedOTPs = ref(null)
|
const renewedPeriod = ref(null)
|
||||||
|
|
||||||
const otpDisplay = ref(null)
|
const otpDisplay = ref(null)
|
||||||
const otpDisplayProps = ref({
|
const otpDisplayProps = ref({
|
||||||
@ -85,12 +84,17 @@
|
|||||||
// This SFC is reached only if the user has some twofaccounts (see the starter middleware).
|
// This SFC is reached only if the user has some twofaccounts (see the starter middleware).
|
||||||
// This allows to display accounts without latency.
|
// This allows to display accounts without latency.
|
||||||
//
|
//
|
||||||
// We sync the store with the backend again to
|
// We sync the store with the backend again to
|
||||||
twofaccounts.fetch().then(() => {
|
if (! user.preferences.getOtpOnRequest) {
|
||||||
if (twofaccounts.backendWasNewer) {
|
updateTotps()
|
||||||
notify.info({ text: trans('commons.data_refreshed_to_reflect_server_changes'), duration: 10000 })
|
}
|
||||||
}
|
else {
|
||||||
})
|
twofaccounts.fetch().then(() => {
|
||||||
|
if (twofaccounts.backendWasNewer) {
|
||||||
|
notify.info({ text: trans('commons.data_refreshed_to_reflect_server_changes'), duration: 10000 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
groups.fetch()
|
groups.fetch()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -207,59 +211,69 @@
|
|||||||
twofaccounts.saveOrder()
|
twofaccounts.saveOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns dots On at the current step and caches the state
|
|
||||||
*/
|
|
||||||
function setCurrentStep(period, stepIndex) {
|
|
||||||
stepIndexesCache.value[period] = stepIndex
|
|
||||||
turnDotsOn(period, stepIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns dots On at the cached step index
|
|
||||||
*/
|
|
||||||
function turnDotsOnFromCache(period, stepIndex) {
|
|
||||||
if (stepIndexesCache.value[period] != undefined) {
|
|
||||||
turnDotsOn(period, stepIndexesCache.value[period])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns dots On for all dots components that match the provided period
|
* Turns dots On for all dots components that match the provided period
|
||||||
*/
|
*/
|
||||||
function turnDotsOn(period, stepIndex) {
|
function turnDotsOn(period, stepIndex) {
|
||||||
dotsRefs.value
|
dotsRefs.value
|
||||||
.filter((dots) => dots.props.period == period)
|
.filter((dots) => dots.props.period == period || period == undefined)
|
||||||
.forEach((dot) => {
|
.forEach((dot) => {
|
||||||
dot.turnOn(stepIndex)
|
dot.turnOn(stepIndex)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates "Always On" OTPs for all TOTP accounts with the given period and restarts loopers
|
* Turns dots Off for all dots components that match the provided period
|
||||||
|
*/
|
||||||
|
function turnDotsOff(period) {
|
||||||
|
dotsRefs.value
|
||||||
|
.filter((dots) => dots.props.period == period || period == undefined)
|
||||||
|
.forEach((dot) => {
|
||||||
|
dot.turnOff()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates "Always On" OTPs for all TOTP accounts and (re)starts loopers
|
||||||
*/
|
*/
|
||||||
async function updateTotps(period) {
|
async function updateTotps(period) {
|
||||||
isRenewingOTPs.value = true
|
isRenewingOTPs.value = true
|
||||||
renewedOTPs.value = period
|
turnDotsOff(period)
|
||||||
|
let fetchPromise
|
||||||
twofaccountService.getByIds(twofaccounts.accountIdsWithPeriod(period).join(','), true).then(response => {
|
|
||||||
|
if (period == undefined) {
|
||||||
|
renewedPeriod.value = -1
|
||||||
|
fetchPromise = twofaccountService.getAll(true)
|
||||||
|
} else {
|
||||||
|
renewedPeriod.value = period
|
||||||
|
fetchPromise = twofaccountService.getByIds(twofaccounts.accountIdsWithPeriod(period).join(','), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPromise.then(response => {
|
||||||
|
let generatedAt = 0
|
||||||
|
|
||||||
|
// twofaccounts OTP updates
|
||||||
response.data.forEach((account) => {
|
response.data.forEach((account) => {
|
||||||
const index = twofaccounts.items.findIndex(acc => acc.id === account.id)
|
const index = twofaccounts.items.findIndex(acc => acc.id === account.id)
|
||||||
twofaccounts.items[index].otp = account.otp
|
if (twofaccounts.items[index] == undefined) {
|
||||||
|
twofaccounts.items.push(account)
|
||||||
looperRefs.value.forEach((looper) => {
|
}
|
||||||
if (looper.props.period == period) {
|
else twofaccounts.items[index].otp = account.otp
|
||||||
nextTick().then(() => {
|
generatedAt = account.otp.generated_at
|
||||||
looper.startLoop(account.otp.generated_at)
|
})
|
||||||
})
|
|
||||||
}
|
// Loopers restart at new timestamp
|
||||||
})
|
looperRefs.value.forEach((looper) => {
|
||||||
|
if (looper.props.period == period || period == undefined) {
|
||||||
|
nextTick().then(() => {
|
||||||
|
looper.startLoop(generatedAt)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isRenewingOTPs.value = false
|
isRenewingOTPs.value = false
|
||||||
renewedOTPs.value = null
|
renewedPeriod.value = null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,6 +353,20 @@
|
|||||||
@please-close-me="showOtpInModal = false">
|
@please-close-me="showOtpInModal = false">
|
||||||
</OtpDisplay>
|
</OtpDisplay>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<!-- totp loopers -->
|
||||||
|
<span v-if="!user.preferences.getOtpOnRequest">
|
||||||
|
<TotpLooper
|
||||||
|
v-for="period in twofaccounts.periods"
|
||||||
|
:key="period.period"
|
||||||
|
:autostart="false"
|
||||||
|
:period="period.period"
|
||||||
|
:generated_at="period.generated_at"
|
||||||
|
v-on:loop-ended="updateTotps(period.period)"
|
||||||
|
v-on:loop-started="turnDotsOn(period.period, $event)"
|
||||||
|
v-on:stepped-up="turnDotsOn(period.period, $event)"
|
||||||
|
ref="looperRefs"
|
||||||
|
></TotpLooper>
|
||||||
|
</span>
|
||||||
<!-- show accounts list -->
|
<!-- show accounts list -->
|
||||||
<div class="container" v-if="showAccounts" :class="bus.inManagementMode ? 'is-edit-mode' : ''">
|
<div class="container" v-if="showAccounts" :class="bus.inManagementMode ? 'is-edit-mode' : ''">
|
||||||
<!-- accounts -->
|
<!-- accounts -->
|
||||||
@ -366,13 +394,17 @@
|
|||||||
<transition name="popLater">
|
<transition name="popLater">
|
||||||
<div v-show="user.preferences.getOtpOnRequest == false && !bus.inManagementMode" class="has-text-right">
|
<div v-show="user.preferences.getOtpOnRequest == false && !bus.inManagementMode" class="has-text-right">
|
||||||
<span v-if="account.otp != undefined">
|
<span v-if="account.otp != undefined">
|
||||||
<span v-if="isRenewingOTPs == true && renewedOTPs == account.period" class="has-nowrap has-text-grey has-text-centered is-size-5">
|
<span v-if="isRenewingOTPs == true && (renewedPeriod == -1 || renewedPeriod == account.period)" class="has-nowrap has-text-grey has-text-centered is-size-5">
|
||||||
<FontAwesomeIcon :icon="['fas', 'circle-notch']" spin />
|
<FontAwesomeIcon :icon="['fas', 'circle-notch']" spin />
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="always-on-otp is-clickable has-nowrap has-text-grey is-size-5 ml-4" @click="copyToClipboard(account.otp.password)" @keyup.enter="copyToClipboard(account.otp.password)" :title="$t('commons.copy_to_clipboard')">
|
<span v-else class="always-on-otp is-clickable has-nowrap has-text-grey is-size-5 ml-4" @click="copyToClipboard(account.otp.password)" @keyup.enter="copyToClipboard(account.otp.password)" :title="$t('commons.copy_to_clipboard')">
|
||||||
{{ useDisplayablePassword(account.otp.password) }}
|
{{ useDisplayablePassword(account.otp.password) }}
|
||||||
</span>
|
</span>
|
||||||
<Dots v-if="account.otp_type.includes('totp')" @hook:mounted="turnDotsOnFromCache(account.period)" :class="'condensed'" ref="dotsRefs" :period="account.period" />
|
<Dots
|
||||||
|
v-if="account.otp_type.includes('totp')"
|
||||||
|
:class="'condensed'"
|
||||||
|
ref="dotsRefs"
|
||||||
|
:period="account.period" />
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<!-- get hotp button -->
|
<!-- get hotp button -->
|
||||||
@ -415,18 +447,5 @@
|
|||||||
</ActionButtons>
|
</ActionButtons>
|
||||||
</VueFooter>
|
</VueFooter>
|
||||||
</div>
|
</div>
|
||||||
<!-- totp loopers -->
|
|
||||||
<span v-if="!user.preferences.getOtpOnRequest">
|
|
||||||
<TotpLooper
|
|
||||||
v-for="period in twofaccounts.periods"
|
|
||||||
:key="period.period"
|
|
||||||
:period="period.period"
|
|
||||||
:generated_at="period.generated_at"
|
|
||||||
v-on:loop-ended="updateTotps(period.period)"
|
|
||||||
v-on:loop-started="setCurrentStep(period.period, $event)"
|
|
||||||
v-on:stepped-up="setCurrentStep(period.period, $event)"
|
|
||||||
ref="looperRefs"
|
|
||||||
></TotpLooper>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user