mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-02-23 05:41:05 +01:00
Set up Always On OTPs on the main view
This commit is contained in:
parent
db295f97e9
commit
4dbbae24dc
@ -8,6 +8,10 @@
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
period: { // Used only to identify the dots component in Accounts.vue
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const activeDot = ref(0)
|
const activeDot = ref(0)
|
||||||
@ -37,7 +41,8 @@
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
turnOn,
|
turnOn,
|
||||||
turnOff
|
turnOff,
|
||||||
|
props
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -58,9 +58,9 @@
|
|||||||
/**
|
/**
|
||||||
* Starts looping
|
* Starts looping
|
||||||
*/
|
*/
|
||||||
const startLoop = () => {
|
const startLoop = (generated_at = null) => {
|
||||||
clearLooper()
|
clearLooper()
|
||||||
generatedAt.value = props.generated_at
|
generatedAt.value = generated_at != null ? generated_at : props.generated_at
|
||||||
|
|
||||||
emit('loop-started', initialStepIndex.value)
|
emit('loop-started', initialStepIndex.value)
|
||||||
|
|
||||||
@ -110,7 +110,8 @@
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
startLoop,
|
startLoop,
|
||||||
clearLooper
|
clearLooper,
|
||||||
|
props
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -7,6 +7,10 @@ export default {
|
|||||||
return apiClient.get('/twofaccounts' + (withOtp ? '?withOtp=1' : ''))
|
return apiClient.get('/twofaccounts' + (withOtp ? '?withOtp=1' : ''))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getByIds(ids, withOtp = false) {
|
||||||
|
return apiClient.get('/twofaccounts?ids=' + ids + (withOtp ? '&withOtp=1' : ''))
|
||||||
|
},
|
||||||
|
|
||||||
get(id, config = {}) {
|
get(id, config = {}) {
|
||||||
return apiClient.get('/twofaccounts/' + id, { ...config })
|
return apiClient.get('/twofaccounts/' + id, { ...config })
|
||||||
},
|
},
|
||||||
|
24
resources/js_vue3/stores/twofaccounts.js
vendored
24
resources/js_vue3/stores/twofaccounts.js
vendored
@ -34,6 +34,19 @@ export const useTwofaccounts = defineStore({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists unique periods used by twofaccounts in the collection
|
||||||
|
* ex: The items collection has 3 accounts with a period of 30s and 5 accounts with a period of 40s
|
||||||
|
* => The method will return [30, 40]
|
||||||
|
*/
|
||||||
|
periods(state) {
|
||||||
|
return state.items.filter(acc => acc.otp_type == 'totp').map(function(item) {
|
||||||
|
return { period: item.period, generated_at: item.otp?.generated_at }
|
||||||
|
}).filter((value, index, self) => index === self.findIndex((t) => (
|
||||||
|
t.period === value.period
|
||||||
|
))).sort()
|
||||||
|
},
|
||||||
|
|
||||||
orderedIds(state) {
|
orderedIds(state) {
|
||||||
return state.items.map(a => a.id)
|
return state.items.map(a => a.id)
|
||||||
},
|
},
|
||||||
@ -141,11 +154,20 @@ export const useTwofaccounts = defineStore({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*Sorts accounts descending
|
* Sorts accounts descending
|
||||||
*/
|
*/
|
||||||
sortDesc() {
|
sortDesc() {
|
||||||
this.items.sort((a, b) => a.service < b.service ? 1 : -1)
|
this.items.sort((a, b) => a.service < b.service ? 1 : -1)
|
||||||
this.saveOrder()
|
this.saveOrder()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the IDs of all accounts that match the given period
|
||||||
|
* @param {*} period
|
||||||
|
* @returns {Array<Number>} IDs of matching accounts
|
||||||
|
*/
|
||||||
|
accountIdsWithPeriod(period) {
|
||||||
|
return this.items.filter(a => a.period == period).map(item => item.id)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import twofaccountService from '@/services/twofaccountService'
|
import twofaccountService from '@/services/twofaccountService'
|
||||||
import groupService from '@/services/groupService'
|
|
||||||
import Spinner from '@/components/Spinner.vue'
|
import Spinner from '@/components/Spinner.vue'
|
||||||
import TotpLooper from '@/components/TotpLooper.vue'
|
import TotpLooper from '@/components/TotpLooper.vue'
|
||||||
import GroupSwitch from '@/components/GroupSwitch.vue'
|
import GroupSwitch from '@/components/GroupSwitch.vue'
|
||||||
@ -9,6 +9,7 @@
|
|||||||
import Toolbar from '@/components/Toolbar.vue'
|
import Toolbar from '@/components/Toolbar.vue'
|
||||||
import OtpDisplay from '@/components/OtpDisplay.vue'
|
import OtpDisplay from '@/components/OtpDisplay.vue'
|
||||||
import ActionButtons from '@/components/ActionButtons.vue'
|
import ActionButtons from '@/components/ActionButtons.vue'
|
||||||
|
import Dots from '@/components/Dots.vue'
|
||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useNotifyStore } from '@/stores/notify'
|
import { useNotifyStore } from '@/stores/notify'
|
||||||
@ -33,8 +34,12 @@
|
|||||||
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 otpDisplay = ref(null)
|
const otpDisplay = ref(null)
|
||||||
|
const looperRefs = ref([])
|
||||||
|
const dotsRefs = ref([])
|
||||||
|
|
||||||
watch(showOtpInModal, (val) => {
|
watch(showOtpInModal, (val) => {
|
||||||
if (val == false) {
|
if (val == false) {
|
||||||
@ -158,6 +163,60 @@
|
|||||||
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
|
||||||
|
*/
|
||||||
|
function turnDotsOn(period, stepIndex) {
|
||||||
|
dotsRefs.value.forEach((dots) => {
|
||||||
|
if (dots.props.period == period) {
|
||||||
|
dots.turnOn(stepIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates "Always On" OTPs for all TOTP accounts with the given period and restarts loopers
|
||||||
|
*/
|
||||||
|
async function updateTotps(period) {
|
||||||
|
isRenewingOTPs.value = true
|
||||||
|
|
||||||
|
twofaccountService.getByIds(twofaccounts.accountIdsWithPeriod(period).join(','), true).then(response => {
|
||||||
|
response.data.forEach((account) => {
|
||||||
|
const index = twofaccounts.items.findIndex(acc => acc.id === account.id)
|
||||||
|
twofaccounts.items[index].otp = account.otp
|
||||||
|
|
||||||
|
looperRefs.value.forEach((looper) => {
|
||||||
|
if (looper.props.period == period) {
|
||||||
|
nextTick().then(() => {
|
||||||
|
looper.startLoop(account.otp.generated_at)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isRenewingOTPs.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -260,7 +319,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</UseColorMode>
|
</UseColorMode>
|
||||||
</span>
|
</span>
|
||||||
<!-- <dots v-if="account.otp_type.includes('totp')" @hook:mounted="turnDotsOnFromCache(account.period)" :class="'condensed'" :ref="'dots_' + account.period"></dots> -->
|
<Dots v-if="account.otp_type.includes('totp')" @hook:mounted="turnDotsOnFromCache(account.period)" :class="'condensed'" ref="dotsRefs" :period="account.period" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="fadeInOut">
|
<transition name="fadeInOut">
|
||||||
@ -295,153 +354,21 @@
|
|||||||
</VueFooter>
|
</VueFooter>
|
||||||
</div>
|
</div>
|
||||||
<!-- totp loopers -->
|
<!-- totp loopers -->
|
||||||
<!-- <span v-if="!user.preferences.getOtpOnRequest">
|
<span v-if="!user.preferences.getOtpOnRequest">
|
||||||
<TotpLooper
|
<TotpLooper
|
||||||
v-for="period in periods"
|
v-for="period in twofaccounts.periods"
|
||||||
:key="period.period"
|
:key="period.period"
|
||||||
:period="period.period"
|
:period="period.period"
|
||||||
:generated_at="period.generated_at"
|
:generated_at="period.generated_at"
|
||||||
v-on:loop-ended="updateTotps(period.period)"
|
v-on:loop-ended="updateTotps(period.period)"
|
||||||
v-on:loop-started="setCurrentStep(period.period, $event)"
|
v-on:loop-started="setCurrentStep(period.period, $event)"
|
||||||
v-on:stepped-up="setCurrentStep(period.period, $event)"
|
v-on:stepped-up="setCurrentStep(period.period, $event)"
|
||||||
ref="loopers"
|
ref="looperRefs"
|
||||||
></TotpLooper>
|
></TotpLooper>
|
||||||
</span> -->
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accounts view
|
|
||||||
*
|
|
||||||
* route: '/account' (alias: '/')
|
|
||||||
*
|
|
||||||
* The main view of 2FAuth that list all existing account recorded in DB.
|
|
||||||
* Available feature in this view :
|
|
||||||
* - {{OTP}} generation
|
|
||||||
* - Account fetching :
|
|
||||||
* ~ Search
|
|
||||||
* ~ Filtering (by group)
|
|
||||||
* - Accounts management :
|
|
||||||
* ~ Sorting
|
|
||||||
* ~ QR code recovering
|
|
||||||
* ~ Mass association to group
|
|
||||||
* ~ Mass account deletion
|
|
||||||
* ~ Access to account editing
|
|
||||||
*
|
|
||||||
* Behavior :
|
|
||||||
* - The view has 2 modes (toggle is done with the 'manage' button) :
|
|
||||||
* ~ The View mode (the default one)
|
|
||||||
* ~ The Edit mode
|
|
||||||
* - User are automatically pushed to the start view if there is no account to list.
|
|
||||||
* - The view is affected by :
|
|
||||||
* ~ 'userPreferences.showAccountsIcons' toggle the icon visibility
|
|
||||||
* ~ 'userPreferences.displayMode' change the account appearance
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// export default {
|
|
||||||
// data(){
|
|
||||||
// return {
|
|
||||||
// stepIndexes: {},
|
|
||||||
// isRenewingOTPs: false
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
|
|
||||||
// computed: {
|
|
||||||
/**
|
|
||||||
* Returns an array of all totp periods present in the twofaccounts list
|
|
||||||
*/
|
|
||||||
// periods() {
|
|
||||||
// return !user.preferences.getOtpOnRequest ?
|
|
||||||
// this.accounts.filter(acc => acc.otp_type == 'totp').map(function(item) {
|
|
||||||
// return {period: item.period, generated_at: item.otp.generated_at}
|
|
||||||
// // return item.period
|
|
||||||
// }).filter((value, index, self) => index === self.findIndex((t) => (
|
|
||||||
// t.period === value.period
|
|
||||||
// ))).sort()
|
|
||||||
// : null
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
|
|
||||||
// props: ['toRefresh'],
|
|
||||||
|
|
||||||
// methods: {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// setCurrentStep(period, stepIndex) {
|
|
||||||
// this.stepIndexes[period] = stepIndex
|
|
||||||
// this.turnDotsOn(period, stepIndex)
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// turnDotsOnFromCache(period, stepIndex) {
|
|
||||||
// if (this.stepIndexes[period] != undefined) {
|
|
||||||
// this.turnDotsOn(period, this.stepIndexes[period])
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
// turnDotsOn(period, stepIndex) {
|
|
||||||
// this.$refs['dots_' + period].forEach((dots) => {
|
|
||||||
// dots.turnOn(stepIndex)
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch all accounts set with the given period to get fresh OTPs
|
|
||||||
*/
|
|
||||||
// async updateTotps(period) {
|
|
||||||
// this.isRenewingOTPs = true
|
|
||||||
// this.axios.get('api/v1/twofaccounts?withOtp=1&ids=' + this.accountIdsWithPeriod(period).join(',')).then(response => {
|
|
||||||
// response.data.forEach((account) => {
|
|
||||||
// const index = this.accounts.findIndex(acc => acc.id === account.id)
|
|
||||||
// this.accounts[index].otp = account.otp
|
|
||||||
|
|
||||||
// this.$refs.loopers.forEach((looper) => {
|
|
||||||
// if (looper.period == period) {
|
|
||||||
// looper.generatedAt = account.otp.generated_at
|
|
||||||
// this.$nextTick(() => {
|
|
||||||
// looper.startLoop()
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .finally(() => {
|
|
||||||
// this.isRenewingOTPs = false
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an array of all accounts (ids) set with the given period
|
|
||||||
*/
|
|
||||||
// accountIdsWithPeriod(period) {
|
|
||||||
// return this.accounts.filter(a => a.period == period).map(item => item.id)
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a fresh OTP for the provided account
|
|
||||||
*/
|
|
||||||
// getOTP(accountId) {
|
|
||||||
// this.axios.get('api/v1/twofaccounts/' + accountId + '/otp').then(response => {
|
|
||||||
// this.$notify({ type: 'is-success', text: this.$t('commons.copied_to_clipboard')+ ' '+response.data })
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.ghost {
|
.ghost {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
Loading…
Reference in New Issue
Block a user