Update Edit form to match with the new TwoFAccount model

This commit is contained in:
Bubka 2020-11-16 14:45:24 +01:00
parent 018a13c25d
commit 7bdd286fb2
3 changed files with 145 additions and 67 deletions

View File

@ -40,7 +40,7 @@ class TwoFAccountController extends Controller
'account' => 'required_without:uri|nullable|string|regex:/^[^:]+$/i',
'icon' => 'nullable|string',
'uri' => 'nullable|string|regex:/^otpauth:\/\/[h,t]otp\//i',
'otpType' => 'required_without:uri|in:totp,hotp,TOTP,HOTP',
'otpType' => 'required_without:uri|in:totp,hotp',
'secret' => 'required_without:uri|string',
'digits' => 'nullable|integer|between:6,10',
'algorithm' => 'nullable|in:sha1,sha256,sha512,md5',
@ -176,10 +176,19 @@ class TwoFAccountController extends Controller
{
$this->validate($request, [
'service' => 'required',
'service' => 'required|string',
'account' => 'required_without:uri|nullable|string|regex:/^[^:]+$/i',
'icon' => 'nullable|string',
'uri' => 'nullable|string|regex:/^otpauth:\/\/[h,t]otp\//i',
'otpType' => 'required_without:uri|in:totp,hotp',
'secret' => 'required_without:uri|string',
'digits' => 'nullable|integer|between:6,10',
'algorithm' => 'nullable|in:sha1,sha256,sha512,md5',
'totpPeriod' => 'required_if:otpType,totp|nullable|integer|min:1',
'hotpCounter' => 'required_if:otpType,hotp|nullable|integer|min:0',
'imageLink' => 'nullable|url',
]);
// Here we catch a possible missing model exception in order to
// delete orphan submited icon
try {
@ -194,33 +203,9 @@ class TwoFAccountController extends Controller
throw $e;
}
if( $twofaccount->otpType === 'hotp' ) {
// HOTP can be desynchronized from the verification
// server so we let the user the possibility to force
// the counter.
$this->validate($request, [
'counter' => 'required|integer',
]);
// we set an OTP object to get the its current counter
// and we update it if a new one has been submited
$otp = OTP::get($twofaccount->uri);
if( $otp->getCounter() !== $request->counter ) {
$otp->setParameter( 'counter', $request->counter );
$twofaccount->uri = $otp->getProvisioningUri();
}
}
$twofaccount->update([
'service' => $request->service,
'account' => $request->account,
'icon' => $request->icon,
'uri' => $twofaccount->uri,
]);
$twofaccount->populate($request->all());
$twofaccount->save();
return response()->json($twofaccount, 200);

View File

@ -1,33 +1,11 @@
<template>
<form-wrapper :title="$t('twofaccounts.forms.edit_account')">
<form @submit.prevent="updateAccount" @keydown="form.onKeydown($event)">
<!-- service -->
<form-field :form="form" fieldName="service" inputType="text" :label="$t('twofaccounts.service')" :placeholder="$t('twofaccounts.forms.service.placeholder')" autofocus />
<!-- account -->
<form-field :form="form" fieldName="account" inputType="text" :label="$t('twofaccounts.account')" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
<div v-if="form.type === 'hotp'">
<div class="field" style="margin-bottom: 0.5rem;">
<label class="label">{{ $t('twofaccounts.forms.hotp_counter') }}</label>
</div>
<div class="field has-addons">
<div class="control is-expanded">
<input class="input" type="text" placeholder="" v-model="form.counter" :disabled="counterIsLocked" />
</div>
<div class="control" v-if="counterIsLocked">
<a class="button is-dark field-lock" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon">
<font-awesome-icon :icon="['fas', 'lock']" />
</span>
</a>
</div>
<div class="control" v-else>
<a class="button is-dark field-unlock" @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger">
<font-awesome-icon :icon="['fas', 'lock-open']" />
</span>
</a>
</div>
</div>
<field-error :form="form" field="uri" class="help-for-file" />
</div>
<!-- icon -->
<div class="field">
<label class="label">{{ $t('twofaccounts.icon') }}</label>
<div class="file is-dark">
@ -47,50 +25,152 @@
</div>
</div>
<field-error :form="form" field="icon" class="help-for-file" />
<div class="field is-grouped">
<div class="control">
<v-button :isLoading="form.isBusy" >{{ $t('commons.save') }}</v-button>
<!-- otp type -->
<form-toggle class="has-uppercased-button" :isDisabled="true" :form="form" :choices="otpTypes" fieldName="otpType" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
<div v-if="form.otpType">
<!-- secret -->
<label class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
<div class="field has-addons">
<p class="control">
<span class="select">
<select v-model="form.secretIsBase32Encoded">
<option v-for="format in secretFormats" :value="format.value">{{ format.text }}</option>
</select>
</span>
</p>
<p class="control is-expanded">
<input class="input" type="text" v-model="form.secret">
</p>
</div>
<div class="control">
<button type="button" class="button is-text" @click="cancelCreation">{{ $t('commons.cancel') }}</button>
<div class="field">
<field-error :form="form" field="secret" class="help-for-file" />
<p class="help" v-html="$t('twofaccounts.forms.secret.help')"></p>
</div>
<h2 class="title is-4 mt-5 mb-2">{{ $t('commons.options') }}</h2>
<p class="help mb-4">
{{ $t('twofaccounts.forms.options_help') }}
</p>
<!-- digits -->
<form-toggle :form="form" :choices="digitsChoices" fieldName="digits" :label="$t('twofaccounts.forms.digits.label')" :help="$t('twofaccounts.forms.digits.help')" />
<!-- algorithm -->
<form-toggle :form="form" :choices="algorithms" fieldName="algorithm" :label="$t('twofaccounts.forms.algorithm.label')" :help="$t('twofaccounts.forms.algorithm.help')" />
<!-- TOTP period -->
<form-field v-if="form.otpType === 'totp'" :form="form" fieldName="totpPeriod" inputType="text" :label="$t('twofaccounts.forms.totpPeriod.label')" :placeholder="$t('twofaccounts.forms.totpPeriod.placeholder')" :help="$t('twofaccounts.forms.totpPeriod.help')" />
<!-- HOTP counter -->
<div v-if="form.otpType === 'hotp'">
<div class="field" style="margin-bottom: 0.5rem;">
<label class="label">{{ $t('twofaccounts.forms.hotpCounter.label') }}</label>
</div>
<div class="field has-addons">
<div class="control is-expanded">
<input class="input" type="text" placeholder="" v-model="form.hotpCounter" :disabled="hotpCounterIsLocked" />
</div>
<div class="control" v-if="hotpCounterIsLocked">
<a class="button is-dark field-lock" @click="hotpCounterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon">
<font-awesome-icon :icon="['fas', 'lock']" />
</span>
</a>
</div>
<div class="control" v-else>
<a class="button is-dark field-unlock" @click="hotpCounterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger">
<font-awesome-icon :icon="['fas', 'lock-open']" />
</span>
</a>
</div>
</div>
<field-error :form="form" field="uri" class="help-for-file" />
<p class="help" v-html="$t('twofaccounts.forms.hotpCounter.help_lock')"></p>
</div>
<!-- image link -->
<form-field :form="form" fieldName="imageLink" inputType="text" :label="$t('twofaccounts.forms.image_link.label')" :placeholder="$t('twofaccounts.forms.image_link.placeholder')" :help="$t('twofaccounts.forms.image_link.help')" />
</div>
<!-- form buttons -->
<vue-footer :showButtons="true">
<p class="control">
<v-button :isLoading="form.isBusy" class="is-rounded" >{{ $t('commons.save') }}</v-button>
</p>
<p class="control" v-if="form.otpType && form.secret">
<button type="button" class="button is-success is-rounded" @click="previewAccount">{{ $t('twofaccounts.forms.test') }}</button>
</p>
<p class="control">
<button type="button" class="button is-text is-rounded" @click="cancelCreation">{{ $t('commons.cancel') }}</button>
</p>
</vue-footer>
</form>
<!-- modal -->
<modal v-model="ShowTwofaccountInModal">
<token-displayer ref="AdvancedFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
</token-displayer>
</modal>
</form-wrapper>
</template>
<script>
import Modal from '../../components/Modal'
import Form from './../../components/Form'
import TokenDisplayer from '../../components/TokenDisplayer'
export default {
data() {
return {
counterIsLocked: true,
ShowTwofaccountInModal : false,
hotpCounterIsLocked: true,
twofaccountExists: false,
tempIcon: '',
form: new Form({
service: '',
account: '',
otpType: '',
uri: '',
icon: '',
qrcode: null,
type: '',
counter: null
})
secret: '',
secretIsBase32Encoded: null,
algorithm: '',
digits: null,
hotpCounter: null,
totpPeriod: null,
imageLink: '',
}),
otpTypes: ['totp', 'hotp'],
digitsChoices: [6,7,8,9,10],
secretFormats: [
{ text: this.$t('twofaccounts.forms.plain_text'), value: 0 },
{ text: 'Base32', value: 1 }
],
algorithms: ['sha1', 'sha256', 'sha512', 'md5'],
}
},
mounted: function () {
// stop TOTP generation on modal close
this.$on('modalClose', function() {
this.$refs.AdvancedFormTokenDisplayer.stopLoop()
});
},
created: function() {
this.getAccount();
},
components: {
Modal,
TokenDisplayer,
},
methods: {
async getAccount () {
const { data } = await this.axios.get('/api/twofaccounts/' + this.$route.params.twofaccountId)
const { data } = await this.axios.get('/api/twofaccounts/' + this.$route.params.twofaccountId + '/withSensitive')
this.form.fill(data)
this.form.secretIsBase32Encoded = 1
this.form.uri = '' // we don't want the uri because the user can change any otp parameter in the form
this.twofaccountExists = true
// set account icon as temp icon
@ -119,6 +199,10 @@
},
previewAccount() {
this.$refs.AdvancedFormTokenDisplayer.getToken()
},
cancelCreation: function() {
// clean new temp icon
this.deleteIcon()
@ -149,6 +233,15 @@
this.tempIcon = ''
},
incrementHotp(payload) {
// The quick form or the preview feature has incremented the HOTP counter so we get the new value from
// the component.
// 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)
this.form.hotpCounter = payload.nextHotpCounter
this.form.uri = payload.nextUri
},
},
}

View File

@ -32,7 +32,6 @@ return [
'new_account' => 'New account',
'edit_account' => 'Edit account',
'otp_uri' => 'OTP Uri',
'hotp_counter' => 'HOTP Counter',
'scan_qrcode' => 'Scan a qrcode',
'prefill_using_qrcode' => 'Prefill using a QR Code',
'use_qrcode' => [
@ -74,7 +73,8 @@ return [
'hotpCounter' => [
'label' => 'Counter',
'placeholder' => 'Default is 0',
'help' => 'The initial counter value'
'help' => 'The initial counter value',
'help_lock' => 'It is risky to edit the counter as you can desynchronize the account with the verification server of the service. Use the lock icon to enable modification, but only if you know for you are doing'
],
'image_link' => [
'label' => 'Image',