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,
},
includeKitchen: false,
skipAuthFailureHandler: true,
});
}
} finally {
+38
View File
@@ -183,6 +183,32 @@ function logApiFailure(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 = {}) {
const { config, session, activeKitchen } = store;
@@ -240,6 +266,9 @@ export async function apiRequest(store, path, options = {}) {
method,
error,
});
if (shouldInvalidateValidatedSession(store, path, options)) {
window.__loncApp?.handleAuthFailure?.(networkError);
}
throw networkError;
}
@@ -252,6 +281,15 @@ export async function apiRequest(store, path, options = {}) {
status: response.status,
payload,
});
if (
isAuthErrorStatus(response.status) &&
(
shouldInvalidateValidatedSession(store, path, options) ||
(store.session?.state === 'connected' && response.status === 403 && isKitchensPath(path))
)
) {
window.__loncApp?.handleAuthFailure?.(apiError);
}
throw apiError;
}
+29
View File
@@ -44,6 +44,7 @@ export function bootstrapApp() {
store,
outlet: document.querySelector('#route-view'),
});
let authFailureHandled = false;
function applyKitchens(kitchens) {
store.setKitchens(kitchens);
@@ -67,6 +68,9 @@ export function bootstrapApp() {
} else if (store.isConnected) {
await window.__loncApp.refreshKitchens();
}
if (store.isConnected) {
authFailureHandled = false;
}
renderNav();
} catch (error) {
renderNav();
@@ -82,11 +86,36 @@ export function bootstrapApp() {
} else if (store.isConnected) {
await window.__loncApp.refreshKitchens();
}
if (store.isConnected) {
authFailureHandled = false;
}
renderNav();
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() {
await logout(store);
authFailureHandled = false;
renderNav();
navigate('/login');
},
+13
View File
@@ -108,5 +108,18 @@ export function createAppStore() {
this.setKitchens([]);
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(),
},
async init() {
if (!store.isConnected) {
return;
}
await this.loadLocations();
this.$watch('form', () => this.persistDraft(), { deep: true });
this.$watch('form.stockType', (value) => {
@@ -536,6 +539,10 @@ export function labelCreatePageData(store) {
}, 250);
},
async loadLocations() {
if (!store.isConnected) {
return;
}
try {
const { flat } = await fetchLocations(store);
this.locations = flat;
+4
View File
@@ -152,6 +152,10 @@ export function stockDetailPageData(store) {
level: 'plenty',
},
async init() {
if (!store.isConnected) {
return;
}
const { params } = getRouteContext();
await runAsyncState(this.state, async () => {
this.entry = await getStockEntry(store, params.id);
+11
View File
@@ -442,9 +442,16 @@ export function stockListPageData(store) {
location: '',
},
async init() {
if (!store.isConnected) {
return;
}
await Promise.all([this.loadLocations(), this.loadEntries()]);
},
async loadEntries() {
if (!store.isConnected) {
return;
}
await runAsyncState(this.state, async () => {
const loadedEntries = await listStockEntries(store);
this.entries = sortEntries(loadedEntries);
@@ -461,6 +468,10 @@ export function stockListPageData(store) {
}).catch(() => {});
},
async loadLocations() {
if (!store.isConnected) {
return;
}
try {
const { flat } = await fetchLocations(store);
this.locations = flat;