Rework logout flow + add no app access state

This commit is contained in:
rijkvanzanten
2020-07-15 16:29:43 -04:00
parent 5448b4fa76
commit 325502a7b1
9 changed files with 27 additions and 67 deletions

View File

@@ -53,11 +53,11 @@ export const onError = async (error: RequestError) => {
/* istanbul ignore next */
const code = error.response?.data?.error?.code;
if (status === 401 && code === 'INVALID_CREDENTIALS') {
if (status === 401 && code === 'INVALID_CREDENTIALS' && error.response?.config?.url !== '/auth/refresh') {
try {
await refresh();
/** @todo retry failed request */
/** @todo retry failed request after successful refresh */
} catch {
logout({ reason: LogoutReason.ERROR_SESSION_EXPIRED });
}

View File

@@ -5,7 +5,16 @@
<v-progress-circular indeterminate />
</div>
</transition>
<router-view v-if="!hydrating" />
<router-view v-if="!hydrating && appAccess" />
<v-info v-else-if="appAccess === false" center :title="$t('no_app_access')" type="danger" icon="block">
{{ $t('no_app_access_copy') }}
<template #append>
<v-button to="/logout">Switch User</v-button>
</template>
</v-info>
<portal-target name="outlet" multiple />
</div>
@@ -69,7 +78,12 @@ export default defineComponent({
}
);
return { hydrating, brandStyle };
const appAccess = computed(() => {
if (!userStore.state.currentUser) return true;
return userStore.state.currentUser?.role?.app_access;
});
return { hydrating, brandStyle, appAccess };
},
});
</script>

View File

@@ -348,6 +348,9 @@
"unexpected_error_copy": "Something went wrong.. Please try again later.",
"copy_details": "Copy Details",
"no_app_access": "No App Access",
"no_app_access_copy": "This user isn't allowed to use the admin app.",
"password_reset_sent": "We've sent you a secure link to reset your password",
"password_reset_successful": "Password successfully reset",
"create_project": "Create Project",

View File

@@ -1,7 +1,6 @@
import VueRouter, { NavigationGuard, RouteConfig, Route } from 'vue-router';
import LoginRoute from '@/routes/login';
import InstallRoute from '@/routes/install';
import LogoutRoute from '@/routes/logout';
import ResetPasswordRoute from '@/routes/reset-password';
import { refresh } from '@/auth';
import { hydrate } from '@/hydrate';
@@ -9,6 +8,7 @@ import useAppStore from '@/stores/app';
import useUserStore from '@/stores/user';
import PrivateNotFoundRoute from '@/routes/private-not-found';
import useSettingsStore from '@/stores/settings';
import { logout } from '@/auth';
import getRootPath from '@/utils/get-root-path';
@@ -50,7 +50,10 @@ export const defaultRoutes: RouteConfig[] = [
{
name: 'logout',
path: '/logout',
component: LogoutRoute,
async beforeEnter(to, from, next) {
await logout({ navigate: false });
next('/login');
},
meta: {
public: true,
},

View File

@@ -1,4 +0,0 @@
import LogoutRoute from './logout.vue';
export { LogoutRoute };
export default LogoutRoute;

View File

@@ -1,23 +0,0 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import LogoutRoute from './logout.vue';
import { logout } from '@/auth';
import VCircularProgress from '@/components/v-progress/circular/';
import VueCompositionAPI from '@vue/composition-api';
jest.mock('@/auth');
const localVue = createLocalVue();
localVue.component('v-progress-circular', VCircularProgress);
localVue.use(VueCompositionAPI);
describe('Routes / Logout', () => {
it('Calls logout on mount', async () => {
const component = shallowMount(LogoutRoute, {
localVue,
});
await (component.vm as any).$nextTick();
expect(logout).toHaveBeenCalled();
});
});

View File

@@ -1,27 +0,0 @@
<template>
<div class="logout">
<v-progress-circular indeterminate />
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted } from '@vue/composition-api';
import { logout } from '@/auth';
export default defineComponent({
setup() {
onMounted(() => {
logout();
});
},
});
</script>
<style lang="scss" scoped>
.logout {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>

View File

@@ -1,7 +0,0 @@
# Logout View
Renders an empty page with a spinner, and logs the user out. Once the user is logged out, it will redirect to login.
## Why a separate route?
If we were to logout on any other route that uses the private view, it would mean that there will be a bunch of errors in the devtools, as a lot of things will be trying to read from the store, which will be cleared. This extra route makes sure we can safely empty the store without any component still reading from it.

View File

@@ -20,6 +20,7 @@ export type Role = {
enforce_2fa: null | boolean;
external_id: null | string;
ip_whitelist: string[];
app_access: boolean;
};
export type Avatar = {