diff --git a/src/Core/Entities/User.cs b/src/Core/Entities/User.cs index 08981ca2d3..8d254adec2 100644 --- a/src/Core/Entities/User.cs +++ b/src/Core/Entities/User.cs @@ -22,6 +22,9 @@ public class User : ITableObject, IStorableSubscriber, IRevisable, ITwoFac [MaxLength(256)] public string Email { get; set; } = null!; public bool EmailVerified { get; set; } + /// + /// The server-side master-password hash + /// [MaxLength(300)] public string? MasterPassword { get; set; } [MaxLength(50)] @@ -42,9 +45,22 @@ public class User : ITableObject, IStorableSubscriber, IRevisable, ITwoFac /// organization membership. /// public DateTime AccountRevisionDate { get; set; } = DateTime.UtcNow; + /// + /// The master-password-sealed user key. + /// public string? Key { get; set; } + /// + /// The raw public key, without a signature from the user's signature key. + /// public string? PublicKey { get; set; } + /// + /// User key wrapped private key. + /// public string? PrivateKey { get; set; } + /// + /// The public key, signed by the user's signature key. + /// + public string? SignedPublicKey { get; set; } public bool Premium { get; set; } public DateTime? PremiumExpirationDate { get; set; } public DateTime? RenewalReminderDate { get; set; } diff --git a/src/Core/Entities/UserSigningKeys.cs b/src/Core/Entities/UserSigningKeys.cs new file mode 100644 index 0000000000..9ab6938f04 --- /dev/null +++ b/src/Core/Entities/UserSigningKeys.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; +using Bit.Core.Utilities; + +#nullable enable + +namespace Bit.Core.Entities; + +public class UserSigningKeys : ITableObject, IRevisable +{ + public Guid Id { get; set; } + public Guid UserId { get; set; } + public SigningKeyType KeyType { get; set; } + + [MaxLength(500)] + public string? VerifyingKey { get; set; } + [MaxLength(500)] + public string? SigningKey { get; set; } + + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime RevisionDate { get; set; } = DateTime.UtcNow; + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } +} diff --git a/src/Core/Enums/SigningKeyType.cs b/src/Core/Enums/SigningKeyType.cs new file mode 100644 index 0000000000..7b470f9a1c --- /dev/null +++ b/src/Core/Enums/SigningKeyType.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Enums; + +public enum SigningKeyType : byte +{ + Ed25519 = 0 +} diff --git a/src/Core/KeyManagement/Models/Data/SigningKeyData.cs b/src/Core/KeyManagement/Models/Data/SigningKeyData.cs new file mode 100644 index 0000000000..7ec68a5a8b --- /dev/null +++ b/src/Core/KeyManagement/Models/Data/SigningKeyData.cs @@ -0,0 +1,10 @@ +using Bit.Core.Enums; + +namespace Bit.Core.KeyManagement.Models.Data; + +public class SigningKeyData +{ + public SigningKeyType KeyAlgorithm { get; set; } + public string WrappedSigningKey { get; set; } + public string VerifyingKey { get; set; } +} diff --git a/src/Core/KeyManagement/Repositories/IUserSigningKeysRepository.cs b/src/Core/KeyManagement/Repositories/IUserSigningKeysRepository.cs new file mode 100644 index 0000000000..de8fd5481b --- /dev/null +++ b/src/Core/KeyManagement/Repositories/IUserSigningKeysRepository.cs @@ -0,0 +1,14 @@ +#nullable enable +using Bit.Core.Entities; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.UserKey; +using Bit.Core.Repositories; + +namespace Bit.Core.KeyManagement.Repositories; + +public interface IUserSigningKeysRepository : IRepository +{ + public Task GetByUserIdAsync(Guid userId); + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid grantorId, SigningKeyData signingKeys); + public UpdateEncryptedDataForKeyRotation SetUserSigningKeys(Guid userId, SigningKeyData signingKeys); +} diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index ba374ae988..ef2b547640 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -68,6 +68,7 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.Dapper/KeyManagement/Repositories/UserSigningKeysRepository.cs b/src/Infrastructure.Dapper/KeyManagement/Repositories/UserSigningKeysRepository.cs new file mode 100644 index 0000000000..1c3f78f4ca --- /dev/null +++ b/src/Infrastructure.Dapper/KeyManagement/Repositories/UserSigningKeysRepository.cs @@ -0,0 +1,79 @@ +#nullable enable +using System.Data; +using Bit.Core.Entities; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Repositories; +using Bit.Core.KeyManagement.UserKey; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.KeyManagement.Repositories; + +public class UserSigningKeysRepository : Repository, IUserSigningKeysRepository +{ + public UserSigningKeysRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public UserSigningKeysRepository(string connectionString, string readOnlyConnectionString) : base( + connectionString, readOnlyConnectionString) + { + } + + public async Task GetByUserIdAsync(Guid userId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.QuerySingleOrDefaultAsync( + "[dbo].[UserSigningKeys_ReadByUserId]", + new + { + UserId = userId + }, + commandType: CommandType.StoredProcedure); + } + } + + public UpdateEncryptedDataForKeyRotation SetUserSigningKeys(Guid userId, SigningKeyData signingKeys) + { + return async (SqlConnection connection, SqlTransaction transaction) => + { + await connection.QueryAsync( + "[dbo].[UserSigningKeys_SetForRotation]", + new + { + Id = Guid.NewGuid(), + UserId = userId, + KeyType = (byte)signingKeys.KeyAlgorithm, + signingKeys.VerifyingKey, + SigningKey = signingKeys.WrappedSigningKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow + }, + commandType: CommandType.StoredProcedure, + transaction: transaction); + }; + } + + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid grantorId, SigningKeyData signingKeys) + { + return async (SqlConnection connection, SqlTransaction transaction) => + { + await connection.QueryAsync( + "[dbo].[UserSigningKeys_UpdateForRotation]", + new + { + UserId = grantorId, + KeyType = (byte)signingKeys.KeyAlgorithm, + signingKeys.VerifyingKey, + SigningKey = signingKeys.WrappedSigningKey, + RevisionDate = DateTime.UtcNow + }, + commandType: CommandType.StoredProcedure, + transaction: transaction); + }; + } +} diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 22818517d3..b23bed14ee 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -104,6 +104,7 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); if (selfHosted) diff --git a/src/Infrastructure.EntityFramework/KeyManagement/Repositories/UserSigningKeysRepository.cs b/src/Infrastructure.EntityFramework/KeyManagement/Repositories/UserSigningKeysRepository.cs new file mode 100644 index 0000000000..6c6d53924f --- /dev/null +++ b/src/Infrastructure.EntityFramework/KeyManagement/Repositories/UserSigningKeysRepository.cs @@ -0,0 +1,74 @@ +#nullable enable +using AutoMapper; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Repositories; +using Bit.Core.KeyManagement.UserKey; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.KeyManagement.Repositories; + +public class UserSigningKeysRepository : Repository, IUserSigningKeysRepository +{ + public UserSigningKeysRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper, context => context.UserSigningKeys) + { + } + + public async Task GetByUserIdAsync(Guid userId) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var signingKeys = await dbContext.UserSigningKeys.FindAsync(userId); + if (signingKeys == null) + { + return null; + } + + return new SigningKeyData + { + KeyAlgorithm = signingKeys.KeyType, + VerifyingKey = signingKeys.VerifyingKey, + WrappedSigningKey = signingKeys.SigningKey, + }; + } + + public UpdateEncryptedDataForKeyRotation SetUserSigningKeys(Guid userId, SigningKeyData signingKeys) + { + return async (_, _) => + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var entity = new Models.UserSigningKeys + { + Id = Guid.NewGuid(), + UserId = userId, + KeyType = signingKeys.KeyAlgorithm, + VerifyingKey = signingKeys.VerifyingKey, + SigningKey = signingKeys.WrappedSigningKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + }; + await dbContext.UserSigningKeys.AddAsync(entity); + await dbContext.SaveChangesAsync(); + }; + } + + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid grantorId, SigningKeyData signingKeys) + { + return async (_, _) => + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.UserSigningKeys.FirstOrDefaultAsync(x => x.UserId == grantorId); + if (entity != null) + { + entity.KeyType = signingKeys.KeyAlgorithm; + entity.VerifyingKey = signingKeys.VerifyingKey; + entity.SigningKey = signingKeys.WrappedSigningKey; + entity.RevisionDate = DateTime.UtcNow; + await dbContext.SaveChangesAsync(); + } + }; + } +} diff --git a/src/Infrastructure.EntityFramework/Models/UserSigningKeys.cs b/src/Infrastructure.EntityFramework/Models/UserSigningKeys.cs new file mode 100644 index 0000000000..f4b5c26d39 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Models/UserSigningKeys.cs @@ -0,0 +1,16 @@ +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.Models; + +public class UserSigningKeys : Core.Entities.UserSigningKeys +{ + public virtual User User { get; set; } +} + +public class UserSigningKeysMapperProfile : Profile +{ + public UserSigningKeysMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 5c1c1bc87f..3a29e322e9 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -71,6 +71,7 @@ public class DatabaseContext : DbContext public DbSet TaxRates { get; set; } public DbSet Transactions { get; set; } public DbSet Users { get; set; } + public DbSet UserSigningKeys { get; set; } public DbSet AuthRequests { get; set; } public DbSet OrganizationDomains { get; set; } public DbSet WebAuthnCredentials { get; set; } @@ -108,6 +109,7 @@ public class DatabaseContext : DbContext var eSsoConfig = builder.Entity(); var eTaxRate = builder.Entity(); var eUser = builder.Entity(); + var eUserSigningKeys = builder.Entity(); var eOrganizationApiKey = builder.Entity(); var eOrganizationConnection = builder.Entity(); var eOrganizationDomain = builder.Entity(); @@ -128,6 +130,7 @@ public class DatabaseContext : DbContext eOrganizationConnection.Property(c => c.Id).ValueGeneratedNever(); eOrganizationDomain.Property(ar => ar.Id).ValueGeneratedNever(); aWebAuthnCredential.Property(ar => ar.Id).ValueGeneratedNever(); + eUserSigningKeys.Property(ar => ar.Id).ValueGeneratedNever(); eCollectionCipher.HasKey(cc => new { cc.CollectionId, cc.CipherId }); eCollectionUser.HasKey(cu => new { cu.CollectionId, cu.OrganizationUserId }); @@ -168,6 +171,7 @@ public class DatabaseContext : DbContext eOrganizationConnection.ToTable(nameof(OrganizationConnection)); eOrganizationDomain.ToTable(nameof(OrganizationDomain)); aWebAuthnCredential.ToTable(nameof(WebAuthnCredential)); + eUserSigningKeys.ToTable(nameof(UserSigningKeys)); ConfigureDateTimeUtcQueries(builder); } diff --git a/src/Sql/dbo/Stored Procedures/UserSigningKeys_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/UserSigningKeys_ReadByUserId.sql new file mode 100644 index 0000000000..25e1ce2239 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/UserSigningKeys_ReadByUserId.sql @@ -0,0 +1,8 @@ +CREATE PROCEDURE [dbo].[UserSigningKeys_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SELECT * + FROM [dbo].[UserSigningKeys] + WHERE [UserId] = @UserId; +END diff --git a/src/Sql/dbo/Stored Procedures/UserSigningKeys_SetForRotation.sql b/src/Sql/dbo/Stored Procedures/UserSigningKeys_SetForRotation.sql new file mode 100644 index 0000000000..d4ee47c980 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/UserSigningKeys_SetForRotation.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[UserSigningKeys_UpdateForRotation] + @UserId UNIQUEIDENTIFIER, + @KeyType TINYINT, + @VerifyingKey VARCHAR(MAX), + @SigningKey VARCHAR(MAX), + @RevisionDate DATETIME2(7) +AS +BEGIN + UPDATE [dbo].[UserSigningKeys] + SET [KeyType] = @KeyType, + [VerifyingKey] = @VerifyingKey, + [SigningKey] = @SigningKey, + [RevisionDate] = @RevisionDate + WHERE [UserId] = @UserId; +END diff --git a/src/Sql/dbo/Stored Procedures/UserSigningKeys_UpdateForRotation.sql b/src/Sql/dbo/Stored Procedures/UserSigningKeys_UpdateForRotation.sql new file mode 100644 index 0000000000..0a8b0b18d7 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/UserSigningKeys_UpdateForRotation.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[UserSigningKeys_SetForRotation] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @KeyType TINYINT, + @VerifyingKey VARCHAR(MAX), + @SigningKey VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + INSERT INTO [dbo].[UserSigningKeys] ([Id], [UserId], [KeyType], [VerifyingKey], [SigningKey], [CreationDate], [RevisionDate]) + VALUES (@Id, @UserId, @KeyType, @VerifyingKey, @SigningKey, @CreationDate, @RevisionDate) +END diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 60d9b5eb32..1e62a35ac8 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -14,6 +14,7 @@ @AccountRevisionDate DATETIME2(7), @Key NVARCHAR(MAX), @PublicKey NVARCHAR(MAX), + @SignedPublicKeyOwnershipClaim NVARCHAR(MAX), @PrivateKey NVARCHAR(MAX), @Premium BIT, @PremiumExpirationDate DATETIME2(7), @@ -63,6 +64,7 @@ BEGIN [AccountRevisionDate], [Key], [PublicKey], + [SignedPublicKeyOwnershipClaim], [PrivateKey], [Premium], [PremiumExpirationDate], @@ -109,6 +111,7 @@ BEGIN @AccountRevisionDate, @Key, @PublicKey, + @SignedPublicKeyOwnershipClaim, @PrivateKey, @Premium, @PremiumExpirationDate, diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index 15d04d72f6..59c2511339 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -14,6 +14,7 @@ @AccountRevisionDate DATETIME2(7), @Key NVARCHAR(MAX), @PublicKey NVARCHAR(MAX), + @SignedPublicKeyOwnershipClaim NVARCHAR(MAX), @PrivateKey NVARCHAR(MAX), @Premium BIT, @PremiumExpirationDate DATETIME2(7), @@ -63,6 +64,7 @@ BEGIN [AccountRevisionDate] = @AccountRevisionDate, [Key] = @Key, [PublicKey] = @PublicKey, + [SignedPublicKeyOwnershipClaim] = @SignedPublicKeyOwnershipClaim, [PrivateKey] = @PrivateKey, [Premium] = @Premium, [PremiumExpirationDate] = @PremiumExpirationDate, diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 188dd4ea3c..ca1d7f996b 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -14,6 +14,7 @@ [AccountRevisionDate] DATETIME2 (7) NOT NULL, [Key] VARCHAR (MAX) NULL, [PublicKey] VARCHAR (MAX) NULL, + [SignedPublicKeyOwnershipClaim] VARCHAR (MAX) NULL, [PrivateKey] VARCHAR (MAX) NULL, [Premium] BIT NOT NULL, [PremiumExpirationDate] DATETIME2 (7) NULL, diff --git a/src/Sql/dbo/Tables/UserSigningKey.sql b/src/Sql/dbo/Tables/UserSigningKey.sql new file mode 100644 index 0000000000..214504fcf8 --- /dev/null +++ b/src/Sql/dbo/Tables/UserSigningKey.sql @@ -0,0 +1,11 @@ +CREATE TABLE [dbo].[UserSigningKeys] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER, + [KeyType] TINYINT NOT NULL, + [VerifyingKey] VARCHAR(MAX) NOT NULL, + [SigningKey] VARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_UserSigningKeys] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_UserSigningKeys_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]), +); \ No newline at end of file diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs b/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs index 90a6af51bd..c49b37705c 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs +++ b/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs @@ -29,7 +29,8 @@ public class UserCompare : IEqualityComparer x.LicenseKey == y.LicenseKey && x.ApiKey == y.ApiKey && x.Kdf == y.Kdf && - x.KdfIterations == y.KdfIterations; + x.KdfIterations == y.KdfIterations && + x.SignedPublicKey == y.SignedPublicKey; } public int GetHashCode([DisallowNull] User obj) diff --git a/util/Migrator/DbScripts/2025-05-01_00_AddSigningKeysTable.sql b/util/Migrator/DbScripts/2025-05-01_00_AddSigningKeysTable.sql new file mode 100644 index 0000000000..57e59a9417 --- /dev/null +++ b/util/Migrator/DbScripts/2025-05-01_00_AddSigningKeysTable.sql @@ -0,0 +1,312 @@ +CREATE TABLE [dbo].[UserSigningKeys] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER, + [KeyType] TINYINT NOT NULL, + [VerifyingKey] VARCHAR(MAX) NOT NULL, + [SigningKey] VARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_UserSigningKeys] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_UserSigningKeys_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) +); +GO + +CREATE PROCEDURE [dbo].[UserSigningKeys_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SELECT * + FROM [dbo].[UserSigningKeys] + WHERE [UserId] = @UserId; +END +GO + +CREATE PROCEDURE [dbo].[UserSigningKeys_UpdateForRotation] + @UserId UNIQUEIDENTIFIER, + @KeyType TINYINT, + @VerifyingKey VARCHAR(MAX), + @SigningKey VARCHAR(MAX), + @RevisionDate DATETIME2(7) +AS +BEGIN + UPDATE [dbo].[UserSigningKeys] + SET [KeyType] = @KeyType, + [VerifyingKey] = @VerifyingKey, + [SigningKey] = @SigningKey, + [RevisionDate] = @RevisionDate + WHERE [UserId] = @UserId; +END +GO + +CREATE PROCEDURE [dbo].[UserSigningKeys_SetForRotation] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @KeyType TINYINT, + @VerifyingKey VARCHAR(MAX), + @SigningKey VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + INSERT INTO [dbo].[UserSigningKeys] ([Id], [UserId], [KeyType], [VerifyingKey], [SigningKey], [CreationDate], [RevisionDate]) + VALUES (@Id, @UserId, @KeyType, @VerifyingKey, @SigningKey, @CreationDate, @RevisionDate) +END +GO + +IF COL_LENGTH('[dbo].[User]', 'SignedPublicKeyOwnershipClaim') IS NULL +BEGIN + ALTER TABLE + [dbo].[User] + ADD + [SignedPublicKeyOwnershipClaim] VARCHAR(MAX) NULL; +END +GO + +EXECUTE sp_refreshview 'dbo.UserView' +GO + +CREATE OR ALTER PROCEDURE [dbo].[User_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @SignedPublicKeyOwnershipClaim NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT = 0, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) = NULL, + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[User] + ( + [Id], + [Name], + [Email], + [EmailVerified], + [MasterPassword], + [MasterPasswordHint], + [Culture], + [SecurityStamp], + [TwoFactorProviders], + [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], + [AccountRevisionDate], + [Key], + [PublicKey], + [SignedPublicKeyOwnershipClaim], + [PrivateKey], + [Premium], + [PremiumExpirationDate], + [RenewalReminderDate], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [LicenseKey], + [Kdf], + [KdfIterations], + [CreationDate], + [RevisionDate], + [ApiKey], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor], + [KdfMemory], + [KdfParallelism], + [LastPasswordChangeDate], + [LastKdfChangeDate], + [LastKeyRotationDate], + [LastEmailChangeDate], + [VerifyDevices] + ) + VALUES + ( + @Id, + @Name, + @Email, + @EmailVerified, + @MasterPassword, + @MasterPasswordHint, + @Culture, + @SecurityStamp, + @TwoFactorProviders, + @TwoFactorRecoveryCode, + @EquivalentDomains, + @ExcludedGlobalEquivalentDomains, + @AccountRevisionDate, + @Key, + @PublicKey, + @SignedPublicKeyOwnershipClaim, + @PrivateKey, + @Premium, + @PremiumExpirationDate, + @RenewalReminderDate, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @LicenseKey, + @Kdf, + @KdfIterations, + @CreationDate, + @RevisionDate, + @ApiKey, + @ForcePasswordReset, + @UsesKeyConnector, + @FailedLoginCount, + @LastFailedLoginDate, + @AvatarColor, + @KdfMemory, + @KdfParallelism, + @LastPasswordChangeDate, + @LastKdfChangeDate, + @LastKeyRotationDate, + @LastEmailChangeDate, + @VerifyDevices + ) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[User_Update] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @SignedPublicKeyOwnershipClaim NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7), + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[User] + SET + [Name] = @Name, + [Email] = @Email, + [EmailVerified] = @EmailVerified, + [MasterPassword] = @MasterPassword, + [MasterPasswordHint] = @MasterPasswordHint, + [Culture] = @Culture, + [SecurityStamp] = @SecurityStamp, + [TwoFactorProviders] = @TwoFactorProviders, + [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, + [AccountRevisionDate] = @AccountRevisionDate, + [Key] = @Key, + [PublicKey] = @PublicKey, + [SignedPublicKeyOwnershipClaim] = @SignedPublicKeyOwnershipClaim, + [PrivateKey] = @PrivateKey, + [Premium] = @Premium, + [PremiumExpirationDate] = @PremiumExpirationDate, + [RenewalReminderDate] = @RenewalReminderDate, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [LicenseKey] = @LicenseKey, + [Kdf] = @Kdf, + [KdfIterations] = @KdfIterations, + [KdfMemory] = @KdfMemory, + [KdfParallelism] = @KdfParallelism, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [ApiKey] = @ApiKey, + [ForcePasswordReset] = @ForcePasswordReset, + [UsesKeyConnector] = @UsesKeyConnector, + [FailedLoginCount] = @FailedLoginCount, + [LastFailedLoginDate] = @LastFailedLoginDate, + [AvatarColor] = @AvatarColor, + [LastPasswordChangeDate] = @LastPasswordChangeDate, + [LastKdfChangeDate] = @LastKdfChangeDate, + [LastKeyRotationDate] = @LastKeyRotationDate, + [LastEmailChangeDate] = @LastEmailChangeDate, + [VerifyDevices] = @VerifyDevices + WHERE + [Id] = @Id +END +GO \ No newline at end of file