Skip to content

Auth: farm-qualified autocomplete username on the login form #585

Closed as not planned

Description

@mforce

Discovered while designing #535 (slice T7). Not caused by that slice — the requirement predates it and is currently owned by no open slice.

The requirement

Epic #530's "Why separate user rows rather than a membership table" section lists this as an in-scope mitigation, in these words:

The login form gives password managers a farm-qualified autocomplete username, even though the API receives {farmCode, email, password} separately — otherwise saving Farm B's password overwrites Farm A's entry and autofill submits the wrong one until the account locks.

Current state

web/src/routes/Login.tsx:191 ships a plain, unqualified attribute on the email field:

autoComplete="username"

There is no farm qualification anywhere on the form — no hidden field, no composed value. The requirement appears unmet.

Why it is unowned

T3 (#532) shipped farm-code login and is closed, so the slice that owned this can no longer carry it. #535 is prefill/remember only and its Scope section does not mention autocomplete. Nothing on epic #530's open slice list picks it up.

Why it matters more now, not less

#535 makes two-farm browsing on one device easy and expected — that is the point of the slice. Every farm added to a device is another chance for a password manager to overwrite the previous farm's saved entry for this origin, and the failure mode is not a bad UX moment: autofill submits the wrong farm's password repeatedly until that account hits its lockout.

It also weakens the mitigation #535 does ship. #535 renders a visible "Signing in to farm: X" notice when the code arrives via ?farm=, precisely because a same-origin link can substitute a farm code silently. That notice tells the operator which farm they are signing into; an unqualified autocomplete still hands the password manager no way to tell the two entries apart.

Scope

  • Decide the qualified form (a composed autocomplete username value, or a farm-qualified hidden field the manager keys on). The API contract does not change — it still receives {farmCode, email, password} separately.
  • Confirm the chosen shape actually causes a real password manager to keep separate entries per farm. This is behaviour of the browser, not of the app, so a Vitest assertion that an attribute has a particular string value is not evidence the requirement is met — say so in the test's comment, and bound the claim to what is actually verified.
  • Vitest tests in the same PR (standing rule).
  • Check whether the Playwright specs in tools/simulation/ui/ need updating — they drive the real login form.

Acceptance

  • The login form gives a password manager a way to distinguish farm A's saved credential from farm B's on the same origin.
  • The epic EPIC: Phase 1.6 — Multi-farm tenancy #530 bullet quoted above is satisfied, or the epic is amended to record a deliberate won't-fix with the reasoning.

Activity

  1. mforce commented on Aug 24, 2026

    @mforce
    OwnerAuthor

    Amendment: the farm-code input itself is anonymous to the browser

    Found by the owner, 2026-08-24. Same surface as this issue and must be designed with it, not separately.

    Login.tsx gives the farm-code input no name and no autocomplete, while its two siblings are both declared:

    field attributes
    farm code type, autoCapitalize, autoCorrect, spellCheck, maxLength, required — no name, no autocomplete
    email autoComplete="username"
    password autoComplete="current-password"

    Two distinct consequences, both worse in the multi-farm world this epic creates:

    1. No autocomplete means heuristics decide. It is a bare text input sitting immediately above a username field on a sign-in form — a shape browsers routinely autofill. A wrong guess puts the operator's email in the farm-code box. Because behaviour here is heuristic and differs across browsers, the absence is a gap whichever way a given browser resolves it: the form is not stating its intent.
    2. No name means password managers cannot key on it. A saved credential cannot carry the farm code, which is exactly what this issue's original finding is about — the manager has no way to tell farm A's entry from farm B's, and now it also has no way to store the discriminator even if it wanted to.

    Why this belongs here rather than in its own issue

    The original scope asks for "a composed autocomplete username value, or a farm-qualified hidden field the manager keys on". You cannot settle that while one of the three fields is undeclared — the strategies interact. Whoever picks this up should decide all three fields' name/autocomplete together.

    Note on the value, because it is not obvious

    There is no standard autocomplete token for "tenant identifier". username belongs to the email; organization is the nearest standard token but means something else; off is widely ignored by browsers on sign-in forms. Do not pick one by assumption — verify the chosen combination against a real browser and a real password manager, and record what was observed. As the original scope already says: an assertion that an attribute has a particular string value is not evidence the requirement is met, because the behaviour under test belongs to the browser, not to the app.

    Not caused by #535

    #535 (PR #588) touches the prefill, picker and URL-source notice around this input; it does not change these attributes, and the gap predates it.

  2. added
    sliceThin vertical work item
    epic-1.6Phase 1.6 — Multi-farm tenancy
    on Aug 24, 2026
  3. mforce commented on Aug 24, 2026

    @mforce
    OwnerAuthor

    Amendment: stable identifiers ship under #587; the manager-behavior criterion is a deliberate won't-fix (owner, 2026-08-24)

    This issue is amended to distinguish two things that were tracked together:

    1. Shipped under SPA: let an operator forget a remembered farm code #587 (in progress): the three login controls now carry stable HTML identifiers — farm code id="farm-code" / name="farmCode", email id="email" / name="email", password id="current-password" / name="password" — while the autocomplete="username" / autocomplete="current-password" tokens stay exactly as they are. This resolves the "anonymous to the browser" half of the amendment comment above: the form now declares each field's intent instead of leaving it to heuristics. No browser credential behaviour is promised by the identifiers.

    2. Deliberate won't-fix: the original acceptance criterion — a password manager keeping separate per-farm credential entries on the same origin — is not implemented. HTML supplies no standard manager storage-key contract for a tenant identifier (no standard autocomplete token, no hidden-field key that shipped managers honour), and the owner's scope decision is to leave the username/password fields and their autocomplete tokens unchanged. The reasoning is recorded in the epic EPIC: Phase 1.6 — Multi-farm tenancy #530 amendment comment.

    The acceptance criterion in the body above is therefore met in its second alternative: the epic is amended to record a deliberate won't-fix with the reasoning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:frontendReact/Vite web clientepic-1.6Phase 1.6 — Multi-farm tenancysliceThin vertical work item

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions