Accept bare local part for mailbox creation; reject foreign domains with 400

closed
#6651065 opened by agent Sep 11

Problem

On the domain page, adding a mailbox requires the full address: test2 fails (splitAddr yields empty host → validateIdentity error → HTTP 500), and [email protected] fails opaquely (500 instead of a 400). Users should be able to type just test2 for [email protected], and a foreign-domain address must be rejected with a clear client error.

Proposal (decided: lowercase + trim the input)

  • Normalize centrally in appview.ProvisionAccount (covers UI + XRPC): trim + lowercase; no @ → append the domain’s own name; @-with-wrong-host (case-insensitive) → new typed ErrDomainMismatch.
  • Map ErrDomainMismatch to 400 InvalidRequest in mail_create_account.go; friendly flash + redirect in ui.createAccount (currently both paths surface 500s).
  • UI: update placeholder/help text (test2 works; full address accepted).
  • Lexicon: document that MailCreateAccount_Input.Address accepts a bare local part (schema unchanged).

Files

  • internal/appview/provision.go, mail_create_account.go, internal/appview/ui/handler.go, domain.templ, lexicon docs

Acceptance

1 Comment

agent 6b60531 Sep 11

Implemented (bug 6651065)

Bare local parts accepted, foreign domains rejected with 4xx, all user errors inline.

internal/appview/provision.go - New ErrDomainMismatch, ErrInvalidAddress, ErrAccountExists + normalizeAccountAddress(raw, domainName): trim + lowercase whole input; no @ → append domain; @-with-wrong-host (case-insensitive) → ErrDomainMismatch; double-@/spaces/empty user-or-host → ErrInvalidAddress. Other local-part rules left to Stalwart per Q1. - ProvisionAccount normalizes centrally (UI + XRPC), passes canonical d.Domain as host to account.Ensure, persists normalized fullAddr (Q2: always lowercase). *account.AssignedError and sqlite UNIQUE constraint collisions map to ErrAccountExists. - New IsUserInputError: sentinels + *stalwart.SetErrorFailure + invalidArguments/forbidden → true; serverFail/serverUnavailable/infra → false (500).

mail_create_account.go — ErrAccountExists → 409 Exists; mismatch/invalid/Stalwart-input → 400 InvalidRequest with no writes; store.ErrNotFound stays 400; rest 500.

ui/hx.go + ui/handler.go — typed errors.Is branches replace the string STUB (doesn't belong… use just the name, valid mailbox name…, already exists). HX path already 422-inline via writeMailboxError (list preserved); non-HX user errors now 303 ?msg= instead of 500 (Q3).

domain.templ — placeholder test2 + help line; domain_templ.go regenerated. Lexicon createAccount.json documents bare-local-part acceptance (schema unchanged, no API regen needed — generated structs don’t propagate property descriptions).

Tests — provision_test.go (13-case matrix + classifier), mail_create_account_test.go (foreign/invalid → 400, no writes), ui/create_account_test.go (HX 422 inline + list preserved, non-JS 303), hx_test.go typed-error cases. go build ./..., go vet, go test -count=1 ./internal/appview/... all pass; integration suite skips (needs live Stalwart). One pre-existing gofmt misalignment in ProvisionDomain left untouched.