frontend/src/components/user/Settings.vue
konrad 99c10d49be TOTP (#109)
Fix not telling the user about invalid totp passcodes when logging in

Add disabling totp authentication

Add totp passcode when logging in

Add totp settings

Add general post method function

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#109
2020-04-17 23:46:07 +00:00

236 lines
7.8 KiB
Vue

<template>
<div class="loader-container" :class="{ 'is-loading': passwordUpdateService.loading || emailUpdateService.loading || totpService.loading }">
<div class="card">
<header class="card-header">
<p class="card-header-title">
Update Your Password
</p>
</header>
<div class="card-content">
<div class="content">
<form @submit.prevent="updatePassword()">
<div class="field">
<label class="label" for="newPassword">New Password</label>
<div class="control">
<input class="input" type="password" id="newPassword" placeholder="The new password..."
v-model="passwordUpdate.newPassword" @keyup.enter="updatePassword"/>
</div>
</div>
<div class="field">
<label class="label" for="newPasswordConfirm">New Password Confirmation</label>
<div class="control">
<input class="input" type="password" id="newPasswordConfirm" placeholder="Confirm your new password..."
v-model="passwordConfirm" @keyup.enter="updatePassword"/>
</div>
</div>
<div class="field">
<label class="label" for="currentPassword">Current Password</label>
<div class="control">
<input class="input" type="password" id="currentPassword" placeholder="Your current password"
v-model="passwordUpdate.oldPassword" @keyup.enter="updatePassword"/>
</div>
</div>
</form>
<div class="bigbuttons">
<button @click="updatePassword()" class="button is-primary is-fullwidth"
:class="{ 'is-loading': passwordUpdateService.loading}">
Save
</button>
</div>
</div>
</div>
</div>
<div class="card">
<header class="card-header">
<p class="card-header-title">
Update Your E-Mail Address
</p>
</header>
<div class="card-content">
<div class="content">
<form @submit.prevent="updateEmail()">
<div class="field">
<label class="label" for="newEmail">New Email Address</label>
<div class="control">
<input class="input" type="email" id="newEmail" placeholder="The new email address..."
v-model="emailUpdate.newEmail" @keyup.enter="updateEmail"/>
</div>
</div>
<div class="field">
<label class="label" for="currentPassword">Current Password</label>
<div class="control">
<input class="input" type="password" id="currentPassword" placeholder="Your current password"
v-model="emailUpdate.password" @keyup.enter="updateEmail"/>
</div>
</div>
</form>
<div class="bigbuttons">
<button @click="updateEmail()" class="button is-primary is-fullwidth"
:class="{ 'is-loading': emailUpdateService.loading}">
Save
</button>
</div>
</div>
</div>
</div>
<div class="card">
<header class="card-header">
<p class="card-header-title">
Two Factor Authentication
</p>
</header>
<div class="card-content">
<a class="button is-primary" v-if="!totpEnrolled && totp.secret === ''" @click="totpEnroll()" :class="{ 'is-loading': totpService.loading }">Enroll</a>
<div class="content" v-else-if="totp.secret !== '' && !totp.enabled">
<p>
To finish your setup, use this secret in your totp app (Google Authenticator or similar): <strong>{{ totp.secret }}</strong><br/>
After that, enter a code from your app below.
</p>
<p>
Alternatively you can scan this QR code:<br/>
<img :src="totpQR" alt=""/>
</p>
<div class="field">
<label class="label" for="totpConfirmPasscode">Passcode</label>
<div class="control">
<input class="input" type="text" id="totpConfirmPasscode" placeholder="A code generated by your totp application"
v-model="totpConfirmPasscode" @keyup.enter="totpConfirm()"/>
</div>
</div>
<a class="button is-primary" @click="totpConfirm()">Confirm</a>
</div>
<div class="content" v-else-if="totp.secret !== '' && totp.enabled">
<p>
You've sucessfully set up two factor authentication!
</p>
<p v-if="!totpDisableForm">
<a class="button is-danger" @click="totpDisableForm = true">Disable</a>
</p>
<div v-if="totpDisableForm">
<div class="field">
<label class="label" for="currentPassword">Please Enter Your Password</label>
<div class="control">
<input class="input" type="password" id="currentPassword" placeholder="Your current password"
v-model="totpDisablePassword" @keyup.enter="totpDisable" v-focus/>
</div>
</div>
<a class="button is-danger" @click="totpDisable()">Disable two factor authentication</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import PasswordUpdateModel from '../../models/passwordUpdate'
import PasswordUpdateService from '../../services/passwordUpdateService'
import EmailUpdateService from '../../services/emailUpdate'
import EmailUpdateModel from '../../models/emailUpdate'
import TotpModel from '../../models/totp'
import TotpService from '../../services/totp'
export default {
name: 'Settings',
data() {
return {
passwordUpdateService: PasswordUpdateService,
passwordUpdate: PasswordUpdateModel,
passwordConfirm: '',
emailUpdateService: EmailUpdateService,
emailUpdate: EmailUpdateModel,
totpService: TotpService,
totp: TotpModel,
totpQR: '',
totpEnrolled: false,
totpConfirmPasscode: '',
totpDisableForm: false,
totpDisablePassword: '',
}
},
created() {
this.passwordUpdateService = new PasswordUpdateService()
this.passwordUpdate = new PasswordUpdateModel()
this.emailUpdateService = new EmailUpdateService()
this.emailUpdate = new EmailUpdateModel()
this.totpService = new TotpService()
this.totp = new TotpModel()
this.totpService.get()
.then(r => {
this.$set(this, 'totp', r)
this.totpSetQrCode()
})
.catch(e => {
// Error code 1016 means totp is not enabled, we don't need an error in that case.
if (e.response && e.response.data && e.response.data.code && e.response.data.code === 1016) {
this.totpEnrolled = false
return
}
this.error(e, this)
})
},
methods: {
updatePassword() {
if (this.passwordConfirm !== this.passwordUpdate.newPassword) {
this.error({message: 'The new password and its confirmation don\'t match.'}, this)
return
}
this.passwordUpdateService.update(this.passwordUpdate)
.then(() => {
this.success({message: 'The password was successfully updated.'}, this)
})
.catch(e => this.error(e, this))
},
updateEmail() {
this.emailUpdateService.update(this.emailUpdate)
.then(() => {
this.success({message: 'Your email address was successfully updated. We\'ve sent you a link to confirm it.'}, this)
})
.catch(e => this.error(e, this))
},
totpSetQrCode() {
this.totpService.qrcode()
.then(qr => {
const urlCreator = window.URL || window.webkitURL
this.totpQR = urlCreator.createObjectURL(qr)
})
},
totpEnroll() {
this.totpService.enroll()
.then(r => {
this.totpEnrolled = true
this.$set(this, 'totp', r)
this.totpSetQrCode()
})
.catch(e => this.error(e, this))
},
totpConfirm() {
this.totpService.enable({passcode: this.totpConfirmPasscode})
.then(() => {
this.$set(this.totp, 'enabled', true)
this.success({message: 'You\'ve successfully confirmed your totp setup and can use it from now on!'}, this)
})
.catch(e => this.error(e, this))
},
totpDisable() {
this.totpService.disable({password: this.totpDisablePassword})
.then(() => {
this.totpEnrolled = false
this.$set(this, 'totp', new TotpModel())
this.success({message: 'Two factor authentication was sucessfully disabled.'}, this)
})
.catch(e => this.error(e, this))
},
},
}
</script>