Skip to content

Single-account mode silently forks a new account when the existing account has an empty domain (self-hosted, embedded IdP) #7197

Description

@bernardgut

Summary
On self-hosted v0.75.0 with --single-account-mode-domain=<domain> and the
embedded IdP (Dex + Google upstream connector), an uninvited-but-legitimate
Google user logging in for the first time got a brand-new account (owner of an
empty tenant) instead of joining the existing account — while single-account
mode was armed. Reading the management source, this appears to be a general
trap, not a config error:

  1. updateUserAuthWithSingleMode() (management/server/account.go ~1572-1599)
    overrides the JWT Domain/DomainCategory with the domain stored on the
    existing account
    (via GetAnyAccountID) — not with the configured
    --single-account-mode-domain value.
  2. If that stored domain is empty (common: any account that existed before the
    first IdP login — e.g. a /api/setup bootstrap account — causes subsequent
    IdP-created accounts to inherit domain=""), isDomainValid("") fails and
    the flow falls through to GetOrCreateAccountByUser() → a NEW account is
    created for every unknown user, silently defeating single-account mode.
  3. The empty-domain account can never self-heal on later logins:
    domainIsUpToDate() returns early whenever the stored domain differs from
    the claims domain.
  4. Once ≥2 accounts exist, the next management restart logs
    single account mode disabled, accounts number N and SAM is off entirely.

Reproduction (self-hosted 0.75.0)

  1. Deploy with --single-account-mode-domain=example.com, embedded IdP +
    Google connector, create the first account such that its domain column
    ends up empty (first login while a setup account existed is enough).
  2. Have a second, uninvited Google user (same Workspace domain) log in.
  3. Observed: user gets a fresh empty account with owner role; management boot
    log subsequently reports single account mode disabled, accounts number 2.
    Expected: user joins the single existing account (pending approval when
    user_approval_required is set).

Impact
The new user sees a fully-featured empty "admin" dashboard (confusing; looks
like a breach or a broken instance), their peers register into a dead tenant,
and the operator's account silently stops being "single". Recovery requires
owner-token account deletion plus direct store surgery to set
domain/domain_category/is_domain_primary_account on the surviving account.

Suggested fix
When single-account mode is armed, fall back to the configured
--single-account-mode-domain whenever the stored account domain is
empty/invalid (or claim the domain for the surviving account at boot). Also
consider logging account creation at INFO — v0.75 has no log line for it, which
makes this failure mode invisible.

Related: #2773, #3545, #1712, #964 (same symptom class reported against
Zitadel/Authentik setups; this analysis may explain them).

Environment: management/signal/relay 0.75.0, dashboard v2.90.6, sqlite
store, embedded IdP (Dex) with Google Workspace connector, single-node k8s
(arm64).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions