mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
fix(ui): bake password-manager autofill guard into PasswordInput
Config/secret fields (BYOK API keys, webhook secrets, ntfy tokens) pass no autocomplete token, so PasswordInput now defaults to autoComplete=off plus data-bwignore / data-1p-ignore / data-lpignore for them — Bitwarden / 1Password / LastPass no longer offer to fill or save a config secret. Credential fields (login, register, password change) pass a real token and keep manager integration untouched. One change covers every secret call site.
This commit is contained in:
1 parent
dbc2b15b1b
commit
9f4c7e48fe
2 files changed
+58
No files matched your search
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { PasswordInput } from "../password-input";
|
||||
|
||||
/**
|
||||
* PasswordInput bakes the password-manager autofill guard in by default so
|
||||
* BYOK API keys / webhook secrets / ntfy tokens (config secrets, not
|
||||
* credentials) are never offered up to Bitwarden / 1Password / LastPass.
|
||||
* Credential fields that pass a real autocomplete token keep manager
|
||||
* integration.
|
||||
*/
|
||||
function render(node: React.ReactNode) {
|
||||
return renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">{node}</I18nProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("PasswordInput", () => {
|
||||
it("suppresses password managers by default for config secrets", () => {
|
||||
const html = render(<PasswordInput id="ntfy-token" />);
|
||||
expect(html).toMatch(/autocomplete="off"/i);
|
||||
expect(html).toContain("data-bwignore");
|
||||
expect(html).toContain("data-1p-ignore");
|
||||
expect(html).toContain('data-lpignore="true"');
|
||||
});
|
||||
|
||||
it("treats an explicit autoComplete=off as a config secret too", () => {
|
||||
const html = render(<PasswordInput id="tg-token" autoComplete="off" />);
|
||||
expect(html).toContain("data-bwignore");
|
||||
expect(html).toContain("data-1p-ignore");
|
||||
});
|
||||
|
||||
it("keeps manager integration for credential fields", () => {
|
||||
const html = render(
|
||||
<PasswordInput id="current-password" autoComplete="current-password" />,
|
||||
);
|
||||
expect(html).toMatch(/autocomplete="current-password"/i);
|
||||
expect(html).not.toContain("data-bwignore");
|
||||
expect(html).not.toContain("data-1p-ignore");
|
||||
expect(html).not.toContain("data-lpignore");
|
||||
});
|
||||
});
|
||||
@@ -22,14 +22,28 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
*/
|
||||
export function PasswordInput({
|
||||
className,
|
||||
autoComplete,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Input>) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useTranslations();
|
||||
|
||||
// Config/secret fields (BYOK API keys, webhook secrets, ntfy tokens) pass no
|
||||
// `autoComplete` — they are not credentials, so a password manager offering
|
||||
// to fill or *save* them is noise and risks clobbering the field. Suppress
|
||||
// Bitwarden / 1Password / LastPass by default for those. Credential fields
|
||||
// (login, register, password change) pass a real token
|
||||
// (`current-password` / `new-password` / `username`) and keep manager
|
||||
// integration; only an explicit `"off"` opts back into suppression.
|
||||
const suppressManagers = autoComplete == null || autoComplete === "off";
|
||||
return (
|
||||
<div className="relative">
|
||||
<Input
|
||||
{...props}
|
||||
autoComplete={autoComplete ?? "off"}
|
||||
data-bwignore={suppressManagers ? "" : undefined}
|
||||
data-1p-ignore={suppressManagers ? "" : undefined}
|
||||
data-lpignore={suppressManagers ? "true" : undefined}
|
||||
type={visible ? "text" : "password"}
|
||||
className={cn("pr-12", className)}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user