246c4b48a712
remove: smart password change — feature never worked, cut entirely
- time
- 2026-05-19 20:50 UTC · 5 months ago
- machine
- koh2
- parent
- 004d1d32f877
↓ peach-browser-246c4b48.tar.gzatlas at this save
files changed13 files
manifest.firefox.json+1 −1manifest.json+1 −1package-lock.json+2 −2package.json+1 −1src/background/health.test.ts+1 −22src/background/health.ts+0 −47src/background/messages.ts+1 −20src/popup/styles/popup.css+0 −6src/popup/views/Entry.tsx+0 −6src/popup/views/Health.test.tsx+86 −0src/popup/views/Health.tsx+0 −26src/popup/views/Settings.tsx+0 −1src/shared/types.ts+0 −4manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach",5- "version": "3.6.43",5+ "version": "3.6.44",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "3.6.43",4+ "version": "3.6.44",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package-lock.json+2 −2
@@ -1,12 +1,12 @@11 {22 "name": "peach",3- "version": "3.6.43",3+ "version": "3.6.44",44 "lockfileVersion": 3,55 "requires": true,66 "packages": {77 "": {88 "name": "peach",9- "version": "3.6.43",9+ "version": "3.6.44",1010 "dependencies": {1111 "@bokuweb/zstd-wasm": "^0.0.27",1212 "@stablelib/blake2s": "^2.0.1",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.6.43",3+ "version": "3.6.44",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/background/health.test.ts+1 −22
@@ -1,5 +1,5 @@11 import { describe, expect, it } from "vitest";2-import { buildAdvancedHealthReport, resolveResetUrl } from "./health";2+import { buildAdvancedHealthReport } from "./health";33 import type { VaultEntry } from "../shared/types";44 55 const NINETY_ONE_DAYS = 91 * 86_400_000;@@ -116,26 +116,5 @@116116 .map((b) => b.toString(16).padStart(2, "0"))117117 .join("");118118 expect(json.includes(hex)).toBe(false);119- });120-});121-122-describe("resolveResetUrl", () => {123- it("maps known domains to their security pages", () => {124- expect(resolveResetUrl(entry({ url: "https://github.com/login" }))).toBe(125- "https://github.com/settings/security"126- );127- expect(resolveResetUrl(entry({ url: "https://mail.google.com/u/0" }))).toBe(128- "https://myaccount.google.com/security"129- );130- });131-132- it("falls back to /account/security on the entry's origin", () => {133- expect(resolveResetUrl(entry({ url: "https://app.example.com/login" }))).toBe(134- "https://app.example.com/account/security"135- );136- });137-138- it("returns null when no URL is set", () => {139- expect(resolveResetUrl(entry({ url: undefined }))).toBeNull();140119 });141120 });src/background/health.ts+0 −47
@@ -168,50 +168,3 @@168168 remediation: reports,169169 };170170 }171-172-/**173- * Resolve a best-effort password reset URL for the entry's domain. Returns174- * the entry's own URL as a fallback so Pro users still land somewhere175- * sensible — a banner inside the in-page popup later picks up the form176- * pre-fill. Form auto-fill is intentionally not done here; that's the177- * content-script half of smart password change and stays event-driven.178- */179-export function resolveResetUrl(entry: VaultEntry): string | null {180- const url = entry.url ?? "";181- const domain = extractDomain(url);182- if (!domain) return null;183-184- const knownResetPaths: Record<string, string> = {185- "google.com": "https://myaccount.google.com/security",186- "gmail.com": "https://myaccount.google.com/security",187- "github.com": "https://github.com/settings/security",188- "twitter.com": "https://twitter.com/settings/password",189- "x.com": "https://x.com/settings/password",190- "facebook.com": "https://www.facebook.com/settings?tab=security",191- "instagram.com": "https://www.instagram.com/accounts/password/change/",192- "linkedin.com":193- "https://www.linkedin.com/psettings/change-password",194- "amazon.com": "https://www.amazon.com/ap/changepassword",195- "apple.com": "https://appleid.apple.com",196- "icloud.com": "https://appleid.apple.com",197- "microsoft.com": "https://account.microsoft.com/security",198- "outlook.com": "https://account.microsoft.com/security",199- "live.com": "https://account.microsoft.com/security",200- "dropbox.com": "https://www.dropbox.com/account/security",201- "netflix.com": "https://www.netflix.com/password",202- "spotify.com": "https://www.spotify.com/account/change-password/",203- "reddit.com": "https://www.reddit.com/settings/account",204- };205-206- for (const [match, target] of Object.entries(knownResetPaths)) {207- if (domain === match || domain.endsWith("." + match)) return target;208- }209-210- try {211- const normalized = url.startsWith("http") ? url : `https://${url}`;212- const u = new URL(normalized);213- return `${u.origin}/account/security`;214- } catch {215- return null;216- }217-}src/background/messages.ts+1 −20
@@ -42,7 +42,7 @@4242 resolvePendingPasskeyRequest,4343 setPasskeyProviderEnabled,4444 } from "./passkey";45-import { buildAdvancedHealthReport, resolveResetUrl } from "./health";45+import { buildAdvancedHealthReport } from "./health";4646 import { storageClear } from "../shared/storage";4747 import { findInvalidWords } from "../shared/recovery-words";4848 import type {@@ -729,25 +729,6 @@729729 if (!vault.isUnlocked()) return { ok: false, error: "vault_locked" };730730 const report = await buildAdvancedHealthReport(vault.getEntries());731731 return { ok: true, data: report };732- }733- case "INITIATE_PASSWORD_CHANGE": {734- const gate = await requirePro("smart_password_change");735- if (!gate.allowed) return { ok: false, error: "pro_required" };736- if (!vault.isUnlocked()) return { ok: false, error: "vault_locked" };737- const { id } = msg.payload as { id: string };738- const entry = vault.getEntryById(id);739- if (!entry) return { ok: false, error: "entry_not_found" };740- const url = resolveResetUrl(entry);741- if (!url) return { ok: false, error: "no_reset_url" };742- try {743- await chrome.tabs.create({ url });744- return { ok: true, data: { url } };745- } catch (err) {746- return {747- ok: false,748- error: err instanceof Error ? err.message : "failed_to_open_tab",749- };750- }751732 }752733 case "IS_DURESS_ENABLED": {753734 const enabled = await vault.isDuressEnabled();src/popup/styles/popup.css+0 −6
@@ -1007,12 +1007,6 @@10071007 letter-spacing: 0.04em;10081008 }10091009 1010-.peach-breach-action {1011- margin-left: auto;1012- font-size: 0.625rem;1013- padding: 3px 8px;1014-}1015-10161010 .peach-password-value.is-breached {10171011 color: rgba(220, 80, 60, 0.75);10181012 border-bottom: 1px solid rgba(220, 80, 60, 0.22);src/popup/views/Entry.tsx+0 −6
@@ -537,12 +537,6 @@537537 <circle cx="6" cy="9" r="0.5" fill="var(--breach)" stroke="none" />538538 </svg>539539 <span style="flex:1">found in a known data breach</span>540- <button541- class="peach-btn peach-btn--sm peach-btn--accent peach-breach-action"542- onClick={() => { startEdit("password", entry().password ?? ""); setShowGenerator(true); }}543- >544- Change password545- </button>546540 </div>547541 </Show>548542 src/popup/views/Health.test.tsx+86 −0
@@ -0,0 +1,86 @@1+// @vitest-environment jsdom2+3+import { afterEach, describe, expect, it, vi } from "vitest";4+5+import { flushView, renderView } from "./view-test-utils";6+import type { AdvancedHealthReport, VaultEntry } from "../../shared/types";7+8+const sendMessageMock = vi.fn<(msg: unknown) => Promise<unknown>>();9+10+vi.mock("../App", () => ({11+ sendMessage: (msg: unknown) => sendMessageMock(msg),12+}));13+14+function entry(overrides: Partial<VaultEntry> = {}): VaultEntry {15+ return {16+ id: "entry-1",17+ type: "login",18+ title: "Example",19+ username: "[email protected]",20+ password: "correct-horse-battery-staple",21+ url: "https://example.com/login",22+ createdAt: Date.now() - 10_000,23+ modifiedAt: Date.now() - 10_000,24+ ...overrides,25+ };26+}27+28+describe("HealthView", () => {29+ let dispose: (() => void) | undefined;30+31+ afterEach(() => {32+ dispose?.();33+ dispose = undefined;34+ sendMessageMock.mockReset();35+ document.body.innerHTML = "";36+ });37+38+ it("renders the Pro remediation queue without the retired password-change button", async () => {39+ const report: AdvancedHealthReport = {40+ vaultScore: 65,41+ generatedAt: Date.now(),42+ entryCount: 1,43+ reusedGroups: 0,44+ agedCount: 1,45+ remediation: [46+ {47+ id: "entry-1",48+ title: "Example",49+ domain: "example.com",50+ url: "https://example.com/login",51+ score: 65,52+ ageDays: 200,53+ reasons: ["200 days old"],54+ factors: {55+ breached: false,56+ breachCount: 0,57+ reused: false,58+ aged: true,59+ weak: false,60+ missingTotpOnSensitive: false,61+ },62+ },63+ ],64+ };65+ sendMessageMock.mockResolvedValue({ ok: true, data: report });66+67+ const { HealthView } = await import("./Health");68+ const mounted = renderView(69+ <HealthView70+ entries={[entry()]}71+ isPro={true}72+ onFixEntry={vi.fn()}73+ onBack={vi.fn()}74+ onRefresh={vi.fn()}75+ />,76+ );77+ dispose = mounted.dispose;78+79+ await flushView();80+ await flushView();81+82+ expect(mounted.container.textContent).toContain("Remediation queue");83+ expect(mounted.container.textContent).toContain("Example");84+ expect(mounted.container.querySelectorAll(".peach-health-pro-row button")).toHaveLength(0);85+ });86+});src/popup/views/Health.tsx+0 −26
@@ -69,7 +69,6 @@6969 // Start true for Pro users so the loading screen is visible on the very first render —7070 // previously initialized to false and flipped in onMount, which left a blank-body flash.7171 const [advancedLoading, setAdvancedLoading] = createSignal<boolean>(!!props.isPro);72- const [smartChangingId, setSmartChangingId] = createSignal<string | null>(null);7372 7473 onMount(async () => {7574 if (!props.isPro) return;@@ -83,24 +82,6 @@8382 }8483 });8584 86- async function handleSmartChange(entryId: string) {87- setSmartChangingId(entryId);88- const res = await sendMessage({89- type: "INITIATE_PASSWORD_CHANGE",90- payload: { id: entryId },91- }) as { ok: boolean; error?: string };92- setSmartChangingId(null);93- if (res.ok) {94- // The reset URL opened in a new tab — close the popup so the user can95- // act on the page. Refresh the report on next mount so the remediation96- // queue reflects any updates the user makes.97- window.close();98- } else if (res.error === "no_reset_url") {99- // Entry has no usable URL — open the entry directly for manual editing.100- props.onFixEntry(props.entries.find((e) => e.id === entryId)!);101- }102- }103-10485 const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;10586 10687 const breachedEntries = createMemo(() =>@@ -256,13 +237,6 @@256237 ></div>257238 </div>258239 </div>259- <button260- class="peach-btn peach-btn--accent peach-btn--sm"261- onClick={() => void handleSmartChange(item.id)}262- disabled={smartChangingId() === item.id}263- >264- {smartChangingId() === item.id ? "opening…" : "Change this password"}265- </button>266240 </div>267241 )}268242 </For>src/popup/views/Settings.tsx+0 −1
@@ -1459,7 +1459,6 @@14591459 { feature: "passkeys", name: "Passkeys", desc: "Zero-knowledge WebAuthn" },14601460 { feature: "ios_app", name: "Mobile apps", desc: "Android · iOS soon" },14611461 { feature: "advanced_health", name: "Vault health", desc: "Reuse · age · exposure" },1462- { feature: "smart_password_change", name: "Smart reset", desc: "Guided password change" },14631462 ];14641463 14651464 const PRO_SECURITY_FEATURES: Array<{src/shared/types.ts+0 −4
@@ -275,7 +275,6 @@275275 | "SUBMIT_FEEDBACK"276276 | "GET_VAULT_STATUS"277277 | "GET_ADVANCED_HEALTH_REPORT"278- | "INITIATE_PASSWORD_CHANGE"279278 | "DEACTIVATE_LICENSE"280279 | "GET_IDENTITIES"281280 | "DESTROY_VAULT"@@ -333,7 +332,6 @@333332 | "sync"334333 | "passkeys"335334 | "advanced_health"336- | "smart_password_change"337335 | "ios_app"338336 | "codex"339337 | "canary_entry"@@ -478,8 +476,6 @@478476 | "invalid_feedback_endpoint"479477 | "message_empty"480478 | "network_error"481- | "no_reset_url"482- | "failed_to_open_tab"483479 | "operation_failed"484480 | "unknown_message_type";485481