1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 13:08:17 -05:00

[PM-19279] Add prelogin response (#5511)

* Add prelogin response

* Fix test

* Fix more tests

* Fix tests

* Fix SQL warnings

* Fix difference between migration and sql SP

* Attempt to fix tests

* Attempt to fix tests

* Attempt to fix

* Fix namespace

* Attempt to fix error

* Fix different SP / migration

* Attempt to fix migration

* Fix

* Fix
This commit is contained in:
Bernd Schoolmann 2025-03-19 11:34:33 +01:00 committed by GitHub
parent 2fd1b25580
commit 7a8ee710da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 239 additions and 24 deletions

View File

@ -15,7 +15,6 @@ using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Entities; using Bit.Core.Entities;
@ -58,7 +57,6 @@ public class AccountsController : Controller
_organizationUserValidator; _organizationUserValidator;
private readonly IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> private readonly IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>
_webauthnKeyValidator; _webauthnKeyValidator;
private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService;
public AccountsController( public AccountsController(
@ -78,8 +76,7 @@ public class AccountsController : Controller
emergencyAccessValidator, emergencyAccessValidator,
IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>> IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>
organizationUserValidator, organizationUserValidator,
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator, IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator
IOpaqueKeyExchangeService opaqueKeyExchangeService
) )
{ {
_organizationService = organizationService; _organizationService = organizationService;
@ -97,7 +94,6 @@ public class AccountsController : Controller
_emergencyAccessValidator = emergencyAccessValidator; _emergencyAccessValidator = emergencyAccessValidator;
_organizationUserValidator = organizationUserValidator; _organizationUserValidator = organizationUserValidator;
_webauthnKeyValidator = webAuthnKeyValidator; _webauthnKeyValidator = webAuthnKeyValidator;
_opaqueKeyExchangeService = opaqueKeyExchangeService;
} }

View File

@ -1,10 +1,13 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using System.Text.Json;
using Bit.Core; using Bit.Core;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Api.Request.Opaque;
using Bit.Core.Auth.Models.Api.Response.Accounts; using Bit.Core.Auth.Models.Api.Response.Accounts;
using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.Services; using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.Registration; using Bit.Core.Auth.UserFeatures.Registration;
using Bit.Core.Auth.UserFeatures.WebAuthnLogin; using Bit.Core.Auth.UserFeatures.WebAuthnLogin;
@ -45,6 +48,7 @@ public class AccountsController : Controller
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory; private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
private readonly IOpaqueKeyExchangeCredentialRepository _opaqueKeyExchangeCredentialRepository;
private readonly byte[] _defaultKdfHmacKey = null; private readonly byte[] _defaultKdfHmacKey = null;
private static readonly List<UserKdfInformation> _defaultKdfResults = private static readonly List<UserKdfInformation> _defaultKdfResults =
@ -93,6 +97,7 @@ public class AccountsController : Controller
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
IFeatureService featureService, IFeatureService featureService,
IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory, IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory,
IOpaqueKeyExchangeCredentialRepository opaqueKeyExchangeCredentialRepository,
GlobalSettings globalSettings GlobalSettings globalSettings
) )
{ {
@ -107,6 +112,7 @@ public class AccountsController : Controller
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_featureService = featureService; _featureService = featureService;
_registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory; _registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory;
_opaqueKeyExchangeCredentialRepository = opaqueKeyExchangeCredentialRepository;
if (CoreHelpers.SettingHasValue(globalSettings.KdfDefaultHashKey)) if (CoreHelpers.SettingHasValue(globalSettings.KdfDefaultHashKey))
{ {
@ -255,11 +261,21 @@ public class AccountsController : Controller
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model) public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
{ {
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email); var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
if (kdfInformation == null) var user = await _userRepository.GetByEmailAsync(model.Email);
if (kdfInformation == null || user == null)
{ {
kdfInformation = GetDefaultKdf(model.Email); kdfInformation = GetDefaultKdf(model.Email);
} }
return new PreloginResponseModel(kdfInformation);
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(user.Id);
if (credential != null)
{
return new PreloginResponseModel(kdfInformation, JsonSerializer.Deserialize<CipherConfiguration>(credential.CipherConfiguration)!);
}
else
{
return new PreloginResponseModel(kdfInformation, null);
}
} }
[HttpGet("webauthn/assertion-options")] [HttpGet("webauthn/assertion-options")]

View File

@ -1,20 +1,23 @@
using Bit.Core.Enums; using Bit.Core.Auth.Models.Api.Request.Opaque;
using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
namespace Bit.Identity.Models.Response.Accounts; namespace Bit.Identity.Models.Response.Accounts;
public class PreloginResponseModel public class PreloginResponseModel
{ {
public PreloginResponseModel(UserKdfInformation kdfInformation) public PreloginResponseModel(UserKdfInformation kdfInformation, CipherConfiguration opaqueConfiguration)
{ {
Kdf = kdfInformation.Kdf; Kdf = kdfInformation.Kdf;
KdfIterations = kdfInformation.KdfIterations; KdfIterations = kdfInformation.KdfIterations;
KdfMemory = kdfInformation.KdfMemory; KdfMemory = kdfInformation.KdfMemory;
KdfParallelism = kdfInformation.KdfParallelism; KdfParallelism = kdfInformation.KdfParallelism;
OpaqueConfiguration = opaqueConfiguration;
} }
public KdfType Kdf { get; set; } public KdfType Kdf { get; set; }
public int KdfIterations { get; set; } public int KdfIterations { get; set; }
public int? KdfMemory { get; set; } public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; } public int? KdfParallelism { get; set; }
public CipherConfiguration OpaqueConfiguration { get; set; }
} }

View File

@ -0,0 +1,24 @@
using AutoMapper;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
using Bit.Core.KeyManagement.UserKey;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Infrastructure.EntityFramework.Repositories;
public class OpaqueKeyExchangeCredentialRepository : Repository<OpaqueKeyExchangeCredential, OpaqueKeyExchangeCredential, Guid>, IOpaqueKeyExchangeCredentialRepository
{
public OpaqueKeyExchangeCredentialRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => null)
{
}
public Task<OpaqueKeyExchangeCredential> GetByUserIdAsync(Guid userId)
{
return null;
}
public UpdateEncryptedDataForKeyRotation UpdateKeysForRotationAsync(Guid userId, IEnumerable<OpaqueKeyExchangeRotateKeyData> credentials)
{
return null;
}
}

