Skip to content

Resolve accounts by (issuer, subject) #543

Closed

Description

@alukach

Phase 1 — Foundations. Gates the Integrations model, the management UI and subject namespacing. This is the epic's schedule risk.

Today
A token is turned into an account by looking up its sub claim alone (oidc.ts), and that lookup then filters to individual accounts. So no other account type can authenticate at all, and two issuers using the same subject string would be indistinguishable. The issuer is already pinned — to the data proxy, not Ory — so this is a subject-namespace problem, not an unvalidated-issuer one.

Do

  • Add a table of identity bindings: (issuer, subject) -> account.
  • Make the account lookup take both claims, for identities the platform does not own (the proxy's own issuer for API keys, GitHub Actions). The existing single-argument lookup stays as it is: an individual's Ory identity lives on the account row as identity_id, and is not a binding.
  • Enforce uniqueness on the pair — two rows claiming the same (issuer, subject) is a hard error, not last-writer-wins.

Decided 2026-09-22 (PR #565): no backfill of Ory identities, no dual-read, no index retirement. identity_id can't leave the row (session, email lookup, proxy credentials read it), so a backfill would be a second copy of the same fact; the Ory "issuer" would be a config value no token carries; and uniqueness only matters for attacker-chosen subjects, which an Ory subject never is. The bindings table holds only other issuers.

Done when

  • The lookup takes (issuer, subject) for non-Ory issuers, uniqueness enforced by the key.
  • Deleting an account removes its bindings.
  • An Ory identity still resolves through identity_id, untouched.

Watch

  • The individual-only filter is applied in two places: the data layer and getPageSession. Fixing only the data layer leaves cookie sessions broken for any new account type.
  • A new table is declared in four places, not two: deploy/lib/database-construct.ts, scripts/init-local.ts, fixtures/, and src/lib/api/utils.mock.ts (which init-local.ts imports for seed data).
  • A binding's issuer must never be an empty string from unset config; the table client refuses one.

Part of #491.

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