mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-05-31 15:25:39 +02:00
Update Edit form to match with the new TwoFAccount model
This commit is contained in:
parent
018a13c25d
commit
7bdd286fb2
@ -40,7 +40,7 @@ class TwoFAccountController extends Controller
|
|||||||
'account' => 'required_without:uri|nullable|string|regex:/^[^:]+$/i',
|
'account' => 'required_without:uri|nullable|string|regex:/^[^:]+$/i',
|
||||||
'icon' => 'nullable|string',
|
'icon' => 'nullable|string',
|
||||||
'uri' => 'nullable|string|regex:/^otpauth:\/\/[h,t]otp\//i',
|
'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',
|
'secret' => 'required_without:uri|string',
|
||||||
'digits' => 'nullable|integer|between:6,10',
|
'digits' => 'nullable|integer|between:6,10',
|
||||||
'algorithm' => 'nullable|in:sha1,sha256,sha512,md5',
|
'algorithm' => 'nullable|in:sha1,sha256,sha512,md5',
|
||||||
@ -176,10 +176,19 @@ class TwoFAccountController extends Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
$this->validate($request, [
|
$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
|
// Here we catch a possible missing model exception in order to
|
||||||
// delete orphan submited icon
|
// delete orphan submited icon
|
||||||
try {
|
try {
|
||||||
@ -194,33 +203,9 @@ class TwoFAccountController extends Controller
|
|||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( $twofaccount->otpType === 'hotp' ) {
|
|
||||||
|
|
||||||
// HOTP can be desynchronized from the verification
|
$twofaccount->populate($request->all());
|
||||||
// server so we let the user the possibility to force
|
$twofaccount->save();
|
||||||
// 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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response()->json($twofaccount, 200);
|
return response()->json($twofaccount, 200);
|
||||||
|
|
||||||
|
@ -1,33 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<form-wrapper :title="$t('twofaccounts.forms.edit_account')">
|
<form-wrapper :title="$t('twofaccounts.forms.edit_account')">
|
||||||
<form @submit.prevent="updateAccount" @keydown="form.onKeydown($event)">
|
<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 />
|
<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')" />
|
<form-field :form="form" fieldName="account" inputType="text" :label="$t('twofaccounts.account')" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
||||||
<div v-if="form.type === 'hotp'">
|
<!-- icon -->
|
||||||
<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>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('twofaccounts.icon') }}</label>
|
<label class="label">{{ $t('twofaccounts.icon') }}</label>
|
||||||
<div class="file is-dark">
|
<div class="file is-dark">
|
||||||
@ -47,50 +25,152 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<field-error :form="form" field="icon" class="help-for-file" />
|
<field-error :form="form" field="icon" class="help-for-file" />
|
||||||
<div class="field is-grouped">
|
<!-- otp type -->
|
||||||
<div class="control">
|
<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" />
|
||||||
<v-button :isLoading="form.isBusy" >{{ $t('commons.save') }}</v-button>
|
<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>
|
||||||
<div class="control">
|
<div class="field">
|
||||||
<button type="button" class="button is-text" @click="cancelCreation">{{ $t('commons.cancel') }}</button>
|
<field-error :form="form" field="secret" class="help-for-file" />
|
||||||
|
<p class="help" v-html="$t('twofaccounts.forms.secret.help')"></p>
|
||||||
</div>
|
</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>
|
</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>
|
</form>
|
||||||
|
<!-- modal -->
|
||||||
|
<modal v-model="ShowTwofaccountInModal">
|
||||||
|
<token-displayer ref="AdvancedFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||||
|
</token-displayer>
|
||||||
|
</modal>
|
||||||
</form-wrapper>
|
</form-wrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import Modal from '../../components/Modal'
|
||||||
import Form from './../../components/Form'
|
import Form from './../../components/Form'
|
||||||
|
import TokenDisplayer from '../../components/TokenDisplayer'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
counterIsLocked: true,
|
ShowTwofaccountInModal : false,
|
||||||
|
hotpCounterIsLocked: true,
|
||||||
twofaccountExists: false,
|
twofaccountExists: false,
|
||||||
tempIcon: '',
|
tempIcon: '',
|
||||||
form: new Form({
|
form: new Form({
|
||||||
service: '',
|
service: '',
|
||||||
account: '',
|
account: '',
|
||||||
|
otpType: '',
|
||||||
uri: '',
|
uri: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
qrcode: null,
|
secret: '',
|
||||||
type: '',
|
secretIsBase32Encoded: null,
|
||||||
counter: 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() {
|
created: function() {
|
||||||
this.getAccount();
|
this.getAccount();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
TokenDisplayer,
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async getAccount () {
|
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.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
|
this.twofaccountExists = true
|
||||||
|
|
||||||
// set account icon as temp icon
|
// set account icon as temp icon
|
||||||
@ -119,6 +199,10 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
previewAccount() {
|
||||||
|
this.$refs.AdvancedFormTokenDisplayer.getToken()
|
||||||
|
},
|
||||||
|
|
||||||
cancelCreation: function() {
|
cancelCreation: function() {
|
||||||
// clean new temp icon
|
// clean new temp icon
|
||||||
this.deleteIcon()
|
this.deleteIcon()
|
||||||
@ -149,6 +233,15 @@
|
|||||||
this.tempIcon = ''
|
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
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ return [
|
|||||||
'new_account' => 'New account',
|
'new_account' => 'New account',
|
||||||
'edit_account' => 'Edit account',
|
'edit_account' => 'Edit account',
|
||||||
'otp_uri' => 'OTP Uri',
|
'otp_uri' => 'OTP Uri',
|
||||||
'hotp_counter' => 'HOTP Counter',
|
|
||||||
'scan_qrcode' => 'Scan a qrcode',
|
'scan_qrcode' => 'Scan a qrcode',
|
||||||
'prefill_using_qrcode' => 'Prefill using a QR Code',
|
'prefill_using_qrcode' => 'Prefill using a QR Code',
|
||||||
'use_qrcode' => [
|
'use_qrcode' => [
|
||||||
@ -74,7 +73,8 @@ return [
|
|||||||
'hotpCounter' => [
|
'hotpCounter' => [
|
||||||
'label' => 'Counter',
|
'label' => 'Counter',
|
||||||
'placeholder' => 'Default is 0',
|
'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' => [
|
'image_link' => [
|
||||||
'label' => 'Image',
|
'label' => 'Image',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user