View File

@ -87,6 +87,7 @@ public static class EntityFrameworkServiceCollectionExtensions
services.AddSingleton<IProviderUserRepository, ProviderUserRepository>(); services.AddSingleton<IProviderUserRepository, ProviderUserRepository>();
services.AddSingleton<ISendRepository, SendRepository>(); services.AddSingleton<ISendRepository, SendRepository>();
services.AddSingleton<ISsoConfigRepository, SsoConfigRepository>(); services.AddSingleton<ISsoConfigRepository, SsoConfigRepository>();
services.AddSingleton<IOpaqueKeyExchangeCredentialRepository, OpaqueKeyExchangeCredentialRepository>();
services.AddSingleton<ISsoUserRepository, SsoUserRepository>(); services.AddSingleton<ISsoUserRepository, SsoUserRepository>();
services.AddSingleton<ITransactionRepository, TransactionRepository>(); services.AddSingleton<ITransactionRepository, TransactionRepository>();
services.AddSingleton<IUserRepository, UserRepository>(); services.AddSingleton<IUserRepository, UserRepository>();

View File

@ -110,6 +110,7 @@ public static class ServiceCollectionExtensions
public static void AddBaseServices(this IServiceCollection services, IGlobalSettings globalSettings) public static void AddBaseServices(this IServiceCollection services, IGlobalSettings globalSettings)
{ {
services.AddScoped<ICipherService, CipherService>(); services.AddScoped<ICipherService, CipherService>();
services.AddSingleton<IOpaqueKeyExchangeService, OpaqueKeyExchangeService>();
services.AddUserServices(globalSettings); services.AddUserServices(globalSettings);
services.AddTrialInitiationServices(); services.AddTrialInitiationServices();
services.AddOrganizationServices(globalSettings); services.AddOrganizationServices(globalSettings);
@ -118,7 +119,6 @@ public static class ServiceCollectionExtensions
services.AddScoped<IGroupService, GroupService>(); services.AddScoped<IGroupService, GroupService>();
services.AddScoped<IEventService, EventService>(); services.AddScoped<IEventService, EventService>();
services.AddScoped<IEmergencyAccessService, EmergencyAccessService>(); services.AddScoped<IEmergencyAccessService, EmergencyAccessService>();
services.AddScoped<IOpaqueKeyExchangeService, OpaqueKeyExchangeService>();
services.AddSingleton<IDeviceService, DeviceService>(); services.AddSingleton<IDeviceService, DeviceService>();
services.AddScoped<ISsoConfigService, SsoConfigService>(); services.AddScoped<ISsoConfigService, SsoConfigService>();
services.AddScoped<IAuthRequestService, AuthRequestService>(); services.AddScoped<IAuthRequestService, AuthRequestService>();

View File

@ -1,11 +1,11 @@
CREATE PROCEDURE [dbo].[OpaqueKeyExchangeCredential_Create] CREATE PROCEDURE [dbo].[OpaqueKeyExchangeCredential_Create]
@Id UNIQUEIDENTIFIER OUTPUT, @Id UNIQUEIDENTIFIER OUTPUT,
@UserId UNIQUEIDENTIFIER, @UserId UNIQUEIDENTIFIER,
@CipherConfiguration VARCHAR(MAX) NOT NULL, @CipherConfiguration VARCHAR(MAX),
@CredentialBlob VARCHAR(MAX) NOT NULL, @CredentialBlob VARCHAR(MAX),
@EncryptedPublicKey VARCHAR(MAX) NOT NULL, @EncryptedPublicKey VARCHAR(MAX),
@EncryptedPrivateKey VARCHAR(MAX) NOT NULL, @EncryptedPrivateKey VARCHAR(MAX),
@EncryptedUserKey VARCHAR(MAX) NOT NULL, @EncryptedUserKey VARCHAR(MAX),
@CreationDate DATETIME2(7) @CreationDate DATETIME2(7)
AS AS
BEGIN BEGIN

View File

@ -1,5 +1,5 @@
CREATE PROCEDURE [dbo].[OpaqueKeyExchangeCredential_DeleteById] CREATE PROCEDURE [dbo].[OpaqueKeyExchangeCredential_DeleteById]
@Id UNIQUEIDENTIFIER, @Id UNIQUEIDENTIFIER
AS AS
BEGIN BEGIN
DELETE DELETE

View File

@ -29,7 +29,7 @@ BEGIN
FROM FROM
[dbo].[OpaqueKeyExchangeCredential] [dbo].[OpaqueKeyExchangeCredential]
WHERE WHERE
[UserId] = @UserId [UserId] = @Id
-- Delete WebAuthnCredentials -- Delete WebAuthnCredentials
DELETE DELETE

View File

@ -291,12 +291,12 @@ public class AccountsControllerTests : IDisposable
{ {
var user = GenerateExampleUser(); var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default) _userService.ChangePasswordAsync(user, default, default, default, default, null)
.Returns(Task.FromResult(IdentityResult.Success)); .Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostPassword(new PasswordRequestModel()); await _sut.PostPassword(new PasswordRequestModel());
await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default); await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default, null);
} }
[Fact] [Fact]
@ -314,7 +314,7 @@ public class AccountsControllerTests : IDisposable
{ {
var user = GenerateExampleUser(); var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default) _userService.ChangePasswordAsync(user, default, default, default, default, null)
.Returns(Task.FromResult(IdentityResult.Failed())); .Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>( await Assert.ThrowsAsync<BadRequestException>(

View File

@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models; using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Services;
using Bit.Core.Billing.Services; using Bit.Core.Billing.Services;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
@ -324,7 +325,8 @@ public class UserServiceTests
sutProvider.GetDependency<IPremiumUserBillingService>(), sutProvider.GetDependency<IPremiumUserBillingService>(),
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(), sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(), sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
sutProvider.GetDependency<IDistributedCache>() sutProvider.GetDependency<IDistributedCache>(),
sutProvider.GetDependency<IOpaqueKeyExchangeService>()
); );
var actualIsVerified = await sut.VerifySecretAsync(user, secret); var actualIsVerified = await sut.VerifySecretAsync(user, secret);
@ -911,7 +913,8 @@ public class UserServiceTests
sutProvider.GetDependency<IPremiumUserBillingService>(), sutProvider.GetDependency<IPremiumUserBillingService>(),
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(), sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(), sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
sutProvider.GetDependency<IDistributedCache>() sutProvider.GetDependency<IDistributedCache>(),
sutProvider.GetDependency<IOpaqueKeyExchangeService>()
); );
} }
} }

