konrad
ec1b039daa
All checks were successful
continuous-integration/drone/push Build is passing
Reload the avatar after changing it Hide cropper after upload Fix aspect ratio Add loading variable Move avatar settings to seperate component Add avatar crop Fix avatar upload Add avatar file upload Add abstract methods for file upload Add saving avatar status Add avatar setting Co-authored-by: kolaente <k@knt.li> Reviewed-on: #200
325 lines
9.2 KiB
Vue
325 lines
9.2 KiB
Vue
<template>
|
|
<div
|
|
class="loader-container is-max-width-desktop"
|
|
:class="{ 'is-loading': passwordUpdateService.loading || emailUpdateService.loading || totpService.loading }">
|
|
<!-- Password update -->
|
|
<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>
|
|
|
|
<!-- Update E-Mail -->
|
|
<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>
|
|
|
|
<!-- Avatar -->
|
|
<avatar-settings/>
|
|
|
|
<!-- TOTP -->
|
|
<div class="card" v-if="totpEnabled">
|
|
<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 class="card" v-if="totpEnabled">
|
|
<header class="card-header">
|
|
<p class="card-header-title">
|
|
Migrate from other services to Vikunja
|
|
</p>
|
|
</header>
|
|
<div class="card-content">
|
|
<router-link
|
|
class="button is-primary is-right noshadow is-outlined"
|
|
:to="{name: 'migrate.start'}"
|
|
v-if="migratorsEnabled"
|
|
>
|
|
Import your data into Vikunja
|
|
</router-link>
|
|
</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'
|
|
|
|
import {mapState} from 'vuex'
|
|
|
|
import AvatarSettings from '../../components/user/avatar-settings'
|
|
|
|
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: '',
|
|
}
|
|
},
|
|
components: {
|
|
AvatarSettings,
|
|
},
|
|
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.totpStatus()
|
|
},
|
|
mounted() {
|
|
this.setTitle('Settings')
|
|
},
|
|
computed: mapState({
|
|
totpEnabled: state => state.config.totpEnabled,
|
|
migratorsEnabled: state => state.config.availableMigrators !== null && state.config.availableMigrators.length > 0,
|
|
}),
|
|
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))
|
|
},
|
|
totpStatus() {
|
|
if (!this.totpEnabled) {
|
|
return
|
|
}
|
|
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)
|
|
})
|
|
},
|
|
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>
|