bitwarden/server · error · BadRequestException

Unknown Organization User

Error message

Unknown Organization User

What it means

Thrown by DELETE /sponsorships/{sponsoringOrgId} (revoke sponsorship) when the calling user is not found as an organization user of the sponsoring org. The lookup GetByOrganizationAsync(sponsoringOrgId, currentContext.UserId) returns null, so there is no org-user record to anchor the revocation. It is a BadRequest (not 404) because the caller is authenticated but addressing the wrong org.

Source

Thrown at src/Api/Controllers/SelfHosted/SelfHostedOrganizationSponsorshipsController.cs:76

    {
        await _offerSponsorshipCommand.CreateSponsorshipAsync(
            await _organizationRepository.GetByIdAsync(sponsoringOrgId),
            await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default),
            model.PlanSponsorshipType,
            model.SponsoredEmail,
            model.FriendlyName,
            model.IsAdminInitiated.GetValueOrDefault(),
            model.Notes);
    }

    [HttpDelete("{sponsoringOrgId}")]
    public async Task RevokeSponsorship(Guid sponsoringOrgId)
    {
        var orgUser = await _organizationUserRepository.GetByOrganizationAsync(sponsoringOrgId, _currentContext.UserId ?? default);

        if (orgUser == null)
        {
            throw new BadRequestException("Unknown Organization User");
        }

        var existingOrgSponsorship = await _organizationSponsorshipRepository
            .GetBySponsoringOrganizationUserIdAsync(orgUser.Id);

        await _revokeSponsorshipCommand.RevokeSponsorshipAsync(existingOrgSponsorship);
    }

    [HttpPost("{sponsoringOrgId}/delete")]
    [Obsolete("This endpoint is deprecated. Use DELETE /{sponsoringOrgId} instead.")]
    public async Task PostRevokeSponsorship(Guid sponsoringOrgId)
    {
        await RevokeSponsorship(sponsoringOrgId);
    }

    [Authorize<ManageUsersRequirement>]
    [HttpDelete("{organizationId}/{sponsoredFriendlyName}/revoke")]
    public async Task AdminInitiatedRevokeSponsorshipAsync([FromRoute(Name = "organizationId")] Guid sponsoringOrgId, string sponsoredFriendlyName)

View on GitHub (pinned to e93b962371)

Solutions

  1. Confirm the caller is a member of sponsoringOrgId and that the id is correct.
  2. Re-authenticate so currentContext.UserId resolves to the actual user.
  3. Have an org admin (who is an org user) perform the revocation.

Example fix

// before: caller is not a member of sponsoringOrgId
//   DELETE /sponsorships/{wrongOrgId}
//
// after: revoke from an org the caller actually belongs to
var myOrgs = await GetMyOrganizationMembershipsAsync();
var sponsoringOrg = myOrgs.First(o => o.Id == sponsoringOrgId);
await RevokeSponsorshipAsync(sponsoringOrg.Id);
Defensive patterns

Strategy: validation

Validate before calling

// Verify the caller is an org user of the sponsoring org before revoking
const myMemberships = await getMyOrgMemberships();
if (!myMemberships.some(m => m.organizationId === sponsoringOrgId)) {
  throw new Error('Caller is not a member of the sponsoring org');
}
await revokeSponsorship(sponsoringOrgId);

Type guard

function isMemberOf(memberships, orgId) {
  return Array.isArray(memberships) && memberships.some(m => m.organizationId === orgId);
}

Try / catch

try {
  await revokeSponsorship(sponsoringOrgId);
} catch (e) {
  if (e.status === 400 && /unknown organization user/i.test(e.message)) {
    // caller is not a member; surface as 'access denied' or switch caller
  } else throw e;
}

Prevention

When it happens

Trigger: A user who is not a member of sponsoringOrgId calls revoke-sponsorship on it; currentContext.UserId is null/default (no user principal); the org-user row was removed between sessions.

Common situations: User removed from the sponsoring org but still holds a token; client hard-codes the wrong sponsoringOrgId; admin testing with a service account that has no org-user membership; off-by-one copy of an org id.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/3566e0f993405c91. Report an issue: GitHub.