Skip to content

ListOrganizationsByUser/ByCurrentUser returns 500 when a verified domain points at a disabled org #1638

@AmanGIT07

Description

@AmanGIT07

Problem

When a user's email-domain matches a domains row with state=verified whose org is disabled, and the user is not a member of that disabled org, FrontierService.ListOrganizationsByUser and FrontierService.ListOrganizationsByCurrentUser return HTTP 500.

Repro

# 1. Fresh user with no policies anywhere
eve@example.test  →  AuthCallback  →  cookie eve_sid

# 2. Disabled org with a verified domain that matches eve's email-domain
INSERT INTO domains (name, org_id, token, state)
  VALUES ('example.test', '<orgZ>', 'x', 'verified');
DisableOrganization { id: '<orgZ>' }

# 3. Call as eve
POST /raystack.frontier.v1beta1.FrontierService/ListOrganizationsByCurrentUser
{}
# → HTTP 500  {"code":"internal","message":"internal server error"}

Same response from ListOrganizationsByUser when an admin asks for eve.

Expected

The disabled org is silently excluded from joinable_via_domain. Organizations and JoinableViaDomain come back as expected (both empty for eve).

Root cause

core/domain/service.go ListJoinableOrgsByDomain queries the domains table on the domain row's state (Verified) only. The org's state is not considered. The disabled-org row matches and its org_id is returned to the handler:

domains, err := s.repository.List(ctx, Filter{
    Name:  domain,
    State: Verified, // ← state of the domain row, NOT the org
})

The slices.Contains(memberOrgIDs, domain.OrgID) check below it filters out orgs the user is already a member of, but a user with no policies has memberOrgIDs == [] so the disabled org survives.

internal/api/v1beta1connect/user.go (inside both ListOrganizationsByUser and ListOrganizationsByCurrentUser) then iterates the returned IDs and calls orgService.Get(ctx, id), which returns organization.ErrDisabled for the disabled org. The handler has no branch for that error and maps any non-nil err to connect.CodeInternal → HTTP 500:

for _, joinableOrg := range joinableOrgIDs {
    org, err := h.orgService.Get(ctx, joinableOrg)
    if err != nil {
        // unconditional 500
        return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
    }
    ...
}

Suggested fix

Filter at the source — ListJoinableOrgsByDomain should never return a disabled org. Easiest options:

  1. In the service: after the s.repository.List call, drop rows whose org state is not Enabled. Requires one extra fetch (orgService.GetByIDs(domain.OrgIDs) if available) or per-row Get.
  2. In the domain repository: extend Filter to take an org-state predicate and join domains against organizations. One SQL roundtrip, semantically correct, and the field is reusable elsewhere.

Option 2 is preferable.

Belt-and-braces (handler side): treat errors.Is(err, organization.ErrDisabled) as "skip this org" inside both ListOrganizationsByUser and ListOrganizationsByCurrentUser joinable-org loops, so the handler degrades gracefully if anything else ever yields a disabled org through this path.

Verification

  • Unit test in core/domain/service_test.go: ListJoinableOrgsByDomain returns no disabled-org IDs even when their domains row is verified.
  • API test in internal/api/v1beta1connect/user_test.go: eve-style scenario returns 200 with empty joinable_via_domain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions