From a1d320dcd593c0aee8413f0d1fec83b32bae861a Mon Sep 17 00:00:00 2001 From: Craig Weber Date: Fri, 17 Jul 2026 13:28:32 -0400 Subject: [PATCH] fix: distinguish account organizations sharing an email Key saved credentials and per-account state by normalized email and organization UUID. Preserve legacy credential slots while validating captures, OAuth logins, switching, and cached usage against the exact organization. --- .claude/skills/verify/SKILL.md | 15 +- CHANGELOG.md | 8 + CONTRIBUTING.md | 5 +- README.md | 18 +- Sources/PitStop/AppDelegate.swift | 333 +++++++++++------- Sources/PitStop/ClaudeDesktop.swift | 5 + Sources/PitStop/ClaudeLoginAdapter.swift | 20 +- Sources/PitStop/CodexLoginAdapter.swift | 15 +- Sources/PitStop/Credentials.swift | 27 +- Sources/PitStop/GeminiLoginAdapter.swift | 13 +- Sources/PitStop/Keychain.swift | 3 +- Sources/PitStop/OAuthLogin.swift | 25 +- Sources/PitStop/OAuthLoginCoordinator.swift | 37 +- Sources/PitStop/ProfileStore.swift | 219 ++++++++---- Sources/PitStop/UsageAPI.swift | 35 +- Sources/PitStop/UsageCache.swift | 28 ++ Sources/PitStop/main.swift | 13 +- Tests/PitStopTests/ClaudeExchangeTests.swift | 17 + .../ClaudeOrganizationTests.swift | 38 ++ .../GeminiLoginAdapterTests.swift | 8 +- Tests/PitStopTests/LoginAdapterTests.swift | 13 +- .../OAuthLoginCoordinatorTests.swift | 42 ++- .../ProfileStoreCaptureTests.swift | 328 +++++++++++++---- Tests/PitStopTests/UsageCacheTests.swift | 35 ++ 24 files changed, 946 insertions(+), 354 deletions(-) create mode 100644 Tests/PitStopTests/ClaudeOrganizationTests.swift diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md index 32cd355..2d2587c 100644 --- a/.claude/skills/verify/SKILL.md +++ b/.claude/skills/verify/SKILL.md @@ -27,12 +27,14 @@ description: How to build, run, and E2E-verify PitStop changes — headless --ch Replicates the "two accounts show the same usage" corruption safely: 1. Back up `~/.config/pitstop/profiles.json`. -2. Copy a real profile's blob: - `security find-generic-password -s "PitStop-profile" -a -w` +2. Copy a real profile's blob using that row's `credentialAccount` from + `profiles.json`: + `security find-generic-password -s "PitStop-profile" -a -w` 3. File it under a fake email: `security add-generic-password -s "PitStop-profile" -a poisoned-test@example.com -w ""` and append a matching row to profiles.json (copy the real row, change - `email` + `oauthAccount.emailAddress`). + `email`, `oauthAccount.emailAddress`, `oauthAccount.organizationUuid`, and + `credentialAccount`). 4. Quit installed app, launch dev binary, wait one cycle. 5. Expect: fake keychain item deleted by the audit (`security find… -a poisoned-test@example.com` exits 44), row gated in the menu with @@ -46,9 +48,10 @@ Replicates "app relaunched during a 429" without touching the network: 1. Quit the installed app. `~/.config/pitstop/usage-cache.json` holds the display state (dates are seconds since 2001-01-01 — unix minus 978307200). -2. Edit it: set `fetchError[""] = "Rate limited"`, - `nextFetchAllowed[""] = now + 600`, and age that account's - `usage[].fetchedAt` back ~15 min. +2. Edit it using the row's provider-namespaced account key: set + `fetchError[""] = "Rate limited"`, + `nextFetchAllowed[""] = now + 600`, and age that account's + `usage[].fetchedAt` back ~15 min. 3. Relaunch. Expect: the row still shows its bars, plus "⚠ Rate limited — retrying in 9m · showing