update MFA settings

This commit is contained in:
Maksim Pechnikov 2019-05-28 16:34:55 +03:00
parent 2545d8beb5
commit a8f5f3f215
8 changed files with 170 additions and 138 deletions

View File

@ -1,131 +1,134 @@
import RecoveryCodes from './mfa_backup_codes.vue'
import TOTP from './mfa_totp.vue'
const Mfa = {
data () {
return {
settings: {}, // current settings of MFA
settings: { // current settings of MFA
enabled: false,
totp: false
},
setupState: { // setup mfa
state: '', // state of setup. '' -> 'getBackupCodes' -> 'setupOTP' -> 'complete'
setupOTPState: '' // state of setup otp. '' -> 'prepare' -> 'confirm' -> 'complete'
},
backupCodes: {
inProgress: false, // progress of fetch codes
codes: []
},
currentPassword: null,
otpConfirmToken: null,
backup_codes: [],
otpSettings: {}, // pre-setup setting of OTP. secret key, qrcode url.
performSetup: null, // step of setup. null -> getBackupCodes -> readyBackupCodes
performSetupOTP: null, // steps of setup otp. null -> init -> confirm
disableMethod: null, // disable method MFA
performGenerateNewCodes: false,
disableError: null,
confirmOTPError: null,
error: null,
readyInit: false
}
},
components: {'recovery-codes': RecoveryCodes},
components: {'recovery-codes': RecoveryCodes, 'totp-item': TOTP},
computed: {
canEnable () {
return !this.settings.enabled && this.performSetup === null
},
showBackupCodes () {
return this.waitBackupCodes || this.readyBackupCodes
},
waitBackupCodes () {
return this.performSetup === 'getBackupCodes'
},
readyBackupCodes () {
return this.performSetup === 'readyBackupCodes'
return !this.settings.enabled && !this.setupInProgress
},
canSetupOTP () {
return (
this.readyBackupCodes || this.settings.enabled
(this.setupInProgress && this.backupCodesPrepared) ||
this.settings.enabled
) && this.settings.totp === false
},
waitPreSetupOTP () {
return this.performSetupOTP === null || this.performSetupOTP === 'init'
},
disableOTP () {
return this.disableMethod === 'otp'
},
setupInProgress () {
return this.setupState.state !== '' && this.setupState.state !== 'complete'
},
prepareOTP () {
return this.setupState.setupOTPState === 'prepare'
},
confirmOTP () {
return this.setupState.setupOTPState === 'confirm'
},
backupCodesPrepared () {
return this.backupCodes.inProgress === false && this.backupCodes.codes.length > 0
}
},
methods: {
enableMFA () {
this.performSetup = 'getBackupCodes'
this.backupCodes().then((codes) => {
this.backup_codes = codes
this.performSetup = 'readyBackupCodes'
this.setupState.state = 'getBackupcodes'
this.$nextTick(() => {
this.startFetchBackupCodes()
this.$store.state.api.backendInteractor.generateMfaBackupCodes()
.then((res) => this.setBackupCodes(res.codes))
.then((res) => { this.setupState.state = 'setupOTP' })
})
},
// gets New Backup codes
getBackupCodes () {
this.performGenerateNewCodes = true
this.backupCodes().then((codes) => {
this.backup_codes = codes
this.performGenerateNewCodes = false
getNewBackupCodes () {
this.$nextTick(() => {
this.startFetchBackupCodes()
this.$store.state.api.backendInteractor.generateMfaBackupCodes().then(
(res) => this.setBackupCodes(res.codes)
)
})
},
backupCodes () {
return new Promise((resolve, reject) => {
this.$store.state.api.backendInteractor.generateMfaBackupCodes({})
.then((res) => { resolve(res.codes) })
})
startFetchBackupCodes () {
this.backupCodes.inProgress = true
this.backupCodes.codes = []
},
setBackupCodes (codes) {
this.backupCodes.codes = codes
this.backupCodes.inProgress = false
},
// Setup OTP
setupOTP () {
this.performSetupOTP = 'init'
setupOTP () { // prepare setup OTP
this.setupState.setupOTPState = 'prepare'
this.$store.state.api.backendInteractor.mfaSetupOTP({})
.then((res) => {
this.otpSettings = res
this.performSetupOTP = 'confirm'
this.setupState.setupOTPState = 'confirm'
})
},
confirmOTP () {
this.confirmOTPError = null
doConfirmOTP () { // handler confirm enable OTP
this.error = null
this.$store.state.api.backendInteractor.mfaConfirmOTP({
token: this.otpConfirmToken,
password: this.currentPassword
})
.then((res) => {
if (res.error) {
this.confirmOTPError = res.error
this.error = res.error
return
}
this.performSetup = null
this.currentPassword = null
this.fetchSettings(true)
this.completeSetup()
})
},
completeSetup () {
this.setupState.setupOTPState = 'complete'
this.setupState.state = 'complete'
this.currentPassword = null
this.fetchSettings()
},
// end Setup OTP
// disable OTP
handleDisableOTP () {
this.disableError = null
this.$store.state.api.backendInteractor.mfaDisableOTP({
password: this.currentPassword
})
.then((res) => {
if (res.error) {
this.disableError = res.error
return
}
this.disableMethod = null
this.currentPassword = null
this.fetchSettings(true)
})
},
// end disable OTP
// fetch settings from server
fetchSettings (force) {
if (force || this.settings.enabled === undefined) {
this.$store.state.api.backendInteractor.fetchSettingsMFA({})
.then((res) => {
this.settings = res.settings
if (!this.readyInit) {
this.readyInit = true
}
})
}
fetchSettings () {
this.$store.state.api.backendInteractor.fetchSettingsMFA()
.then((res) => {
this.settings = res.settings
if (!this.readyInit) {
this.readyInit = true
}
})
}
// end fetch settings
},
mounted () {
this.fetchSettings(true)
this.fetchSettings()
}
}
export default Mfa

View File

@ -8,21 +8,19 @@
</button>
</div>
<div v-if="showBackupCodes" class="setting-item">
<recovery-codes :codes="backup_codes" :progress="waitBackupCodes" />
<div v-if="setupInProgress" class="setting-item">
<h4>{{$t('settings.mfa.recovery_codes')}}</h4>
<recovery-codes :backupCodes="backupCodes"/>
</div>
<div v-if="canSetupOTP">
<button class="btn btn-default"
@click="setupOTP"
v-if="waitPreSetupOTP"
:disabled="performSetupOTP === 'init'">
<button class="btn btn-default" @click="setupOTP" v-if="!confirmOTP" :disabled="prepareOTP">
{{$t('settings.mfa.setup_otp')}}
</button>
<i v-if="performSetupOTP === 'init'">
<i v-if="prepareOTP">
{{$t('settings.mfa.wait_pre_setup_otp')}}
</i>
<div v-if="performSetupOTP === 'confirm'">
<div v-if="confirmOTP">
<h3>{{$t('settings.mfa.setup_otp')}}</h3>
<h4>{{$t('settings.mfa.scan.title')}}</h4>
<p>{{$t('settings.mfa.scan.desc')}}</p>
@ -40,10 +38,10 @@
<p>{{$t('settings.enter_current_password_to_confirm')}}:</p>
<input type="password" v-model="currentPassword">
<button class="btn btn-default" @click="confirmOTP()">
{{$t('settings.mfa.confirm_and_enabled')}}
<button class="btn btn-default" @click="doConfirmOTP()">
{{$t('settings.mfa.confirm_and_enable')}}
</button>
<div class="alert error" v-if="confirmOTPError">{{confirmOTPError}}</div>
<div class="alert error" v-if="error">{{error}}</div>
</div>
</div>
@ -53,44 +51,23 @@
<div class="setting-item">
<!-- Enabled methods -->
<h4>{{$t('settings.mfa.methods')}}</h4>
<div class="method-item" v-if="settings.totp">
<strong>{{$t('settings.mfa.otp')}}</strong>
<button class="btn btn-default"
:disabled="disableMethod === 'totp'"
@click="disableMethod = 'totp'">
{{$t('general.disable')}}
</button>
</div>
<div v-if="disableMethod === 'totp'">
{{$t('settings.enter_current_password_to_confirm')}}:
<input type="password" v-model="currentPassword">
<button class="btn btn-default" @click="handleDisableOTP">
{{$t('general.confirm')}}
</button>
<button class="btn btn-default" @click="disableMethod = null">
{{$t('general.cancel')}}
</button>
<div class="alert error" v-if="disableError">{{disableError}}</div>
</div>
<totp-item v-if="settings.totp" v-on:disable="fetchSettings" />
</div>
<div class="setting-item">
<!-- Generate new recovery codes -->
<button class="btn btn-default"
:disabled="performGenerateNewCodes"
@click="getBackupCodes">
:disabled="backupCodes.inProgress"
@click="getNewBackupCodes">
{{$t('settings.mfa.generate_new_recovery_codes')}}
</button>
<p class="alert error">
{{$t('settings.mfa.warning_of_generate_new_codes')}}
</p>
<recovery-codes :codes="backup_codes" :progress="performGenerateNewCodes"/>
<h4 v-if="backupCodesPrepared">{{$t('settings.mfa.recovery_codes')}}</h4>
<recovery-codes :backupCodes="backupCodes"/>
</div>
</div>
</div>

View File

@ -1,21 +1,18 @@
export default {
props: {
progress: {type: Boolean, default: false},
codes: {
type: Array,
default: () => { return [] }
backupCodes: {
type: Object,
default: () => ({
inProgress: false,
codes: []
})
}
},
data () { return { title: this.progress } },
data: () => ({}),
computed: {
ready () { return this.codes.length > 0 }
},
mounted () {
let unwatch = this.$watch('progress', function (value) {
if (value) {
this.title = true
unwatch()
}
})
inProgress () {
return this.backupCodes.inProgress
},
ready () { return this.backupCodes.codes.length > 0 }
}
}

View File

@ -1,16 +1,18 @@
<template>
<div class="mfa-recovery-codes">
<h4 v-if="title">{{$t('settings.mfa.recovery_codes')}}</h4>
<i v-if="progress">{{$t('settings.mfa.waiting_a_recovery_codes')}}</i>
<div v-if="ready">
<p class="warning">{{$t('settings.mfa.recovery_codes_warning')}}</p>
<ul>
<li v-for="code in codes">{{code}}</li>
</ul>
</div>
<div>
<i v-if="inProgress">{{$t('settings.mfa.waiting_a_recovery_codes')}}</i>
<template v-if="ready">
<p class="alert warning">{{$t('settings.mfa.recovery_codes_warning')}}</p>
<ul><li v-for="code in backupCodes.codes">{{code}}</li></ul>
</template>
</div>
</template>
<script src="./mfa_backup_codes.js"></script>
<style lang="scss">
.warning { color: orange; }
@import '../../_variables.scss';
.warning {
color: $fallback--cOrange;
color: var(--cOrange, $fallback--cOrange);
}
</style>

View File

@ -0,0 +1,30 @@
export default {
data: () => ({
error: false,
currentPassword: '',
disable: false,
inProgress: false
}),
methods: {
doCancel () { this.disable = false },
doDisable () {
this.error = null
this.disable = true
},
confirmDisable () {
this.error = null
this.inProgress = true
this.$store.state.api.backendInteractor.mfaDisableOTP({
password: this.currentPassword
})
.then((res) => {
this.inProgress = false
if (res.error) {
this.error = res.error
return
}
this.$emit('disable')
})
}
}
}

View File

@ -0,0 +1,23 @@
<template>
<div>
<div class="method-item">
<strong>{{$t('settings.mfa.otp')}}</strong>
<button class="btn btn-default" :disabled="disable" @click="doDisable">
{{$t('general.disable')}}
</button>
</div>
<div v-if="disable">
{{$t('settings.enter_current_password_to_confirm')}}:
<input type="password" v-model="currentPassword">
<button class="btn btn-default" @click="confirmDisable" :disabled="inProgress">
{{$t('general.confirm')}}
</button>
<button class="btn btn-default" @click="doCancel" :disabled="inProgress">
{{$t('general.cancel')}}
</button>
<div class="alert error" v-if="error">{{error}}</div>
</div>
</div>
</template>
<script src="./mfa_totp.js"></script>

View File

@ -160,7 +160,7 @@
"otp" : "OTP",
"setup_otp" : "Setup OTP",
"wait_pre_setup_otp" : "waiting pre-setup OTP",
"confirm_and_enabled" : "Confirm & enable OTP",
"confirm_and_enable" : "Confirm & enable OTP",
"title": "Two-factor Authentication",
"generate_new_recovery_codes" : "Generate new recovery codes",
"generate_recovery_codes" : "Generate recovery codes.",

View File

@ -100,7 +100,7 @@
"mfa": {
"otp" : "OTP",
"setup_otp" : "Настройка OTP",
"confirm_and_enabled" : "Подтвердить и включить OTP",
"confirm_and_enable" : "Подтвердить и включить OTP",
"title": "Двухфакторная аутентификация",
"generate_new_recovery_codes" : "Получить новые коды востановления",
"generate_recovery_codes" : "Коды восстановления",