From 5d51ac6833611b277a9cba29b074212a740c7094 Mon Sep 17 00:00:00 2001 From: Yusuf Suleman Date: Sat, 4 Apr 2026 11:31:44 -0500 Subject: [PATCH] =?UTF-8?q?harden:=20widget=20edge=20cases=20=E2=80=94=20e?= =?UTF-8?q?xpired=20session,=20account=20switch,=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Session expires: widget gets 401 → clears stale cookie from App Group → stops retrying with bad auth → shows cached data until user opens app and re-authenticates 2. Account switch: login() now calls clearWidgetAuth() BEFORE syncCookieToWidget() — clears previous user's cached calories before writing new user's cookie. No brief display of wrong data. 3. Logout: already correct — clearWidgetAuth removes cookie + cached data, widget shows 0/2000 4. Minimum data: only session cookie + 2 cached numbers + timestamp in App Group. No passwords, no user IDs, no PII. Co-Authored-By: Claude Opus 4.6 (1M context) --- ios/Platform/Platform/Core/AuthManager.swift | 1 + ios/Platform/PlatformWidget/PlatformWidget.swift | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ios/Platform/Platform/Core/AuthManager.swift b/ios/Platform/Platform/Core/AuthManager.swift index ee220b9..6f3a431 100644 --- a/ios/Platform/Platform/Core/AuthManager.swift +++ b/ios/Platform/Platform/Core/AuthManager.swift @@ -77,6 +77,7 @@ final class AuthManager { currentUser = response.user isLoggedIn = true UserDefaults.standard.set(true, forKey: loggedInKey) + clearWidgetAuth() // Clear previous user's cached data syncCookieToWidget() WidgetCenter.shared.reloadAllTimelines() } diff --git a/ios/Platform/PlatformWidget/PlatformWidget.swift b/ios/Platform/PlatformWidget/PlatformWidget.swift index 6303459..2b44ee0 100644 --- a/ios/Platform/PlatformWidget/PlatformWidget.swift +++ b/ios/Platform/PlatformWidget/PlatformWidget.swift @@ -116,8 +116,15 @@ struct CalorieProvider: TimelineProvider { do { let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, - (200...299).contains(http.statusCode) else { return nil } + guard let http = response as? HTTPURLResponse else { return nil } + + // Session expired — clear stale cookie so we don't retry + if http.statusCode == 401 { + sharedDefaults.removeObject(forKey: "widget_sessionCookie") + return nil + } + + guard (200...299).contains(http.statusCode) else { return nil } return try JSONSerialization.jsonObject(with: data) as? [String: Any] } catch { return nil