diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index ccc7d396ab..66206025b0 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -378,5 +378,72 @@ namespace Bit.Api.Controllers model.Users.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()), model.Users.Where(u => u.Deleted).Select(u => u.ExternalId)); } + + [HttpPost("{id}/api-key")] + public async Task ApiKey(string id, [FromBody]ApiKeyRequestModel model) + { + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationOwner(orgIdGuid)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if(organization == null) + { + throw new NotFoundException(); + } + + var user = await _userService.GetUserByPrincipalAsync(User); + if(user == null) + { + throw new UnauthorizedAccessException(); + } + + if(!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + { + await Task.Delay(2000); + throw new BadRequestException("MasterPasswordHash", "Invalid password."); + } + else + { + var response = new ApiKeyResponseModel(organization); + return response; + } + } + + [HttpPost("{id}/rotate-api-key")] + public async Task RotateApiKey(string id, [FromBody]ApiKeyRequestModel model) + { + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationOwner(orgIdGuid)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if(organization == null) + { + throw new NotFoundException(); + } + + var user = await _userService.GetUserByPrincipalAsync(User); + if(user == null) + { + throw new UnauthorizedAccessException(); + } + + if(!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + { + await Task.Delay(2000); + throw new BadRequestException("MasterPasswordHash", "Invalid password."); + } + else + { + await _organizationService.RotateApiKeyAsync(organization); + var response = new ApiKeyResponseModel(organization); + return response; + } + } } } diff --git a/src/Core/Models/Api/Request/ApiKeyRequestModel.cs b/src/Core/Models/Api/Request/ApiKeyRequestModel.cs new file mode 100644 index 0000000000..6166a31e43 --- /dev/null +++ b/src/Core/Models/Api/Request/ApiKeyRequestModel.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Models.Api +{ + public class ApiKeyRequestModel + { + [Required] + public string MasterPasswordHash { get; set; } + } +} diff --git a/src/Core/Models/Api/Response/ApiKeyResponseModel.cs b/src/Core/Models/Api/Response/ApiKeyResponseModel.cs new file mode 100644 index 0000000000..bd5caa5abb --- /dev/null +++ b/src/Core/Models/Api/Response/ApiKeyResponseModel.cs @@ -0,0 +1,20 @@ +using System; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api +{ + public class ApiKeyResponseModel : ResponseModel + { + public ApiKeyResponseModel(Organization organization, string obj = "apiKey") + : base(obj) + { + if(organization == null) + { + throw new ArgumentNullException(nameof(organization)); + } + ApiKey = organization.ApiKey; + } + + public string ApiKey { get; set; } + } +} diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index ea1012ae84..3ae5d5d2f8 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -43,5 +43,6 @@ namespace Bit.Core.Services Task GenerateLicenseAsync(Organization organization, Guid installationId); Task ImportAsync(Guid organizationId, Guid importingUserId, IEnumerable groups, IEnumerable newUsers, IEnumerable removeUserExternalIds); + Task RotateApiKeyAsync(Organization organization); } } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index ac65ded3ee..701f0aece9 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -1341,6 +1341,13 @@ namespace Bit.Core.Services } } + public async Task RotateApiKeyAsync(Organization organization) + { + organization.ApiKey = CoreHelpers.SecureRandomString(30); + organization.RevisionDate = DateTime.UtcNow; + await ReplaceAndUpdateCache(organization); + } + private async Task UpdateUsersAsync(Group group, HashSet groupUsers, Dictionary existingUsersIdDict, HashSet existingUsers = null) {