From 0a2aa0c27da59a7cba72ce23cd5ae719a2c6eea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Thu, 25 Sep 2025 10:44:35 +0200 Subject: [PATCH] Add WebauthnPRFOptions to syncResponse --- src/Api/Vault/Controllers/SyncController.cs | 9 +++++++-- .../Models/Response/SyncResponseModel.cs | 19 +++++++++++++++++-- .../Response/UserDecryptionResponseModel.cs | 11 ++++++++++- .../UserDecryptionOptionsBuilder.cs | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/Api/Vault/Controllers/SyncController.cs b/src/Api/Vault/Controllers/SyncController.cs index 54f1b9e70b..c25b94bd52 100644 --- a/src/Api/Vault/Controllers/SyncController.cs +++ b/src/Api/Vault/Controllers/SyncController.cs @@ -18,6 +18,7 @@ using Bit.Core.Settings; using Bit.Core.Tools.Repositories; using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Repositories; +using Bit.Core.Auth.Repositories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -42,6 +43,7 @@ public class SyncController : Controller private readonly IFeatureService _featureService; private readonly IApplicationCacheService _applicationCacheService; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; + private readonly IWebAuthnCredentialRepository _webAuthnCredentialRepository; public SyncController( IUserService userService, @@ -57,7 +59,8 @@ public class SyncController : Controller ICurrentContext currentContext, IFeatureService featureService, IApplicationCacheService applicationCacheService, - ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, + IWebAuthnCredentialRepository webAuthnCredentialRepository) { _userService = userService; _folderRepository = folderRepository; @@ -73,6 +76,7 @@ public class SyncController : Controller _featureService = featureService; _applicationCacheService = applicationCacheService; _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; + _webAuthnCredentialRepository = webAuthnCredentialRepository; } [HttpGet("")] @@ -115,10 +119,11 @@ public class SyncController : Controller var organizationIdsClaimingActiveUser = organizationClaimingActiveUser.Select(o => o.Id); var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + var webAuthnCredentials = await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id); var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationAbilities, organizationIdsClaimingActiveUser, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, - folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends); + folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends, webAuthnCredentials); return response; } diff --git a/src/Api/Vault/Models/Response/SyncResponseModel.cs b/src/Api/Vault/Models/Response/SyncResponseModel.cs index e19defce51..5066ac61d7 100644 --- a/src/Api/Vault/Models/Response/SyncResponseModel.cs +++ b/src/Api/Vault/Models/Response/SyncResponseModel.cs @@ -16,6 +16,9 @@ using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Models.Data; +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models.Api.Response; namespace Bit.Api.Vault.Models.Response; @@ -37,7 +40,8 @@ public class SyncResponseModel() : ResponseModel("sync") IDictionary> collectionCiphersDict, bool excludeDomains, IEnumerable policies, - IEnumerable sends) + IEnumerable sends, + IEnumerable webAuthnCredentials) : this() { Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, @@ -55,6 +59,16 @@ public class SyncResponseModel() : ResponseModel("sync") Domains = excludeDomains ? null : new DomainsResponseModel(user, false); Policies = policies?.Select(p => new PolicyResponseModel(p)) ?? new List(); Sends = sends.Select(s => new SendResponseModel(s, globalSettings)); + var webAuthnPrfOptions = webAuthnCredentials + .Where(c => c.GetPrfStatus() == WebAuthnPrfStatus.Enabled) + .Select(c => new WebAuthnPrfDecryptionOption( + c.EncryptedPrivateKey, + c.EncryptedUserKey, + c.CredentialId, + [] // transports as empty array + )) + .ToArray(); + UserDecryption = new UserDecryptionResponseModel { MasterPasswordUnlock = user.HasMasterPassword() @@ -70,7 +84,8 @@ public class SyncResponseModel() : ResponseModel("sync") MasterKeyEncryptedUserKey = user.Key!, Salt = user.Email.ToLowerInvariant() } - : null + : null, + WebAuthnPrfOptions = webAuthnPrfOptions.Length > 0 ? webAuthnPrfOptions : null }; } diff --git a/src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs b/src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs index a4d259a00a..6816ef69c4 100644 --- a/src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs +++ b/src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs @@ -1,4 +1,7 @@ -namespace Bit.Core.KeyManagement.Models.Response; +using System.Text.Json.Serialization; +using Bit.Core.Auth.Models.Api.Response; + +namespace Bit.Core.KeyManagement.Models.Response; public class UserDecryptionResponseModel { @@ -6,4 +9,10 @@ public class UserDecryptionResponseModel /// Returns the unlock data when the user has a master password that can be used to decrypt their vault. /// public MasterPasswordUnlockResponseModel? MasterPasswordUnlock { get; set; } + + /// + /// Gets or sets the WebAuthn PRF decryption keys. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public WebAuthnPrfDecryptionOption[]? WebAuthnPrfOptions { get; set; } } diff --git a/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs b/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs index 1d16333ed3..22affe6a19 100644 --- a/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs +++ b/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs @@ -57,7 +57,7 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder public IUserDecryptionOptionsBuilder WithDevice(Device device) { - _device = device;> + _device = device; return this; }