fix(dashboard-api): support Hydra-only deployments and pre-Ory user migration#3224
fix(dashboard-api): support Hydra-only deployments and pre-Ory user migration#3224AdaAibaby wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements fallback mechanisms to the local database when the Ory admin API is unavailable, ensuring that user profile lookups, email retrievals, and team member additions can still succeed. It also adds logic to preserve pre-Ory user accounts by reusing their user IDs during bootstrap. The review feedback highlights three main issues: first, database errors during the pre-Ory user migration lookup are silently ignored, which could lead to duplicate account creation under transient DB failures; second, using an untyped gin.H map in the bootstrap handler bypasses compile-time type safety and OpenAPI contracts; and third, the case-insensitive email lookup in the database will cause full table scans unless a functional index is created on lower(email).
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0d66563e2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e289eb47f5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| WHERE ut.user_id = ANY($1::uuid[]) | ||
| AND ut.is_default = true |
There was a problem hiding this comment.
Fail or cover SSO members in DB profile fallback
When Ory is unavailable for an SSO-managed team, this fallback only reads emails through default-team memberships. enrollSSOMember inserts SSO memberships with is_default=false and SSO users have no default team, so those members are absent from profiles and the handler silently skips them while returning 200, making the member list incomplete exactly in the outage/Hydra-only path; fail closed for SSO teams or use another email source.
Useful? React with 👍 / 👎.
SSO-backed identities (IdentityOrganizationID != uuid.Nil) must go through enrollSSOMember so the user lands in the org's auto-join team. Previously the email-match migration reused the pre-Ory personal default team and returned early, silently skipping SSO team enrollment entirely. Gate the migration on ssoOrgID == uuid.Nil: pre-Ory users authenticating via a plain OIDC issuer still get their account preserved; SSO-backed identities fall through to the normal enrollSSOMember path and get a fresh account in the org team. A follow-up can handle the case where a pre-Ory SSO user also needs their old account merged. Addresses Codex P2 review comment on PR e2b-dev#3224. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new methods on authdb.Client that query public.teams/users_teams directly, avoiding the Ory admin API: - GetUserEmailsByUserIDs: bulk user_id → email via default-team record - FindUserIDsByEmail: email → []user_id via default-team record; returns all matches so callers can detect and reject ambiguous lookups Also adds a functional index on lower(t.email) so the case-insensitive WHERE clause stays O(log n) on large deployments.
…igration Problem 1 — Ory admin API unavailable (fixes e2b-dev#3222): GetTeamsTeamIDMembers and PostTeamsTeamIDMembers fall back to DB email lookups when the Ory admin SDK call fails, so member list and add-member work in Hydra-only or CDN-proxied deployments. Problem 2 — pre-Ory users get a blank account on first login (fixes e2b-dev#3223): bootstrapUser checks for an existing user by email when no user_identities row exists for the incoming OIDC sub. If exactly one unambiguous match is found and that user has no existing identity for this issuer, the existing user_id is reused — preserving API keys, templates, and team memberships. Gate is skipped for SSO-backed issuers (ssoOrgID != uuid.Nil) so enrollSSOMember is not bypassed. Also returns user_id in AdminUserBootstrapResponse (OpenAPI spec updated) and enforces the SSO-org membership check in the DB fallback path of PostTeamsTeamIDMembers so that a Ory outage cannot bypass SSO gates.
provisioning/bootstrap_test.go: - TestBootstrapOIDCUser_ExternalIDFailureKeepsUserProvisioned: non-fatal backfill does not abort provisioning - TestBootstrapOIDCUser_ReRunBackfillsExternalID: second login sets external_id when first was a Hydra-only deployment - TestBootstrapOIDCUser_PreOryUserEmailMatchReusesExistingAccount - TestBootstrapOIDCUser_EmailMatchBlockedIfUserAlreadyLinked - TestBootstrapOIDCUser_PreOryUserSecondLoginUsesIdentityLookup - TestBootstrapOIDCUser_ConcurrentEmailMatchOnlyFirstSubMerges handlers/ory_fallback_test.go: - TestPostAdminUsersBootstrap_ResponseIncludesUserID - TestGetTeamsTeamIDMembers_OryUnavailableFallsBackToDBEmail - TestPostTeamsTeamIDMembers_OryUnavailableFallsBackToDBEmailLookup - TestPostTeamsTeamIDMembers_OryUnavailableUnknownEmailReturns404
8b565b4 to
afa0510
Compare
|
Reopening with clean commit history. |
Problem
Two related issues affect self-hosted deployments using Hydra without Kratos, or with a CDN that blocks
/admin/paths:1. Ory admin API calls fail silently (fixes #3222)
GetTeamsTeamIDMembersandPostTeamsTeamIDMemberscall the Ory admin SDK to resolve user profiles (email, name). In Hydra-only deployments the Kratos identity admin endpoint does not exist. When Hydra is behind a reverse proxy (e.g. Nginx, Cloudflare) the/admin/*paths are commonly blocked. Both cases cause the handlers to error out — members list returns empty, add-member returns 500.2. Pre-Ory users get a new empty account on first login (fixes #3223)
When migrating from a non-Ory auth system, existing users have no
user_identitiesrow.bootstrapUserWithIdentityfinds no(oidc_iss, oidc_sub)match and creates a fresh user + team, making all their existing API keys and template sandboxes invisible.Changes
packages/db/pkg/auth/profiles.go(new)Two helper methods on
authdb.Clientthat querypublic.teams/public.users_teamsdirectly:GetUserEmailsByUserIDs— bulk email lookup by user ID via default teamFindUserIDByEmail— reverse lookup: email → user_id via default teampackages/dashboard-api/internal/handlers/team_members.goGetTeamsTeamIDMembers: falls back toGetUserEmailsByUserIDswhen Ory admin call failsPostTeamsTeamIDMembers: falls back toFindUserIDByEmailwhen Ory admin call failspackages/dashboard-api/internal/handlers/utils_team_provisioning.gobootstrapUserWithIdentity: adds adefaultcase to the identity lookup switch. When nouser_identitiesrow exists for the incoming OIDC sub, checks whether an existing user's default-team email matches the OIDC email and that user has no existing Ory identity for this issuer. If both conditions hold, reuses the existinguser_id— preserving API keys, templates, and team memberships across the auth migration.resolveProfile: same DB fallback pattern for single-user profile resolution.packages/dashboard-api/internal/handlers/admin_users_bootstrap.goReturns
user_idalongsideidandslugin the bootstrap response so callers can read the canonicalpublic.usersid.Safety
The email-match merge only fires when:
user_identitiesrow exists for(oidc_iss, oidc_sub)— new identitypublic.teams(default team)user_identitiesrows for this issuerCondition 3 prevents email-based account takeover for users already linked to Ory.
Testing
/admin/*user_identitiesrow is written for future logins