mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-03-15 07:28:30 +01:00
Update Vue front-end according to the new API definition and paths
This commit is contained in:
parent
83f7370b57
commit
184237697b
51
resources/js/components/Form.js
vendored
51
resources/js/components/Form.js
vendored
@ -1,8 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import Errors from './FormErrors'
|
||||
|
||||
// import { deepCopy } from './util'
|
||||
|
||||
class Form {
|
||||
/**
|
||||
* Create a new form instance.
|
||||
@ -14,7 +12,7 @@ class Form {
|
||||
this.isDisabled = false
|
||||
// this.successful = false
|
||||
this.errors = new Errors()
|
||||
// this.originalData = deepCopy(data)
|
||||
this.originalData = this.deepCopy(data)
|
||||
|
||||
Object.assign(this, data)
|
||||
}
|
||||
@ -30,6 +28,31 @@ class Form {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update original form data.
|
||||
*/
|
||||
setOriginal () {
|
||||
Object.keys(this)
|
||||
.filter(key => !Form.ignore.includes(key))
|
||||
.forEach(key => {
|
||||
this.originalData[key] = this.deepCopy(this[key])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill form data.
|
||||
*
|
||||
* @param {Object} data
|
||||
*/
|
||||
fillWithKeyValueObject (data) {
|
||||
this.keys().forEach(key => {
|
||||
const keyValueObject = data.find(s => s.key === key.toString())
|
||||
if(keyValueObject != undefined) {
|
||||
this[key] = keyValueObject.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form data.
|
||||
*
|
||||
@ -83,7 +106,7 @@ class Form {
|
||||
Object.keys(this)
|
||||
.filter(key => !Form.ignore.includes(key))
|
||||
.forEach(key => {
|
||||
this[key] = deepCopy(this.originalData[key])
|
||||
this[key] = this.deepCopy(this.originalData[key])
|
||||
})
|
||||
}
|
||||
|
||||
@ -265,6 +288,26 @@ class Form {
|
||||
this.errors.clear(event.target.name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy the given object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
*/
|
||||
deepCopy (obj) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj
|
||||
}
|
||||
|
||||
const copy = Array.isArray(obj) ? [] : {}
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
copy[key] = this.deepCopy(obj[key])
|
||||
})
|
||||
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
Form.routes = {}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="field">
|
||||
<input :id="fieldName" type="checkbox" :name="fieldName" class="is-checkradio is-info" v-model="form[fieldName]">
|
||||
<input :id="fieldName" type="checkbox" :name="fieldName" class="is-checkradio is-info" v-model="form[fieldName]" v-on:change="$emit(fieldName, form[fieldName])" >
|
||||
<label :for="fieldName" class="label" v-html="label"></label>
|
||||
<p class="help" v-html="help" v-if="help"></p>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<label class="label" v-html="label"></label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select v-model="form[fieldName]">
|
||||
<select v-model="form[fieldName]" v-on:change="$emit(fieldName, form[fieldName])">
|
||||
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<label class="label" v-html="label"></label>
|
||||
<div class="is-toggle buttons">
|
||||
<label class="button is-dark" :disabled="isDisabled" v-for="choice in choices" :class="{ 'is-link' : form[fieldName] === choice.value }">
|
||||
<input type="radio" class="is-hidden" :checked="form[fieldName] === choice.value" :value="choice.value" v-model="form[fieldName]" :disabled="isDisabled" />
|
||||
<input type="radio" class="is-hidden" :checked="form[fieldName] === choice.value" :value="choice.value" v-model="form[fieldName]" v-on:change="$emit(fieldName, form[fieldName])" :disabled="isDisabled" />
|
||||
<font-awesome-icon :icon="['fas', choice.icon]" v-if="choice.icon" class="mr-3" /> {{ choice.text }}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -5,109 +5,118 @@
|
||||
</figure>
|
||||
<p class="is-size-4 has-text-grey-light has-ellipsis">{{ internal_service }}</p>
|
||||
<p class="is-size-6 has-text-grey has-ellipsis">{{ internal_account }}</p>
|
||||
<p class="is-size-1 has-text-white is-clickable" :title="$t('commons.copy_to_clipboard')" v-clipboard="() => token.replace(/ /g, '')" v-clipboard:success="clipboardSuccessHandler">{{ displayedToken }}</p>
|
||||
<ul class="dots" v-if="internal_otpType === 'totp'">
|
||||
<p class="is-size-1 has-text-white is-clickable" :title="$t('commons.copy_to_clipboard')" v-clipboard="() => password.replace(/ /g, '')" v-clipboard:success="clipboardSuccessHandler">{{ displayedOtp }}</p>
|
||||
<ul class="dots" v-show="internal_otp_type === 'totp'">
|
||||
<li v-for="n in 10"></li>
|
||||
</ul>
|
||||
<ul v-else-if="internal_otpType === 'hotp'">
|
||||
<li>counter: {{ internal_hotpCounter }}</li>
|
||||
<ul v-show="internal_otp_type === 'hotp'">
|
||||
<li>counter: {{ internal_counter }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TokenDisplayer',
|
||||
name: 'OtpDisplayer',
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
token : '',
|
||||
remainingTimeout: null,
|
||||
firstDotToNextOneTimeout: null,
|
||||
dotToDotInterval: null,
|
||||
position: null,
|
||||
totpTimestamp: null,
|
||||
internal_otpType: '',
|
||||
internal_id: null,
|
||||
internal_otp_type: '',
|
||||
internal_account: '',
|
||||
internal_service: '',
|
||||
internal_icon: '',
|
||||
internal_hotpCounter: null,
|
||||
internal_secret: null,
|
||||
internal_digits: null,
|
||||
internal_algorithm: null,
|
||||
internal_period: null,
|
||||
internal_counter: null,
|
||||
internal_password : '',
|
||||
internal_uri : '',
|
||||
lastActiveDot: null,
|
||||
dotToDotCounter: null,
|
||||
remainingTimeout: null,
|
||||
firstDotToNextOneTimeout: null,
|
||||
dotToDotInterval: null
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
otp_type : String,
|
||||
account : String,
|
||||
algorithm : String,
|
||||
digits : Number,
|
||||
hotpCounter : null,
|
||||
icon : String,
|
||||
imageLink : String,
|
||||
otpType : String,
|
||||
qrcode : null,
|
||||
secret : String,
|
||||
secretIsBase32Encoded : Number,
|
||||
service : String,
|
||||
totpPeriod : null,
|
||||
icon : String,
|
||||
secret : String,
|
||||
digits : Number,
|
||||
algorithm : String,
|
||||
period : null,
|
||||
counter : null,
|
||||
image : String,
|
||||
qrcode : null,
|
||||
secretIsBase32Encoded : Number,
|
||||
uri : String
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayedToken() {
|
||||
return this.$root.appSettings.showTokenAsDot ? this.token.replace(/[0-9]/g, '●') : this.token
|
||||
displayedOtp() {
|
||||
const spacePosition = Math.ceil(this.internal_password.length / 2)
|
||||
let pwd = this.internal_password.substr(0, spacePosition) + " " + this.internal_password.substr(spacePosition)
|
||||
return this.$root.appSettings.showOtpAsDot ? pwd.replace(/[0-9]/g, '●') : pwd
|
||||
}
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
this.getToken()
|
||||
this.show()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async getToken(id) {
|
||||
async show(id) {
|
||||
|
||||
// 3 possible cases :
|
||||
// - Trigger when user ask for a token of an existing account: the ID is provided so we fetch the account data
|
||||
// - Trigger when user ask for an otp of an existing account: the ID is provided so we fetch the account data
|
||||
// from db but without the uri.
|
||||
// This prevent the uri (a sensitive data) to transit via http request unnecessarily. In this
|
||||
// case this.otpType is sent by the backend.
|
||||
// case this.otp_type is sent by the backend.
|
||||
// - Trigger when user use the Quick Uploader and preview the account: No ID but we have an URI.
|
||||
// - Trigger when user use the Advanced form and preview the account: We should have all OTP parameter
|
||||
// to obtain a token, including Secret and otpType which are required
|
||||
// to obtain an otp, including Secret and otp_type which are required
|
||||
|
||||
this.internal_service = this.service
|
||||
this.internal_otp_type = this.otp_type
|
||||
this.internal_account = this.account
|
||||
this.internal_service = this.service
|
||||
this.internal_icon = this.icon
|
||||
this.internal_otpType = this.otpType
|
||||
this.internal_hotpCounter = this.hotpCounter
|
||||
this.internal_secret = this.secret
|
||||
this.internal_digits = this.digits
|
||||
this.internal_algorithm = this.algorithm
|
||||
this.internal_period = this.period
|
||||
this.internal_counter = this.counter
|
||||
|
||||
if( id ) {
|
||||
|
||||
this.id = id
|
||||
const { data } = await this.axios.get('api/twofaccounts/' + this.id)
|
||||
this.internal_id = id
|
||||
const { data } = await this.axios.get('api/twofaccounts/' + this.internal_id)
|
||||
|
||||
this.internal_service = data.service
|
||||
this.internal_account = data.account
|
||||
this.internal_icon = data.icon
|
||||
this.internal_otpType = data.otpType
|
||||
this.internal_otp_type = data.otp_type
|
||||
|
||||
if( data.otpType === 'hotp' && data.hotpCounter ) {
|
||||
this.internal_hotpCounter = data.hotpCounter
|
||||
if( data.otp_type === 'hotp' && data.counter ) {
|
||||
this.internal_counter = data.counter
|
||||
}
|
||||
}
|
||||
|
||||
// We force the otpType to be based on the uri
|
||||
// We force the otp_type to be based on the uri
|
||||
if( this.uri ) {
|
||||
this.internal_otpType = this.uri.slice(0, 15 ).toLowerCase() === "otpauth://totp/" ? 'totp' : 'hotp';
|
||||
this.internal_uri = this.uri
|
||||
this.internal_otp_type = this.uri.slice(0, 15 ).toLowerCase() === "otpauth://totp/" ? 'totp' : 'hotp';
|
||||
}
|
||||
|
||||
if( this.id || this.uri || this.secret ) { // minimun required vars to get a token from the backend
|
||||
if( this.internal_id || this.uri || this.secret ) { // minimun required vars to get an otp from the backend
|
||||
|
||||
switch(this.internal_otpType) {
|
||||
switch(this.internal_otp_type) {
|
||||
case 'totp':
|
||||
await this.getTOTP()
|
||||
await this.startTotpLoop()
|
||||
break;
|
||||
case 'hotp':
|
||||
await this.getHOTP()
|
||||
@ -120,94 +129,125 @@
|
||||
}
|
||||
},
|
||||
|
||||
getOtp: async function() {
|
||||
|
||||
getTOTP: function() {
|
||||
if(this.internal_id) {
|
||||
const { data } = await this.axios.get('/api/twofaccounts/' + this.internal_id + '/otp')
|
||||
return data
|
||||
}
|
||||
else if(this.internal_uri) {
|
||||
const { data } = await this.axios.post('/api/twofaccounts/otp', {
|
||||
uri: this.internal_uri
|
||||
})
|
||||
return data
|
||||
}
|
||||
else {
|
||||
const { data } = await this.axios.post('/api/twofaccounts/otp', {
|
||||
service : this.internal_service,
|
||||
account : this.internal_account,
|
||||
icon : this.internal_icon,
|
||||
otp_type : this.internal_otp_type,
|
||||
secret : this.internal_secret,
|
||||
digits : this.internal_digits,
|
||||
algorithm : this.internal_algorithm,
|
||||
period : this.internal_period,
|
||||
counter : this.internal_counter,
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
|
||||
this.dotToDotCounter = 0
|
||||
startTotpLoop: async function() {
|
||||
|
||||
let otp = await this.getOtp()
|
||||
|
||||
this.axios.post('/api/twofaccounts/token', { id: this.id, otp: this.$props }).then(response => {
|
||||
this.internal_password = otp.password
|
||||
this.internal_otp_type = otp.otp_type
|
||||
let generated_at = otp.generated_at
|
||||
let period = otp.period
|
||||
|
||||
let spacePosition = Math.ceil(response.data.token.length / 2);
|
||||
|
||||
this.token = response.data.token.substr(0, spacePosition) + " " + response.data.token.substr(spacePosition);
|
||||
this.totpTimestamp = response.data.totpTimestamp; // the timestamp used to generate the token
|
||||
this.position = this.totpTimestamp % response.data.totpPeriod // The position of the totp timestamp in the current period
|
||||
let elapsedTimeInCurrentPeriod,
|
||||
remainingTimeBeforeEndOfPeriod,
|
||||
durationBetweenTwoDots,
|
||||
durationFromFirstToNextDot,
|
||||
dots
|
||||
|
||||
// Hide all dots
|
||||
let dots = this.$el.querySelector('.dots');
|
||||
// |<----period p----->|
|
||||
// | | |
|
||||
// |------- ··· ------------|--------|----------|---------->
|
||||
// | | | |
|
||||
// unix T0 Tp.start Tgen_at Tp.end
|
||||
// | | |
|
||||
// elapsedTimeInCurrentPeriod--|<------>| |
|
||||
// (in ms) | | |
|
||||
// ● ● ● ● ●|● ◌ ◌ ◌ ◌ |
|
||||
// | | || |
|
||||
// | | |<-------->|--remainingTimeBeforeEndOfPeriod (for remainingTimeout)
|
||||
// durationBetweenTwoDots-->|-|< ||
|
||||
// (for dotToDotInterval) | | >||<---durationFromFirstToNextDot (for firstDotToNextOneTimeout)
|
||||
// |
|
||||
// |
|
||||
// dotIndex
|
||||
|
||||
while (dots.querySelector('[data-is-active]')) {
|
||||
dots.querySelector('[data-is-active]').removeAttribute('data-is-active');
|
||||
// The elapsed time from the start of the period that contains the OTP generated_at timestamp and the OTP generated_at timestamp itself
|
||||
elapsedTimeInCurrentPeriod = generated_at % period
|
||||
|
||||
// Switch off all dots
|
||||
dots = this.$el.querySelector('.dots')
|
||||
while (dots.querySelector('[data-is-active]')) {
|
||||
dots.querySelector('[data-is-active]').removeAttribute('data-is-active');
|
||||
}
|
||||
|
||||
// We determine the position of the closest dot next to the generated_at timestamp
|
||||
let relativePosition = (elapsedTimeInCurrentPeriod * 10) / period
|
||||
let dotIndex = (Math.floor(relativePosition) +1)
|
||||
|
||||
// We switch the dot on
|
||||
this.lastActiveDot = dots.querySelector('li:nth-child(' + dotIndex + ')');
|
||||
this.lastActiveDot.setAttribute('data-is-active', true);
|
||||
|
||||
// Main timeout that run until the end of the period
|
||||
remainingTimeBeforeEndOfPeriod = period - elapsedTimeInCurrentPeriod
|
||||
let self = this; // because of the setInterval/setTimeout closures
|
||||
|
||||
this.remainingTimeout = setTimeout(function() {
|
||||
self.stopLoop()
|
||||
self.startTotpLoop();
|
||||
}, remainingTimeBeforeEndOfPeriod*1000);
|
||||
|
||||
// During the remainingTimeout countdown we have to show a next dot every durationBetweenTwoDots seconds
|
||||
// except for the first next dot
|
||||
durationBetweenTwoDots = period / 10 // we have 10 dots
|
||||
durationFromFirstToNextDot = (Math.ceil(elapsedTimeInCurrentPeriod / durationBetweenTwoDots) * durationBetweenTwoDots) - elapsedTimeInCurrentPeriod
|
||||
|
||||
this.firstDotToNextOneTimeout = setTimeout(function() {
|
||||
if( durationFromFirstToNextDot > 0 ) {
|
||||
self.activateNextDot()
|
||||
dotIndex += 1
|
||||
}
|
||||
|
||||
// Activate the dot at the totp position
|
||||
let relativePosition = (this.position * 10) / response.data.totpPeriod
|
||||
let dotNumber = (Math.floor(relativePosition) +1)
|
||||
|
||||
this.lastActiveDot = dots.querySelector('li:nth-child(' + dotNumber + ')');
|
||||
this.lastActiveDot.setAttribute('data-is-active', true);
|
||||
|
||||
// Main timeout which run all over the totpPeriod.
|
||||
|
||||
let remainingTimeBeforeEndOfPeriod = response.data.totpPeriod - this.position
|
||||
let self = this; // because of the setInterval/setTimeout closures
|
||||
|
||||
this.remainingTimeout = setTimeout(function() {
|
||||
self.stopLoop()
|
||||
self.getTOTP();
|
||||
}, remainingTimeBeforeEndOfPeriod*1000);
|
||||
|
||||
// During the remainingTimeout countdown we have to show a next dot every durationBetweenTwoDots seconds
|
||||
// except for the first next dot
|
||||
|
||||
let durationBetweenTwoDots = response.data.totpPeriod / 10 // we have 10 dots
|
||||
let firstDotTimeout = (Math.ceil(this.position / durationBetweenTwoDots) * durationBetweenTwoDots) - this.position
|
||||
|
||||
this.firstDotToNextOneTimeout = setTimeout(function() {
|
||||
|
||||
if( firstDotTimeout > 0 ) {
|
||||
self.activeNextDot()
|
||||
dotNumber += 1
|
||||
}
|
||||
|
||||
self.dotToDotInterval = setInterval(function() {
|
||||
|
||||
self.dotToDotCounter += 1
|
||||
self.activeNextDot()
|
||||
dotNumber += 1
|
||||
|
||||
}, durationBetweenTwoDots*1000)
|
||||
|
||||
}, firstDotTimeout*1000)
|
||||
})
|
||||
.catch(error => {
|
||||
this.$router.push({ name: 'genericError', params: { err: error.response } });
|
||||
});
|
||||
self.dotToDotInterval = setInterval(function() {
|
||||
self.activateNextDot()
|
||||
dotIndex += 1
|
||||
}, durationBetweenTwoDots*1000)
|
||||
}, durationFromFirstToNextDot*1000)
|
||||
},
|
||||
|
||||
|
||||
getHOTP: function() {
|
||||
getHOTP: async function() {
|
||||
|
||||
this.axios.post('/api/twofaccounts/token', { id: this.id, otp: this.$props }).then(response => {
|
||||
let spacePosition = Math.ceil(response.data.token.length / 2);
|
||||
|
||||
this.token = response.data.token.substr(0, spacePosition) + " " + response.data.token.substr(spacePosition)
|
||||
let otp = await this.getOtp()
|
||||
|
||||
// returned counter & uri are incremented
|
||||
this.$emit('increment-hotp', { nextHotpCounter: response.data.hotpCounter, nextUri: response.data.uri })
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
this.$router.push({ name: 'genericError', params: { err: error.response } });
|
||||
});
|
||||
// returned counter & uri are incremented
|
||||
this.$emit('increment-hotp', { nextHotpCounter: otp.counter, nextUri: otp.uri })
|
||||
},
|
||||
|
||||
|
||||
clearOTP: function() {
|
||||
|
||||
this.stopLoop()
|
||||
this.id = this.remainingTimeout = this.dotToDotInterval = this.firstDotToNextOneTimeout = this.position = this.internal_hotpCounter = null
|
||||
this.internal_service = this.internal_account = this.internal_icon = this.internal_otpType = ''
|
||||
this.token = '... ...'
|
||||
this.internal_id = this.remainingTimeout = this.dotToDotInterval = this.firstDotToNextOneTimeout = this.elapsedTimeInCurrentPeriod = this.internal_counter = null
|
||||
this.internal_service = this.internal_account = this.internal_icon = this.internal_otp_type = ''
|
||||
this.internal_password = '... ...'
|
||||
|
||||
try {
|
||||
this.$el.querySelector('[data-is-active]').removeAttribute('data-is-active');
|
||||
@ -220,7 +260,7 @@
|
||||
|
||||
|
||||
stopLoop: function() {
|
||||
if( this.internal_otpType === 'totp' ) {
|
||||
if( this.internal_otp_type === 'totp' ) {
|
||||
clearTimeout(this.remainingTimeout)
|
||||
clearTimeout(this.firstDotToNextOneTimeout)
|
||||
clearInterval(this.dotToDotInterval)
|
||||
@ -228,9 +268,8 @@
|
||||
},
|
||||
|
||||
|
||||
activeNextDot: function() {
|
||||
activateNextDot: function() {
|
||||
if(this.lastActiveDot.nextSibling !== null) {
|
||||
|
||||
this.lastActiveDot.removeAttribute('data-is-active')
|
||||
this.lastActiveDot.nextSibling.setAttribute('data-is-active', true)
|
||||
this.lastActiveDot = this.lastActiveDot.nextSibling
|
||||
@ -243,7 +282,7 @@
|
||||
if(this.$root.appSettings.kickUserAfter == -1) {
|
||||
this.appLogout()
|
||||
}
|
||||
else if(this.$root.appSettings.closeTokenOnCopy) {
|
||||
else if(this.$root.appSettings.closeOtpOnCopy) {
|
||||
this.$parent.isActive = false
|
||||
this.clearOTP()
|
||||
}
|
||||
|
2
resources/js/mixins.js
vendored
2
resources/js/mixins.js
vendored
@ -12,7 +12,7 @@ Vue.mixin({
|
||||
|
||||
async appLogout(evt) {
|
||||
|
||||
await this.axios.get('api/logout')
|
||||
await this.axios.get('api/user/logout')
|
||||
|
||||
this.$storage.clear()
|
||||
delete this.axios.defaults.headers.common['Authorization']
|
||||
|
6
resources/js/routes.js
vendored
6
resources/js/routes.js
vendored
@ -27,12 +27,12 @@ const router = new Router({
|
||||
|
||||
{ path: '/accounts', name: 'accounts', component: Accounts, meta: { requiresAuth: true }, alias: '/', props: true },
|
||||
{ path: '/account/create', name: 'createAccount', component: CreateAccount, meta: { requiresAuth: true } },
|
||||
{ path: '/account/edit/:twofaccountId', name: 'editAccount', component: EditAccount, meta: { requiresAuth: true } },
|
||||
{ path: '/account/qrcode/:twofaccountId', name: 'showQRcode', component: QRcodeAccount, meta: { requiresAuth: true } },
|
||||
{ path: '/account/:twofaccountId/edit', name: 'editAccount', component: EditAccount, meta: { requiresAuth: true } },
|
||||
{ path: '/account/:twofaccountId/qrcode', name: 'showQRcode', component: QRcodeAccount, meta: { requiresAuth: true } },
|
||||
|
||||
{ path: '/groups', name: 'groups', component: Groups, meta: { requiresAuth: true }, props: true },
|
||||
{ path: '/group/create', name: 'createGroup', component: CreateGroup, meta: { requiresAuth: true } },
|
||||
{ path: '/group/edit/:groupId', name: 'editGroup', component: EditGroup, meta: { requiresAuth: true }, props: true },
|
||||
{ path: '/group/:groupId/edit', name: 'editGroup', component: EditGroup, meta: { requiresAuth: true }, props: true },
|
||||
|
||||
{ path: '/settings', name: 'settings', component: Settings, meta: { requiresAuth: true } },
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-full" v-for="group in groups" v-if="group.count > 0" :key="group.id">
|
||||
<div class="column is-full" v-for="group in groups" v-if="group.twofaccounts_count > 0" :key="group.id">
|
||||
<button :disabled="group.id == $root.appSettings.activeGroup" class="button is-fullwidth is-dark has-text-light is-outlined" @click="setActiveGroup(group.id)">{{ group.name }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -84,7 +84,7 @@
|
||||
<div class="tfa-cell tfa-content is-size-3 is-size-4-mobile" @click.stop="showAccount(account)">
|
||||
<div class="tfa-text has-ellipsis">
|
||||
<img :src="'/storage/icons/' + account.icon" v-if="account.icon && $root.appSettings.showAccountsIcons">
|
||||
{{ account.service }}<font-awesome-icon class="has-text-danger is-size-5 ml-2" v-if="$root.appSettings.useEncryption && account.isConsistent === false" :icon="['fas', 'exclamation-circle']" />
|
||||
{{ displayService(account.service) }}<font-awesome-icon class="has-text-danger is-size-5 ml-2" v-if="$root.appSettings.useEncryption && account.isConsistent === false" :icon="['fas', 'exclamation-circle']" />
|
||||
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey ">{{ account.account }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -178,7 +178,7 @@
|
||||
</div>
|
||||
<!-- modal -->
|
||||
<modal v-model="showTwofaccountInModal">
|
||||
<token-displayer ref="TokenDisplayer" ></token-displayer>
|
||||
<otp-displayer ref="OtpDisplayer"></otp-displayer>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
@ -193,7 +193,7 @@
|
||||
*
|
||||
* The main view of 2FAuth that list all existing account recorded in DB.
|
||||
* Available feature in this view :
|
||||
* - Token generation
|
||||
* - {{OTP}} generation
|
||||
* - Account fetching :
|
||||
* ~ Search
|
||||
* ~ Filtering (by group)
|
||||
@ -219,7 +219,7 @@
|
||||
*/
|
||||
|
||||
import Modal from '../components/Modal'
|
||||
import TokenDisplayer from '../components/TokenDisplayer'
|
||||
import OtpDisplayer from '../components/OtpDisplayer'
|
||||
import draggable from 'vuedraggable'
|
||||
import Form from './../components/Form'
|
||||
import objectEquals from 'object-equals'
|
||||
@ -238,24 +238,27 @@
|
||||
showGroupSelector: false,
|
||||
moveAccountsTo: false,
|
||||
form: new Form({
|
||||
activeGroup: this.$root.appSettings.activeGroup,
|
||||
value: this.$root.appSettings.activeGroup,
|
||||
}),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* The actual list of displayed accounts
|
||||
*/
|
||||
filteredAccounts: {
|
||||
get: function() {
|
||||
|
||||
return this.accounts.filter(
|
||||
item => {
|
||||
if( parseInt(this.$root.appSettings.activeGroup) > 0 ) {
|
||||
return (item.service.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||
return ((item.service ? item.service.toLowerCase().includes(this.search.toLowerCase()) : false) ||
|
||||
item.account.toLowerCase().includes(this.search.toLowerCase())) &&
|
||||
(item.group_id == parseInt(this.$root.appSettings.activeGroup))
|
||||
}
|
||||
else {
|
||||
return (item.service.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||
return ((item.service ? item.service.toLowerCase().includes(this.search.toLowerCase()) : false) ||
|
||||
item.account.toLowerCase().includes(this.search.toLowerCase()))
|
||||
}
|
||||
}
|
||||
@ -266,10 +269,16 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether or not the accounts should be displayed
|
||||
*/
|
||||
showAccounts() {
|
||||
return this.accounts.length > 0 && !this.showGroupSwitch && !this.showGroupSelector ? true : false
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the name of a group
|
||||
*/
|
||||
activeGroupName() {
|
||||
let g = this.groups.find(el => el.id === parseInt(this.$root.appSettings.activeGroup))
|
||||
|
||||
@ -303,14 +312,14 @@
|
||||
// stop OTP generation on modal close
|
||||
this.$on('modalClose', function() {
|
||||
console.log('modalClose triggered')
|
||||
this.$refs.TokenDisplayer.clearOTP()
|
||||
this.$refs.OtpDisplayer.clearOTP()
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
components: {
|
||||
Modal,
|
||||
TokenDisplayer,
|
||||
OtpDisplayer,
|
||||
draggable,
|
||||
},
|
||||
|
||||
@ -340,14 +349,7 @@
|
||||
|
||||
this.axios.get('api/twofaccounts').then(response => {
|
||||
response.data.forEach((data) => {
|
||||
accounts.push({
|
||||
id : data.id,
|
||||
service : data.service,
|
||||
account : data.account ? data.account : '-',
|
||||
icon : data.icon,
|
||||
isConsistent : data.isConsistent,
|
||||
group_id : data.group_id,
|
||||
})
|
||||
accounts.push(data)
|
||||
})
|
||||
|
||||
if ( this.accounts.length > 0 && !objectEquals(accounts, this.accounts) && !forceRefresh ) {
|
||||
@ -366,10 +368,10 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Show account with a generated token rotation
|
||||
* Show account with a generated {{OTP}} rotation
|
||||
*/
|
||||
showAccount(account) {
|
||||
// In Edit mode clicking an account do not show the tokenDisplayer but select the account
|
||||
// In Edit mode clicking an account do not show the otpDisplayer but select the account
|
||||
if(this.editMode) {
|
||||
|
||||
for (var i=0 ; i<this.selectedAccounts.length ; i++) {
|
||||
@ -382,7 +384,7 @@
|
||||
this.selectedAccounts.push(account.id)
|
||||
}
|
||||
else {
|
||||
this.$refs.TokenDisplayer.getToken(account.id)
|
||||
this.$refs.OtpDisplayer.show(account.id)
|
||||
}
|
||||
},
|
||||
|
||||
@ -391,7 +393,7 @@
|
||||
*/
|
||||
saveOrder() {
|
||||
this.drag = false
|
||||
this.axios.patch('/api/twofaccounts/reorder', {orderedIds: this.accounts.map(a => a.id)})
|
||||
this.axios.post('/api/twofaccounts/reorder', {orderedIds: this.accounts.map(a => a.id)})
|
||||
},
|
||||
|
||||
/**
|
||||
@ -404,7 +406,7 @@
|
||||
this.selectedAccounts.forEach(id => ids.push(id))
|
||||
|
||||
// Backend will delete all accounts at the same time
|
||||
await this.axios.delete('/api/twofaccounts/batch', {data: ids} )
|
||||
await this.axios.delete('/api/twofaccounts?ids=' + ids.join())
|
||||
|
||||
// we fetch the accounts again to prevent the js collection being
|
||||
// desynchronize from the backend php collection
|
||||
@ -413,7 +415,7 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Move accounts selected from the Edit mode to another group
|
||||
* Move accounts selected from the Edit mode to another group or withdraw them
|
||||
*/
|
||||
async moveAccounts() {
|
||||
|
||||
@ -421,7 +423,11 @@
|
||||
this.selectedAccounts.forEach(id => accountsIds.push(id))
|
||||
|
||||
// Backend will associate all accounts with the selected group in the same move
|
||||
await this.axios.patch('/api/group/accounts', {accountsIds: accountsIds, groupId: this.moveAccountsTo} )
|
||||
// or withdraw the accounts if destination is 'no group' (id = 0)
|
||||
if(this.moveAccountsTo === 0) {
|
||||
await this.axios.patch('/api/twofaccounts/withdraw?ids=' + accountsIds.join() )
|
||||
}
|
||||
else await this.axios.post('/api/groups/' + this.moveAccountsTo + '/assign', {ids: accountsIds} )
|
||||
|
||||
// we fetch the accounts again to prevent the js collection being
|
||||
// desynchronize from the backend php collection
|
||||
@ -439,12 +445,7 @@
|
||||
|
||||
this.axios.get('api/groups').then(response => {
|
||||
response.data.forEach((data) => {
|
||||
groups.push({
|
||||
id : data.id,
|
||||
name : data.name,
|
||||
isActive: data.isActive,
|
||||
count: data.twofaccounts_count
|
||||
})
|
||||
groups.push(data)
|
||||
})
|
||||
|
||||
if ( !objectEquals(groups, this.groups) ) {
|
||||
@ -460,10 +461,12 @@
|
||||
*/
|
||||
setActiveGroup(id) {
|
||||
|
||||
this.form.activeGroup = this.$root.appSettings.activeGroup = id
|
||||
// In memomry saving
|
||||
this.form.value = this.$root.appSettings.activeGroup = id
|
||||
|
||||
// In db saving if the user set 2FAuth to memorize the active group
|
||||
if( this.$root.appSettings.rememberActiveGroup ) {
|
||||
this.form.post('/api/settings/options', {returnError: true})
|
||||
this.form.put('/api/settings/activeGroup', {returnError: true})
|
||||
.then(response => {
|
||||
// everything's fine
|
||||
})
|
||||
@ -515,6 +518,13 @@
|
||||
|
||||
this.editMode = state
|
||||
this.$parent.showToolbar = state
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
displayService(service) {
|
||||
return service ? service : this.$t('twofaccounts.no_service')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -90,6 +90,8 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Push a decoded URI to the Create form
|
||||
*
|
||||
* The basicQRcodeReader option is Off, so qrcode decoding has already be done by vue-qrcode-reader, whether
|
||||
* from livescan or file input.
|
||||
* We simply check the uri validity to prevent useless push to the Create form, but the form will check uri validity too.
|
||||
|
@ -23,7 +23,7 @@
|
||||
<router-link :to="{ name: 'editGroup', params: { id: group.id, name: group.name }}" class="tag is-dark">
|
||||
{{ $t('commons.rename') }}
|
||||
</router-link>
|
||||
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ group.count }} {{ $t('twofaccounts.accounts') }}</span>
|
||||
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ group.twofaccounts_count }} {{ $t('twofaccounts.accounts') }}</span>
|
||||
</div>
|
||||
<div class="mt-2 is-size-7 is-pulled-right" v-if="groups.length > 0">
|
||||
{{ $t('groups.deleting_group_does_not_delete_accounts')}}
|
||||
@ -38,7 +38,7 @@
|
||||
<vue-footer :showButtons="true">
|
||||
<!-- close button -->
|
||||
<p class="control">
|
||||
<router-link :to="{ name: 'accounts' }" class="button is-dark is-rounded" @click="">{{ $t('commons.close') }}</router-link>
|
||||
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
|
||||
</p>
|
||||
</vue-footer>
|
||||
</div>
|
||||
@ -72,6 +72,9 @@
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Get all groups from backend
|
||||
*/
|
||||
async fetchGroups() {
|
||||
|
||||
this.isFetching = true
|
||||
@ -80,14 +83,11 @@
|
||||
const groups = []
|
||||
|
||||
response.data.forEach((data) => {
|
||||
groups.push({
|
||||
id : data.id,
|
||||
name : data.name,
|
||||
count: data.twofaccounts_count
|
||||
})
|
||||
groups.push(data)
|
||||
})
|
||||
|
||||
// Remove the pseudo 'All' group
|
||||
// Remove the 'All' pseudo group from the collection
|
||||
// and push it the TheAllGroup
|
||||
this.TheAllGroup = groups.shift()
|
||||
|
||||
this.groups = groups
|
||||
@ -96,6 +96,9 @@
|
||||
this.isFetching = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a group (after confirmation)
|
||||
*/
|
||||
deleteGroup(id) {
|
||||
if(confirm(this.$t('groups.confirm.delete'))) {
|
||||
this.axios.delete('/api/groups/' + id)
|
||||
@ -104,10 +107,9 @@
|
||||
this.groups = this.groups.filter(a => a.id !== id)
|
||||
|
||||
// Reset persisted group filter to 'All' (groupId=0)
|
||||
// (backend will save to change automatically)
|
||||
if( parseInt(this.$root.appSettings.activeGroup) === id ) {
|
||||
this.axios.post('/api/settings/options', { activeGroup: 0 }).then(response => {
|
||||
this.$root.appSettings.activeGroup = 0
|
||||
})
|
||||
this.$root.appSettings.activeGroup = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,8 +117,8 @@
|
||||
},
|
||||
|
||||
beforeRouteLeave(to, from, next) {
|
||||
// reinject the 'All' pseudo group before refreshing the localstorage
|
||||
this.groups.unshift(this.TheAllGroup)
|
||||
// Refresh localstorage
|
||||
this.$storage.set('groups', this.groups)
|
||||
|
||||
next()
|
||||
|
@ -96,7 +96,8 @@
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Send the submitted QR code to the backend for decoding then push ser to the create form
|
||||
* Upload the submitted QR code file to the backend for decoding, then route the user
|
||||
* to the Create form with decoded URI to prefill the form
|
||||
*/
|
||||
async submitQrCode() {
|
||||
|
||||
@ -106,7 +107,7 @@
|
||||
|
||||
const { data } = await this.form.upload('/api/qrcode/decode', imgdata)
|
||||
|
||||
this.$router.push({ name: 'createAccount', params: { decodedUri: data.uri } });
|
||||
this.$router.push({ name: 'createAccount', params: { decodedUri: data.data } });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -37,10 +37,10 @@
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.post('/api/login', {returnError: true})
|
||||
this.form.post('/api/user/login', {returnError: true})
|
||||
.then(response => {
|
||||
localStorage.setItem('user',response.data.message.name)
|
||||
localStorage.setItem('jwt',response.data.message.token)
|
||||
localStorage.setItem('user',response.data.name)
|
||||
localStorage.setItem('jwt',response.data.token)
|
||||
|
||||
if (localStorage.getItem('jwt') != null){
|
||||
this.$router.push({ name: 'accounts', params: { toRefresh: true } })
|
||||
@ -66,13 +66,13 @@
|
||||
}
|
||||
|
||||
next(async vm => {
|
||||
const { data } = await vm.axios.post('api/checkuser')
|
||||
const { data } = await vm.axios.get('api/user/name')
|
||||
|
||||
if( !data.username ) {
|
||||
return next({ name: 'register' });
|
||||
if( data.name ) {
|
||||
vm.username = data.name
|
||||
}
|
||||
else {
|
||||
vm.username = data.username
|
||||
return next({ name: 'register' });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -31,10 +31,10 @@
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.post('/api/register', {returnError: true})
|
||||
this.form.post('/api/user', {returnError: true})
|
||||
.then(response => {
|
||||
localStorage.setItem('user',response.data.message.name)
|
||||
localStorage.setItem('jwt',response.data.message.token)
|
||||
localStorage.setItem('user',response.data.name)
|
||||
localStorage.setItem('jwt',response.data.token)
|
||||
|
||||
if (localStorage.getItem('jwt') != null){
|
||||
this.$router.push({ name: 'accounts', params: { toRefresh: true } })
|
||||
@ -42,7 +42,7 @@
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error.response)
|
||||
if( error.response.status === 422 && error.response.data.errors.taken ) {
|
||||
if( error.response.status === 422 && error.response.data.errors.name ) {
|
||||
|
||||
this.$notify({ type: 'is-danger', text: this.$t('errors.already_one_user_registered') + ' ' + this.$t('errors.cannot_register_more_user'), duration:-1 })
|
||||
}
|
||||
|
@ -23,10 +23,10 @@
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.post('/api/password/email', {returnError: true})
|
||||
this.form.post('/api/user/password/lost', {returnError: true})
|
||||
.then(response => {
|
||||
|
||||
this.$notify({ type: 'is-success', text: response.data.status, duration:-1 })
|
||||
this.$notify({ type: 'is-success', text: response.data.message, duration:-1 })
|
||||
})
|
||||
.catch(error => {
|
||||
if( error.response.data.requestFailed ) {
|
||||
|
@ -36,10 +36,10 @@
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.post('/api/password/reset', {returnError: true})
|
||||
this.form.post('/api/user/password/reset', {returnError: true})
|
||||
.then(response => {
|
||||
|
||||
this.$notify({ type: 'is-success', text: response.data.status, duration:-1 })
|
||||
this.$notify({ type: 'is-success', text: response.data.message, duration:-1 })
|
||||
})
|
||||
.catch(error => {
|
||||
if( error.response.data.resetFailed ) {
|
||||
|
@ -25,7 +25,7 @@
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
const { data } = await this.form.get('/api/settings/account')
|
||||
const { data } = await this.form.get('/api/user')
|
||||
|
||||
this.form.fill(data)
|
||||
},
|
||||
@ -34,10 +34,9 @@
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.patch('/api/settings/account', {returnError: true})
|
||||
this.form.put('/api/user', {returnError: true})
|
||||
.then(response => {
|
||||
|
||||
this.$notify({ type: 'is-success', text: response.data.message })
|
||||
this.$notify({ type: 'is-success', text: this.$t('auth.forms.profile_saved') })
|
||||
})
|
||||
.catch(error => {
|
||||
if( error.response.status === 400 ) {
|
||||
|
@ -1,37 +1,38 @@
|
||||
<template>
|
||||
<form-wrapper>
|
||||
<form @submit.prevent="handleSubmit" @change="handleSubmit" @keydown="form.onKeydown($event)">
|
||||
<!-- <form @submit.prevent="handleSubmit" @change="handleSubmit" @keydown="form.onKeydown($event)"> -->
|
||||
<form>
|
||||
<h4 class="title is-4">{{ $t('settings.general') }}</h4>
|
||||
<!-- Language -->
|
||||
<form-select :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" />
|
||||
<form-select v-on:lang="saveSetting('lang', $event)" :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" />
|
||||
<!-- display mode -->
|
||||
<form-toggle :choices="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
|
||||
<form-toggle v-on:displayMode="saveSetting('displayMode', $event)" :choices="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
|
||||
<!-- show icon -->
|
||||
<form-checkbox :form="form" fieldName="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
|
||||
<form-checkbox v-on:showAccountsIcons="saveSetting('showAccountsIcons', $event)" :form="form" fieldName="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
|
||||
|
||||
<h4 class="title is-4 pt-4">{{ $t('groups.groups') }}</h4>
|
||||
<!-- default group -->
|
||||
<form-select :options="groups" :form="form" fieldName="defaultGroup" :label="$t('settings.forms.default_group.label')" :help="$t('settings.forms.default_group.help')" />
|
||||
<form-select v-on:defaultGroup="saveSetting('defaultGroup', $event)" :options="groups" :form="form" fieldName="defaultGroup" :label="$t('settings.forms.default_group.label')" :help="$t('settings.forms.default_group.help')" />
|
||||
<!-- retain active group -->
|
||||
<form-checkbox :form="form" fieldName="rememberActiveGroup" :label="$t('settings.forms.remember_active_group.label')" :help="$t('settings.forms.remember_active_group.help')" />
|
||||
<form-checkbox v-on:rememberActiveGroup="saveSetting('rememberActiveGroup', $event)" :form="form" fieldName="rememberActiveGroup" :label="$t('settings.forms.remember_active_group.label')" :help="$t('settings.forms.remember_active_group.help')" />
|
||||
|
||||
<h4 class="title is-4 pt-4">{{ $t('settings.security') }}</h4>
|
||||
<!-- auto lock -->
|
||||
<form-select :options="kickUserAfters" :form="form" fieldName="kickUserAfter" :label="$t('settings.forms.auto_lock.label')" :help="$t('settings.forms.auto_lock.help')" />
|
||||
<form-select v-on:kickUserAfter="saveSetting('kickUserAfter', $event)" :options="kickUserAfters" :form="form" fieldName="kickUserAfter" :label="$t('settings.forms.auto_lock.label')" :help="$t('settings.forms.auto_lock.help')" />
|
||||
<!-- protect db -->
|
||||
<form-checkbox :form="form" fieldName="useEncryption" :label="$t('settings.forms.use_encryption.label')" :help="$t('settings.forms.use_encryption.help')" />
|
||||
<!-- token as dot -->
|
||||
<form-checkbox :form="form" fieldName="showTokenAsDot" :label="$t('settings.forms.show_token_as_dot.label')" :help="$t('settings.forms.show_token_as_dot.help')" />
|
||||
<!-- close token on copy -->
|
||||
<form-checkbox :form="form" fieldName="closeTokenOnCopy" :label="$t('settings.forms.close_token_on_copy.label')" :help="$t('settings.forms.close_token_on_copy.help')" />
|
||||
<form-checkbox v-on:useEncryption="saveSetting('useEncryption', $event)" :form="form" fieldName="useEncryption" :label="$t('settings.forms.use_encryption.label')" :help="$t('settings.forms.use_encryption.help')" />
|
||||
<!-- otp as dot -->
|
||||
<form-checkbox v-on:showOtpAsDot="saveSetting('showOtpAsDot', $event)" :form="form" fieldName="showOtpAsDot" :label="$t('settings.forms.show_otp_as_dot.label')" :help="$t('settings.forms.show_otp_as_dot.help')" />
|
||||
<!-- close otp on copy -->
|
||||
<form-checkbox v-on:closeOtpOnCopy="saveSetting('closeOtpOnCopy', $event)" :form="form" fieldName="closeOtpOnCopy" :label="$t('settings.forms.close_otp_on_copy.label')" :help="$t('settings.forms.close_otp_on_copy.help')" />
|
||||
|
||||
<h4 class="title is-4 pt-4">{{ $t('settings.data_input') }}</h4>
|
||||
<!-- basic qrcode -->
|
||||
<form-checkbox :form="form" fieldName="useBasicQrcodeReader" :label="$t('settings.forms.use_basic_qrcode_reader.label')" :help="$t('settings.forms.use_basic_qrcode_reader.help')" />
|
||||
<form-checkbox v-on:useBasicQrcodeReader="saveSetting('useBasicQrcodeReader', $event)" :form="form" fieldName="useBasicQrcodeReader" :label="$t('settings.forms.use_basic_qrcode_reader.label')" :help="$t('settings.forms.use_basic_qrcode_reader.help')" />
|
||||
<!-- direct capture -->
|
||||
<form-checkbox :form="form" fieldName="useDirectCapture" :label="$t('settings.forms.useDirectCapture.label')" :help="$t('settings.forms.useDirectCapture.help')" />
|
||||
<form-checkbox v-on:useDirectCapture="saveSetting('useDirectCapture', $event)" :form="form" fieldName="useDirectCapture" :label="$t('settings.forms.useDirectCapture.label')" :help="$t('settings.forms.useDirectCapture.help')" />
|
||||
<!-- default capture mode -->
|
||||
<form-select :options="captureModes" :form="form" fieldName="defaultCaptureMode" :label="$t('settings.forms.defaultCaptureMode.label')" :help="$t('settings.forms.defaultCaptureMode.help')" />
|
||||
<form-select v-on:defaultCaptureMode="saveSetting('defaultCaptureMode', $event)" :options="captureModes" :form="form" fieldName="defaultCaptureMode" :label="$t('settings.forms.defaultCaptureMode.label')" :help="$t('settings.forms.defaultCaptureMode.help')" />
|
||||
</form>
|
||||
</form-wrapper>
|
||||
</template>
|
||||
@ -61,8 +62,8 @@
|
||||
return {
|
||||
form: new Form({
|
||||
lang: '',
|
||||
showTokenAsDot: null,
|
||||
closeTokenOnCopy: null,
|
||||
showOtpAsDot: null,
|
||||
closeOtpOnCopy: null,
|
||||
useBasicQrcodeReader: null,
|
||||
showAccountsIcons: null,
|
||||
displayMode: '',
|
||||
@ -84,7 +85,7 @@
|
||||
],
|
||||
kickUserAfters: [
|
||||
{ text: this.$t('settings.forms.never'), value: '0' },
|
||||
{ text: this.$t('settings.forms.on_token_copy'), value: '-1' },
|
||||
{ text: this.$t('settings.forms.on_otp_copy'), value: '-1' },
|
||||
{ text: this.$t('settings.forms.1_minutes'), value: '1' },
|
||||
{ text: this.$t('settings.forms.5_minutes'), value: '5' },
|
||||
{ text: this.$t('settings.forms.10_minutes'), value: '10' },
|
||||
@ -105,28 +106,46 @@
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.form.fill(this.$root.appSettings)
|
||||
async mounted() {
|
||||
const { data } = await this.form.get('/api/settings')
|
||||
|
||||
this.form.fillWithKeyValueObject(data)
|
||||
this.form.lang = this.$root.$i18n.locale
|
||||
this.form.setOriginal()
|
||||
this.fetchGroups()
|
||||
},
|
||||
|
||||
methods : {
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
console.log(e)
|
||||
|
||||
this.form.post('/api/settings/options', {returnError: false})
|
||||
.then(response => {
|
||||
// this.form.post('/api/settings/options', {returnError: false})
|
||||
// .then(response => {
|
||||
|
||||
this.$notify({ type: 'is-success', text: response.data.message })
|
||||
// this.$notify({ type: 'is-success', text: response.data.message })
|
||||
|
||||
if(response.data.settings.lang !== this.$root.$i18n.locale) {
|
||||
// if(response.data.settings.lang !== this.$root.$i18n.locale) {
|
||||
// this.$router.go()
|
||||
// }
|
||||
// else {
|
||||
// this.$root.appSettings = response.data.settings
|
||||
// }
|
||||
// });
|
||||
},
|
||||
|
||||
saveSetting(settingName, event) {
|
||||
|
||||
this.axios.put('/api/settings/' + settingName, { value: event }).then(response => {
|
||||
this.$notify({ type: 'is-success', text: this.$t('settings.forms.setting_saved') })
|
||||
|
||||
if(settingName === 'lang' && response.data.value !== this.$root.$i18n.locale) {
|
||||
this.$router.go()
|
||||
}
|
||||
else {
|
||||
this.$root.appSettings = response.data.settings
|
||||
this.$root.appSettings[response.data.key] = response.data.value
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
fetchGroups() {
|
||||
|
@ -28,7 +28,7 @@
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.patch('/api/settings/password', {returnError: true})
|
||||
this.form.patch('/api/user/password', {returnError: true})
|
||||
.then(response => {
|
||||
|
||||
this.$notify({ type: 'is-success', text: response.data.message })
|
||||
|
@ -10,8 +10,8 @@
|
||||
<font-awesome-icon :icon="['fas', 'image']" size="2x" />
|
||||
</label>
|
||||
<button class="delete delete-icon-button is-medium" v-if="tempIcon" @click.prevent="deleteIcon"></button>
|
||||
<token-displayer ref="QuickFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||
</token-displayer>
|
||||
<otp-displayer ref="QuickFormOtpDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||
</otp-displayer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-mobile" v-if="form.errors.any()">
|
||||
@ -80,8 +80,8 @@
|
||||
</div>
|
||||
<field-error :form="form" field="icon" class="help-for-file" />
|
||||
<!-- otp type -->
|
||||
<form-toggle class="has-uppercased-button" :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">
|
||||
<form-toggle class="has-uppercased-button" :form="form" :choices="otp_types" fieldName="otp_type" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
|
||||
<div v-if="form.otp_type">
|
||||
<!-- secret -->
|
||||
<label class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
|
||||
<div class="field has-addons">
|
||||
@ -109,15 +109,15 @@
|
||||
<!-- 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')" />
|
||||
<form-field v-if="form.otp_type === 'totp'" :form="form" fieldName="period" inputType="text" :label="$t('twofaccounts.forms.period.label')" :placeholder="$t('twofaccounts.forms.period.placeholder')" :help="$t('twofaccounts.forms.period.help')" />
|
||||
<!-- HOTP counter -->
|
||||
<form-field v-if="form.otpType === 'hotp'" :form="form" fieldName="hotpCounter" inputType="text" :label="$t('twofaccounts.forms.hotpCounter.label')" :placeholder="$t('twofaccounts.forms.hotpCounter.placeholder')" :help="$t('twofaccounts.forms.hotpCounter.help')" />
|
||||
<form-field v-if="form.otp_type === 'hotp'" :form="form" fieldName="counter" inputType="text" :label="$t('twofaccounts.forms.counter.label')" :placeholder="$t('twofaccounts.forms.counter.placeholder')" :help="$t('twofaccounts.forms.counter.help')" />
|
||||
</div>
|
||||
<vue-footer :showButtons="true">
|
||||
<p class="control">
|
||||
<v-button :isLoading="form.isBusy" class="is-rounded" >{{ $t('commons.create') }}</v-button>
|
||||
</p>
|
||||
<p class="control" v-if="form.otpType && form.secret">
|
||||
<p class="control" v-if="form.otp_type && form.secret">
|
||||
<button type="button" class="button is-success is-rounded" @click="previewAccount">{{ $t('twofaccounts.forms.test') }}</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
@ -127,8 +127,8 @@
|
||||
</form>
|
||||
<!-- modal -->
|
||||
<modal v-model="ShowTwofaccountInModal">
|
||||
<token-displayer ref="AdvancedFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||
</token-displayer>
|
||||
<otp-displayer ref="AdvancedFormOtpDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||
</otp-displayer>
|
||||
</modal>
|
||||
</form-wrapper>
|
||||
</div>
|
||||
@ -147,7 +147,7 @@
|
||||
* ~ A qrcode can be used to automatically fill the form
|
||||
* ~ If an 'image' parameter is embeded in the qrcode, the remote image is downloaded and preset in the icon field
|
||||
*
|
||||
* Both design use the tokenDisplayer component to preview the account with a token rotation.
|
||||
* Both design use the otpDisplayer component to preview the account with an otp rotation.
|
||||
*
|
||||
* input : [optional, for the Quick Form] an URI previously decoded by the Start view
|
||||
* submit : post account data to php backend to create the account
|
||||
@ -155,7 +155,7 @@
|
||||
|
||||
import Modal from '../../components/Modal'
|
||||
import Form from './../../components/Form'
|
||||
import TokenDisplayer from '../../components/TokenDisplayer'
|
||||
import OtpDisplayer from '../../components/OtpDisplayer'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -167,19 +167,18 @@
|
||||
form: new Form({
|
||||
service: '',
|
||||
account: '',
|
||||
otpType: '',
|
||||
uri: '',
|
||||
otp_type: '',
|
||||
icon: '',
|
||||
secret: '',
|
||||
secretIsBase32Encoded: 0,
|
||||
algorithm: '',
|
||||
digits: null,
|
||||
hotpCounter: null,
|
||||
totpPeriod: null,
|
||||
imageLink: '',
|
||||
counter: null,
|
||||
period: null,
|
||||
image: '',
|
||||
qrcode: null,
|
||||
}),
|
||||
otpTypes: [
|
||||
otp_types: [
|
||||
{ text: 'TOTP', value: 'totp' },
|
||||
{ text: 'HOTP', value: 'hotp' },
|
||||
],
|
||||
@ -206,7 +205,7 @@
|
||||
watch: {
|
||||
tempIcon: function(val) {
|
||||
if( this.showQuickForm ) {
|
||||
this.$refs.QuickFormTokenDisplayer.internal_icon = val
|
||||
this.$refs.QuickFormOtpDisplayer.internal_icon = val
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -235,13 +234,13 @@
|
||||
// stop TOTP generation on modal close
|
||||
this.$on('modalClose', function() {
|
||||
|
||||
this.$refs.AdvancedFormTokenDisplayer.stopLoop()
|
||||
this.$refs.AdvancedFormOtpDisplayer.stopLoop()
|
||||
});
|
||||
},
|
||||
|
||||
components: {
|
||||
Modal,
|
||||
TokenDisplayer,
|
||||
OtpDisplayer,
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -259,12 +258,12 @@
|
||||
},
|
||||
|
||||
previewAccount() {
|
||||
this.$refs.AdvancedFormTokenDisplayer.getToken()
|
||||
this.$refs.AdvancedFormOtpDisplayer.show()
|
||||
},
|
||||
|
||||
cancelCreation: function() {
|
||||
|
||||
if( this.form.service && this.form.uri ) {
|
||||
if( this.form.service ) {
|
||||
if( confirm(this.$t('twofaccounts.confirm.cancel')) === false ) {
|
||||
return
|
||||
}
|
||||
@ -286,11 +285,10 @@
|
||||
const { data } = await this.form.upload('/api/qrcode/decode', imgdata)
|
||||
|
||||
// Then the otp described by the uri
|
||||
this.axios.post('/api/twofaccounts/preview', { uri: data.uri }).then(response => {
|
||||
this.axios.post('/api/twofaccounts/preview', { uri: data.data }).then(response => {
|
||||
this.form.fill(response.data)
|
||||
this.form.secretIsBase32Encoded = 1
|
||||
this.tempIcon = response.data.icon ? response.data.icon : null
|
||||
this.form.uri = '' // we don't want the uri because the user can change any otp parameter in the form
|
||||
})
|
||||
},
|
||||
|
||||
@ -302,15 +300,15 @@
|
||||
let imgdata = new FormData();
|
||||
imgdata.append('icon', this.$refs.iconInput.files[0]);
|
||||
|
||||
const { data } = await this.form.upload('/api/icon/upload', imgdata)
|
||||
const { data } = await this.form.upload('/api/icons', imgdata)
|
||||
|
||||
this.tempIcon = data;
|
||||
this.tempIcon = data.filename;
|
||||
|
||||
},
|
||||
|
||||
deleteIcon(event) {
|
||||
if(this.tempIcon) {
|
||||
this.axios.delete('/api/icon/delete/' + this.tempIcon)
|
||||
this.axios.delete('/api/icons/' + this.tempIcon)
|
||||
this.tempIcon = ''
|
||||
}
|
||||
},
|
||||
@ -320,8 +318,7 @@
|
||||
// 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
|
||||
this.form.counter = payload.nextHotpCounter
|
||||
},
|
||||
|
||||
},
|
||||
|
@ -26,8 +26,8 @@
|
||||
</div>
|
||||
<field-error :form="form" field="icon" class="help-for-file" />
|
||||
<!-- 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">
|
||||
<form-toggle class="has-uppercased-button" :isDisabled="true" :form="form" :choices="otp_types" fieldName="otp_type" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
|
||||
<div v-if="form.otp_type">
|
||||
<!-- secret -->
|
||||
<label class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
|
||||
<div class="field has-addons">
|
||||
@ -55,33 +55,33 @@
|
||||
<!-- 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')" />
|
||||
<form-field v-if="form.otp_type === 'totp'" :form="form" fieldName="period" inputType="text" :label="$t('twofaccounts.forms.period.label')" :placeholder="$t('twofaccounts.forms.period.placeholder')" :help="$t('twofaccounts.forms.period.help')" />
|
||||
<!-- HOTP counter -->
|
||||
<div v-if="form.otpType === 'hotp'">
|
||||
<div v-if="form.otp_type === 'hotp'">
|
||||
<div class="field" style="margin-bottom: 0.5rem;">
|
||||
<label class="label">{{ $t('twofaccounts.forms.hotpCounter.label') }}</label>
|
||||
<label class="label">{{ $t('twofaccounts.forms.counter.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" />
|
||||
<input class="input" type="text" placeholder="" v-model="form.counter" :disabled="counterIsLocked" />
|
||||
</div>
|
||||
<div class="control" v-if="hotpCounterIsLocked">
|
||||
<a class="button is-dark field-lock" @click="hotpCounterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
|
||||
<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="hotpCounterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
|
||||
<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" />
|
||||
<p class="help" v-html="$t('twofaccounts.forms.hotpCounter.help_lock')"></p>
|
||||
<field-error :form="form" field="counter" />
|
||||
<p class="help" v-html="$t('twofaccounts.forms.counter.help_lock')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- form buttons -->
|
||||
@ -89,7 +89,7 @@
|
||||
<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">
|
||||
<p class="control" v-if="form.otp_type && form.secret">
|
||||
<button type="button" class="button is-success is-rounded" @click="previewAccount">{{ $t('twofaccounts.forms.test') }}</button>
|
||||
</p>
|
||||
<p class="control">
|
||||
@ -99,8 +99,8 @@
|
||||
</form>
|
||||
<!-- modal -->
|
||||
<modal v-model="ShowTwofaccountInModal">
|
||||
<token-displayer ref="AdvancedFormTokenDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||
</token-displayer>
|
||||
<otp-displayer ref="AdvancedFormOtpDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp">
|
||||
</otp-displayer>
|
||||
</modal>
|
||||
</form-wrapper>
|
||||
</template>
|
||||
@ -109,30 +109,30 @@
|
||||
|
||||
import Modal from '../../components/Modal'
|
||||
import Form from './../../components/Form'
|
||||
import TokenDisplayer from '../../components/TokenDisplayer'
|
||||
import OtpDisplayer from '../../components/OtpDisplayer'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ShowTwofaccountInModal : false,
|
||||
hotpCounterIsLocked: true,
|
||||
counterIsLocked: true,
|
||||
twofaccountExists: false,
|
||||
tempIcon: '',
|
||||
form: new Form({
|
||||
service: '',
|
||||
account: '',
|
||||
otpType: '',
|
||||
otp_type: '',
|
||||
uri: '',
|
||||
icon: '',
|
||||
secret: '',
|
||||
secretIsBase32Encoded: null,
|
||||
algorithm: '',
|
||||
digits: null,
|
||||
hotpCounter: null,
|
||||
totpPeriod: null,
|
||||
imageLink: '',
|
||||
counter: null,
|
||||
period: null,
|
||||
image: '',
|
||||
}),
|
||||
otpTypes: [
|
||||
otp_types: [
|
||||
{ text: 'TOTP', value: 'totp' },
|
||||
{ text: 'HOTP', value: 'hotp' },
|
||||
],
|
||||
@ -161,7 +161,7 @@
|
||||
// stop TOTP generation on modal close
|
||||
this.$on('modalClose', function() {
|
||||
|
||||
this.$refs.AdvancedFormTokenDisplayer.stopLoop()
|
||||
this.$refs.AdvancedFormOtpDisplayer.stopLoop()
|
||||
});
|
||||
},
|
||||
|
||||
@ -171,18 +171,16 @@
|
||||
|
||||
components: {
|
||||
Modal,
|
||||
TokenDisplayer,
|
||||
OtpDisplayer,
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getAccount () {
|
||||
|
||||
const { data } = await this.axios.get('/api/twofaccounts/' + this.$route.params.twofaccountId + '/withSensitive')
|
||||
const { data } = await this.axios.get('/api/twofaccounts/' + this.$route.params.twofaccountId)
|
||||
|
||||
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
|
||||
@ -212,7 +210,7 @@
|
||||
},
|
||||
|
||||
previewAccount() {
|
||||
this.$refs.AdvancedFormTokenDisplayer.getToken()
|
||||
this.$refs.AdvancedFormOtpDisplayer.show()
|
||||
},
|
||||
|
||||
cancelCreation: function() {
|
||||
@ -230,7 +228,7 @@
|
||||
let imgdata = new FormData();
|
||||
imgdata.append('icon', this.$refs.iconInput.files[0]);
|
||||
|
||||
const { data } = await this.form.upload('/api/icon/upload', imgdata)
|
||||
const { data } = await this.form.upload('/api/icons', imgdata)
|
||||
|
||||
this.tempIcon = data;
|
||||
|
||||
@ -239,7 +237,7 @@
|
||||
deleteIcon(event) {
|
||||
|
||||
if( this.tempIcon && this.tempIcon !== this.form.icon ) {
|
||||
this.axios.delete('/api/icon/delete/' + this.tempIcon)
|
||||
this.axios.delete('/api/icons/' + this.tempIcon)
|
||||
}
|
||||
|
||||
this.tempIcon = ''
|
||||
@ -250,7 +248,7 @@
|
||||
// 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.counter = payload.nextHotpCounter
|
||||
this.form.uri = payload.nextUri
|
||||
},
|
||||
|
||||
|
@ -31,9 +31,12 @@
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Get a QR code image resource from backend
|
||||
*/
|
||||
async getQRcode () {
|
||||
|
||||
const { data } = await this.axios.get('/api/qrcode/' + this.$route.params.twofaccountId)
|
||||
const { data } = await this.axios.get('/api/twofaccounts/' + this.$route.params.twofaccountId + '/qrcode')
|
||||
this.qrcode = data.qrcode
|
||||
|
||||
},
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Sprache',
|
||||
'help' => 'Ändern Sie die Sprache, in der die App-Oberfläche angezeigt wird.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Generiertes Token als Punkte anzeigen',
|
||||
'help' => 'Tokenzeichen werden als *** angezeigt, um die Vertraulichkeit zu gewährleisten. Dies beeinflusst nicht die Kopieren/Einfügen Funktion.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Token nach dem Kopieren schließen',
|
||||
'help' => 'Schließe automatisch das Popup-Fenster mit dem generierten Token nachdem es kopiert wurde'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Speichert den letzten Gruppenfilter und stellt ihn bei Ihrem nächsten Besuch wieder her',
|
||||
],
|
||||
'never' => 'Niemals',
|
||||
'on_token_copy' => 'Beim Kopieren des Tokens',
|
||||
'on_otp_copy' => 'Beim Kopieren des Tokens',
|
||||
'1_minutes' => 'Nach 1 Minute',
|
||||
'5_minutes' => 'Nach 5 Minuten',
|
||||
'10_minutes' => 'Nach 10 Minuten',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithmus',
|
||||
'help' => 'Der Algorithmus, der zur Sicherung Ihrer Sicherheitscodes verwendet wird'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Gültigkeitsdauer',
|
||||
'placeholder' => 'Standard ist 30',
|
||||
'help' => 'Die Gültigkeitsdauer der generierten Sicherheitscodes in Sekunden'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Zähler',
|
||||
'placeholder' => 'Standard ist 0',
|
||||
'help' => 'Der Zählerwert am Anfang',
|
||||
'help_lock' => 'Es ist riskant, den Zähler zu bearbeiten, da Sie das Konto mit dem Verifizierungsserver des Dienstes desynchronisieren könnten. Verwenden Sie das Schloss-Symbol, um die Änderung zu aktivieren, wenn Sie sich sicher sind, was Sie tun'
|
||||
],
|
||||
'image_link' => [
|
||||
'image' => [
|
||||
'label' => 'Bild',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'Die URL eines externen Bildes, das als Kontosymbol benutzt wird'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'Kein Konto mit dieser E-Mail gefunden',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'Das Feld :attribute ist erforderlich.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -35,7 +35,7 @@
|
||||
'confirm_new_password' => 'Confirm new password',
|
||||
'dont_have_account_yet' => 'Don\'t have your account yet?',
|
||||
'already_register' => 'Already registered?',
|
||||
'password_do_not_match' => 'Password do not match',
|
||||
'password_do_not_match' => 'Password does not match',
|
||||
'forgot_your_password' => 'Forgot your password?',
|
||||
'request_password_reset' => 'Reset it',
|
||||
'reset_password' => 'Reset password',
|
||||
|
@ -30,13 +30,13 @@
|
||||
'label' => 'Language',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated one-time passwords as dot',
|
||||
'help' => 'Replace generated password caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close OTP after copy',
|
||||
'help' => 'Automatically close the popup showing the generated password after it has been copied'
|
||||
],
|
||||
'use_basic_qrcode_reader' => [
|
||||
'label' => 'Use basic QR code reader',
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -22,6 +22,7 @@
|
||||
'use_full_form' => 'Or use the full form',
|
||||
'add_one' => 'Add one',
|
||||
'show_qrcode' => 'Show QR code',
|
||||
'no_service' => '- no service -',
|
||||
'forms' => [
|
||||
'service' => [
|
||||
'placeholder' => 'example.com',
|
||||
@ -67,18 +68,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Idioma',
|
||||
'help' => 'Cambiar el idioma utilizado para traducir la interfaz de la aplicación.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Mostrar tokens generados como punto',
|
||||
'help' => 'Sustituya los carácteres de token generados por *** para asegurar la confidencialidad. No afecta a la función de copiar/pegar.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Cerrar token después de la copia',
|
||||
'help' => 'Cerrar automáticamente la ventana emergente mostrando el token generado después de que ha sido copiado'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorítmo',
|
||||
'help' => 'El algoritmo usado para proteger sus códigos de seguridad'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Plazo',
|
||||
'placeholder' => 'Por defecto es 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Contador',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Langue',
|
||||
'help' => 'Traduit l\'application dans la langue choisie'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Rendre illisibles les codes générés',
|
||||
'help' => 'Remplace les caractères des codes générés par des ●●● pour garantir leur confidentialité. N\'affecte pas la fonction de copier/coller qui reste utilisable.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Ne plus afficher les codes copiés',
|
||||
'help' => 'Ferme automatiquement le popup affichant le code généré dès que ce dernier a été copié.'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Enregistre le dernier groupe affiché et le restaure lors de votre prochaine visite',
|
||||
],
|
||||
'never' => 'Jamais',
|
||||
'on_token_copy' => 'Après copie d\'un code de sécurité',
|
||||
'on_otp_copy' => 'Après copie d\'un code de sécurité',
|
||||
'1_minutes' => 'Après 1 minute',
|
||||
'5_minutes' => 'Après 5 minutes',
|
||||
'10_minutes' => 'Après 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithme',
|
||||
'help' => 'L\'algorithme utilisé pour sécuriser vos codes de sécurité'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Durée de validité',
|
||||
'placeholder' => '30s par défaut',
|
||||
'help' => 'La durée de validité des codes de sécurité générés, en seconde'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Compteur',
|
||||
'placeholder' => '0 par défaut',
|
||||
'help' => 'La valeur initiale du compteur',
|
||||
'help_lock' => 'Il est risqué de modifier le compteur car vous pouvez désynchroniser le compte avec le serveur de vérification du service. Utilisez l\'icône cadenas pour activer la modification, mais seulement si vous savez ce que vous faites'
|
||||
],
|
||||
'image_link' => [
|
||||
'image' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'L\'url d\'une image externe à utiliser comme icône du compte'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'Aucun compte utilisateur n\'utilise cette email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'Le champ :attribute est obligatoire.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Language',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Language',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Language',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Language',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'භාෂාව',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
@ -30,11 +30,11 @@
|
||||
'label' => 'Language',
|
||||
'help' => 'Change the language used to translate the app interface.'
|
||||
],
|
||||
'show_token_as_dot' => [
|
||||
'show_otp_as_dot' => [
|
||||
'label' => 'Show generated tokens as dot',
|
||||
'help' => 'Replace generated token caracters with *** to ensure confidentiality. Do not affect the copy/paste feature.'
|
||||
],
|
||||
'close_token_on_copy' => [
|
||||
'close_otp_on_copy' => [
|
||||
'label' => 'Close token after copy',
|
||||
'help' => 'Automatically close the popup showing the generated token after it has been copied'
|
||||
],
|
||||
@ -77,7 +77,7 @@
|
||||
'help' => 'Save the last group filter applied and restore it on your next visit',
|
||||
],
|
||||
'never' => 'Never',
|
||||
'on_token_copy' => 'On security code copy',
|
||||
'on_otp_copy' => 'On security code copy',
|
||||
'1_minutes' => 'After 1 minute',
|
||||
'5_minutes' => 'After 5 minutes',
|
||||
'10_minutes' => 'After 10 minutes',
|
||||
|
@ -67,18 +67,18 @@
|
||||
'label' => 'Algorithm',
|
||||
'help' => 'The algorithm used to secure your security codes'
|
||||
],
|
||||
'totpPeriod' => [
|
||||
'period' => [
|
||||
'label' => 'Period',
|
||||
'placeholder' => 'Default is 30',
|
||||
'help' => 'The period of validity of the generated security codes in second'
|
||||
],
|
||||
'hotpCounter' => [
|
||||
'counter' => [
|
||||
'label' => 'Counter',
|
||||
'placeholder' => 'Default is 0',
|
||||
'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' => [
|
||||
'label' => 'Image',
|
||||
'placeholder' => 'http://...',
|
||||
'help' => 'The url of an external image to use as the account icon'
|
||||
|
@ -142,7 +142,7 @@
|
||||
'email' => [
|
||||
'exists' => 'No account found using this email',
|
||||
],
|
||||
'otpType' => [
|
||||
'otp_type' => [
|
||||
'required_without' => 'The :attribute field is required.',
|
||||
],
|
||||
'secret' => [
|
||||
|
Loading…
Reference in New Issue
Block a user