Skip to content

feat: support linking a SAML identity to an existing account #800

Description

@ericfitz

Summary

A user cannot link a SAML identity to their account. The identity-link flow supports OAuth providers only, so someone who signs in with Entra SAML has no way to also attach their Google (or Microsoft OAuth) identity — or vice versa.

Acceptance Criteria

  • POST /me/identities/link/start accepts a SAML provider id (e.g. entra-tmidev-saml) and returns something the client can drive the round-trip with
  • The SAML assertion coming back is bound to the pending-link record for the initiating user, with the same step-up freshness requirement the OAuth path enforces at confirm time
  • A linked SAML identity appears in GET /me/identities and can be unlinked like any other
  • Linking is refused when the SAML identity already belongs to another account
  • The API spec documents whichever provider ids are linkable

Notes

Current state:

  • auth/handlers_identity_link.go has no SAML awareness. StartIdentityLink resolves the provider through getProviderWithContext / providerConfig and builds an OAuth authorization URL via BuildIdentityLinkAuthorizationURL; HandleIdentityLinkCallback completes a confidential authorization-code exchange. None of that maps onto SAML, which is a POST-binding assertion flow with no code to redeem.
  • The server already exposes SAML providers at GET /saml/providers — on api.tmi.dev that returns entra-tmidev-saml ("Microsoft Entra ID (SAML)").

This is the blocker for the tmi-ux side: the identities tab (identities-tab.component.ts:205) populates its "link another account" list from AuthService.getAvailableProviders(), which fetches GET /oauth2/providers and therefore only ever lists OAuth providers. tmi-ux already has a getAvailableSAMLProviders() helper, so once a server flow exists the client change is small — but offering SAML in the picker before then would just produce failures.

Note that users.email is deliberately not unique (uniqueness is (provider, provider_user_id)), so the same person legitimately exists as separate rows per provider. Linking is the mechanism that reconciles them, which makes this gap more than cosmetic.

Reported from https://www.tmi.dev/: signed in with the Entra SAML account, wanted to link the Google account, and only OAuth providers were listed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions