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
- Confirm the caller is a member of sponsoringOrgId and that the id is correct.
- Re-authenticate so currentContext.UserId resolves to the actual user.
- 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
- Have an org member (ideally admin) perform sponsorship revocations.
- Confirm sponsoringOrgId is correct and that the user belongs to it.
- Ensure currentContext.UserId resolves (re-auth if the token is stale).
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
- The specified sponsored organization could not be found unde
- Invalid license
- ExternalId cannot exceed 300 characters.
- ExternalId cannot exceed 300 characters.
- result.AsError.Message
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/3566e0f993405c91.
Report an issue: GitHub.