mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-01-23 06:38:34 +01:00
Set up a global notification handler & Error view & Modal component
This commit is contained in:
parent
e37c0c9ea5
commit
73e36edd9c
28
resources/js_vue3/app.js
vendored
28
resources/js_vue3/app.js
vendored
@ -1,8 +1,5 @@
|
||||
import '/resources/js_vue3/assets/app.scss';
|
||||
|
||||
// import { createApp } from 'vue'
|
||||
// import { i18nVue } from 'laravel-vue-i18n'
|
||||
// import { createPinia } from 'pinia'
|
||||
import Notifications from '@kyvg/vue3-notification'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
@ -13,7 +10,7 @@ const app = createApp(App)
|
||||
// Immutable app properties provided by the laravel blade view
|
||||
const $2fauth = {
|
||||
prefix: '2fauth_',
|
||||
config: window.appConfig, //{"proxyAuth":false,"proxyLogoutUrl":false,"subdirectory":""}
|
||||
config: window.appConfig,
|
||||
version: window.appVersion,
|
||||
isDemoApp: window.isDemoApp,
|
||||
isTestingApp: window.isTestingApp,
|
||||
@ -21,13 +18,17 @@ const $2fauth = {
|
||||
}
|
||||
app.provide('2fauth', readonly($2fauth))
|
||||
|
||||
// Stores
|
||||
const pinia = createPinia()
|
||||
pinia.use(({ store }) => {
|
||||
store.$2fauth = $2fauth;
|
||||
});
|
||||
app.use(pinia)
|
||||
|
||||
// Router
|
||||
app.use(router)
|
||||
|
||||
// Localization
|
||||
app.use(i18nVue, {
|
||||
lang: document.documentElement.lang.substring(0, 2),
|
||||
resolve: async lang => {
|
||||
@ -39,26 +40,41 @@ app.use(i18nVue, {
|
||||
})
|
||||
app.use(Notifications)
|
||||
|
||||
// Components registration
|
||||
import ResponsiveWidthWrapper from '@/layouts/ResponsiveWidthWrapper.vue'
|
||||
import FormWrapper from '@/layouts/FormWrapper.vue'
|
||||
import Footer from '@/layouts/Footer.vue'
|
||||
import Modal from '@/layouts/Modal.vue'
|
||||
import VueButton from '@/components/formElements/Button.vue'
|
||||
import FieldError from '@/components/formElements/FieldError.vue'
|
||||
import FormField from '@/components/formElements/FormField.vue'
|
||||
import FormPasswordField from '@/components/formElements/FormPasswordField.vue'
|
||||
// import FormSelect from './FormSelect'
|
||||
// import FormSwitch from './FormSwitch'
|
||||
// import FormToggle from './FormToggle'
|
||||
// import FormCheckbox from './FormCheckbox'
|
||||
import FormButtons from '@/components/formElements/FormButtons.vue'
|
||||
// import Kicker from './Kicker'
|
||||
// import SettingTabs from './SettingTabs'
|
||||
|
||||
// Components registration
|
||||
app
|
||||
.component('FontAwesomeIcon', FontAwesomeIcon)
|
||||
.component('ResponsiveWidthWrapper', ResponsiveWidthWrapper)
|
||||
.component('FormWrapper', FormWrapper)
|
||||
.component('VueFooter', Footer)
|
||||
.component('Modal', Modal)
|
||||
.component('VueButton', VueButton)
|
||||
.component('FieldError', FieldError)
|
||||
.component('FormField', FormField)
|
||||
.component('FormPasswordField', FormPasswordField)
|
||||
.component('FormButtons', FormButtons)
|
||||
|
||||
// Global error handling
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
useNotifyStore().error(err)
|
||||
}
|
||||
|
||||
// App mounting
|
||||
app.mount('#app')
|
||||
app.mount('#app')
|
52
resources/js_vue3/layouts/Modal.vue
Normal file
52
resources/js_vue3/layouts/Modal.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
const { notify } = useNotification()
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['modalClosed'])
|
||||
|
||||
const isActive = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('modalClosed')
|
||||
}
|
||||
})
|
||||
|
||||
function closeModal(event) {
|
||||
if (event) {
|
||||
notify({ clean: true })
|
||||
isActive.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal modal-otp" v-bind:class="{ 'is-active': isActive }">
|
||||
<div class="modal-background" @click.stop="closeModal"></div>
|
||||
<div class="modal-content">
|
||||
<section class="section">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-three-quarters">
|
||||
<div class="modal-slot box has-text-centered is-shadowless">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div v-if="props.closable" class="fullscreen-footer">
|
||||
<!-- Close button -->
|
||||
<button id="btnClose" class="button is-rounded" :class="{'is-dark' : false}" @click.stop="closeModal">
|
||||
{{ $t('commons.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
7
resources/js_vue3/router/index.js
vendored
7
resources/js_vue3/router/index.js
vendored
@ -25,10 +25,11 @@ import SettingsOptions from '../views/settings/Options.vue'
|
||||
// import SettingsWebAuthn from './views/settings/WebAuthn.vue'
|
||||
// import EditCredential from './views/settings/Credentials/Edit.vue'
|
||||
// import GeneratePAT from './views/settings/PATokens/Create.vue'
|
||||
// import Errors from './views/Error.vue'
|
||||
import Errors from '../views/Error.vue'
|
||||
import About from '../views/About.vue'
|
||||
|
||||
import authGuard from './middlewares/authGuard'
|
||||
import noEmptyError from './middlewares/noEmptyError'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory('/'),
|
||||
@ -61,8 +62,8 @@ const router = createRouter({
|
||||
{ path: '/webauthn/lost', name: 'webauthn.lost', component: WebauthnLost, meta: { disabledWithAuthProxy: true, showAbout: true } },
|
||||
// { path: '/webauthn/recover', name: 'webauthn.recover', component: WebauthnRecover, meta: { disabledWithAuthProxy: true, showAbout: true } },
|
||||
|
||||
{ path: '/about', name: 'about',component: About, meta: { showAbout: true } },
|
||||
// { path: '/error', name: 'genericError',component: Errors, props: true },
|
||||
{ path: '/about', name: 'about', component: About, meta: { showAbout: true } },
|
||||
{ path: '/error', name: 'genericError', component: Errors, meta: { middlewares: [noEmptyError], err: null } },
|
||||
// { path: '/404', name: '404',component: Errors, props: true },
|
||||
// { path: '*', redirect: { name: '404' } },
|
||||
|
||||
|
8
resources/js_vue3/router/middlewares/noEmptyError.js
vendored
Normal file
8
resources/js_vue3/router/middlewares/noEmptyError.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export default function noEmptyError({ to, next }) {
|
||||
next()
|
||||
if (to.params.err == undefined) {
|
||||
// return to home if no err object is provided to prevent an empty error message
|
||||
next({ name: 'accounts' });
|
||||
}
|
||||
else next()
|
||||
}
|
67
resources/js_vue3/stores/notify.js
vendored
Normal file
67
resources/js_vue3/stores/notify.js
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import router from '@/router'
|
||||
const { notify } = useNotification()
|
||||
|
||||
export const useNotifyStore = defineStore({
|
||||
id: 'notify',
|
||||
|
||||
state: () => {
|
||||
return {
|
||||
err: null,
|
||||
message: null,
|
||||
originalMessage: null,
|
||||
debug: null,
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
},
|
||||
|
||||
actions: {
|
||||
parseError(err) {
|
||||
this.$reset
|
||||
this.err = err
|
||||
|
||||
// Hnalde axios response error
|
||||
if (err.response) {
|
||||
if (err.response.status === 407) {
|
||||
this.message = trans('errors.auth_proxy_failed'),
|
||||
this.originalMessage = trans('errors.auth_proxy_failed_legend')
|
||||
}
|
||||
else if (err.response.status === 403) {
|
||||
this.message = trans('errors.unauthorized'),
|
||||
this.originalMessage = trans('errors.unauthorized_legend')
|
||||
}
|
||||
else if(err.response.data) {
|
||||
this.message = err.response.data.message,
|
||||
this.originalMessage = err.response.data.originalMessage ?? null
|
||||
this.debug = err.response.data.debug ?? null
|
||||
}
|
||||
} else {
|
||||
this.message = err.message
|
||||
this.debug = err.stack ?? null
|
||||
}
|
||||
// else if (err.request) {
|
||||
|
||||
//
|
||||
},
|
||||
|
||||
error(err) {
|
||||
this.parseError(err)
|
||||
router.push({ name: 'genericError' })
|
||||
},
|
||||
|
||||
info(notification) {
|
||||
notify({ type: 'is-success', ...notification})
|
||||
},
|
||||
|
||||
warn(notification) {
|
||||
notify({ type: 'is-warning', ...notification})
|
||||
},
|
||||
|
||||
alert(notification) {
|
||||
notify({ type: 'is-danger', ...notification})
|
||||
},
|
||||
|
||||
},
|
||||
})
|
41
resources/js_vue3/views/Error.vue
Normal file
41
resources/js_vue3/views/Error.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
|
||||
const errorHandler = useNotifyStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const showModal = true
|
||||
const showDebug = computed(() => process.env.NODE_ENV === 'development')
|
||||
|
||||
const props = defineProps({
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
function exit() {
|
||||
window.history.length > 1 && route.name !== '404' ? router.go(-1) : router.push({ name: 'accounts' })
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="error-message">
|
||||
<modal v-model="showModal" :closable="props.closable" @modal-closed="exit">
|
||||
<div class="error-message" v-if="$route.name == '404'">
|
||||
<p class="error-404"></p>
|
||||
<p>{{ $t('errors.resource_not_found') }}</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="error-generic"></p>
|
||||
<p>{{ $t('errors.error_occured') }} </p>
|
||||
<p v-if="errorHandler.message" class="has-text-grey-lighter">{{ errorHandler.message }}</p>
|
||||
<p v-if="errorHandler.originalMessage" class="has-text-grey-lighter">{{ errorHandler.originalMessage }}</p>
|
||||
<p v-if="showDebug && errorHandler.debug" class="is-size-7 is-family-code"><br>{{ errorHandler.debug }}
|
||||
</p>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
@ -4,15 +4,16 @@
|
||||
// import { useStorage } from '@vueuse/core'
|
||||
import Form from '@/components/formElements/Form'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
// import { useRouter } from 'vue-router';
|
||||
// import { useNotification } from "@kyvg/vue3-notification";
|
||||
// import { trans } from 'laravel-vue-i18n';
|
||||
|
||||
const $2fauth = inject('2fauth')
|
||||
const { notify } = useNotification()
|
||||
const router = useRouter()
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const showWebauthnForm = user.preferences.useWebauthnOnly ? true : useStorage($2fauth.prefix + 'showWebauthnForm', true)
|
||||
const form = reactive(new Form({
|
||||
@ -43,10 +44,10 @@
|
||||
})
|
||||
.catch(error => {
|
||||
if( error.response.status === 401 ) {
|
||||
notify({ type: 'is-danger', text: trans('auth.forms.authentication_failed'), duration:-1 })
|
||||
notify.alert({text: trans('auth.forms.authentication_failed'), duration:5 })
|
||||
}
|
||||
else if( error.response.status !== 422 ) {
|
||||
router.push({ name: 'genericError', params: { err: error.response } });
|
||||
notify.error(error)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user