diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 5cdd857f3f..e8915d3b13 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -309,7 +309,7 @@ public class OrganizationUsersController : BaseAdminConsoleController } [HttpPost("{organizationUserId}/accept-init")] - public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model) + public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -317,9 +317,21 @@ public class OrganizationUsersController : BaseAdminConsoleController throw new UnauthorizedAccessException(); } + if (_featureService.IsEnabled(FeatureFlagKeys.RefactorOrgAcceptInit)) + { + var result = await _initPendingOrganizationCommand.InitPendingOrganizationVNextAsync( + user, orgId, organizationUserId, + model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, + model.CollectionName, model.Token, model.Key); + + return Handle(result); + } + await _initPendingOrganizationCommand.InitPendingOrganizationAsync(user, orgId, organizationUserId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName, model.Token); await _acceptOrgUserCommand.AcceptOrgUserByEmailTokenAsync(organizationUserId, user, model.Token, _userService); await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id); + + return TypedResults.Ok(); } [HttpPost("{organizationUserId}/accept")] diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs index 4b079370c8..d106fd03b0 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUsersControllerAcceptInitTests.cs @@ -2,13 +2,16 @@ using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Helpers; +using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Core.Tokens; +using NSubstitute; using Xunit; namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; @@ -27,6 +30,7 @@ public class OrganizationUsersControllerAcceptInitTests : IClassFixture(_ => { }); _client = _factory.CreateClient(); _loginHelper = new LoginHelper(_factory, _client); + _featureService = _factory.GetService(); } public async Task InitializeAsync() @@ -92,6 +98,8 @@ public class OrganizationUsersControllerAcceptInitTests : IClassFixture(); _pendingOrganization.Enabled = true; @@ -275,7 +289,10 @@ public class OrganizationUsersControllerAcceptInitTests : IClassFixture(); + var updatedOrganization = await organizationRepository.GetByIdAsync(_pendingOrganization.Id); + + Assert.NotNull(updatedOrganization); + Assert.True(updatedOrganization.Enabled); + Assert.Equal(OrganizationStatusType.Created, updatedOrganization.Status); + Assert.Equal(_mockPublicKey, updatedOrganization.PublicKey); + Assert.Equal(_mockEncryptedPrivateKey, updatedOrganization.PrivateKey); + + // Verify user was confirmed (not just accepted) + var organizationUserRepository = _factory.GetService(); + var confirmedOrgUser = await organizationUserRepository.GetByIdAsync(_invitedOrgUser.Id); + + Assert.NotNull(confirmedOrgUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, confirmedOrgUser.Status); + Assert.Equal("test-user-key", confirmedOrgUser.Key); + Assert.Equal(_invitedUser.Id, confirmedOrgUser.UserId); + Assert.Null(confirmedOrgUser.Email); + + // Verify default collection was created + var collectionRepository = _factory.GetService(); + var collections = await collectionRepository.GetManyByOrganizationIdAsync(_pendingOrganization.Id); + + Assert.Single(collections); + Assert.Equal(_mockEncryptedString, collections.First().Name); + } + + [Fact] + public async Task AcceptInit_WithFeatureFlagEnabled_InvalidToken_ReturnsBadRequest() + { + // Arrange + _featureService.IsEnabled(FeatureFlagKeys.RefactorOrgAcceptInit).Returns(true); + + await _loginHelper.LoginAsync(_invitedUserEmail); + + var acceptInitRequest = new OrganizationUserAcceptInitRequestModel + { + Token = "invalid-token", + Key = "test-user-key", + Keys = new OrganizationKeysRequestModel + { + PublicKey = _mockPublicKey, + EncryptedPrivateKey = _mockEncryptedPrivateKey + }, + CollectionName = _mockEncryptedString + }; + + // Act + var response = await _client.PostAsJsonAsync( + $"organizations/{_pendingOrganization.Id}/users/{_invitedOrgUser.Id}/accept-init", + acceptInitRequest); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + + // Verify NO state changes occurred (atomic behavior) + var organizationRepository = _factory.GetService(); + var organization = await organizationRepository.GetByIdAsync(_pendingOrganization.Id); + + Assert.False(organization.Enabled); + Assert.Equal(OrganizationStatusType.Pending, organization.Status); + Assert.Null(organization.PublicKey); + Assert.Null(organization.PrivateKey); + + var organizationUserRepository = _factory.GetService(); + var orgUser = await organizationUserRepository.GetByIdAsync(_invitedOrgUser.Id); + + Assert.Equal(OrganizationUserStatusType.Invited, orgUser.Status); + Assert.Null(orgUser.UserId); + } }