diff --git a/client/src/components/Planner/PlaceContactFields.test.tsx b/client/src/components/Planner/PlaceContactFields.test.tsx index 8b0a6f2f0..7e1213873 100644 --- a/client/src/components/Planner/PlaceContactFields.test.tsx +++ b/client/src/components/Planner/PlaceContactFields.test.tsx @@ -73,5 +73,19 @@ describe('PlaceContactFields (#2472)', () => { await user.click(screen.getByRole('button', { name: 'Take over from the place details' })) expect(JSON.parse(onChange.mock.lastCall![1])).toEqual(looked) }) + + it('FE-PLANNER-CONTACT-005: the place\'s phone and e-mail keep password managers out', () => { + render() + // A place's address is not the user's login; 1Password filled it in (5.0.0-pre.1 feedback). + for (const label of ['Phone', 'E-mail']) { + const field = screen.getByLabelText(label) + expect(field).toHaveAttribute('data-1p-ignore', 'true') + expect(field).toHaveAttribute('data-lpignore', 'true') + expect(field).toHaveAttribute('data-bwignore', 'true') + expect(field).toHaveAttribute('autocomplete', 'off') + } + // Still an e-mail field, so the keyboard and the browser's check stay. + expect(screen.getByLabelText('E-mail')).toHaveAttribute('type', 'email') + }) }) diff --git a/client/src/components/Planner/PlaceContactFields.tsx b/client/src/components/Planner/PlaceContactFields.tsx index aea631b5a..33be74c8b 100644 --- a/client/src/components/Planner/PlaceContactFields.tsx +++ b/client/src/components/Planner/PlaceContactFields.tsx @@ -7,6 +7,7 @@ import { DialogSection, fs } from '../shared/DialogShell' import { AddRowButton, EditorField, GRID_2, INPUT } from '../shared/dialogParts' import { cleanWeek, readWeek, weekdayNames, writeWeek } from './placeHours' import { useTranslation } from '../../i18n' +import { NO_AUTOFILL } from '../../utils/noAutofill' interface Props { /** Anchor for the "add by hand" jump from an empty search. */ @@ -80,14 +81,14 @@ export function PlaceContactFields({ id: anchorId, phone, email, openingHours, o
onChange('phone', e.target.value)} - maxLength={50} placeholder="+49 …" className={`${INPUT} ps-8`} /> + maxLength={50} placeholder="+49 …" className={`${INPUT} ps-8`} {...NO_AUTOFILL} />
onChange('email', e.target.value)} - maxLength={254} placeholder="info@…" className={`${INPUT} ps-8`} /> + maxLength={254} placeholder="info@…" className={`${INPUT} ps-8`} {...NO_AUTOFILL} />
diff --git a/client/src/mobile/screens/admin/MAdminSheets.tsx b/client/src/mobile/screens/admin/MAdminSheets.tsx index dc48e1d71..4092cfec0 100644 --- a/client/src/mobile/screens/admin/MAdminSheets.tsx +++ b/client/src/mobile/screens/admin/MAdminSheets.tsx @@ -6,6 +6,7 @@ import type { useAdmin } from '../../../pages/admin/useAdmin' import MSheet from '../../components/MSheet' import MSegmented from '../../components/MSegmented' import { MAdminButton, MAdminField, MAdminInput, MAdminSecretInput, MAdminSheetFrame } from './MAdminUi' +import { NO_AUTOFILL } from '../../../utils/noAutofill' interface MAdminSheetsProps { admin: ReturnType @@ -78,6 +79,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) { value={createForm.username} onChange={(e) => setCreateForm((f) => ({ ...f, username: e.target.value }))} placeholder={t('settings.username')} + {...NO_AUTOFILL} /> @@ -86,6 +88,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) { value={createForm.email} onChange={(e) => setCreateForm((f) => ({ ...f, email: e.target.value }))} placeholder={t('common.email')} + {...NO_AUTOFILL} /> @@ -93,6 +96,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) { value={createForm.password} onChange={(e) => setCreateForm((f) => ({ ...f, password: e.target.value }))} placeholder={t('common.password')} + autoComplete="new-password" /> @@ -127,6 +131,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) { type="text" value={editForm.username} onChange={(e) => setEditForm((f) => ({ ...f, username: e.target.value }))} + {...NO_AUTOFILL} /> @@ -134,6 +139,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) { type="email" value={editForm.email} onChange={(e) => setEditForm((f) => ({ ...f, email: e.target.value }))} + {...NO_AUTOFILL} /> @@ -141,6 +147,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) { value={editForm.password} onChange={(e) => setEditForm((f) => ({ ...f, password: e.target.value }))} placeholder={t('admin.newPasswordPlaceholder')} + autoComplete="new-password" /> diff --git a/client/src/pages/admin/AdminUserModals.tsx b/client/src/pages/admin/AdminUserModals.tsx index e37e7aaa4..81d84b5a2 100644 --- a/client/src/pages/admin/AdminUserModals.tsx +++ b/client/src/pages/admin/AdminUserModals.tsx @@ -31,6 +31,7 @@ import { SETTINGS_BUTTON, SETTINGS_BUTTON_DANGER } from '../../components/Settin import type { TranslationFn } from '../../types'; import type { useAdmin } from './useAdmin'; import PasswordChecklist from '../../components/shared/PasswordChecklist'; +import { NO_AUTOFILL } from '../../utils/noAutofill'; interface AdminUserModalsProps { admin: ReturnType; @@ -66,6 +67,8 @@ function PasswordInput({ onChange(e.target.value)} placeholder={placeholder} @@ -166,6 +169,7 @@ export default function AdminUserModals({ admin, t }: AdminUserModalsProps): Rea onChange={(e) => setCreateForm((f) => ({ ...f, username: e.target.value }))} placeholder={t('settings.username')} className={INPUT} + {...NO_AUTOFILL} /> @@ -176,6 +180,7 @@ export default function AdminUserModals({ admin, t }: AdminUserModalsProps): Rea onChange={(e) => setCreateForm((f) => ({ ...f, email: e.target.value }))} placeholder={t('common.email')} className={INPUT} + {...NO_AUTOFILL} /> @@ -239,6 +244,7 @@ export default function AdminUserModals({ admin, t }: AdminUserModalsProps): Rea value={editForm.username} onChange={(e) => setEditForm((f) => ({ ...f, username: e.target.value }))} className={INPUT} + {...NO_AUTOFILL} /> @@ -248,6 +254,7 @@ export default function AdminUserModals({ admin, t }: AdminUserModalsProps): Rea value={editForm.email} onChange={(e) => setEditForm((f) => ({ ...f, email: e.target.value }))} className={INPUT} + {...NO_AUTOFILL} /> diff --git a/client/src/utils/noAutofill.ts b/client/src/utils/noAutofill.ts new file mode 100644 index 000000000..b7bcf1999 --- /dev/null +++ b/client/src/utils/noAutofill.ts @@ -0,0 +1,15 @@ +/** + * For a field that holds somebody else's details: a place's e-mail and phone, + * the account an admin is creating. Password managers read such a field as a + * login and offer, or simply fill in, the signed-in user's own credentials. + * `autocomplete="off"` alone does not stop them (1Password ignores it on + * purpose), so each one gets its own opt-out. Never use it on a login or on the + * user's own account fields, where autofill is wanted. + */ +export const NO_AUTOFILL = { + autoComplete: 'off', + 'data-1p-ignore': 'true', + 'data-lpignore': 'true', + 'data-bwignore': 'true', + 'data-form-type': 'other', +} as const;