{"record":{"id":"c25213f5280661e1","repo":"bitwarden/server","slug":"provider-must-have-at-least-one-confirmed-provider","errorCode":null,"errorMessage":"Provider must have at least one confirmed ProviderAdmin.","messagePattern":"Provider must have at least one confirmed ProviderAdmin\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs","lineNumber":364,"sourceCode":"            }\n        }\n\n        await _eventService.LogProviderUsersEventAsync(events);\n\n        return result;\n    }\n\n    public async Task SaveUserAsync(ProviderUser user, Guid savingUserId)\n    {\n        if (user.Id.Equals(default))\n        {\n            throw new BadRequestException(\"Invite the user first.\");\n        }\n\n        if (user.Type != ProviderUserType.ProviderAdmin &&\n            !await HasConfirmedProviderAdminExceptAsync(user.ProviderId, new[] { user.Id }))\n        {\n            throw new BadRequestException(\"Provider must have at least one confirmed ProviderAdmin.\");\n        }\n\n        await _providerUserRepository.ReplaceAsync(user);\n        await _eventService.LogProviderUserEventAsync(user, EventType.ProviderUser_Updated);\n    }\n\n    public async Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId,\n        IEnumerable<Guid> providerUserIds, Guid deletingUserId)\n    {\n        var provider = await _providerRepository.GetByIdAsync(providerId);\n\n        if (provider == null)\n        {\n            throw new NotFoundException();\n        }\n\n        var providerUsers = await _providerUserRepository.GetManyAsync(providerUserIds);\n        var users = await _userRepository.GetManyAsync(providerUsers.Where(pu => pu.UserId.HasValue)","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs#L346-L382","documentation":"Thrown inside SaveUserAsync when changing the user's Type away from ProviderAdmin would leave the provider with zero confirmed ProviderAdmins. HasConfirmedProviderAdminExceptAsync excludes the user being saved and finds no remaining confirmed admins. BadRequestException (HTTP 400).","triggerScenarios":"Demoting the last confirmed ProviderAdmin to a lower role; all other admins are only Invited (not Confirmed).","commonSituations":"Demoting the sole admin; admins never completed their own acceptance/confirmation.","solutions":["Confirm or promote another user to ProviderAdmin before demoting the current one.","Pre-check with HasConfirmedProviderAdminExceptAsync(providerId, new[] { user.Id }).","Ensure at least one other confirmed ProviderAdmin exists."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (user.Type != ProviderUserType.ProviderAdmin &&\n    !await HasConfirmedProviderAdminExceptAsync(user.ProviderId, new[] { user.Id }))\n    throw new InvalidOperationException(\"Demoting this user would leave the provider with no confirmed ProviderAdmin.\");","typeGuard":null,"tryCatchPattern":"try { await _providerService.SaveUserAsync(user, savingUserId); }\ncatch (BadRequestException ex) when (ex.Message.Contains(\"confirmed ProviderAdmin\"))\n{ /* promote/confirm another admin first */ }","preventionTips":["Promote or confirm another ProviderAdmin before demoting the last one.","Pre-check HasConfirmedProviderAdminExceptAsync before role changes.","Ensure at least one admin completes acceptance/confirmation."],"tags":["provider","provider-admin","ownership","validation","bitwarden"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}