fix: expired token issue

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-07-05 11:36:20 -04:00
parent 502be95d7d
commit 6931dc9ff7
5 changed files with 33 additions and 23 deletions
+5 -5
View File
@@ -29,7 +29,7 @@ class DefaultController extends ControllerAbstract
return new FileResponse(
$this->rootDir . '/public/private.html',
Response::HTTP_OK,
['Content-Type' => 'text/html']
['Content-Type' => 'text/html', 'Cache-Control' => 'no-store']
);
}
@@ -38,7 +38,7 @@ class DefaultController extends ControllerAbstract
$response = new FileResponse(
$this->rootDir . '/public/public.html',
Response::HTTP_OK,
['Content-Type' => 'text/html']
['Content-Type' => 'text/html', 'Cache-Control' => 'no-store']
);
// Clear any stale auth cookies since the user is not authenticated
@@ -58,7 +58,7 @@ class DefaultController extends ControllerAbstract
return new FileResponse(
$this->rootDir . '/public/public.html',
Response::HTTP_OK,
['Content-Type' => 'text/html']
['Content-Type' => 'text/html', 'Cache-Control' => 'no-store']
);
}
@@ -120,7 +120,7 @@ class DefaultController extends ControllerAbstract
return new FileResponse(
$this->rootDir . '/public/private.html',
Response::HTTP_OK,
['Content-Type' => 'text/html']
['Content-Type' => 'text/html', 'Cache-Control' => 'no-store']
);
}
@@ -128,7 +128,7 @@ class DefaultController extends ControllerAbstract
$response = new FileResponse(
$this->rootDir . '/public/public.html',
Response::HTTP_OK,
['Content-Type' => 'text/html']
['Content-Type' => 'text/html', 'Cache-Control' => 'no-store']
);
// Clear any stale auth cookies since the user is not authenticated
+7
View File
@@ -8,6 +8,7 @@ import { useModuleStore } from '@KTXC/stores/moduleStore'
import { useTenantStore } from '@KTXC/stores/tenantStore'
import { useUserStore } from '@KTXC/stores/userStore'
import { fetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper'
import { FetchError } from '@KTXC/utils/helpers/fetch-wrapper-core'
import { initializeModules } from '@KTXC/utils/modules'
import { createSessionMonitor } from '@KTXC/services/authManager'
import App from './App.vue'
@@ -62,5 +63,11 @@ globalWindow.Pinia = PiniaLib as unknown
app.mount('#app');
} catch (e) {
console.error('Bootstrap failed:', e);
// The private shell is unusable without /init. If the server rejected
// the session, drop the stale local identity and return to login.
if (e instanceof FetchError && (e.status === 401 || e.status === 403)) {
userStore.clearAuth();
window.location.href = '/login';
}
}
})();
+6 -15
View File
@@ -2,16 +2,18 @@ import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import { router } from './router';
import { AUTH_STORAGE_KEY } from '@KTXC/stores/userStore';
import vuetify from './plugins/vuetify/index';
// Material Design Icons (Vuetify mdi icon set)
import '@mdi/font/css/materialdesignicons.min.css';
import '@fontsource/public-sans/index.css'
// The public app is served when the user has no valid server session.
// Clear any stale identity data from localStorage to ensure the client
// state matches the server's determination that the user is unauthenticated.
//localStorage.removeItem('identityStore.self');
// The public app is only served when the server has determined the user is
// unauthenticated. Clear any stale identity persisted by a previous session,
// otherwise the shared router guard would treat the user as authenticated
// and render the private shell without any of its state loaded.
localStorage.removeItem(AUTH_STORAGE_KEY);
const app = createApp(App);
const pinia = createPinia();
@@ -19,15 +21,4 @@ app.use(pinia);
app.use(router);
app.use(vuetify);
// Wait for router to be ready, then ensure we're on a public route
//router.isReady().then(() => {
// If the current route requires auth, redirect to login
// This handles the case where user navigates to / with an expired session
//const currentRoute = router.currentRoute.value;
//const requiresAuth = currentRoute.matched.some(record => record.meta?.requiresAuth);
//if (requiresAuth || currentRoute.path === '/') {
// router.replace('/login');
//}
//});
app.mount('#app');
+2 -1
View File
@@ -8,7 +8,8 @@ import type { UserSettingsInterface } from '@KTXC/types/user/userSettingsTypes';
import { UserProfile } from '@KTXC/models/userProfile';
import { UserSettings } from '@KTXC/models/userSettings';
const STORAGE_KEY = 'userStore.auth';
export const AUTH_STORAGE_KEY = 'userStore.auth';
const STORAGE_KEY = AUTH_STORAGE_KEY;
// Flush pending updates before page unload
if (typeof window !== 'undefined') {
+13 -2
View File
@@ -22,6 +22,14 @@ export interface RequestCallOptions {
headers?: Record<string, string>;
}
/** Error thrown for non-2xx responses, carrying the HTTP status code */
export class FetchError extends Error {
constructor(message: string, public readonly status: number) {
super(message);
this.name = 'FetchError';
}
}
function getCsrfToken(): string | null {
if (typeof document === 'undefined') return null;
const cookie = document.cookie.split('; ').find(r => r.startsWith('X-CSRF-TOKEN='));
@@ -61,8 +69,11 @@ export function createFetchWrapper(options: FetchWrapperOptions = {}) {
if (!response.ok) {
const text = await response.text();
const data = text ? JSON.parse(text) : null;
throw new Error(data?.message || response.statusText);
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch { /* non-JSON error body */ }
throw new FetchError(data?.message || response.statusText, response.status);
}
if (callOptions?.onStream) {