diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index eeb44830d..8d039e7e2 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -263,6 +263,22 @@ namespace Bit.Api.Controllers return response; } + [HttpPut("keys")] + [HttpPost("keys")] + public async Task PutKeys([FromBody]KeysRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + await _userService.SaveUserAsync(model.ToUser(user)); + return new KeysResponseModel(user); + } + + [HttpGet("keys")] + public async Task GetKeys() + { + var user = await _userService.GetUserByPrincipalAsync(User); + return new KeysResponseModel(user); + } + [HttpPost("delete")] public async Task PostDelete([FromBody]DeleteAccountRequestModel model) { diff --git a/src/Api/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Api/IdentityServer/ResourceOwnerPasswordValidator.cs index 6ab3f3e92..0d475d440 100644 --- a/src/Api/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Api/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -118,8 +118,13 @@ namespace Bit.Api.IdentityServer claims.Add(new Claim("device", device.Identifier)); } - context.Result = new GrantValidationResult(user.Id.ToString(), "Application", identityProvider: "bitwarden", - claims: claims.Count > 0 ? claims : null); + context.Result = new GrantValidationResult(user.Id.ToString(), "Application", + identityProvider: "bitwarden", + claims: claims.Count > 0 ? claims : null, + customResponse: new Dictionary + { + { "PrivateKey", user.PrivateKey } + }); } private void BuildTwoFactorResult(User user, ResourceOwnerPasswordValidationContext context) @@ -139,8 +144,8 @@ namespace Bit.Api.IdentityServer private void BuildErrorResult(bool twoFactorRequest, ResourceOwnerPasswordValidationContext context) { - context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse: - new Dictionary + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, + customResponse: new Dictionary {{ "ErrorModel", new ErrorResponseModel(twoFactorRequest ? "Code is not correct. Try again." : "Username or password is incorrect. Try again.") diff --git a/src/Api/Models/Request/Accounts/KeysRequestModel.cs b/src/Api/Models/Request/Accounts/KeysRequestModel.cs new file mode 100644 index 000000000..b59545b9d --- /dev/null +++ b/src/Api/Models/Request/Accounts/KeysRequestModel.cs @@ -0,0 +1,23 @@ +using Bit.Core.Domains; +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.Models +{ + public class KeysRequestModel + { + public string PublicKey { get; set; } + [Required] + public string PrivateKey { get; set; } + + public User ToUser(User existingUser) + { + if(!string.IsNullOrWhiteSpace(PublicKey)) + { + existingUser.PublicKey = PublicKey; + } + + existingUser.PrivateKey = PrivateKey; + return existingUser; + } + } +} diff --git a/src/Api/Models/Response/KeysResponseModel.cs b/src/Api/Models/Response/KeysResponseModel.cs new file mode 100644 index 000000000..f1377603f --- /dev/null +++ b/src/Api/Models/Response/KeysResponseModel.cs @@ -0,0 +1,23 @@ +using System; +using Bit.Core.Domains; + +namespace Bit.Api.Models +{ + public class KeysResponseModel : ResponseModel + { + public KeysResponseModel(User user) + : base("keys") + { + if(user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + PublicKey = user.PublicKey; + PrivateKey = user.PrivateKey; + } + + public string PublicKey { get; set; } + public string PrivateKey { get; set; } + } +} diff --git a/src/Core/Domains/User.cs b/src/Core/Domains/User.cs index 127d74e50..3cb3d6dfb 100644 --- a/src/Core/Domains/User.cs +++ b/src/Core/Domains/User.cs @@ -21,6 +21,8 @@ namespace Bit.Core.Domains public string EquivalentDomains { get; set; } public string ExcludedGlobalEquivalentDomains { get; set; } public DateTime AccountRevisionDate { get; internal set; } = DateTime.UtcNow; + public string PublicKey { get; set; } + public string PrivateKey { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 3eef87f22..3259bd0a3 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -14,6 +14,8 @@ [EquivalentDomains] NVARCHAR (MAX) NULL, [ExcludedGlobalEquivalentDomains] NVARCHAR (MAX) NULL, [AccountRevisionDate] DATETIME2 (7) NOT NULL, + [PublicKey] NVARCHAR (MAX) NULL, + [PrivateKey] NVARCHAR (MAX) NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)