From b4681ccf99696f35cb3d3cafe465217e666be902 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 9 Dec 2025 18:46:35 +0100 Subject: [PATCH] Add support for external actions --- src/Core/Repositories/IUserRepository.cs | 9 +- .../Repositories/UserRepository.cs | 65 ++++++++---- .../Repositories/UserRepository.cs | 14 ++- .../User_UpdateAccountCryptographicState.sql | 99 +++++++++---------- ...0_User_UpdateAccountCryptographicState.sql | 99 +++++++++---------- 5 files changed, 153 insertions(+), 133 deletions(-) diff --git a/src/Core/Repositories/IUserRepository.cs b/src/Core/Repositories/IUserRepository.cs index d596307071..7cdd159224 100644 --- a/src/Core/Repositories/IUserRepository.cs +++ b/src/Core/Repositories/IUserRepository.cs @@ -48,7 +48,14 @@ public interface IUserRepository : IRepository /// /// Sets the account cryptographic state to a user in a single transaction. The provided /// MUST be a V2 encryption state. Passing in a V1 encryption state will throw. + /// Extra actions can be passed in case other user data needs to be updated in the same transaction. /// - Task SetV2AccountCryptographicStateAsync(Guid userId, UserAccountKeysData accountKeysData); + Task SetV2AccountCryptographicStateAsync( + Guid userId, + UserAccountKeysData accountKeysData, + IEnumerable? updateUserDataActions = null); Task DeleteManyAsync(IEnumerable users); } + +public delegate Task UpdateUserData(Microsoft.Data.SqlClient.SqlConnection? connection = null, + Microsoft.Data.SqlClient.SqlTransaction? transaction = null); diff --git a/src/Infrastructure.Dapper/Repositories/UserRepository.cs b/src/Infrastructure.Dapper/Repositories/UserRepository.cs index d9c8ad8ca2..86ab063a5f 100644 --- a/src/Infrastructure.Dapper/Repositories/UserRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/UserRepository.cs @@ -288,7 +288,10 @@ public class UserRepository : Repository, IUserRepository UnprotectData(user); } - public async Task SetV2AccountCryptographicStateAsync(Guid userId, UserAccountKeysData accountKeysData) + public async Task SetV2AccountCryptographicStateAsync( + Guid userId, + UserAccountKeysData accountKeysData, + IEnumerable? updateUserDataActions = null) { if (!accountKeysData.IsV2Encryption()) { @@ -299,27 +302,47 @@ public class UserRepository : Repository, IUserRepository var signatureKeyPairId = CoreHelpers.GenerateComb(); await using var connection = new SqlConnection(ConnectionString); - await using var cmd = new SqlCommand("[dbo].[User_UpdateAccountCryptographicState]", connection); - cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add("@Id", SqlDbType.UniqueIdentifier).Value = userId; - cmd.Parameters.Add("@PublicKey", SqlDbType.NVarChar).Value = accountKeysData.PublicKeyEncryptionKeyPairData.PublicKey; - cmd.Parameters.Add("@PrivateKey", SqlDbType.NVarChar).Value = accountKeysData.PublicKeyEncryptionKeyPairData.WrappedPrivateKey; - cmd.Parameters.Add("@SignedPublicKey", SqlDbType.NVarChar).Value = - accountKeysData.PublicKeyEncryptionKeyPairData.SignedPublicKey; - cmd.Parameters.Add("@SecurityState", SqlDbType.NVarChar).Value = - accountKeysData.SecurityStateData!.SecurityState; - cmd.Parameters.Add("@SecurityVersion", SqlDbType.Int).Value = - accountKeysData.SecurityStateData!.SecurityVersion; - cmd.Parameters.Add("@SignatureKeyPairId", SqlDbType.UniqueIdentifier).Value = signatureKeyPairId; - cmd.Parameters.Add("@SignatureAlgorithm", SqlDbType.TinyInt).Value = accountKeysData.SignatureKeyPairData!.SignatureAlgorithm; - cmd.Parameters.Add("@SigningKey", SqlDbType.VarChar).Value = - accountKeysData.SignatureKeyPairData!.WrappedSigningKey; - cmd.Parameters.Add("@VerifyingKey", SqlDbType.VarChar).Value = - accountKeysData.SignatureKeyPairData!.VerifyingKey; - cmd.Parameters.Add("@RevisionDate", SqlDbType.DateTime2).Value = timestamp; - cmd.Parameters.Add("@AccountRevisionDate", SqlDbType.DateTime2).Value = timestamp; await connection.OpenAsync(); - await cmd.ExecuteNonQueryAsync(); + + await using var transaction = connection.BeginTransaction(); + try + { + await connection.ExecuteAsync( + "[dbo].[User_UpdateAccountCryptographicState]", + new + { + Id = userId, + PublicKey = accountKeysData.PublicKeyEncryptionKeyPairData.PublicKey, + PrivateKey = accountKeysData.PublicKeyEncryptionKeyPairData.WrappedPrivateKey, + SignedPublicKey = accountKeysData.PublicKeyEncryptionKeyPairData.SignedPublicKey, + SecurityState = accountKeysData.SecurityStateData!.SecurityState, + SecurityVersion = accountKeysData.SecurityStateData!.SecurityVersion, + SignatureKeyPairId = signatureKeyPairId, + SignatureAlgorithm = accountKeysData.SignatureKeyPairData!.SignatureAlgorithm, + SigningKey = accountKeysData.SignatureKeyPairData!.WrappedSigningKey, + VerifyingKey = accountKeysData.SignatureKeyPairData!.VerifyingKey, + RevisionDate = timestamp, + AccountRevisionDate = timestamp + }, + transaction: transaction, + commandType: CommandType.StoredProcedure); + + // Update user data that depends on cryptographic state + if (updateUserDataActions != null) + { + foreach (var action in updateUserDataActions) + { + await action(connection, transaction); + } + } + + await transaction.CommitAsync(); + } + catch + { + await transaction.RollbackAsync(); + throw; + } } public async Task> GetManyAsync(IEnumerable ids) diff --git a/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs b/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs index e4c2cba80e..a43c692be3 100644 --- a/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs @@ -242,7 +242,10 @@ public class UserRepository : Repository, IUserR await transaction.CommitAsync(); } - public async Task SetV2AccountCryptographicStateAsync(Guid userId, UserAccountKeysData accountKeysData) + public async Task SetV2AccountCryptographicStateAsync( + Guid userId, + UserAccountKeysData accountKeysData, + IEnumerable? updateUserDataActions = null) { if (!accountKeysData.IsV2Encryption()) { @@ -301,6 +304,15 @@ public class UserRepository : Repository, IUserR } await dbContext.SaveChangesAsync(); + + // Update additional user data within the same transaction + if (updateUserDataActions != null) + { + foreach (var action in updateUserDataActions) + { + await action(); + } + } await transaction.CommitAsync(); } diff --git a/src/Sql/dbo/Stored Procedures/User_UpdateAccountCryptographicState.sql b/src/Sql/dbo/Stored Procedures/User_UpdateAccountCryptographicState.sql index d64f44013c..8f1fb664ea 100644 --- a/src/Sql/dbo/Stored Procedures/User_UpdateAccountCryptographicState.sql +++ b/src/Sql/dbo/Stored Procedures/User_UpdateAccountCryptographicState.sql @@ -15,62 +15,51 @@ AS BEGIN SET NOCOUNT ON - BEGIN TRANSACTION + UPDATE + [dbo].[User] + SET + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [SignedPublicKey] = @SignedPublicKey, + [SecurityState] = @SecurityState, + [SecurityVersion] = @SecurityVersion, + [RevisionDate] = @RevisionDate, + [AccountRevisionDate] = @AccountRevisionDate + WHERE + [Id] = @Id - BEGIN TRY - UPDATE - [dbo].[User] + IF EXISTS (SELECT 1 FROM [dbo].[UserSignatureKeyPair] WHERE [UserId] = @Id) + BEGIN + UPDATE [dbo].[UserSignatureKeyPair] SET - [PublicKey] = @PublicKey, - [PrivateKey] = @PrivateKey, - [SignedPublicKey] = @SignedPublicKey, - [SecurityState] = @SecurityState, - [SecurityVersion] = @SecurityVersion, - [RevisionDate] = @RevisionDate, - [AccountRevisionDate] = @AccountRevisionDate + [SignatureAlgorithm] = @SignatureAlgorithm, + [SigningKey] = @SigningKey, + [VerifyingKey] = @VerifyingKey, + [RevisionDate] = @RevisionDate WHERE - [Id] = @Id - - IF EXISTS (SELECT 1 FROM [dbo].[UserSignatureKeyPair] WHERE [UserId] = @Id) - BEGIN - UPDATE [dbo].[UserSignatureKeyPair] - SET - [SignatureAlgorithm] = @SignatureAlgorithm, - [SigningKey] = @SigningKey, - [VerifyingKey] = @VerifyingKey, - [RevisionDate] = @RevisionDate - WHERE - [UserId] = @Id - END - ELSE - BEGIN - INSERT INTO [dbo].[UserSignatureKeyPair] - ( - [Id], - [UserId], - [SignatureAlgorithm], - [SigningKey], - [VerifyingKey], - [CreationDate], - [RevisionDate] - ) - VALUES - ( - @SignatureKeyPairId, - @Id, - @SignatureAlgorithm, - @SigningKey, - @VerifyingKey, - @RevisionDate, - @RevisionDate - ) - END - - COMMIT TRANSACTION - END TRY - BEGIN CATCH - IF @@TRANCOUNT > 0 - ROLLBACK TRANSACTION - THROW - END CATCH + [UserId] = @Id + END + ELSE + BEGIN + INSERT INTO [dbo].[UserSignatureKeyPair] + ( + [Id], + [UserId], + [SignatureAlgorithm], + [SigningKey], + [VerifyingKey], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @SignatureKeyPairId, + @Id, + @SignatureAlgorithm, + @SigningKey, + @VerifyingKey, + @RevisionDate, + @RevisionDate + ) + END END diff --git a/util/Migrator/DbScripts/2025-12-08_00_User_UpdateAccountCryptographicState.sql b/util/Migrator/DbScripts/2025-12-08_00_User_UpdateAccountCryptographicState.sql index 9729c0559b..259a126220 100644 --- a/util/Migrator/DbScripts/2025-12-08_00_User_UpdateAccountCryptographicState.sql +++ b/util/Migrator/DbScripts/2025-12-08_00_User_UpdateAccountCryptographicState.sql @@ -21,63 +21,52 @@ AS BEGIN SET NOCOUNT ON - BEGIN TRANSACTION + UPDATE + [dbo].[User] + SET + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [SignedPublicKey] = @SignedPublicKey, + [SecurityState] = @SecurityState, + [SecurityVersion] = @SecurityVersion, + [RevisionDate] = @RevisionDate, + [AccountRevisionDate] = @AccountRevisionDate + WHERE + [Id] = @Id - BEGIN TRY - UPDATE - [dbo].[User] + IF EXISTS (SELECT 1 FROM [dbo].[UserSignatureKeyPair] WHERE [UserId] = @Id) + BEGIN + UPDATE [dbo].[UserSignatureKeyPair] SET - [PublicKey] = @PublicKey, - [PrivateKey] = @PrivateKey, - [SignedPublicKey] = @SignedPublicKey, - [SecurityState] = @SecurityState, - [SecurityVersion] = @SecurityVersion, - [RevisionDate] = @RevisionDate, - [AccountRevisionDate] = @AccountRevisionDate + [SignatureAlgorithm] = @SignatureAlgorithm, + [SigningKey] = @SigningKey, + [VerifyingKey] = @VerifyingKey, + [RevisionDate] = @RevisionDate WHERE - [Id] = @Id - - IF EXISTS (SELECT 1 FROM [dbo].[UserSignatureKeyPair] WHERE [UserId] = @Id) - BEGIN - UPDATE [dbo].[UserSignatureKeyPair] - SET - [SignatureAlgorithm] = @SignatureAlgorithm, - [SigningKey] = @SigningKey, - [VerifyingKey] = @VerifyingKey, - [RevisionDate] = @RevisionDate - WHERE - [UserId] = @Id - END - ELSE - BEGIN - INSERT INTO [dbo].[UserSignatureKeyPair] - ( - [Id], - [UserId], - [SignatureAlgorithm], - [SigningKey], - [VerifyingKey], - [CreationDate], - [RevisionDate] - ) - VALUES - ( - @SignatureKeyPairId, - @Id, - @SignatureAlgorithm, - @SigningKey, - @VerifyingKey, - @RevisionDate, - @RevisionDate - ) - END - - COMMIT TRANSACTION - END TRY - BEGIN CATCH - IF @@TRANCOUNT > 0 - ROLLBACK TRANSACTION - THROW - END CATCH + [UserId] = @Id + END + ELSE + BEGIN + INSERT INTO [dbo].[UserSignatureKeyPair] + ( + [Id], + [UserId], + [SignatureAlgorithm], + [SigningKey], + [VerifyingKey], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @SignatureKeyPairId, + @Id, + @SignatureAlgorithm, + @SigningKey, + @VerifyingKey, + @RevisionDate, + @RevisionDate + ) + END END GO