View File

@ -3,6 +3,7 @@ using System.Text;
using Bit.Core; using Bit.Core;
using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.Services; using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.Registration; using Bit.Core.Auth.UserFeatures.Registration;
using Bit.Core.Auth.UserFeatures.WebAuthnLogin; using Bit.Core.Auth.UserFeatures.WebAuthnLogin;
@ -45,6 +46,7 @@ public class AccountsControllerTests : IDisposable
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory; private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
private readonly IOpaqueKeyExchangeCredentialRepository _opaqueKeyExchangeCredentialRepository;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
@ -61,6 +63,7 @@ public class AccountsControllerTests : IDisposable
_referenceEventService = Substitute.For<IReferenceEventService>(); _referenceEventService = Substitute.For<IReferenceEventService>();
_featureService = Substitute.For<IFeatureService>(); _featureService = Substitute.For<IFeatureService>();
_registrationEmailVerificationTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>(); _registrationEmailVerificationTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>();
_opaqueKeyExchangeCredentialRepository = Substitute.For<IOpaqueKeyExchangeCredentialRepository>();
_globalSettings = Substitute.For<GlobalSettings>(); _globalSettings = Substitute.For<GlobalSettings>();
_sut = new AccountsController( _sut = new AccountsController(
@ -75,6 +78,7 @@ public class AccountsControllerTests : IDisposable
_referenceEventService, _referenceEventService,
_featureService, _featureService,
_registrationEmailVerificationTokenDataFactory, _registrationEmailVerificationTokenDataFactory,
_opaqueKeyExchangeCredentialRepository,
_globalSettings _globalSettings
); );
} }
@ -93,6 +97,11 @@ public class AccountsControllerTests : IDisposable
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default
}; };
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(userKdfInfo); _userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(userKdfInfo);
var mockUser = new User
{
Email = "user@example.com"
};
_userRepository.GetByEmailAsync(Arg.Any<string>()).Returns(mockUser);
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" }); var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
@ -105,6 +114,11 @@ public class AccountsControllerTests : IDisposable
{ {
SetDefaultKdfHmacKey(null); SetDefaultKdfHmacKey(null);
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null)); _userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));
var mockUser = new User
{
Email = "user@example.com"
};
_userRepository.GetByEmailAsync(Arg.Any<string>()).Returns(mockUser);
var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" }); var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" });
@ -121,6 +135,11 @@ public class AccountsControllerTests : IDisposable
SetDefaultKdfHmacKey(defaultKey); SetDefaultKdfHmacKey(defaultKey);
_userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null)); _userRepository.GetKdfInformationByEmailAsync(Arg.Any<string>()).Returns(Task.FromResult<UserKdfInformation?>(null));
var mockUser = new User
{
Email = "user@example.com"
};
_userRepository.GetByEmailAsync(Arg.Any<string>()).Returns(mockUser);
var fieldInfo = typeof(AccountsController).GetField("_defaultKdfResults", BindingFlags.NonPublic | BindingFlags.Static); var fieldInfo = typeof(AccountsController).GetField("_defaultKdfResults", BindingFlags.NonPublic | BindingFlags.Static);
if (fieldInfo == null) if (fieldInfo == null)

