From 3ddb47ac029c2f711443c8fb4e1a677edadf5326 Mon Sep 17 00:00:00 2001 From: Ivan Bondarenko Date: Thu, 2 Jul 2026 19:52:15 +0200 Subject: [PATCH] fix: clear cached recipe actions on logout to prevent cross-user leak (#7815) Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com> Co-authored-by: Michael Genson --- frontend/app/composables/recipes/use-recipes.ts | 5 +++++ frontend/app/composables/use-auth-backend.ts | 15 +++++++++++---- frontend/app/composables/use-backups.ts | 7 +++++++ .../composables/use-clear-composable-caches.ts | 17 +++++++++++++++++ .../app/composables/use-group-recipe-actions.ts | 8 +++++--- frontend/app/composables/use-groups.ts | 5 +++++ frontend/app/composables/use-households.ts | 5 +++++ .../app/composables/use-users/user-ratings.ts | 6 ++++++ .../use-users/user-registration-form.ts | 10 ++++++++++ 9 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 frontend/app/composables/use-clear-composable-caches.ts diff --git a/frontend/app/composables/recipes/use-recipes.ts b/frontend/app/composables/recipes/use-recipes.ts index f89780622..15b307638 100644 --- a/frontend/app/composables/recipes/use-recipes.ts +++ b/frontend/app/composables/recipes/use-recipes.ts @@ -8,6 +8,11 @@ import type { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe"; export const allRecipes = ref([]); export const recentRecipes = ref([]); +export function resetRecipes() { + allRecipes.value = []; + recentRecipes.value = []; +} + function getParams( orderBy: string | null = null, orderDirection = "desc", diff --git a/frontend/app/composables/use-auth-backend.ts b/frontend/app/composables/use-auth-backend.ts index 48bbdca94..5f43a4c1c 100644 --- a/frontend/app/composables/use-auth-backend.ts +++ b/frontend/app/composables/use-auth-backend.ts @@ -1,6 +1,7 @@ import { ref, computed } from "vue"; import type { UserOut } from "~/lib/api/types/user"; import { clearAllStores } from "~/composables/store"; +import { clearComposableCaches } from "~/composables/use-clear-composable-caches"; import { getTokenCookieOptions } from "~/composables/use-token-cookie"; interface AuthData { @@ -25,6 +26,11 @@ interface AuthState { const authUser = ref(null); const authStatus = ref<"loading" | "authenticated" | "unauthenticated">("loading"); +export function resetAuth() { + authUser.value = null; + authStatus.value = "unauthenticated"; +} + export const useAuthBackend = function (): AuthState { const { $axios } = useNuxtApp(); const router = useRouter(); @@ -41,8 +47,7 @@ export const useAuthBackend = function (): AuthState { // Only clear token on auth errors, not network errors if (error?.response?.status === 401) { setToken(null); - authUser.value = null; - authStatus.value = "unauthenticated"; + resetAuth(); if (redirect) { router.push("/login"); } @@ -99,12 +104,14 @@ export const useAuthBackend = function (): AuthState { } finally { setToken(null); - authUser.value = null; - authStatus.value = "unauthenticated"; + resetAuth(); // Clear all cached store data to prevent data leakage between users clearAllStores(); + // Clear cached composable refs to prevent data leakage between users + clearComposableCaches(); + // Clear Nuxt's useAsyncData cache clearNuxtData(); diff --git a/frontend/app/composables/use-backups.ts b/frontend/app/composables/use-backups.ts index fbcbed6c4..b66cbea9d 100644 --- a/frontend/app/composables/use-backups.ts +++ b/frontend/app/composables/use-backups.ts @@ -12,6 +12,13 @@ const backups = ref({ templates: [], }); +export function resetBackups() { + backups.value = { + imports: [], + templates: [], + }; +} + function setBackups(newBackups: AllBackups | null) { if (newBackups) { backups.value = newBackups; diff --git a/frontend/app/composables/use-clear-composable-caches.ts b/frontend/app/composables/use-clear-composable-caches.ts new file mode 100644 index 000000000..28a50a52a --- /dev/null +++ b/frontend/app/composables/use-clear-composable-caches.ts @@ -0,0 +1,17 @@ +import { resetGroupRecipeActions } from "~/composables/use-group-recipe-actions"; +import { resetGroupSelf } from "~/composables/use-groups"; +import { resetHouseholdSelf } from "~/composables/use-households"; +import { resetUserSelfRatings } from "~/composables/use-users/user-ratings"; +import { resetBackups } from "~/composables/use-backups"; +import { resetRecipes } from "~/composables/recipes/use-recipes"; +import { resetUserRegistrationForm } from "~/composables/use-users/user-registration-form"; + +export function clearComposableCaches() { + resetGroupRecipeActions(); + resetGroupSelf(); + resetHouseholdSelf(); + resetUserSelfRatings(); + resetBackups(); + resetRecipes(); + resetUserRegistrationForm(); +} diff --git a/frontend/app/composables/use-group-recipe-actions.ts b/frontend/app/composables/use-group-recipe-actions.ts index 90ff6d3c0..e875c7786 100644 --- a/frontend/app/composables/use-group-recipe-actions.ts +++ b/frontend/app/composables/use-group-recipe-actions.ts @@ -7,6 +7,11 @@ import type { Recipe } from "~/lib/api/types/recipe"; const groupRecipeActions = ref(null); const loading = ref(false); +export function resetGroupRecipeActions() { + groupRecipeActions.value = null; + loading.value = false; +} + export function useGroupRecipeActionData() { const data = reactive({ id: "", @@ -85,9 +90,6 @@ export const useGroupRecipeActions = function ( loading, { orderBy: orderBy }, ), - flushStore() { - groupRecipeActions.value = []; - }, }; return { diff --git a/frontend/app/composables/use-groups.ts b/frontend/app/composables/use-groups.ts index 9b9326255..944882879 100644 --- a/frontend/app/composables/use-groups.ts +++ b/frontend/app/composables/use-groups.ts @@ -4,6 +4,11 @@ import type { GroupBase, GroupInDB, GroupSummary } from "~/lib/api/types/user"; const groupSelfRef = ref(null); const loading = ref(false); +export function resetGroupSelf() { + groupSelfRef.value = null; + loading.value = false; +} + export const useGroupSelf = function () { const api = useUserApi(); const auth = useMealieAuth(); diff --git a/frontend/app/composables/use-households.ts b/frontend/app/composables/use-households.ts index 9ca7edd2f..41dcbbe0c 100644 --- a/frontend/app/composables/use-households.ts +++ b/frontend/app/composables/use-households.ts @@ -4,6 +4,11 @@ import type { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household"; const householdSelfRef = ref(null); const loading = ref(false); +export function resetHouseholdSelf() { + householdSelfRef.value = null; + loading.value = false; +} + export const useHouseholdSelf = function () { const api = useUserApi(); const auth = useMealieAuth(); diff --git a/frontend/app/composables/use-users/user-ratings.ts b/frontend/app/composables/use-users/user-ratings.ts index 33e9cd054..c745ebe4e 100644 --- a/frontend/app/composables/use-users/user-ratings.ts +++ b/frontend/app/composables/use-users/user-ratings.ts @@ -5,6 +5,12 @@ const userRatings = ref([]); const loading = ref(false); const ready = ref(false); +export function resetUserSelfRatings() { + userRatings.value = []; + loading.value = false; + ready.value = false; +} + export const useUserSelfRatings = function () { const auth = useMealieAuth(); diff --git a/frontend/app/composables/use-users/user-registration-form.ts b/frontend/app/composables/use-users/user-registration-form.ts index be24ffd84..22c87f24d 100644 --- a/frontend/app/composables/use-users/user-registration-form.ts +++ b/frontend/app/composables/use-users/user-registration-form.ts @@ -10,6 +10,16 @@ const password1 = ref(""); const password2 = ref(""); const advancedOptions = ref(false); +export function resetUserRegistrationForm() { + domAccountForm.value = null; + username.value = ""; + fullName.value = ""; + email.value = ""; + password1.value = ""; + password2.value = ""; + advancedOptions.value = false; +} + export const useUserRegistrationForm = () => { const i18n = useI18n();