Add session persistence across refresh

This commit is contained in:
rijkvanzanten
2020-07-07 13:58:09 -04:00
parent 7768b317be
commit 78b5723526
3 changed files with 32 additions and 60 deletions

View File

@@ -1,6 +1,6 @@
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { useRequestsStore } from '@/stores/requests';
import { LogoutReason, logout, checkAuth } from '@/auth';
import { LogoutReason, logout, refresh } from '@/auth';
import getRootPath from '@/utils/get-root-path';
const api = axios.create({
@@ -53,12 +53,10 @@ export const onError = async (error: RequestError) => {
/* istanbul ignore next */
const code = error.response?.data?.error?.code;
if (status === 401 && code === 3) {
const loggedIn = await checkAuth();
/** @todo try refreshing the token */
if (loggedIn === false) {
if (status === 401 && code === 'INVALID_USER_CREDENTIALS') {
try {
await refresh();
} catch {
logout({ reason: LogoutReason.ERROR_SESSION_EXPIRED });
}
}

View File

@@ -2,20 +2,7 @@ import { RawLocation } from 'vue-router';
import api from '@/api';
import { hydrate, dehydrate } from '@/hydrate';
import router from '@/router';
/**
* Check if the current user is authenticated to the current project
*/
export async function checkAuth() {
/** @todo base this on existence of access token / response token */
return true;
// try {
// const response = await api.get(`/auth/check`);
// return response.data.data.authenticated;
// } catch {
// return false;
// }
}
import useAppStore from '@/stores/app';
export type LoginCredentials = {
email: string;
@@ -23,7 +10,9 @@ export type LoginCredentials = {
};
export async function login(credentials: LoginCredentials) {
const response = await api.post(`/auth/authenticate`, {
const appStore = useAppStore();
const response = await api.post(`/auth/login`, {
...credentials,
mode: 'cookie',
});
@@ -37,10 +26,14 @@ export async function login(credentials: LoginCredentials) {
// logged in without any noticable hickups or delays
setTimeout(() => refresh(), response.data.data.expires * 1000 + 10 * 1000);
appStore.state.authenticated = true;
await hydrate();
}
export async function refresh() {
export async function refresh({ navigate }: LogoutOptions = { navigate: true }) {
const appStore = useAppStore();
try {
const response = await api.post('/auth/refresh');
@@ -52,8 +45,9 @@ export async function refresh() {
// Refresh the token 10 seconds before the access token expires. This means the user will stay
// logged in without any noticable hickups or delays
setTimeout(() => refresh(), response.data.data.expires * 1000 + 10 * 1000);
appStore.state.authenticated = true;
} catch (error) {
await logout({ navigate: true, reason: LogoutReason.ERROR_SESSION_EXPIRED });
await logout({ navigate, reason: LogoutReason.ERROR_SESSION_EXPIRED });
}
}
@@ -71,6 +65,8 @@ export type LogoutOptions = {
* Everything that should happen when someone logs out, or is logged out through an external factor
*/
export async function logout(optionsRaw: LogoutOptions = {}) {
const appStore = useAppStore();
const defaultOptions: Required<LogoutOptions> = {
navigate: true,
reason: LogoutReason.SIGN_OUT,
@@ -83,6 +79,8 @@ export async function logout(optionsRaw: LogoutOptions = {}) {
await api.post(`/auth/logout`);
}
appStore.state.authenticated = false;
await dehydrate();
if (options.navigate === true) {

View File

@@ -3,7 +3,7 @@ import LoginRoute from '@/routes/login';
import InstallRoute from '@/routes/install';
import LogoutRoute from '@/routes/logout';
import ResetPasswordRoute from '@/routes/reset-password';
import { checkAuth } from '@/auth';
import { refresh } from '@/auth';
import { hydrate } from '@/hydrate';
import useAppStore from '@/stores/app';
import useUserStore from '@/stores/user';
@@ -96,46 +96,22 @@ export const onBeforeEach: NavigationGuard = async (to, from, next) => {
const appStore = useAppStore();
const settingsStore = useSettingsStore();
// First load
if (from.name === null) {
// Try retrieving a fresh access token on first load
try {
await refresh({ navigate: false });
} catch {}
}
if (settingsStore.state.settings === null) {
await settingsStore.hydrate();
}
// When there aren't any projects, we should redirect to the install page to force the
// user to setup a project.
/** @todo base this on another flag*/
// if (projectsStore.state.needsInstall === true && to.path !== '/install') {
// return next('/install');
// }
// Keep the projects store currentProjectKey in sync with the route
// If we switch projects to a public route, we don't need the store to be hyrdated
// if (to.params.project && projectsStore.state.currentProjectKey !== to.params.project) {
// // If the store is hydrated for the current project, make sure to dehydrate it
// if (to.meta?.public !== true && appStore.state.hydrated === true) {
// appStore.state.hydrating = true;
// await dehydrate();
// }
// const projectExists = await projectsStore.setCurrentProject(to.params.project);
// // If the project you're trying to access doesn't exist, redirect to `/`
// if (to.path !== '/' && projectExists === false) {
// return next('/');
// }
// }
// The store can only be hydrated if you're an authenticated user. If the store is hydrated, we
// can safely assume you're logged in
if (to.meta?.public !== true && appStore.state.hydrated === false) {
const authenticated = await checkAuth();
if (authenticated === true) {
appStore.state.hydrating = false;
await hydrate();
} else if (to.meta?.public !== true) {
appStore.state.hydrating = false;
return next(`/login`);
}
appStore.state.hydrating = false;
if (appStore.state.authenticated) await hydrate();
else return next('/login');
}
return next();