Set up the Edit twofaccount view

This commit is contained in:
Bubka 2023-10-30 12:53:41 +01:00
parent fb49e55e34
commit beb5ee027a
4 changed files with 187 additions and 47 deletions

View File

@ -871,15 +871,15 @@ button.button.tag.is-white,
background-color: $success-dark;
}
.is-mid-width-field input {
.is-mid-width-field {
width: 50% !important;
}
.is-half-width-field input {
.is-half-width-field {
width: 50% !important;
}
.is-third-width-field input {
.is-third-width-field {
width: 33% !important;
}

View File

@ -0,0 +1,97 @@
<script setup>
import { useIdGenerator } from '@/composables/helpers'
import { UseColorMode } from '@vueuse/components'
defineOptions({
inheritAttrs: false
})
const { inputId } = useIdGenerator(props.inputType, props.fieldName)
const props = defineProps({
modelValue: [String, Number, Boolean],
isEditMode: {
type: Boolean,
default: false
},
label: {
type: String,
default: ''
},
fieldName: {
type: String,
default: '',
required: true
},
fieldError: [String],
inputType: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
help: {
type: String,
default: ''
},
hasOffset: {
type: Boolean,
default: false
},
isDisabled: {
type: Boolean,
default: false
},
isExpanded: {
type: Boolean,
default: true
},
maxLength: {
type: Number,
default: null
}
})
const fieldIsLocked = ref(props.isDisabled || props.isEditMode)
</script>
<template>
<div class="field" style="margin-bottom: 0.5rem;">
<label :for="inputId" class="label" v-html="$t(label)" />
</div>
<div class="field has-addons" :class="{ 'pt-3' : hasOffset }">
<div class="control" :class="{ 'is-expanded': isExpanded }">
<input
:disabled="fieldIsLocked"
:id="inputId"
:type="inputType"
class="input"
:value="modelValue"
:placeholder="placeholder"
v-bind="$attrs"
v-on:change="$emit('update:modelValue', $event.target.value)"
:maxlength="this.maxLength"
/>
</div>
<UseColorMode v-slot="{ mode }" v-if="isEditMode">
<div class="control" v-if="fieldIsLocked">
<button type="button" class="button field-lock" :class="{'is-dark' : mode == 'dark'}" @click.stop="fieldIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon">
<FontAwesomeIcon :icon="['fas', 'lock']" />
</span>
</button>
</div>
<div class="control" v-else>
<button type="button" class="button field-unlock" :class="{'is-dark' : mode == 'dark'}" @click.stop="fieldIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger">
<FontAwesomeIcon :icon="['fas', 'lock-open']" />
</span>
</button>
</div>
</UseColorMode>
</div>
<FieldError v-if="fieldError != undefined" :error="fieldError" :field="fieldName" />
<p class="help" v-html="$t(help)" v-if="help"></p>
</template>

View File

@ -3,29 +3,29 @@ import middlewarePipeline from "@/router/middlewarePipeline";
import { useUserStore } from '@/stores/user'
import { useTwofaccounts } from '@/stores/twofaccounts'
import Start from '../views/Start.vue'
import Accounts from '../views/Accounts.vue'
import Capture from '../views/twofaccounts/Capture.vue'
import CreateAccount from '../views/twofaccounts/Create.vue'
import EditAccount from '../views/twofaccounts/Edit.vue'
import ImportAccount from '../views/twofaccounts/Import.vue'
import QRcodeAccount from '../views/twofaccounts/QRcode.vue'
import Groups from '../views/groups/Groups.vue'
import CreateUpdateGroup from '../views/groups/CreateUpdate.vue'
import Login from '../views/auth/Login.vue'
import Register from '../views/auth/Register.vue'
// import Autolock from './views/auth/Autolock.vue'
import PasswordRequest from '../views/auth/password/Request.vue'
// import PasswordReset from './views/auth/password/Reset.vue'
import WebauthnLost from '../views/auth/webauthn/Lost.vue'
// import WebauthnRecover from './views/auth/webauthn/Recover.vue'
import SettingsOptions from '../views/settings/Options.vue'
import SettingsAccount from '../views/settings/Account.vue'
import SettingsOAuth from '../views/settings/OAuth.vue'
import SettingsWebAuthn from '../views/settings/WebAuthn.vue'
import EditCredential from '../views/settings/Credentials/Edit.vue'
import Errors from '../views/Error.vue'
import About from '../views/About.vue'
import Start from '../views/Start.vue'
import Accounts from '../views/Accounts.vue'
import Capture from '../views/twofaccounts/Capture.vue'
import CreateUpdateAccount from '../views/twofaccounts/CreateUpdate.vue'
import EditAccount from '../views/twofaccounts/Edit.vue'
import ImportAccount from '../views/twofaccounts/Import.vue'
import QRcodeAccount from '../views/twofaccounts/QRcode.vue'
import Groups from '../views/groups/Groups.vue'
import CreateUpdateGroup from '../views/groups/CreateUpdate.vue'
import Login from '../views/auth/Login.vue'
import Register from '../views/auth/Register.vue'
// import Autolock from './views/auth/Autolock.vue'
import PasswordRequest from '../views/auth/password/Request.vue'
// import PasswordReset from './views/auth/password/Reset.vue'
import WebauthnLost from '../views/auth/webauthn/Lost.vue'
// import WebauthnRecover from './views/auth/webauthn/Recover.vue'
import SettingsOptions from '../views/settings/Options.vue'
import SettingsAccount from '../views/settings/Account.vue'
import SettingsOAuth from '../views/settings/OAuth.vue'
import SettingsWebAuthn from '../views/settings/WebAuthn.vue'
import EditCredential from '../views/settings/Credentials/Edit.vue'
import Errors from '../views/Error.vue'
import About from '../views/About.vue'
import authGuard from './middlewares/authGuard'
import starter from './middlewares/starter'
@ -38,9 +38,9 @@ const router = createRouter({
{ path: '/capture', name: 'capture', component: Capture, meta: { middlewares: [authGuard] } },
{ path: '/accounts', name: 'accounts', component: Accounts, meta: { middlewares: [authGuard, starter] }, alias: '/' },
{ path: '/account/create', name: 'createAccount', component: CreateAccount, meta: { middlewares: [authGuard] } },
{ path: '/account/create', name: 'createAccount', component: CreateUpdateAccount, meta: { middlewares: [authGuard] } },
{ path: '/account/import', name: 'importAccounts', component: ImportAccount, meta: { middlewares: [authGuard] } },
{ path: '/account/:twofaccountId/edit', name: 'editAccount', component: EditAccount, meta: { middlewares: [authGuard] } },
{ path: '/account/:twofaccountId/edit', name: 'editAccount', component: CreateUpdateAccount, meta: { middlewares: [authGuard] }, props: true },
{ path: '/account/:twofaccountId/qrcode', name: 'showQRcode', component: QRcodeAccount, meta: { middlewares: [authGuard] } },
{ path: '/groups', name: 'groups', component: Groups, meta: { middlewares: [authGuard] }, props: true },

View File

@ -1,6 +1,7 @@
<script setup>
import Form from '@/components/formElements/Form'
import OtpDisplay from '@/components/OtpDisplay.vue'
import FormLockField from '@/components/formelements/FormLockField.vue'
import twofaccountService from '@/services/twofaccountService'
import { useUserStore } from '@/stores/user'
import { useBusStore } from '@/stores/bus'
@ -11,6 +12,7 @@
const { copy } = useClipboard({ legacy: true })
const $2fauth = inject('2fauth')
const router = useRouter()
const route = useRoute()
const user = useUserStore()
const bus = useBusStore()
const notify = useNotifyStore()
@ -52,6 +54,7 @@
const showAdvancedForm = ref(false)
const ShowTwofaccountInModal = ref(false)
const fetchingLogo = ref(false)
const secretIsLocked = ref(false)
// $refs
const iconInput = ref(null)
@ -60,9 +63,25 @@
const qrcodeInputLabel = ref(null)
const qrcodeInput = ref(null)
const iconInputLabel = ref(null)
const props = defineProps({
twofaccountId: [Number, String]
})
const isEditMode = computed(() => {
return props.twofaccountId != undefined
})
onMounted(() => {
if( bus.decodedUri ) {
if (route.name == 'editAccount') {
twofaccountService.get(props.twofaccountId).then(response => {
form.fill(response.data)
// set account icon as temp icon
tempIcon.value = form.icon
showAdvancedForm.value = true
})
}
else if( bus.decodedUri ) {
// the Start view provided an uri via the bus store so we parse it and prefill the quick form
uri.value = bus.decodedUri
bus.decodedUri = null
@ -115,6 +134,13 @@
}
)
/**
* Wrapper to call the appropriate function at form submit
*/
function handleSubmit() {
isEditMode.value ? updateAccount() : createAccount()
}
/**
* Submits the form to the backend to store the new account
*/
@ -130,6 +156,29 @@
}
}
/**
* Submits the form to the backend to save the edited account
*/
async function updateAccount() {
// Set new icon and delete old one
if( tempIcon.value !== form.icon ) {
let oldIcon = ''
oldIcon = form.icon
form.icon = tempIcon.value
tempIcon.value = oldIcon
deleteIcon()
}
await form.put('/api/v1/twofaccounts/' + props.twofaccountId)
if( form.errors.any() === false ) {
notify.success({ text: trans('twofaccounts.account_updated') })
router.push({ name: 'accounts' })
}
}
/**
* Shows an OTP generated with the infos filled in the form
* in order to preview or validated the password/the form data
@ -196,6 +245,8 @@
// 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)
form.counter = payload.nextHotpCounter
//form.uri = payload.nextUri
}
/**
@ -315,7 +366,7 @@
<template>
<div>
<!-- Quick form -->
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)" v-if="showQuickForm">
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)" v-if="!isEditMode && showQuickForm">
<div class="container preview has-text-centered">
<div class="columns is-mobile">
<div class="column">
@ -357,10 +408,10 @@
</div>
</form>
<!-- Full form -->
<FormWrapper :title="$t('twofaccounts.forms.new_account')" v-if="showAdvancedForm">
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)">
<FormWrapper :title="$t(isEditMode ? 'twofaccounts.forms.edit_account' : 'twofaccounts.forms.new_account')" v-if="showAdvancedForm">
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
<!-- qcode fileupload -->
<div class="field is-grouped">
<div v-if="!isEditMode" class="field is-grouped">
<div class="control">
<div role="button" tabindex="0" class="file is-black is-small" @keyup.enter="qrcodeInputLabel.click()">
<label class="file-label" :title="$t('twofaccounts.forms.use_qrcode.title')" ref="qrcodeInputLabel">
@ -375,7 +426,7 @@
</div>
</div>
</div>
<FieldError v-if="form.errors.hasAny('qrcode')" :error="form.errors.get('qrcode')" :field="'qrcode'" class="help-for-file" />
<FieldError v-if="!isEditMode && form.errors.hasAny('qrcode')" :error="form.errors.get('qrcode')" :field="'qrcode'" class="help-for-file" />
<!-- service -->
<FormField v-model="form.service" fieldName="service" :fieldError="form.errors.get('email')" :isDisabled="form.otp_type === 'steamtotp'" label="twofaccounts.service" :placeholder="$t('twofaccounts.forms.service.placeholder')" autofocus />
<!-- account -->
@ -420,19 +471,11 @@
<p v-if="user.preferences.getOfficialIcons" class="help" v-html="$t('twofaccounts.forms.i_m_lucky_legend')"></p>
</div>
<!-- otp type -->
<FormToggle v-model="form.otp_type" :choices="otp_types" fieldName="otp_type" :fieldError="form.errors.get('otp_type')" label="twofaccounts.forms.otp_type.label" help="twofaccounts.forms.otp_type.help" :hasOffset="true" />
<FormToggle v-model="form.otp_type" :isDisabled="isEditMode" :choices="otp_types" fieldName="otp_type" :fieldError="form.errors.get('otp_type')" label="twofaccounts.forms.otp_type.label" help="twofaccounts.forms.otp_type.help" :hasOffset="true" />
<div v-if="form.otp_type != ''">
<!-- secret -->
<label :for="useIdGenerator('text','secret')" class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
<div class="field">
<p class="control is-expanded">
<input :id="useIdGenerator('text','secret')" class="input" type="text" v-model="form.secret">
</p>
</div>
<div class="field">
<FieldError v-if="form.errors.hasAny('secret')" :error="form.errors.get('secret')" :field="'secret'" />
<p class="help" v-html="$t('twofaccounts.forms.secret.help')"></p>
</div>
<FormLockField :isEditMode="isEditMode" v-model="form.secret" fieldName="secret" :fieldError="form.errors.get('secret')" label="twofaccounts.forms.secret.label" help="twofaccounts.forms.secret.help" />
<!-- Options -->
<div v-if="form.otp_type !== 'steamtotp'">
<h2 class="title is-4 mt-5 mb-2">{{ $t('commons.options') }}</h2>
<p class="help mb-4">
@ -445,12 +488,12 @@
<!-- TOTP period -->
<FormField v-if="form.otp_type === 'totp'" pattern="[0-9]{1,4}" :class="'is-third-width-field'" v-model="form.period" fieldName="period" :fieldError="form.errors.get('period')" label="twofaccounts.forms.period.label" help="twofaccounts.forms.period.help" :placeholder="$t('twofaccounts.forms.period.placeholder')" />
<!-- HOTP counter -->
<FormField v-if="form.otp_type === 'hotp'" pattern="[0-9]{1,4}" :class="'is-third-width-field'" v-model="form.counter" fieldName="counter" :fieldError="form.errors.get('counter')" label="twofaccounts.forms.counter.label" help="twofaccounts.forms.counter.help" :placeholder="$t('twofaccounts.forms.counter.placeholder')" />
<FormLockField v-if="form.otp_type === 'hotp'" pattern="[0-9]{1,4}" :isEditMode="isEditMode" :isExpanded="false" v-model="form.counter" fieldName="counter" :fieldError="form.errors.get('counter')" label="twofaccounts.forms.counter.label" :placeholder="$t('twofaccounts.forms.counter.placeholder')" :help="isEditMode ? 'twofaccounts.forms.counter.help_lock' : 'twofaccounts.forms.counter.help'" />
</div>
</div>
<VueFooter :showButtons="true">
<p class="control">
<VueButton id="btnCreate" :isLoading="form.isBusy" class="is-rounded" >{{ $t('commons.create') }}</VueButton>
<VueButton :id="isEditMode ? 'btnUpdate' : 'btnCreate'" :isLoading="form.isBusy" class="is-rounded" >{{ isEditMode ? $t('commons.save') : $t('commons.create') }}</VueButton>
</p>
<p class="control" v-if="form.otp_type && form.secret">
<button id="btnPreview" type="button" class="button is-success is-rounded" @click="previewOTP">{{ $t('twofaccounts.forms.test') }}</button>