Handle invalidated application keys after auth failures

This commit is contained in:
2026-04-06 18:31:31 +02:00
parent 35c30120b8
commit 34664be951
7 changed files with 103 additions and 0 deletions
+1
View File
@@ -67,6 +67,7 @@ export async function logout(store) {
application: TRYTON_APPLICATION, application: TRYTON_APPLICATION,
}, },
includeKitchen: false, includeKitchen: false,
skipAuthFailureHandler: true,
}); });
} }
} finally { } finally {
+38
View File
@@ -183,6 +183,32 @@ function logApiFailure(message, context) {
console.error(message, context); console.error(message, context);
} }
function isAuthErrorStatus(status) {
return status === 401 || status === 403;
}
function isKitchensPath(path) {
return String(path || '').replace(/^\/+/, '').replace(/\/+$/, '') === API_PATHS.kitchens;
}
function shouldInvalidateValidatedSession(store, path, options = {}) {
if (options.skipAuthFailureHandler) {
return false;
}
if (!store.session?.applicationKey || !store.session?.hasValidated) {
return false;
}
return (
isKitchensPath(path) ||
options.includeKitchen !== false ||
path === API_PATHS.items ||
path === API_PATHS.locations ||
String(path || '').startsWith(`${API_PATHS.items}/`)
);
}
export async function apiRequest(store, path, options = {}) { export async function apiRequest(store, path, options = {}) {
const { config, session, activeKitchen } = store; const { config, session, activeKitchen } = store;
@@ -240,6 +266,9 @@ export async function apiRequest(store, path, options = {}) {
method, method,
error, error,
}); });
if (shouldInvalidateValidatedSession(store, path, options)) {
window.__loncApp?.handleAuthFailure?.(networkError);
}
throw networkError; throw networkError;
} }
@@ -252,6 +281,15 @@ export async function apiRequest(store, path, options = {}) {
status: response.status, status: response.status,
payload, payload,
}); });
if (
isAuthErrorStatus(response.status) &&
(
shouldInvalidateValidatedSession(store, path, options) ||
(store.session?.state === 'connected' && response.status === 403 && isKitchensPath(path))
)
) {
window.__loncApp?.handleAuthFailure?.(apiError);
}
throw apiError; throw apiError;
} }
+29
View File
@@ -44,6 +44,7 @@ export function bootstrapApp() {
store, store,
outlet: document.querySelector('#route-view'), outlet: document.querySelector('#route-view'),
}); });
let authFailureHandled = false;
function applyKitchens(kitchens) { function applyKitchens(kitchens) {
store.setKitchens(kitchens); store.setKitchens(kitchens);
@@ -67,6 +68,9 @@ export function bootstrapApp() {
} else if (store.isConnected) { } else if (store.isConnected) {
await window.__loncApp.refreshKitchens(); await window.__loncApp.refreshKitchens();
} }
if (store.isConnected) {
authFailureHandled = false;
}
renderNav(); renderNav();
} catch (error) { } catch (error) {
renderNav(); renderNav();
@@ -82,11 +86,36 @@ export function bootstrapApp() {
} else if (store.isConnected) { } else if (store.isConnected) {
await window.__loncApp.refreshKitchens(); await window.__loncApp.refreshKitchens();
} }
if (store.isConnected) {
authFailureHandled = false;
}
renderNav(); renderNav();
return result; return result;
}, },
handleAuthFailure(error) {
if (!store.session?.applicationKey || !store.session?.hasValidated || authFailureHandled) {
return;
}
authFailureHandled = true;
store.markSessionInvalid();
renderNav();
const status = error?.status || error?.cause?.status;
const message =
status === 401 || status === 403
? 'This application key is no longer accepted by Tryton. Please verify it again or disconnect and create a new key.'
: 'Authenticated requests are no longer succeeding. The application key may have been cancelled, or access is being denied by the server. Please reconnect or create a new key.';
store.addAlert({
type: 'warning',
timeout: 0,
message,
});
navigate('/login');
window.setTimeout(() => router.render(), 0);
},
async logout() { async logout() {
await logout(store); await logout(store);
authFailureHandled = false;
renderNav(); renderNav();
navigate('/login'); navigate('/login');
}, },
+13
View File
@@ -108,5 +108,18 @@ export function createAppStore() {
this.setKitchens([]); this.setKitchens([]);
this.setActiveKitchen(null); this.setActiveKitchen(null);
}, },
markSessionInvalid() {
if (!this.session) {
return;
}
this.setSession({
...this.session,
state: CONNECTION_STATES.invalidKey,
hasValidated: true,
});
this.setKitchens([]);
this.setActiveKitchen(null);
},
}; };
} }
+7
View File
@@ -515,6 +515,9 @@ export function labelCreatePageData(store) {
...loadLabelDraft(), ...loadLabelDraft(),
}, },
async init() { async init() {
if (!store.isConnected) {
return;
}
await this.loadLocations(); await this.loadLocations();
this.$watch('form', () => this.persistDraft(), { deep: true }); this.$watch('form', () => this.persistDraft(), { deep: true });
this.$watch('form.stockType', (value) => { this.$watch('form.stockType', (value) => {
@@ -536,6 +539,10 @@ export function labelCreatePageData(store) {
}, 250); }, 250);
}, },
async loadLocations() { async loadLocations() {
if (!store.isConnected) {
return;
}
try { try {
const { flat } = await fetchLocations(store); const { flat } = await fetchLocations(store);
this.locations = flat; this.locations = flat;
+4
View File
@@ -152,6 +152,10 @@ export function stockDetailPageData(store) {
level: 'plenty', level: 'plenty',
}, },
async init() { async init() {
if (!store.isConnected) {
return;
}
const { params } = getRouteContext(); const { params } = getRouteContext();
await runAsyncState(this.state, async () => { await runAsyncState(this.state, async () => {
this.entry = await getStockEntry(store, params.id); this.entry = await getStockEntry(store, params.id);
+11
View File
@@ -442,9 +442,16 @@ export function stockListPageData(store) {
location: '', location: '',
}, },
async init() { async init() {
if (!store.isConnected) {
return;
}
await Promise.all([this.loadLocations(), this.loadEntries()]); await Promise.all([this.loadLocations(), this.loadEntries()]);
}, },
async loadEntries() { async loadEntries() {
if (!store.isConnected) {
return;
}
await runAsyncState(this.state, async () => { await runAsyncState(this.state, async () => {
const loadedEntries = await listStockEntries(store); const loadedEntries = await listStockEntries(store);
this.entries = sortEntries(loadedEntries); this.entries = sortEntries(loadedEntries);
@@ -461,6 +468,10 @@ export function stockListPageData(store) {
}).catch(() => {}); }).catch(() => {});
}, },
async loadLocations() { async loadLocations() {
if (!store.isConnected) {
return;
}
try { try {
const { flat } = await fetchLocations(store); const { flat } = await fetchLocations(store);
this.locations = flat; this.locations = flat;