{"record":{"id":"2a80c4daf5a2ad6e","repo":"bitwarden/server","slug":"already-accepted","errorCode":null,"errorMessage":"Already accepted.","messagePattern":"Already accepted\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs","lineNumber":255,"sourceCode":"\n            await SendInviteAsync(providerUser, provider);\n            result.Add(Tuple.Create(providerUser, \"\"));\n        }\n\n        return result;\n    }\n\n    public async Task<ProviderUser> AcceptUserAsync(Guid providerUserId, User user, string token)\n    {\n        var providerUser = await _providerUserRepository.GetByIdAsync(providerUserId);\n        if (providerUser == null)\n        {\n            throw new BadRequestException(\"User invalid.\");\n        }\n\n        if (providerUser.Status != ProviderUserStatusType.Invited)\n        {\n            throw new BadRequestException(\"Already accepted.\");\n        }\n\n        if (!CoreHelpers.TokenIsValid(\"ProviderUserInvite\", _dataProtector, token, user.Email, providerUser.Id,\n            _globalSettings.OrganizationInviteExpirationHours))\n        {\n            throw new BadRequestException(\"Invalid token.\");\n        }\n\n        if (string.IsNullOrWhiteSpace(providerUser.Email) ||\n            !providerUser.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))\n        {\n            throw new BadRequestException(\"User email does not match invite.\");\n        }\n\n        var organizationAutoConfirmPolicyRequirement = await _policyRequirementQuery\n            .GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id);\n\n        if (organizationAutoConfirmPolicyRequirement","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs#L237-L273","documentation":"Thrown inside AcceptUserAsync when the ProviderUser's Status is not Invited — the user already accepted or was confirmed. Acceptance is a one-time state transition. BadRequestException (HTTP 400).","triggerScenarios":"Clicking an already-used accept link; double-submit; the user was already confirmed by a provider admin.","commonSituations":"Resent/reloaded accept URL; race between self-accept and admin confirmation.","solutions":["Check Status == ProviderUserStatusType.Invited before calling AcceptUserAsync.","Treat already-accepted as an idempotent success/no-op in the UI.","Disable the accept button after first use."],"exampleFix":"// before\nawait _providerService.AcceptUserAsync(providerUserId, user, token);\n\n// after\nif (providerUser.Status != ProviderUserStatusType.Invited) return; // already accepted\nawait _providerService.AcceptUserAsync(providerUserId, user, token);","handlingStrategy":"validation","validationCode":"var providerUser = await _providerUserRepository.GetByIdAsync(providerUserId);\nif (providerUser is { Status: not ProviderUserStatusType.Invited })\n    return; // already accepted — idempotent","typeGuard":"static bool IsInviteAwaitingAcceptance(ProviderUser? pu) =>\n    pu is { Status: ProviderUserStatusType.Invited };","tryCatchPattern":"try { await _providerService.AcceptUserAsync(providerUserId, user, token); }\ncatch (BadRequestException ex) when (ex.Message == \"Already accepted.\")\n{ /* treat as success */ }","preventionTips":["Check Status == Invited before calling AcceptUserAsync.","Make the accept flow idempotent in the UI.","Disable the accept control after first use."],"tags":["provider","invite","state-machine","idempotency","bitwarden"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}