2FAuth/resources/js/views/auth/webauthn/Recover.vue

132 lines
4.9 KiB
Vue
Raw Normal View History

2022-03-15 14:47:07 +01:00
<template>
<form-wrapper :title="$t('auth.webauthn.register_a_new_device')" :punchline="$t('auth.webauthn.recover_account_instructions')" >
<div v-if="deviceRegistered" class="field">
<label class="label mb-5">{{ $t('auth.webauthn.device_successfully_registered') }}&nbsp;<font-awesome-icon :icon="['fas', 'check']" /></label>
<form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
<form-field :form="form" fieldName="name" inputType="text" placeholder="iPhone 12, TouchID, Yubikey 5C" :label="$t('auth.forms.name_this_device')" />
<form-buttons :isBusy="form.isBusy" :isDisabled="form.isDisabled" :caption="$t('commons.continue')" />
</form>
</div>
<div v-else>
<div class="field">
<input id="unique" name="unique" type="checkbox" class="is-checkradio is-info" v-model="unique" >
<label for="unique" class="label">{{ $t('auth.webauthn.disable_all_other_devices') }}</label>
</div>
<div class="field is-grouped">
<div class="control">
<a class="button is-link" @click="register()">{{ $t('auth.webauthn.register_a_new_device')}}</a>
</div>
<div class="control">
<router-link :to="{ name: 'login' }" class="button is-text">{{ $t('commons.cancel') }}</router-link>
</div>
</div>
</div>
</form-wrapper>
</template>
<script>
import Form from './../../../components/Form'
export default {
data(){
return {
email : '',
token: '',
unique: false,
deviceRegistered: false,
deviceId : null,
form: new Form({
name : '',
}),
}
},
created () {
this.email = this.$route.query.email
this.token = this.$route.query.token
},
methods : {
/**
* Register a new security device
*/
async register() {
// Check https context
if (!window.isSecureContext) {
this.$notify({ type: 'is-danger', text: this.$t('errors.https_required') })
return false
}
// Check browser support
if (!window.PublicKeyCredential) {
this.$notify({ type: 'is-danger', text: this.$t('errors.browser_does_not_support_webauthn') })
return false
}
const registerOptions = await this.axios.post('/webauthn/recover/options',
{
email : this.email,
token: this.token
},
{ returnError: true })
.then(res => res.data)
.catch(error => {
this.$notify({ type: 'is-danger', text: error.response.data.message })
});
const publicKey = this.parseIncomingServerOptions(registerOptions)
let bufferedCredentials
try {
bufferedCredentials = await navigator.credentials.create({ publicKey })
}
catch (error) {
if (error.name == 'AbortError') {
this.$notify({ type: 'is-warning', text: this.$t('errors.aborted_by_user') })
}
else if (error.name == 'NotAllowedError' || 'InvalidStateError') {
this.$notify({ type: 'is-danger', text: this.$t('errors.security_device_unsupported') })
}
return false
}
const publicKeyCredential = this.parseOutgoingCredentials(bufferedCredentials);
this.axios.post('/webauthn/recover', publicKeyCredential, {
headers: {
email : this.email,
token: this.token,
unique: this.unique,
}
}).then(response => {
this.$notify({ type: 'is-success', text: this.$t('auth.webauthn.device_successfully_registered') })
this.deviceId = publicKeyCredential.id
this.deviceRegistered = true
})
},
/**
* Rename the registered device
*/
async handleSubmit(e) {
await this.form.patch('/webauthn/credentials/' + this.deviceId + '/name')
if( this.form.errors.any() === false ) {
this.$router.push({name: 'accounts', params: { toRefresh: true }})
}
},
},
beforeRouteLeave (to, from, next) {
this.$notify({
clean: true
})
next()
}
}
</script>