update MFA settings
This commit is contained in:
parent
2545d8beb5
commit
a8f5f3f215
|
@ -1,131 +1,134 @@
|
||||||
import RecoveryCodes from './mfa_backup_codes.vue'
|
import RecoveryCodes from './mfa_backup_codes.vue'
|
||||||
|
import TOTP from './mfa_totp.vue'
|
||||||
const Mfa = {
|
const Mfa = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
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,
|
currentPassword: null,
|
||||||
otpConfirmToken: null,
|
otpConfirmToken: null,
|
||||||
backup_codes: [],
|
|
||||||
otpSettings: {}, // pre-setup setting of OTP. secret key, qrcode url.
|
otpSettings: {}, // pre-setup setting of OTP. secret key, qrcode url.
|
||||||
performSetup: null, // step of setup. null -> getBackupCodes -> readyBackupCodes
|
error: null,
|
||||||
performSetupOTP: null, // steps of setup otp. null -> init -> confirm
|
|
||||||
disableMethod: null, // disable method MFA
|
|
||||||
performGenerateNewCodes: false,
|
|
||||||
disableError: null,
|
|
||||||
confirmOTPError: null,
|
|
||||||
readyInit: false
|
readyInit: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {'recovery-codes': RecoveryCodes},
|
components: {'recovery-codes': RecoveryCodes, 'totp-item': TOTP},
|
||||||
computed: {
|
computed: {
|
||||||
canEnable () {
|
canEnable () {
|
||||||
return !this.settings.enabled && this.performSetup === null
|
return !this.settings.enabled && !this.setupInProgress
|
||||||
},
|
|
||||||
showBackupCodes () {
|
|
||||||
return this.waitBackupCodes || this.readyBackupCodes
|
|
||||||
},
|
|
||||||
waitBackupCodes () {
|
|
||||||
return this.performSetup === 'getBackupCodes'
|
|
||||||
},
|
|
||||||
readyBackupCodes () {
|
|
||||||
return this.performSetup === 'readyBackupCodes'
|
|
||||||
},
|
},
|
||||||
canSetupOTP () {
|
canSetupOTP () {
|
||||||
return (
|
return (
|
||||||
this.readyBackupCodes || this.settings.enabled
|
(this.setupInProgress && this.backupCodesPrepared) ||
|
||||||
|
this.settings.enabled
|
||||||
) && this.settings.totp === false
|
) && this.settings.totp === false
|
||||||
},
|
},
|
||||||
waitPreSetupOTP () {
|
waitPreSetupOTP () {
|
||||||
return this.performSetupOTP === null || this.performSetupOTP === 'init'
|
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: {
|
methods: {
|
||||||
enableMFA () {
|
enableMFA () {
|
||||||
this.performSetup = 'getBackupCodes'
|
this.setupState.state = 'getBackupcodes'
|
||||||
this.backupCodes().then((codes) => {
|
this.$nextTick(() => {
|
||||||
this.backup_codes = codes
|
this.startFetchBackupCodes()
|
||||||
this.performSetup = 'readyBackupCodes'
|
this.$store.state.api.backendInteractor.generateMfaBackupCodes()
|
||||||
|
.then((res) => this.setBackupCodes(res.codes))
|
||||||
|
.then((res) => { this.setupState.state = 'setupOTP' })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// gets New Backup codes
|
// gets New Backup codes
|
||||||
getBackupCodes () {
|
getNewBackupCodes () {
|
||||||
this.performGenerateNewCodes = true
|
this.$nextTick(() => {
|
||||||
this.backupCodes().then((codes) => {
|
this.startFetchBackupCodes()
|
||||||
this.backup_codes = codes
|
this.$store.state.api.backendInteractor.generateMfaBackupCodes().then(
|
||||||
this.performGenerateNewCodes = false
|
(res) => this.setBackupCodes(res.codes)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
startFetchBackupCodes () {
|
||||||
backupCodes () {
|
this.backupCodes.inProgress = true
|
||||||
return new Promise((resolve, reject) => {
|
this.backupCodes.codes = []
|
||||||
this.$store.state.api.backendInteractor.generateMfaBackupCodes({})
|
},
|
||||||
.then((res) => { resolve(res.codes) })
|
setBackupCodes (codes) {
|
||||||
})
|
this.backupCodes.codes = codes
|
||||||
|
this.backupCodes.inProgress = false
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup OTP
|
// Setup OTP
|
||||||
setupOTP () {
|
setupOTP () { // prepare setup OTP
|
||||||
this.performSetupOTP = 'init'
|
this.setupState.setupOTPState = 'prepare'
|
||||||
this.$store.state.api.backendInteractor.mfaSetupOTP({})
|
this.$store.state.api.backendInteractor.mfaSetupOTP({})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.otpSettings = res
|
this.otpSettings = res
|
||||||
this.performSetupOTP = 'confirm'
|
this.setupState.setupOTPState = 'confirm'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
confirmOTP () {
|
doConfirmOTP () { // handler confirm enable OTP
|
||||||
this.confirmOTPError = null
|
this.error = null
|
||||||
this.$store.state.api.backendInteractor.mfaConfirmOTP({
|
this.$store.state.api.backendInteractor.mfaConfirmOTP({
|
||||||
token: this.otpConfirmToken,
|
token: this.otpConfirmToken,
|
||||||
password: this.currentPassword
|
password: this.currentPassword
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.confirmOTPError = res.error
|
this.error = res.error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.performSetup = null
|
this.completeSetup()
|
||||||
this.currentPassword = null
|
|
||||||
this.fetchSettings(true)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
completeSetup () {
|
||||||
|
this.setupState.setupOTPState = 'complete'
|
||||||
|
this.setupState.state = 'complete'
|
||||||
|
this.currentPassword = null
|
||||||
|
this.fetchSettings()
|
||||||
|
},
|
||||||
|
|
||||||
// end Setup OTP
|
// 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
|
// fetch settings from server
|
||||||
fetchSettings (force) {
|
fetchSettings () {
|
||||||
if (force || this.settings.enabled === undefined) {
|
this.$store.state.api.backendInteractor.fetchSettingsMFA()
|
||||||
this.$store.state.api.backendInteractor.fetchSettingsMFA({})
|
.then((res) => {
|
||||||
.then((res) => {
|
this.settings = res.settings
|
||||||
this.settings = res.settings
|
if (!this.readyInit) {
|
||||||
if (!this.readyInit) {
|
this.readyInit = true
|
||||||
this.readyInit = true
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// end fetch settings
|
// end fetch settings
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.fetchSettings(true)
|
this.fetchSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default Mfa
|
export default Mfa
|
||||||
|
|
|
@ -8,21 +8,19 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showBackupCodes" class="setting-item">
|
<div v-if="setupInProgress" class="setting-item">
|
||||||
<recovery-codes :codes="backup_codes" :progress="waitBackupCodes" />
|
<h4>{{$t('settings.mfa.recovery_codes')}}</h4>
|
||||||
|
<recovery-codes :backupCodes="backupCodes"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="canSetupOTP">
|
<div v-if="canSetupOTP">
|
||||||
<button class="btn btn-default"
|
<button class="btn btn-default" @click="setupOTP" v-if="!confirmOTP" :disabled="prepareOTP">
|
||||||
@click="setupOTP"
|
|
||||||
v-if="waitPreSetupOTP"
|
|
||||||
:disabled="performSetupOTP === 'init'">
|
|
||||||
{{$t('settings.mfa.setup_otp')}}
|
{{$t('settings.mfa.setup_otp')}}
|
||||||
</button>
|
</button>
|
||||||
<i v-if="performSetupOTP === 'init'">
|
<i v-if="prepareOTP">
|
||||||
{{$t('settings.mfa.wait_pre_setup_otp')}}
|
{{$t('settings.mfa.wait_pre_setup_otp')}}
|
||||||
</i>
|
</i>
|
||||||
<div v-if="performSetupOTP === 'confirm'">
|
<div v-if="confirmOTP">
|
||||||
<h3>{{$t('settings.mfa.setup_otp')}}</h3>
|
<h3>{{$t('settings.mfa.setup_otp')}}</h3>
|
||||||
<h4>{{$t('settings.mfa.scan.title')}}</h4>
|
<h4>{{$t('settings.mfa.scan.title')}}</h4>
|
||||||
<p>{{$t('settings.mfa.scan.desc')}}</p>
|
<p>{{$t('settings.mfa.scan.desc')}}</p>
|
||||||
|
@ -40,10 +38,10 @@
|
||||||
<p>{{$t('settings.enter_current_password_to_confirm')}}:</p>
|
<p>{{$t('settings.enter_current_password_to_confirm')}}:</p>
|
||||||
<input type="password" v-model="currentPassword">
|
<input type="password" v-model="currentPassword">
|
||||||
|
|
||||||
<button class="btn btn-default" @click="confirmOTP()">
|
<button class="btn btn-default" @click="doConfirmOTP()">
|
||||||
{{$t('settings.mfa.confirm_and_enabled')}}
|
{{$t('settings.mfa.confirm_and_enable')}}
|
||||||
</button>
|
</button>
|
||||||
<div class="alert error" v-if="confirmOTPError">{{confirmOTPError}}</div>
|
<div class="alert error" v-if="error">{{error}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,44 +51,23 @@
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<!-- Enabled methods -->
|
<!-- Enabled methods -->
|
||||||
<h4>{{$t('settings.mfa.methods')}}</h4>
|
<h4>{{$t('settings.mfa.methods')}}</h4>
|
||||||
|
<totp-item v-if="settings.totp" v-on:disable="fetchSettings" />
|
||||||
<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>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<!-- Generate new recovery codes -->
|
<!-- Generate new recovery codes -->
|
||||||
|
|
||||||
<button class="btn btn-default"
|
<button class="btn btn-default"
|
||||||
:disabled="performGenerateNewCodes"
|
:disabled="backupCodes.inProgress"
|
||||||
@click="getBackupCodes">
|
@click="getNewBackupCodes">
|
||||||
{{$t('settings.mfa.generate_new_recovery_codes')}}
|
{{$t('settings.mfa.generate_new_recovery_codes')}}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p class="alert error">
|
<p class="alert error">
|
||||||
{{$t('settings.mfa.warning_of_generate_new_codes')}}
|
{{$t('settings.mfa.warning_of_generate_new_codes')}}
|
||||||
</p>
|
</p>
|
||||||
|
<h4 v-if="backupCodesPrepared">{{$t('settings.mfa.recovery_codes')}}</h4>
|
||||||
<recovery-codes :codes="backup_codes" :progress="performGenerateNewCodes"/>
|
<recovery-codes :backupCodes="backupCodes"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
progress: {type: Boolean, default: false},
|
backupCodes: {
|
||||||
codes: {
|
type: Object,
|
||||||
type: Array,
|
default: () => ({
|
||||||
default: () => { return [] }
|
inProgress: false,
|
||||||
|
codes: []
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () { return { title: this.progress } },
|
data: () => ({}),
|
||||||
computed: {
|
computed: {
|
||||||
ready () { return this.codes.length > 0 }
|
inProgress () {
|
||||||
},
|
return this.backupCodes.inProgress
|
||||||
mounted () {
|
},
|
||||||
let unwatch = this.$watch('progress', function (value) {
|
ready () { return this.backupCodes.codes.length > 0 }
|
||||||
if (value) {
|
|
||||||
this.title = true
|
|
||||||
unwatch()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mfa-recovery-codes">
|
<div>
|
||||||
<h4 v-if="title">{{$t('settings.mfa.recovery_codes')}}</h4>
|
<i v-if="inProgress">{{$t('settings.mfa.waiting_a_recovery_codes')}}</i>
|
||||||
<i v-if="progress">{{$t('settings.mfa.waiting_a_recovery_codes')}}</i>
|
<template v-if="ready">
|
||||||
<div v-if="ready">
|
<p class="alert warning">{{$t('settings.mfa.recovery_codes_warning')}}</p>
|
||||||
<p class="warning">{{$t('settings.mfa.recovery_codes_warning')}}</p>
|
<ul><li v-for="code in backupCodes.codes">{{code}}</li></ul>
|
||||||
<ul>
|
</template>
|
||||||
<li v-for="code in codes">{{code}}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script src="./mfa_backup_codes.js"></script>
|
<script src="./mfa_backup_codes.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.warning { color: orange; }
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: $fallback--cOrange;
|
||||||
|
color: var(--cOrange, $fallback--cOrange);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -160,7 +160,7 @@
|
||||||
"otp" : "OTP",
|
"otp" : "OTP",
|
||||||
"setup_otp" : "Setup OTP",
|
"setup_otp" : "Setup OTP",
|
||||||
"wait_pre_setup_otp" : "waiting pre-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",
|
"title": "Two-factor Authentication",
|
||||||
"generate_new_recovery_codes" : "Generate new recovery codes",
|
"generate_new_recovery_codes" : "Generate new recovery codes",
|
||||||
"generate_recovery_codes" : "Generate recovery codes.",
|
"generate_recovery_codes" : "Generate recovery codes.",
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
"mfa": {
|
"mfa": {
|
||||||
"otp" : "OTP",
|
"otp" : "OTP",
|
||||||
"setup_otp" : "Настройка OTP",
|
"setup_otp" : "Настройка OTP",
|
||||||
"confirm_and_enabled" : "Подтвердить и включить OTP",
|
"confirm_and_enable" : "Подтвердить и включить OTP",
|
||||||
"title": "Двухфакторная аутентификация",
|
"title": "Двухфакторная аутентификация",
|
||||||
"generate_new_recovery_codes" : "Получить новые коды востановления",
|
"generate_new_recovery_codes" : "Получить новые коды востановления",
|
||||||
"generate_recovery_codes" : "Коды восстановления",
|
"generate_recovery_codes" : "Коды восстановления",
|
||||||
|
|
Loading…
Reference in New Issue