mirror of
https://github.com/directus/directus.git
synced 2026-01-29 14:48:02 -05:00
Rework logout flow + add no app access state
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
|
||||
18
src/app.vue
18
src/app.vue
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import LogoutRoute from './logout.vue';
|
||||
|
||||
export { LogoutRoute };
|
||||
export default LogoutRoute;
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -20,6 +20,7 @@ export type Role = {
|
||||
enforce_2fa: null | boolean;
|
||||
external_id: null | string;
|
||||
ip_whitelist: string[];
|
||||
app_access: boolean;
|
||||
};
|
||||
|
||||
export type Avatar = {
|
||||
|
||||
Reference in New Issue
Block a user