Skip to content

Commit 4fe6241

Browse files
committed
fix: fixed the cache reordering issue and fixed the inter-user cache data leak issue.
1 parent 6a97527 commit 4fe6241

3 files changed

Lines changed: 52 additions & 11 deletions

File tree

infrastructure/eid-wallet/src/lib/utils/photoCache.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface CachedPhoto {
1717
metaEnvelopeId: string;
1818
dataUrl: string;
1919
description: string;
20+
ename: string;
2021
}
2122

2223
const store = localforage.createInstance({
@@ -38,6 +39,23 @@ export async function getAllCachedPhotos(): Promise<CachedPhoto[]> {
3839
}
3940
}
4041

42+
/** Return cached photos belonging to a specific ename. */
43+
export async function getCachedPhotosForEname(
44+
ename: string,
45+
): Promise<CachedPhoto[]> {
46+
const all = await getAllCachedPhotos();
47+
return all.filter((p) => p.ename === ename);
48+
}
49+
50+
/** Remove all cached photos. Call on logout to prevent cross-user data leaks. */
51+
export async function clearAllCachedPhotos(): Promise<void> {
52+
try {
53+
await store.clear();
54+
} catch {
55+
// non-fatal
56+
}
57+
}
58+
4159
/** Insert or update a single photo entry. */
4260
export async function setCachedPhoto(photo: CachedPhoto): Promise<void> {
4361
try {
@@ -66,7 +84,9 @@ export async function replaceAllCachedPhotos(
6684
): Promise<void> {
6785
try {
6886
await store.clear();
69-
await Promise.all(photos.map((p) => store.setItem(p.metaEnvelopeId, p)));
87+
await Promise.all(
88+
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
89+
);
7090
} catch {
7191
// non-fatal
7292
}

infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
} from "$lib/utils/personalBinding";
2525
import {
2626
deleteCachedPhoto,
27-
getAllCachedPhotos,
27+
getCachedPhotosForEname,
2828
setCachedPhoto,
2929
} from "$lib/utils/photoCache";
3030
import { Delete02Icon, PencilEdit02Icon } from "@hugeicons/core-free-icons";
@@ -36,6 +36,11 @@ import AddPhotoSheet from "./components/AddPhotoSheet.svelte";
3636
3737
const binding = $derived($personalBinding);
3838
const achieved = $derived(marksAchieved(binding));
39+
const sortedPhotos = $derived(
40+
[...binding.photos].sort((a, b) =>
41+
(a.metaEnvelopeId ?? a.id).localeCompare(b.metaEnvelopeId ?? b.id),
42+
),
43+
);
3944
const photosFilled = $derived(binding.photos.length > 0);
4045
const parametersFilled = $derived(
4146
!!binding.parameters && binding.parameters.text.trim().length > 0,
@@ -98,7 +103,7 @@ onMount(() => {
98103
// This runs before the network requests so photos are already
99104
// in the store when loading = false fires and the UI becomes
100105
// visible — no skeleton flash on repeat visits.
101-
const cachedPhotos = await getAllCachedPhotos();
106+
const cachedPhotos = await getCachedPhotosForEname(ename);
102107
if (cachedPhotos.length > 0) {
103108
replaceAll({
104109
...$personalBinding,
@@ -118,26 +123,38 @@ onMount(() => {
118123
// base64 blobs and arrive later — they update the store and
119124
// refresh the cache when done.
120125
const paramsPromise = loadPersonalParameters(gqlUrl, ename).then(
121-
(params) => { setParametersLocal(params); },
122-
);
123-
const securityPromise = loadPersonalSecurityQuestion(gqlUrl, ename).then(
124-
(security) => { setKnowledgeLocal(security); },
126+
(params) => {
127+
setParametersLocal(params);
128+
},
125129
);
130+
const securityPromise = loadPersonalSecurityQuestion(
131+
gqlUrl,
132+
ename,
133+
).then((security) => {
134+
setKnowledgeLocal(security);
135+
});
126136
const photosPromise = loadPersonalPhotographs(gqlUrl, ename).then(
127137
async (photographs) => {
138+
if (!ename) return;
128139
const current = $personalBinding;
129140
130141
// Strip photos that the user deleted during this session
131142
// so a slow server response never resurrects them.
132143
const visibleFromServer = photographs.filter(
133144
(p) => !locallyDeletedIds.has(p.metaEnvelopeId),
134145
);
135-
const visibleIds = new Set(visibleFromServer.map((p) => p.metaEnvelopeId));
146+
const visibleIds = new Set(
147+
visibleFromServer.map((p) => p.metaEnvelopeId),
148+
);
136149
137150
// Photos in the store not in the server response were
138151
// uploaded after this fetch started — keep them.
139152
const localPending = current.photos.filter(
140-
(p) => p.dataUrl && p.metaEnvelopeId !== null && !visibleIds.has(p.metaEnvelopeId) && !locallyDeletedIds.has(p.metaEnvelopeId),
153+
(p) =>
154+
p.dataUrl &&
155+
p.metaEnvelopeId !== null &&
156+
!visibleIds.has(p.metaEnvelopeId) &&
157+
!locallyDeletedIds.has(p.metaEnvelopeId),
141158
);
142159
143160
replaceAll({
@@ -161,6 +178,7 @@ onMount(() => {
161178
metaEnvelopeId: p.metaEnvelopeId,
162179
dataUrl: p.photoBlob,
163180
description: p.description,
181+
ename: ename,
164182
}),
165183
),
166184
);
@@ -170,7 +188,7 @@ onMount(() => {
170188
const localPendingIds = new Set(
171189
localPending.map((p) => p.metaEnvelopeId),
172190
);
173-
const allCached = await getAllCachedPhotos();
191+
const allCached = await getCachedPhotosForEname(ename);
174192
await Promise.all(
175193
allCached
176194
.filter(
@@ -267,6 +285,7 @@ async function handlePhotoSave(data: {
267285
metaEnvelopeId: id,
268286
dataUrl: data.dataUrl,
269287
description: data.description,
288+
ename: ename ?? "",
270289
});
271290
editingPhoto = null;
272291
@@ -434,7 +453,7 @@ async function handleKnowledgeSave(data: {
434453
{#snippet photosBody()}
435454
{#if binding.photos.length > 0 || loading}
436455
<ul class="flex flex-col gap-3 mt-3">
437-
{#each binding.photos as photo (photo.id)}
456+
{#each sortedPhotos as photo (photo.id)}
438457
{#if photo.dataUrl}
439458
<li class="flex items-center gap-3">
440459
<img

infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
subscribe as subscribeLanguage,
99
} from "$lib/stores/language";
1010
import { clearAllNotifications } from "$lib/stores/notifications";
11+
import { clearAllCachedPhotos } from "$lib/utils/photoCache";
1112
import { BottomSheet, ButtonAction } from "$lib/ui";
1213
import { PinIcon, PrivacyIcon } from "$lib/ui/icons";
1314
import { isPermissionGranted } from "@choochmeque/tauri-plugin-notifications-api";
@@ -82,6 +83,7 @@ function cancelLogout() {
8283
async function performLogout() {
8384
isLogoutDrawerOpen = false;
8485
clearAllNotifications();
86+
await clearAllCachedPhotos();
8587
if (!globalState) {
8688
console.error("Cannot logout: global state not ready");
8789
return;

0 commit comments

Comments
 (0)