View File

@ -25,7 +25,7 @@ CREATE OR ALTER PROCEDURE [dbo].[OpaqueKeyExchangeCredential_Create]
@CipherConfiguration VARCHAR(MAX), @CipherConfiguration VARCHAR(MAX),
@CredentialBlob VARCHAR(MAX), @CredentialBlob VARCHAR(MAX),
@EncryptedPublicKey VARCHAR(MAX), @EncryptedPublicKey VARCHAR(MAX),
@EncryptedPrivateKey TINYINT, @EncryptedPrivateKey VARCHAR(MAX),
@EncryptedUserKey VARCHAR(MAX), @EncryptedUserKey VARCHAR(MAX),
@CreationDate DATETIME2(7) @CreationDate DATETIME2(7)
AS AS
@ -127,4 +127,157 @@ BEGIN
[Id] = @Id [Id] = @Id
END END
GO GO
ALTER PROCEDURE [dbo].[User_DeleteById]
@Id UNIQUEIDENTIFIER
WITH
RECOMPILE
AS
BEGIN
SET NOCOUNT ON
DECLARE @BatchSize INT = 100
-- Delete ciphers
WHILE @BatchSize > 0
BEGIN
BEGIN TRANSACTION User_DeleteById_Ciphers
DELETE TOP(@BatchSize)
FROM
[dbo].[Cipher]
WHERE
[UserId] = @Id
SET @BatchSize = @@ROWCOUNT
COMMIT TRANSACTION User_DeleteById_Ciphers
END
BEGIN TRANSACTION User_DeleteById
-- Delete OpaqueKeyExchangeCredentials
DELETE
FROM
[dbo].[OpaqueKeyExchangeCredential]
WHERE
[UserId] = @Id
-- Delete WebAuthnCredentials
DELETE
FROM
[dbo].[WebAuthnCredential]
WHERE
[UserId] = @Id
-- Delete folders
DELETE
FROM
[dbo].[Folder]
WHERE
[UserId] = @Id
-- Delete AuthRequest, must be before Device
DELETE
FROM
[dbo].[AuthRequest]
WHERE
[UserId] = @Id
-- Delete devices
DELETE
FROM
[dbo].[Device]
WHERE
[UserId] = @Id
-- Delete collection users
DELETE
CU
FROM
[dbo].[CollectionUser] CU
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[Id] = CU.[OrganizationUserId]
WHERE
OU.[UserId] = @Id
-- Delete group users
DELETE
GU
FROM
[dbo].[GroupUser] GU
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[Id] = GU.[OrganizationUserId]
WHERE
OU.[UserId] = @Id
-- Delete AccessPolicy
DELETE
AP
FROM
[dbo].[AccessPolicy] AP
INNER JOIN
[dbo].[OrganizationUser] OU ON OU.[Id] = AP.[OrganizationUserId]
WHERE
[UserId] = @Id
-- Delete organization users
DELETE
FROM
[dbo].[OrganizationUser]
WHERE
[UserId] = @Id
-- Delete provider users
DELETE
FROM
[dbo].[ProviderUser]
WHERE
[UserId] = @Id
-- Delete SSO Users
DELETE
FROM
[dbo].[SsoUser]
WHERE
[UserId] = @Id
-- Delete Emergency Accesses
DELETE
FROM
[dbo].[EmergencyAccess]
WHERE
[GrantorId] = @Id
OR
[GranteeId] = @Id
-- Delete Sends
DELETE
FROM
[dbo].[Send]
WHERE
[UserId] = @Id
-- Delete Notification Status
DELETE
FROM
[dbo].[NotificationStatus]
WHERE
[UserId] = @Id
-- Delete Notification
DELETE
FROM
[dbo].[Notification]
WHERE
[UserId] = @Id
-- Finally, delete the user
DELETE
FROM
[dbo].[User]
WHERE
[Id] = @Id
COMMIT TRANSACTION User_DeleteById
END
GO