403 lines
10 KiB
Vue
403 lines
10 KiB
Vue
<style scoped>
|
|
.time {
|
|
color: gray;
|
|
}
|
|
</style>
|
|
|
|
<template>
|
|
<div class="form-group">
|
|
<notifications group="webauthn" position="top middle" :duration="5000" width="400" />
|
|
|
|
<div v-if="method === 'register-modal'">
|
|
<h3>{{ $t('settings.webauthn_title') }}</h3>
|
|
|
|
<div v-if="currentkeys !== null">
|
|
<ul class="table">
|
|
<li v-for="key in currentkeys"
|
|
:key="key.id"
|
|
class="table-row"
|
|
>
|
|
<div class="table-cell w-30">
|
|
<strong>{{ key.name }}</strong>
|
|
</div>
|
|
<div class="table-cell time w-50">
|
|
<template v-if="key.counter > 0">
|
|
{{ $t('settings.webauthn_last_use', {timestamp: formatTime(key.updated_at)}) }}
|
|
</template>
|
|
</div>
|
|
<div class="table-cell actions">
|
|
<a class="pointer" href="" @click.prevent="showDeleteModal(key.id)">
|
|
{{ $t('app.delete') }}
|
|
</a>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<slot></slot>
|
|
|
|
<a v-if="isSupported" class="btn btn-primary" href="" @click.prevent="showRegisterModal">
|
|
{{ $t('settings.webauthn_enable_description') }}
|
|
</a>
|
|
<small v-else>
|
|
{{ notSupportedMessage() }}
|
|
</small>
|
|
|
|
|
|
<sweet-modal
|
|
id="registerModal"
|
|
ref="registerModal"
|
|
overlay-theme="dark"
|
|
:title="$t('settings.webauthn_title')"
|
|
>
|
|
<div v-if="registerTab === '1'">
|
|
<p>
|
|
{{ $t('settings.webauthn_key_name_help') }}
|
|
</p>
|
|
<form-input
|
|
:id="'keyName'"
|
|
v-model="keyName"
|
|
:title="$t('settings.webauthn_key_name')"
|
|
:value="keyName"
|
|
:input-type="'text'"
|
|
:width="150"
|
|
:required="true"
|
|
@keyup.enter="showRegisterModalTab('2');startRegister();"
|
|
/>
|
|
</div>
|
|
<div v-if="registerTab === '2'">
|
|
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
|
<div class="pa2">
|
|
<p class="mb0">
|
|
{{ errorMessage }}
|
|
</p>
|
|
<p>
|
|
<a href="" @click.prevent="startRegister()">
|
|
{{ $t('app.retry') }}
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div v-if="infoMessage !== ''" class="form-information-message mb3">
|
|
<div class="pa2">
|
|
<p class="mb0">
|
|
{{ infoMessage }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="errorMessage === ''" class="tc">
|
|
<img src="https://ssl.gstatic.com/accounts/strongauth/Challenge_2SV-Gnubby_graphic.png"
|
|
:alt="$t('settings.webauthn_insertKey')"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="errorMessage === ''" class="pa2">
|
|
<p>
|
|
{{ $t('settings.webauthn_insertKey') }}
|
|
</p>
|
|
<p>
|
|
{{ $t('settings.webauthn_buttonAdvise') }}
|
|
<br />
|
|
{{ $t('settings.webauthn_noButtonAdvise') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div slot="button">
|
|
<a v-if="registerTab === '1'" class="btn" href="" @click.prevent="showRegisterModalTab('2');startRegister();">
|
|
{{ $t('pagination.next') }}
|
|
</a>
|
|
<a v-else class="btn" href="" @click.prevent="showRegisterModalTab('1')">
|
|
{{ $t('pagination.previous') }}
|
|
</a>
|
|
<a class="btn" href="" @click.prevent="closeRegisterModal()">
|
|
{{ $t('app.cancel') }}
|
|
</a>
|
|
</div>
|
|
</sweet-modal>
|
|
</div>
|
|
<div v-else>
|
|
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
|
<div class="pa2">
|
|
<p class="mb0">
|
|
{{ errorMessage }}
|
|
</p>
|
|
<p>
|
|
<a href="" @click.prevent="start()">
|
|
{{ $t('app.retry') }}
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div v-if="infoMessage !== ''" class="form-information-message mb3">
|
|
<div class="pa2">
|
|
<p class="mb0">
|
|
{{ infoMessage }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tc">
|
|
<img src="https://ssl.gstatic.com/accounts/strongauth/Challenge_2SV-Gnubby_graphic.png"
|
|
:alt="$t('settings.webauthn_insertKey')"
|
|
/>
|
|
</div>
|
|
|
|
<div class="pa2">
|
|
<p>
|
|
{{ $t('settings.webauthn_insertKey') }}
|
|
</p>
|
|
<p>
|
|
{{ $t('settings.webauthn_buttonAdvise') }}
|
|
<br />
|
|
{{ $t('settings.webauthn_noButtonAdvise') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<sweet-modal ref="delete" overlay-theme="dark" title="Remove a key">
|
|
<form>
|
|
<div class="mb4">
|
|
{{ $t('settings.webauthn_delete_confirmation') }}
|
|
</div>
|
|
</form>
|
|
<div slot="button">
|
|
<a class="btn" href="" @click.prevent="closeDeleteModal()">
|
|
{{ $t('app.cancel') }}
|
|
</a>
|
|
<a class="btn" href="" @click.prevent="webauthnRemove(keyToTrash)">
|
|
{{ $t('app.delete') }}
|
|
</a>
|
|
</div>
|
|
</sweet-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { SweetModal } from 'sweet-modal-vue';
|
|
import * as WebAuthn from '../../../../vendor/asbiin/laravel-webauthn/resources/js/webauthn.js';
|
|
|
|
export default {
|
|
|
|
components: {
|
|
SweetModal
|
|
},
|
|
|
|
props: {
|
|
keys: {
|
|
type: Array,
|
|
default: function () {
|
|
return [];
|
|
}
|
|
},
|
|
publicKey: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
method: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
timezone: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
script: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
isSupported: true,
|
|
errorMessage: '',
|
|
infoMessage: '',
|
|
success: false,
|
|
currentkeys: [],
|
|
keyToTrash: '',
|
|
keyName: '',
|
|
registerTab: '',
|
|
data: null,
|
|
webauthn: null,
|
|
};
|
|
},
|
|
|
|
mounted() {
|
|
this.prepareComponent();
|
|
this.start();
|
|
},
|
|
|
|
methods: {
|
|
prepareComponent() {
|
|
this.currentkeys = this.keys;
|
|
this.data = this.registerdata;
|
|
|
|
this.webauthn = new WebAuthn((name, message) => {
|
|
this.errorMessage = this._errorMessage(name, message);
|
|
});
|
|
},
|
|
|
|
_errorMessage(name, message) {
|
|
switch (name) {
|
|
case 'InvalidStateError':
|
|
return this.$t('settings.webauthn_error_already_used');
|
|
case 'NotAllowedError':
|
|
return this.$t('settings.webauthn_error_not_allowed');
|
|
default:
|
|
return message;
|
|
}
|
|
},
|
|
|
|
notSupportedMessage() {
|
|
return this.$t('settings.webauthn_'+this.webauthn.notSupportedMessage());
|
|
},
|
|
|
|
start() {
|
|
var self = this;
|
|
this.errorMessage = '';
|
|
|
|
if (! this.webauthn.webAuthnSupport()) {
|
|
this.isSupported = false;
|
|
this.errorMessage = this.notSupportedMessage();
|
|
}
|
|
|
|
switch(this.method) {
|
|
case 'register':
|
|
setTimeout(function () {
|
|
self.webauthn.register(
|
|
self.publicKey,
|
|
function (datas) { self.webauthnRegisterCallback(datas, true); }
|
|
);
|
|
}, 10);
|
|
break;
|
|
case 'login':
|
|
this.webauthn.sign(
|
|
this.publicKey,
|
|
function (data) { self.webauthnLoginCallback(data); }
|
|
);
|
|
break;
|
|
}
|
|
},
|
|
|
|
showRegisterModal() {
|
|
this.errorMessage = '';
|
|
this.infoMessage = '';
|
|
this.keyName = '';
|
|
this.success = false;
|
|
this.showRegisterModalTab('1');
|
|
this.$refs.registerModal.open();
|
|
},
|
|
|
|
showRegisterModalTab(tab) {
|
|
this.registerTab = tab;
|
|
},
|
|
|
|
startRegister() {
|
|
var self = this;
|
|
this.errorMessage = '';
|
|
axios.post('webauthn/keys/options')
|
|
.then(response => {
|
|
if (self.registerTab === '2') {
|
|
var data = response.data.publicKey;
|
|
setTimeout(function () {
|
|
self.webauthn.register(
|
|
data,
|
|
function (datas) { self.webauthnRegisterCallback(datas, false); }
|
|
);
|
|
}, 10);
|
|
}
|
|
}).catch(error => {
|
|
self.notify(error.response.data.message, false);
|
|
});
|
|
},
|
|
|
|
closeRegisterModal() {
|
|
this.$refs.registerModal.close();
|
|
this.showRegisterModalTab('');
|
|
},
|
|
|
|
webauthnRegisterCallback(data, redirect) {
|
|
var self = this;
|
|
axios.post('webauthn/keys', {
|
|
...data,
|
|
name: self.keyName,
|
|
}).then(response => {
|
|
self.success = true;
|
|
self.notify(self.$t('settings.webauthn_success'), true);
|
|
self.currentkeys.push({
|
|
id: response.result.id,
|
|
name: response.result.name,
|
|
});
|
|
}).then(response => {
|
|
if (redirect) {
|
|
setTimeout(function () {
|
|
window.location = response.data.callback;
|
|
}, 100);
|
|
} else {
|
|
self.closeRegisterModal();
|
|
}
|
|
}).catch(error => {
|
|
self.errorMessage = error.message ? error.message : error.response.data.message;
|
|
});
|
|
},
|
|
|
|
webauthnLoginCallback(data) {
|
|
var self = this;
|
|
axios.post('webauthn/auth', {
|
|
...data
|
|
}).then(response => {
|
|
self.success = true;
|
|
self.notify(self.$t('settings.webauthn_success'), true);
|
|
|
|
window.location = response.data.callback;
|
|
}).catch(error => {
|
|
self.errorMessage = error.message ? error.message : error.response.data.message;
|
|
});
|
|
},
|
|
|
|
webauthnRemove(id) {
|
|
var self = this;
|
|
axios.delete('webauthn/keys/'+id)
|
|
.then(response => {
|
|
self.currentkeys.splice(self.currentkeys.indexOf(self.currentkeys.find(item => item.id === response.data.id)), 1);
|
|
self.success = true;
|
|
self.notify(self.$t('settings.webauthn_delete_success'), true);
|
|
self.closeDeleteModal();
|
|
}).catch(error => {
|
|
self.errorMessage = error.response.data.message;
|
|
});
|
|
},
|
|
|
|
showDeleteModal(id) {
|
|
this.keyToTrash = id;
|
|
this.$refs.delete.open();
|
|
},
|
|
|
|
closeDeleteModal() {
|
|
this.$refs.delete.close();
|
|
},
|
|
|
|
formatTime(value) {
|
|
var moment = require('moment-timezone');
|
|
moment.locale(this._i18n.locale);
|
|
moment.tz.setDefault('UTC');
|
|
|
|
var t = moment(value);
|
|
var date = moment.tz(t, this.timezone);
|
|
|
|
return date.format('LLLL');
|
|
},
|
|
|
|
notify(text, success) {
|
|
this.$notify({
|
|
group: 'webauthn',
|
|
title: text,
|
|
text: '',
|
|
type: success ? 'success' : 'error'
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|