mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-03-26 22:16:06 +01:00
Set up the Edit twofaccount view
This commit is contained in:
parent
fb49e55e34
commit
beb5ee027a
6
resources/js_vue3/assets/app.scss
vendored
6
resources/js_vue3/assets/app.scss
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
97
resources/js_vue3/components/formElements/FormLockField.vue
Normal file
97
resources/js_vue3/components/formElements/FormLockField.vue
Normal 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>
|
50
resources/js_vue3/router/index.js
vendored
50
resources/js_vue3/router/index.js
vendored
@ -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 },
|
||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user