From 02bea3c48df68521ddb3b0ae95a00a3412e16931 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Mon, 26 Sep 2022 13:21:13 -0400 Subject: [PATCH] [SG-167] Implement Passwordless Authentication via Notifications (#2276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SG-549] Commit Initial AuthRequest Repository (#2174) * Model Passwordless * Scaffold database for Passwordless * Implement SQL Repository * [SG-167] Base Passwordless API (#2185) * Implement Passwordless notifications * Implement Controller * Add documentation to BaseRequestValidator * Register AuthRequestRepo * Remove ExpirationDate from the AuthRequest table * [SG-407] Create job to delete expired requests (#2187) * chore: init * remove exp date * fix: log name * [SG-167] Added fingerprint phrase to response model. (#2233) * Remove FailedLoginAttempt logic * Block unknown devices * Add EF Support for passwordless * Got SignalR working for responses * Added delete job method to EF repo * Implement a GetMany API endpoint for AuthRequests * Ran dotnet format * Fix a merge issues * Redated migration scripts * tried sorting sqlproj * Remove FailedLoginAttempts from SQL * Groom Postgres script * Remove extra commas from migration script * Correct isSpent() * [SG-167] Adde identity validation for passwordless requests. Registered IAuthRepository. * [SG-167] Added origin of the request to response model * Use display name for device identifier in response * Add datetime conversions back to postgres migration script * [SG-655] Add anonymous endpoint for checking if a device & user combo match * [review] Consolidate error conditions Co-authored-by: Brandon Maharaj <107377945+BrandonM-Bitwarden@users.noreply.github.com> Co-authored-by: André Filipe da Silva Bispo Co-authored-by: André Bispo --- src/Admin/Jobs/DeleteAuthRequestsJob.cs | 27 + src/Admin/Jobs/JobsHostedService.cs | 9 +- src/Api/Controllers/AuthRequestsController.cs | 145 ++ src/Api/Controllers/DevicesController.cs | 24 +- .../Models/Request/AuthRequestRequestModel.cs | 32 + .../Response/AuthRequestResponseModel.cs | 43 + src/Core/Entities/AuthRequest.cs | 41 + src/Core/Enums/AuthRequestType.cs | 7 + src/Core/Enums/PushType.cs | 3 + .../ResourceOwnerPasswordValidator.cs | 28 +- src/Core/Models/PushNotification.cs | 6 + .../Repositories/IAuthRequestRepository.cs | 9 + src/Core/Services/IPushNotificationService.cs | 2 + .../AzureQueuePushNotificationService.cs | 21 + .../MultiServicePushNotificationService.cs | 12 + .../NotificationHubPushNotificationService.cs | 21 + ...NotificationsApiPushNotificationService.cs | 21 + .../RelayPushNotificationService.cs | 21 + .../NoopPushNotificationService.cs | 10 + src/Core/Settings/GlobalSettings.cs | 6 + src/Core/Settings/IGlobalSettings.cs | 1 + .../Settings/IPasswordlessAuthSettings.cs | 6 + .../DapperServiceCollectionExtensions.cs | 1 + .../Repositories/AuthRequestRepository.cs | 43 + ...ityFrameworkServiceCollectionExtensions.cs | 1 + .../Models/AuthRequest.cs | 17 + .../Repositories/AuthRequestRepository.cs | 35 + .../Repositories/DatabaseContext.cs | 4 + .../AnonymousNotificationsHub.cs | 19 + src/Notifications/AzureQueueHostedService.cs | 5 +- .../Controllers/SendController.cs | 2 +- src/Notifications/HubHelpers.cs | 22 +- src/Notifications/INotificationHub.cs | 7 + src/Notifications/Startup.cs | 7 +- src/Sql/Sql.sqlproj | 8 + .../Stored Procedures/AuthRequest_Create.sql | 57 + .../AuthRequest_DeleteById.sql | 12 + .../AuthRequest_DeleteIfExpired.sql | 6 + .../AuthRequest_ReadById.sql | 13 + .../AuthRequest_ReadByUserId.sql | 13 + .../Stored Procedures/AuthRequest_Update.sql | 22 + src/Sql/dbo/Tables/AuthRequest.sql | 23 + src/Sql/dbo/Views/AuthRequestView.sql | 6 + .../AutoFixture/AuthRequestFixtures.cs | 61 + .../EntityFrameworkRepositoryFixtures.cs | 1 + .../AuthRequestRepositoryTests.cs | 50 + .../EqualityComparers/AuthRequestCompare.cs | 23 + .../2022-09-12_00_AuthRequestInit.sql | 218 +++ ...44222_PasswordlessAuthRequests.Designer.cs | 1672 ++++++++++++++++ ...20220912144222_PasswordlessAuthRequests.cs | 71 + .../DatabaseContextModelSnapshot.cs | 76 + .../2022-09-12_00_PasswordlessAuth.sql | 31 + ...63921_PasswordlessAuthRequests.Designer.cs | 1680 +++++++++++++++++ ...20220830163921_PasswordlessAuthRequests.cs | 905 +++++++++ .../DatabaseContextModelSnapshot.cs | 175 +- ...022-09-12_00_PasswordlessAuthRequests.psql | 133 ++ 56 files changed, 5853 insertions(+), 61 deletions(-) create mode 100644 src/Admin/Jobs/DeleteAuthRequestsJob.cs create mode 100644 src/Api/Controllers/AuthRequestsController.cs create mode 100644 src/Api/Models/Request/AuthRequestRequestModel.cs create mode 100644 src/Api/Models/Response/AuthRequestResponseModel.cs create mode 100644 src/Core/Entities/AuthRequest.cs create mode 100644 src/Core/Enums/AuthRequestType.cs create mode 100644 src/Core/Repositories/IAuthRequestRepository.cs create mode 100644 src/Core/Settings/IPasswordlessAuthSettings.cs create mode 100644 src/Infrastructure.Dapper/Repositories/AuthRequestRepository.cs create mode 100644 src/Infrastructure.EntityFramework/Models/AuthRequest.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/AuthRequestRepository.cs create mode 100644 src/Notifications/AnonymousNotificationsHub.cs create mode 100644 src/Notifications/INotificationHub.cs create mode 100644 src/Sql/dbo/Stored Procedures/AuthRequest_Create.sql create mode 100644 src/Sql/dbo/Stored Procedures/AuthRequest_DeleteById.sql create mode 100644 src/Sql/dbo/Stored Procedures/AuthRequest_DeleteIfExpired.sql create mode 100644 src/Sql/dbo/Stored Procedures/AuthRequest_ReadById.sql create mode 100644 src/Sql/dbo/Stored Procedures/AuthRequest_ReadByUserId.sql create mode 100644 src/Sql/dbo/Stored Procedures/AuthRequest_Update.sql create mode 100644 src/Sql/dbo/Tables/AuthRequest.sql create mode 100644 src/Sql/dbo/Views/AuthRequestView.sql create mode 100644 test/Infrastructure.EFIntegration.Test/AutoFixture/AuthRequestFixtures.cs create mode 100644 test/Infrastructure.EFIntegration.Test/Repositories/AuthRequestRepositoryTests.cs create mode 100644 test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/AuthRequestCompare.cs create mode 100644 util/Migrator/DbScripts/2022-09-12_00_AuthRequestInit.sql create mode 100644 util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.cs create mode 100644 util/MySqlMigrations/Scripts/2022-09-12_00_PasswordlessAuth.sql create mode 100644 util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.cs create mode 100644 util/PostgresMigrations/Scripts/2022-09-12_00_PasswordlessAuthRequests.psql diff --git a/src/Admin/Jobs/DeleteAuthRequestsJob.cs b/src/Admin/Jobs/DeleteAuthRequestsJob.cs new file mode 100644 index 0000000000..f0d26a33ac --- /dev/null +++ b/src/Admin/Jobs/DeleteAuthRequestsJob.cs @@ -0,0 +1,27 @@ +using Bit.Core; +using Bit.Core.Jobs; +using Bit.Core.Repositories; +using Quartz; + +namespace Bit.Admin.Jobs; + +public class DeleteAuthRequestsJob : BaseJob +{ + private readonly IAuthRequestRepository _authRepo; + + public DeleteAuthRequestsJob( + IAuthRequestRepository authrepo, + ILogger logger) + : base(logger) + { + _authRepo = authrepo; + } + + protected async override Task ExecuteJobAsync(IJobExecutionContext context) + { + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: Start"); + var count = await _authRepo.DeleteExpiredAsync(); + _logger.LogInformation(Constants.BypassFiltersEventId, $"{count} records deleted from AuthRequests."); + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: End"); + } +} diff --git a/src/Admin/Jobs/JobsHostedService.cs b/src/Admin/Jobs/JobsHostedService.cs index 53b5c05660..fd83e07af8 100644 --- a/src/Admin/Jobs/JobsHostedService.cs +++ b/src/Admin/Jobs/JobsHostedService.cs @@ -59,6 +59,11 @@ public class JobsHostedService : BaseJobsHostedService .StartNow() .WithCronSchedule("0 0 0 * * ?") .Build(); + var everyFifteenMinutesTrigger = TriggerBuilder.Create() + .WithIdentity("everyFifteenMinutesTrigger") + .StartNow() + .WithCronSchedule("0 */15 * ? * *") + .Build(); var jobs = new List> { @@ -67,7 +72,8 @@ public class JobsHostedService : BaseJobsHostedService new Tuple(typeof(DatabaseUpdateStatisticsJob), everySaturdayAtMidnightTrigger), new Tuple(typeof(DatabaseRebuildlIndexesJob), everySundayAtMidnightTrigger), new Tuple(typeof(DeleteCiphersJob), everyDayAtMidnightUtc), - new Tuple(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger) + new Tuple(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger), + new Tuple(typeof(DeleteAuthRequestsJob), everyFifteenMinutesTrigger), }; if (!_globalSettings.SelfHosted) @@ -91,5 +97,6 @@ public class JobsHostedService : BaseJobsHostedService services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } diff --git a/src/Api/Controllers/AuthRequestsController.cs b/src/Api/Controllers/AuthRequestsController.cs new file mode 100644 index 0000000000..32c4ef8467 --- /dev/null +++ b/src/Api/Controllers/AuthRequestsController.cs @@ -0,0 +1,145 @@ +using Bit.Api.Models.Request; +using Bit.Api.Models.Response; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Controllers; + +[Route("auth-requests")] +[Authorize("Application")] +public class AuthRequestsController : Controller +{ + private readonly IUserRepository _userRepository; + private readonly IDeviceRepository _deviceRepository; + private readonly IUserService _userService; + private readonly IAuthRequestRepository _authRequestRepository; + private readonly ICurrentContext _currentContext; + private readonly IPushNotificationService _pushNotificationService; + private readonly IGlobalSettings _globalSettings; + + public AuthRequestsController( + IUserRepository userRepository, + IDeviceRepository deviceRepository, + IUserService userService, + IAuthRequestRepository authRequestRepository, + ICurrentContext currentContext, + IPushNotificationService pushNotificationService, + IGlobalSettings globalSettings) + { + _userRepository = userRepository; + _deviceRepository = deviceRepository; + _userService = userService; + _authRequestRepository = authRequestRepository; + _currentContext = currentContext; + _pushNotificationService = pushNotificationService; + _globalSettings = globalSettings; + } + + [HttpGet("")] + public async Task> Get() + { + var userId = _userService.GetProperUserId(User).Value; + var authRequests = await _authRequestRepository.GetManyByUserIdAsync(userId); + var responses = authRequests.Select(a => new AuthRequestResponseModel(a, _globalSettings.SelfHosted)).ToList(); + return new ListResponseModel(responses); + } + + [HttpGet("{id}")] + public async Task Get(string id) + { + var userId = _userService.GetProperUserId(User).Value; + var authRequest = await _authRequestRepository.GetByIdAsync(new Guid(id)); + if (authRequest == null || authRequest.UserId != userId) + { + throw new NotFoundException(); + } + + return new AuthRequestResponseModel(authRequest, _globalSettings.SelfHosted); + } + + [HttpGet("{id}/response")] + [AllowAnonymous] + public async Task GetResponse(string id, [FromQuery] string code) + { + var authRequest = await _authRequestRepository.GetByIdAsync(new Guid(id)); + if (authRequest == null || code != authRequest.AccessCode || authRequest.GetExpirationDate() < DateTime.UtcNow) + { + throw new NotFoundException(); + } + + return new AuthRequestResponseModel(authRequest, _globalSettings.SelfHosted); + } + + [HttpPost("")] + [AllowAnonymous] + public async Task Post([FromBody] AuthRequestCreateRequestModel model) + { + var user = await _userRepository.GetByEmailAsync(model.Email); + if (user == null) + { + throw new NotFoundException(); + } + if (!_currentContext.DeviceType.HasValue) + { + throw new BadRequestException("Device type not provided."); + } + if (!_globalSettings.PasswordlessAuth.KnownDevicesOnly) + { + var d = await _deviceRepository.GetByIdentifierAsync(_currentContext.DeviceIdentifier); + if (d == null || d.UserId != user.Id) + { + throw new NotFoundException(); + } + } + + var authRequest = new AuthRequest + { + RequestDeviceIdentifier = model.DeviceIdentifier, + RequestDeviceType = _currentContext.DeviceType.Value, + RequestIpAddress = _currentContext.IpAddress, + AccessCode = model.AccessCode, + PublicKey = model.PublicKey, + UserId = user.Id, + Type = model.Type.Value, + RequestFingerprint = model.FingerprintPhrase + }; + authRequest = await _authRequestRepository.CreateAsync(authRequest); + await _pushNotificationService.PushAuthRequestAsync(authRequest); + return new AuthRequestResponseModel(authRequest, _globalSettings.SelfHosted); + } + + [HttpPut("{id}")] + public async Task Put(string id, [FromBody] AuthRequestUpdateRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + var authRequest = await _authRequestRepository.GetByIdAsync(new Guid(id)); + if (authRequest == null || authRequest.UserId != userId || authRequest.GetExpirationDate() < DateTime.UtcNow) + { + throw new NotFoundException(); + } + + var device = await _deviceRepository.GetByIdentifierAsync(model.DeviceIdentifier); + if (device == null) + { + throw new BadRequestException("Invalid device."); + } + + if (model.RequestApproved) + { + authRequest.Key = model.Key; + authRequest.MasterPasswordHash = model.MasterPasswordHash; + authRequest.ResponseDeviceId = device.Id; + authRequest.ResponseDate = DateTime.UtcNow; + await _authRequestRepository.ReplaceAsync(authRequest); + } + + await _pushNotificationService.PushAuthRequestResponseAsync(authRequest); + return new AuthRequestResponseModel(authRequest, _globalSettings.SelfHosted); + } +} diff --git a/src/Api/Controllers/DevicesController.cs b/src/Api/Controllers/DevicesController.cs index 77fb34c648..cc4ae0f128 100644 --- a/src/Api/Controllers/DevicesController.cs +++ b/src/Api/Controllers/DevicesController.cs @@ -16,15 +16,18 @@ public class DevicesController : Controller private readonly IDeviceRepository _deviceRepository; private readonly IDeviceService _deviceService; private readonly IUserService _userService; + private readonly IUserRepository _userRepository; public DevicesController( IDeviceRepository deviceRepository, IDeviceService deviceService, - IUserService userService) + IUserService userService, + IUserRepository userRepository) { _deviceRepository = deviceRepository; _deviceService = deviceService; _userService = userService; + _userRepository = userRepository; } [HttpGet("{id}")] @@ -126,4 +129,23 @@ public class DevicesController : Controller await _deviceService.DeleteAsync(device); } + + [AllowAnonymous] + [HttpGet("knowndevice/{email}/{identifier}")] + public async Task GetByIdentifier(string email, string identifier) + { + if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(identifier)) + { + throw new BadRequestException("Please provide an email and device identifier"); + } + + var user = await _userRepository.GetByEmailAsync(email); + if (user == null) + { + return false; + } + + var device = await _deviceRepository.GetByIdentifierAsync(identifier, user.Id); + return device != null; + } } diff --git a/src/Api/Models/Request/AuthRequestRequestModel.cs b/src/Api/Models/Request/AuthRequestRequestModel.cs new file mode 100644 index 0000000000..8dab8036b6 --- /dev/null +++ b/src/Api/Models/Request/AuthRequestRequestModel.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; +using Newtonsoft.Json; + +namespace Bit.Api.Models.Request; + +public class AuthRequestCreateRequestModel +{ + [Required] + public string Email { get; set; } + [Required] + public string PublicKey { get; set; } + [Required] + public string DeviceIdentifier { get; set; } + [Required] + [StringLength(25)] + public string AccessCode { get; set; } + [Required] + public AuthRequestType? Type { get; set; } + [Required] + public string FingerprintPhrase { get; set; } +} + +public class AuthRequestUpdateRequestModel +{ + public string Key { get; set; } + public string MasterPasswordHash { get; set; } + [Required] + public string DeviceIdentifier { get; set; } + [Required] + public bool RequestApproved { get; set; } +} diff --git a/src/Api/Models/Response/AuthRequestResponseModel.cs b/src/Api/Models/Response/AuthRequestResponseModel.cs new file mode 100644 index 0000000000..488dd7848d --- /dev/null +++ b/src/Api/Models/Response/AuthRequestResponseModel.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Api; + +namespace Bit.Api.Models.Response; + +public class AuthRequestResponseModel : ResponseModel +{ + public AuthRequestResponseModel(AuthRequest authRequest, bool isSelfHosted, string obj = "auth-request") + : base(obj) + { + if (authRequest == null) + { + throw new ArgumentNullException(nameof(authRequest)); + } + + Id = authRequest.Id.ToString(); + PublicKey = authRequest.PublicKey; + RequestDeviceType = authRequest.RequestDeviceType.GetType().GetMember(authRequest.RequestDeviceType.ToString()) + .FirstOrDefault()?.GetCustomAttribute()?.GetName(); + RequestIpAddress = authRequest.RequestIpAddress; + RequestFingerprint = authRequest.RequestFingerprint; + Key = authRequest.Key; + MasterPasswordHash = authRequest.MasterPasswordHash; + CreationDate = authRequest.CreationDate; + RequestApproved = !string.IsNullOrWhiteSpace(Key) && + (authRequest.Type == AuthRequestType.Unlock || !string.IsNullOrWhiteSpace(MasterPasswordHash)); + Origin = Origin = isSelfHosted ? "SelfHosted" : "bitwarden.com"; + } + + public string Id { get; set; } + public string PublicKey { get; set; } + public string RequestDeviceType { get; set; } + public string RequestIpAddress { get; set; } + public string RequestFingerprint { get; set; } + public string Key { get; set; } + public string MasterPasswordHash { get; set; } + public DateTime CreationDate { get; set; } + public bool RequestApproved { get; set; } + public string Origin { get; set; } +} diff --git a/src/Core/Entities/AuthRequest.cs b/src/Core/Entities/AuthRequest.cs new file mode 100644 index 0000000000..75646ccd54 --- /dev/null +++ b/src/Core/Entities/AuthRequest.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Utilities; + +namespace Bit.Core.Entities; + +public class AuthRequest : ITableObject +{ + public Guid Id { get; set; } + public Guid UserId { get; set; } + public Enums.AuthRequestType Type { get; set; } + [MaxLength(50)] + public string RequestDeviceIdentifier { get; set; } + public Enums.DeviceType RequestDeviceType { get; set; } + [MaxLength(50)] + public string RequestIpAddress { get; set; } + public string RequestFingerprint { get; set; } + public Guid? ResponseDeviceId { get; set; } + [MaxLength(25)] + public string AccessCode { get; set; } + public string PublicKey { get; set; } + public string Key { get; set; } + public string MasterPasswordHash { get; set; } + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime? ResponseDate { get; set; } + public DateTime? AuthenticationDate { get; set; } + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } + + public bool IsSpent() + { + return ResponseDate.HasValue || AuthenticationDate.HasValue || GetExpirationDate() < DateTime.UtcNow; + } + + public DateTime GetExpirationDate() + { + return CreationDate.AddMinutes(15); + } +} diff --git a/src/Core/Enums/AuthRequestType.cs b/src/Core/Enums/AuthRequestType.cs new file mode 100644 index 0000000000..e8e88465c3 --- /dev/null +++ b/src/Core/Enums/AuthRequestType.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Enums; + +public enum AuthRequestType : byte +{ + AuthenticateAndUnlock = 0, + Unlock = 1 +} diff --git a/src/Core/Enums/PushType.cs b/src/Core/Enums/PushType.cs index 9054d1d40b..82029e9213 100644 --- a/src/Core/Enums/PushType.cs +++ b/src/Core/Enums/PushType.cs @@ -20,4 +20,7 @@ public enum PushType : byte SyncSendCreate = 12, SyncSendUpdate = 13, SyncSendDelete = 14, + + AuthRequest = 15, + AuthRequestResponse = 16, } diff --git a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs index 82b3cf50a8..3cce62db1b 100644 --- a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -20,6 +20,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator userManager, IDeviceRepository deviceRepository, @@ -36,6 +37,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator +{ + Task DeleteExpiredAsync(); + Task> GetManyByUserIdAsync(Guid userId); +} diff --git a/src/Core/Services/IPushNotificationService.cs b/src/Core/Services/IPushNotificationService.cs index 34e98515ff..0efddb4cac 100644 --- a/src/Core/Services/IPushNotificationService.cs +++ b/src/Core/Services/IPushNotificationService.cs @@ -19,6 +19,8 @@ public interface IPushNotificationService Task PushSyncSendCreateAsync(Send send); Task PushSyncSendUpdateAsync(Send send); Task PushSyncSendDeleteAsync(Send send); + Task PushAuthRequestAsync(AuthRequest authRequest); + Task PushAuthRequestResponseAsync(AuthRequest authRequest); Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, string deviceId = null); Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, string deviceId = null); diff --git a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs b/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs index fb7bcafca2..ab5bdfbe5d 100644 --- a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs +++ b/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs @@ -130,6 +130,27 @@ public class AzureQueuePushNotificationService : IPushNotificationService await SendMessageAsync(type, message, false); } + public async Task PushAuthRequestAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequest); + } + + public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); + } + + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) + { + var message = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId + }; + + await SendMessageAsync(type, message, true); + } + public async Task PushSyncSendCreateAsync(Send send) { await PushSendAsync(send, PushType.SyncSendCreate); diff --git a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs b/src/Core/Services/Implementations/MultiServicePushNotificationService.cs index 4e1678da6d..0e0b3d3a34 100644 --- a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs +++ b/src/Core/Services/Implementations/MultiServicePushNotificationService.cs @@ -133,6 +133,18 @@ public class MultiServicePushNotificationService : IPushNotificationService return Task.FromResult(0); } + public Task PushAuthRequestAsync(AuthRequest authRequest) + { + PushToServices((s) => s.PushAuthRequestAsync(authRequest)); + return Task.FromResult(0); + } + + public Task PushAuthRequestResponseAsync(AuthRequest authRequest) + { + PushToServices((s) => s.PushAuthRequestResponseAsync(authRequest)); + return Task.FromResult(0); + } + public Task PushSyncSendDeleteAsync(Send send) { PushToServices((s) => s.PushSyncSendDeleteAsync(send)); diff --git a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs index fbd7ab9ce1..908dcb76d0 100644 --- a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs @@ -167,6 +167,27 @@ public class NotificationHubPushNotificationService : IPushNotificationService } } + public async Task PushAuthRequestAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequest); + } + + public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); + } + + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) + { + var message = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId + }; + + await SendPayloadToUserAsync(authRequest.UserId, type, message, true); + } + private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext) { await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext)); diff --git a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs b/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs index 144178f84d..bff7f392f5 100644 --- a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs @@ -137,6 +137,27 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await SendMessageAsync(type, message, false); } + public async Task PushAuthRequestAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequest); + } + + public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); + } + + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) + { + var message = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId + }; + + await SendMessageAsync(type, message, true); + } + public async Task PushSyncSendCreateAsync(Send send) { await PushSendAsync(send, PushType.SyncSendCreate); diff --git a/src/Core/Services/Implementations/RelayPushNotificationService.cs b/src/Core/Services/Implementations/RelayPushNotificationService.cs index b3670ad7b1..573102ac91 100644 --- a/src/Core/Services/Implementations/RelayPushNotificationService.cs +++ b/src/Core/Services/Implementations/RelayPushNotificationService.cs @@ -167,6 +167,27 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti } } + public async Task PushAuthRequestAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequest); + } + + public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) + { + await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); + } + + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) + { + var message = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId + }; + + await SendPayloadToUserAsync(authRequest.UserId, type, message, true); + } + private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext) { var request = new PushSendRequestModel diff --git a/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs b/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs index ee2c6a498b..a99ef96fb8 100644 --- a/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs +++ b/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs @@ -81,6 +81,16 @@ public class NoopPushNotificationService : IPushNotificationService return Task.FromResult(0); } + public Task PushAuthRequestAsync(AuthRequest authRequest) + { + return Task.FromResult(0); + } + + public Task PushAuthRequestResponseAsync(AuthRequest authRequest) + { + return Task.FromResult(0); + } + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, string deviceId = null) { diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 7bb66377de..c75a74ba7c 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -71,6 +71,7 @@ public class GlobalSettings : IGlobalSettings public virtual ITwoFactorAuthSettings TwoFactorAuth { get; set; } = new TwoFactorAuthSettings(); public virtual DistributedIpRateLimitingSettings DistributedIpRateLimiting { get; set; } = new DistributedIpRateLimitingSettings(); + public virtual IPasswordlessAuthSettings PasswordlessAuth { get; set; } = new PasswordlessAuthSettings(); public string BuildExternalUri(string explicitValue, string name) { @@ -453,6 +454,7 @@ public class GlobalSettings : IGlobalSettings get => string.IsNullOrWhiteSpace(_apiUri) ? "https://api.bitwarden.com" : _apiUri; set => _apiUri = value; } + } public class AmazonSettings @@ -519,4 +521,8 @@ public class GlobalSettings : IGlobalSettings public int SlidingWindowSeconds { get; set; } = 120; } + public class PasswordlessAuthSettings : IPasswordlessAuthSettings + { + public bool KnownDevicesOnly { get; set; } = true; + } } diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index 1929da1f3d..16a06c3ec9 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -15,4 +15,5 @@ public interface IGlobalSettings IBaseServiceUriSettings BaseServiceUri { get; set; } ITwoFactorAuthSettings TwoFactorAuth { get; set; } ISsoSettings Sso { get; set; } + IPasswordlessAuthSettings PasswordlessAuth { get; set; } } diff --git a/src/Core/Settings/IPasswordlessAuthSettings.cs b/src/Core/Settings/IPasswordlessAuthSettings.cs new file mode 100644 index 0000000000..98bd8112d7 --- /dev/null +++ b/src/Core/Settings/IPasswordlessAuthSettings.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Settings; + +public interface IPasswordlessAuthSettings +{ + bool KnownDevicesOnly { get; set; } +} diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 9c138f7b02..bb374e3686 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -34,6 +34,7 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.Dapper/Repositories/AuthRequestRepository.cs b/src/Infrastructure.Dapper/Repositories/AuthRequestRepository.cs new file mode 100644 index 0000000000..1b843575b8 --- /dev/null +++ b/src/Infrastructure.Dapper/Repositories/AuthRequestRepository.cs @@ -0,0 +1,43 @@ +using System.Data; +using System.Data.SqlClient; +using Bit.Core.Entities; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Dapper; + +namespace Bit.Infrastructure.Dapper.Repositories; + +public class AuthRequestRepository : Repository, IAuthRequestRepository +{ + public AuthRequestRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { } + + public AuthRequestRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { } + + public async Task DeleteExpiredAsync() + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.ExecuteAsync( + $"[{Schema}].[AuthRequest_DeleteIfExpired]", + null, + commandType: CommandType.StoredProcedure); + } + } + + public async Task> GetManyByUserIdAsync(Guid userId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[{Schema}].[AuthRequest_ReadByUserId]", + new { UserId = userId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } +} diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index c8a99b2740..06897f1396 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -56,6 +56,7 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.EntityFramework/Models/AuthRequest.cs b/src/Infrastructure.EntityFramework/Models/AuthRequest.cs new file mode 100644 index 0000000000..2e5420d908 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Models/AuthRequest.cs @@ -0,0 +1,17 @@ +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.Models; + +public class AuthRequest : Core.Entities.AuthRequest +{ + public virtual User User { get; set; } + public virtual Device ResponseDevice { get; set; } +} + +public class AuthRequestMapperProfile : Profile +{ + public AuthRequestMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/AuthRequestRepository.cs b/src/Infrastructure.EntityFramework/Repositories/AuthRequestRepository.cs new file mode 100644 index 0000000000..2f5f7aa8ec --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/AuthRequestRepository.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Repositories; + +public class AuthRequestRepository : Repository, IAuthRequestRepository +{ + public AuthRequestRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.AuthRequests) + { } + public async Task DeleteExpiredAsync() + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var expiredRequests = await dbContext.AuthRequests.Where(a => a.CreationDate < DateTime.Now.AddMinutes(-15)).ToListAsync(); + dbContext.AuthRequests.RemoveRange(expiredRequests); + await dbContext.SaveChangesAsync(); + return 1; + } + } + + public async Task> GetManyByUserIdAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var userAuthRequests = await dbContext.AuthRequests.Where(a => a.UserId.Equals(userId)).ToListAsync(); + return Mapper.Map>(userAuthRequests); + } + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 88c2bb464a..1df5cc2586 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -39,6 +39,7 @@ public class DatabaseContext : DbContext public DbSet TaxRates { get; set; } public DbSet Transactions { get; set; } public DbSet Users { get; set; } + public DbSet AuthRequests { get; set; } protected override void OnModelCreating(ModelBuilder builder) { @@ -70,6 +71,7 @@ public class DatabaseContext : DbContext var eUser = builder.Entity(); var eOrganizationApiKey = builder.Entity(); var eOrganizationConnection = builder.Entity(); + var eAuthRequest = builder.Entity(); eCipher.Property(c => c.Id).ValueGeneratedNever(); eCollection.Property(c => c.Id).ValueGeneratedNever(); @@ -90,6 +92,7 @@ public class DatabaseContext : DbContext eUser.Property(c => c.Id).ValueGeneratedNever(); eOrganizationApiKey.Property(c => c.Id).ValueGeneratedNever(); eOrganizationConnection.Property(c => c.Id).ValueGeneratedNever(); + eAuthRequest.Property(ar => ar.Id).ValueGeneratedNever(); eCollectionCipher.HasKey(cc => new { cc.CollectionId, cc.CipherId }); eCollectionUser.HasKey(cu => new { cu.CollectionId, cu.OrganizationUserId }); @@ -135,5 +138,6 @@ public class DatabaseContext : DbContext eUser.ToTable(nameof(User)); eOrganizationApiKey.ToTable(nameof(OrganizationApiKey)); eOrganizationConnection.ToTable(nameof(OrganizationConnection)); + eAuthRequest.ToTable(nameof(AuthRequest)); } } diff --git a/src/Notifications/AnonymousNotificationsHub.cs b/src/Notifications/AnonymousNotificationsHub.cs new file mode 100644 index 0000000000..e3e7d478c8 --- /dev/null +++ b/src/Notifications/AnonymousNotificationsHub.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; + +namespace Bit.Notifications; + +[AllowAnonymous] +public class AnonymousNotificationsHub : Microsoft.AspNetCore.SignalR.Hub, INotificationHub +{ + public override async Task OnConnectedAsync() + { + var httpContext = Context.GetHttpContext(); + var token = httpContext.Request.Query["Token"].FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(token)) + { + await Groups.AddToGroupAsync(Context.ConnectionId, token); + } + await base.OnConnectedAsync(); + } +} diff --git a/src/Notifications/AzureQueueHostedService.cs b/src/Notifications/AzureQueueHostedService.cs index ba2e38d2c0..f0cc28ac8e 100644 --- a/src/Notifications/AzureQueueHostedService.cs +++ b/src/Notifications/AzureQueueHostedService.cs @@ -9,6 +9,7 @@ public class AzureQueueHostedService : IHostedService, IDisposable { private readonly ILogger _logger; private readonly IHubContext _hubContext; + private readonly IHubContext _anonymousHubContext; private readonly GlobalSettings _globalSettings; private Task _executingTask; @@ -18,11 +19,13 @@ public class AzureQueueHostedService : IHostedService, IDisposable public AzureQueueHostedService( ILogger logger, IHubContext hubContext, + IHubContext anonymousHubContext, GlobalSettings globalSettings) { _logger = logger; _hubContext = hubContext; _globalSettings = globalSettings; + _anonymousHubContext = anonymousHubContext; } public Task StartAsync(CancellationToken cancellationToken) @@ -62,7 +65,7 @@ public class AzureQueueHostedService : IHostedService, IDisposable try { await HubHelpers.SendNotificationToHubAsync( - message.DecodeMessageText(), _hubContext, cancellationToken); + message.DecodeMessageText(), _hubContext, _anonymousHubContext, cancellationToken); await _queueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt); } catch (Exception e) diff --git a/src/Notifications/Controllers/SendController.cs b/src/Notifications/Controllers/SendController.cs index 90fdac7d09..247036a646 100644 --- a/src/Notifications/Controllers/SendController.cs +++ b/src/Notifications/Controllers/SendController.cs @@ -25,7 +25,7 @@ public class SendController : Controller var notificationJson = await reader.ReadToEndAsync(); if (!string.IsNullOrWhiteSpace(notificationJson)) { - await HubHelpers.SendNotificationToHubAsync(notificationJson, _hubContext); + await HubHelpers.SendNotificationToHubAsync(notificationJson, _hubContext, null); } } } diff --git a/src/Notifications/HubHelpers.cs b/src/Notifications/HubHelpers.cs index 38b87e2276..bf4a1ab946 100644 --- a/src/Notifications/HubHelpers.cs +++ b/src/Notifications/HubHelpers.cs @@ -7,8 +7,12 @@ namespace Bit.Notifications; public static class HubHelpers { - public static async Task SendNotificationToHubAsync(string notificationJson, - IHubContext hubContext, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SendNotificationToHubAsync( + string notificationJson, + IHubContext hubContext, + IHubContext anonymousHubContext, + CancellationToken cancellationToken = default(CancellationToken) + ) { var notification = JsonSerializer.Deserialize>(notificationJson); switch (notification.Type) @@ -61,6 +65,20 @@ public static class HubHelpers await hubContext.Clients.User(sendNotification.Payload.UserId.ToString()) .SendAsync("ReceiveMessage", sendNotification, cancellationToken); break; + case PushType.AuthRequestResponse: + var authRequestResponseNotification = + JsonSerializer.Deserialize>( + notificationJson); + await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString()) + .SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken); + break; + case PushType.AuthRequest: + var authRequestNotification = + JsonSerializer.Deserialize>( + notificationJson); + await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString()) + .SendAsync("ReceiveMessage", authRequestNotification, cancellationToken); + break; default: break; } diff --git a/src/Notifications/INotificationHub.cs b/src/Notifications/INotificationHub.cs new file mode 100644 index 0000000000..c1a7954586 --- /dev/null +++ b/src/Notifications/INotificationHub.cs @@ -0,0 +1,7 @@ +namespace Bit.Notifications; + +public interface INotificationHub +{ + Task OnConnectedAsync(); + Task OnDisconnectedAsync(Exception exception); +} diff --git a/src/Notifications/Startup.cs b/src/Notifications/Startup.cs index e2509e46c5..c548e9072b 100644 --- a/src/Notifications/Startup.cs +++ b/src/Notifications/Startup.cs @@ -110,7 +110,12 @@ public class Startup { endpoints.MapHub("/hub", options => { - options.ApplicationMaxBufferSize = 2048; // client => server messages are not even used + options.ApplicationMaxBufferSize = 2048; + options.TransportMaxBufferSize = 4096; + }); + endpoints.MapHub("/anonymousHub", options => + { + options.ApplicationMaxBufferSize = 2048; options.TransportMaxBufferSize = 4096; }); endpoints.MapDefaultControllerRoute(); diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 8df155dd93..99f09ccbfb 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -73,6 +73,12 @@ + + + + + + @@ -344,6 +350,7 @@ + @@ -378,6 +385,7 @@ + diff --git a/src/Sql/dbo/Stored Procedures/AuthRequest_Create.sql b/src/Sql/dbo/Stored Procedures/AuthRequest_Create.sql new file mode 100644 index 0000000000..9dac3c0eb7 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/AuthRequest_Create.sql @@ -0,0 +1,57 @@ +CREATE PROCEDURE [dbo].[AuthRequest_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @Type TINYINT, + @RequestDeviceIdentifier NVARCHAR(50), + @RequestDeviceType TINYINT, + @RequestIpAddress VARCHAR(50), + @RequestFingerprint VARCHAR(MAX), + @ResponseDeviceId UNIQUEIDENTIFIER, + @AccessCode VARCHAR(25), + @PublicKey VARCHAR(MAX), + @Key VARCHAR(MAX), + @MasterPasswordHash VARCHAR(MAX), + @CreationDate DATETIME2(7), + @ResponseDate DATETIME2(7), + @AuthenticationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[AuthRequest] + ( + [Id], + [UserId], + [Type], + [RequestDeviceIdentifier], + [RequestDeviceType], + [RequestIpAddress], + [RequestFingerprint], + [ResponseDeviceId], + [AccessCode], + [PublicKey], + [Key], + [MasterPasswordHash], + [CreationDate], + [ResponseDate], + [AuthenticationDate] + ) + VALUES + ( + @Id, + @UserId, + @Type, + @RequestDeviceIdentifier, + @RequestDeviceType, + @RequestIpAddress, + @RequestFingerprint, + @ResponseDeviceId, + @AccessCode, + @PublicKey, + @Key, + @MasterPasswordHash, + @CreationDate, + @ResponseDate, + @AuthenticationDate + ) +END diff --git a/src/Sql/dbo/Stored Procedures/AuthRequest_DeleteById.sql b/src/Sql/dbo/Stored Procedures/AuthRequest_DeleteById.sql new file mode 100644 index 0000000000..af3acc2e6b --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/AuthRequest_DeleteById.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[AuthRequest_DeleteById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[AuthRequest] + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/AuthRequest_DeleteIfExpired.sql b/src/Sql/dbo/Stored Procedures/AuthRequest_DeleteIfExpired.sql new file mode 100644 index 0000000000..736729c7b2 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/AuthRequest_DeleteIfExpired.sql @@ -0,0 +1,6 @@ +CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired] +AS +BEGIN + SET NOCOUNT OFF + DELETE FROM [dbo].[AuthRequest] WHERE [CreationDate] < DATEADD(minute, -15, GETUTCDATE()); +END diff --git a/src/Sql/dbo/Stored Procedures/AuthRequest_ReadById.sql b/src/Sql/dbo/Stored Procedures/AuthRequest_ReadById.sql new file mode 100644 index 0000000000..84e30a26b9 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/AuthRequest_ReadById.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[AuthRequest_ReadById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[AuthRequestView] + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/AuthRequest_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/AuthRequest_ReadByUserId.sql new file mode 100644 index 0000000000..0654e9d7e2 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/AuthRequest_ReadByUserId.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[AuthRequest_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[AuthRequestView] + WHERE + [UserId] = @UserId +END diff --git a/src/Sql/dbo/Stored Procedures/AuthRequest_Update.sql b/src/Sql/dbo/Stored Procedures/AuthRequest_Update.sql new file mode 100644 index 0000000000..220a563824 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/AuthRequest_Update.sql @@ -0,0 +1,22 @@ +CREATE PROCEDURE [dbo].[AuthRequest_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @ResponseDeviceId UNIQUEIDENTIFIER, + @Key VARCHAR(MAX), + @MasterPasswordHash VARCHAR(MAX), + @ResponseDate DATETIME2(7), + @AuthenticationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[AuthRequest] + SET + [ResponseDeviceId] = @ResponseDeviceId, + [Key] = @Key, + [MasterPasswordHash] = @MasterPasswordHash, + [ResponseDate] = @ResponseDate, + [AuthenticationDate] = @AuthenticationDate + WHERE + [Id] = @Id +END diff --git a/src/Sql/dbo/Tables/AuthRequest.sql b/src/Sql/dbo/Tables/AuthRequest.sql new file mode 100644 index 0000000000..e02f59c51a --- /dev/null +++ b/src/Sql/dbo/Tables/AuthRequest.sql @@ -0,0 +1,23 @@ +CREATE TABLE [dbo].[AuthRequest] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [Type] SMALLINT NOT NULL, + [RequestDeviceIdentifier] NVARCHAR(50) NOT NULL, + [RequestDeviceType] SMALLINT NOT NULL, + [RequestIpAddress] VARCHAR(50) NOT NULL, + [RequestFingerprint] VARCHAR(MAX) NOT NULL, + [ResponseDeviceId] UNIQUEIDENTIFIER NULL, + [AccessCode] VARCHAR(25) NOT NULL, + [PublicKey] VARCHAR(MAX) NOT NULL, + [Key] VARCHAR(MAX) NULL, + [MasterPasswordHash] VARCHAR(MAX) NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [ResponseDate] DATETIME2 (7) NULL, + [AuthenticationDate] DATETIME2 (7) NULL, + CONSTRAINT [PK_AuthRequest] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_AuthRequest_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]), + CONSTRAINT [FK_AuthRequest_ResponseDevice] FOREIGN KEY ([ResponseDeviceId]) REFERENCES [dbo].[Device] ([Id]) +); + + +GO diff --git a/src/Sql/dbo/Views/AuthRequestView.sql b/src/Sql/dbo/Views/AuthRequestView.sql new file mode 100644 index 0000000000..0b4eccb471 --- /dev/null +++ b/src/Sql/dbo/Views/AuthRequestView.sql @@ -0,0 +1,6 @@ +CREATE VIEW [dbo].[AuthRequestView] +AS +SELECT + * +FROM + [dbo].[AuthRequest] diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/AuthRequestFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/AuthRequestFixtures.cs new file mode 100644 index 0000000000..e50c7a4af2 --- /dev/null +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/AuthRequestFixtures.cs @@ -0,0 +1,61 @@ +using AutoFixture; +using AutoFixture.Kernel; +using Bit.Core.Entities; +using Bit.Core.Test.AutoFixture.UserFixtures; +using Bit.Infrastructure.EFIntegration.Test.AutoFixture.Relays; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; + +namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture; + +internal class AuthRequestBuilder : ISpecimenBuilder +{ + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != typeof(AuthRequest)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + fixture.Customizations.Insert(0, new MaxLengthStringRelay()); + var obj = fixture.WithAutoNSubstitutions().Create(); + return obj; + } +} + +internal class EfAuthRequest : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new AuthRequestBuilder()); + fixture.Customizations.Add(new DeviceBuilder()); + fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + } +} + +internal class EfAuthRequestAutoDataAttribute : CustomAutoDataAttribute +{ + public EfAuthRequestAutoDataAttribute() : base(new SutProviderCustomization(), new EfAuthRequest()) + { } +} + +internal class InlineEfAuthRequestAutoDataAttribute : InlineCustomAutoDataAttribute +{ + public InlineEfAuthRequestAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfAuthRequest) }, values) + { } +} + diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs index 4a403b70bc..1fecaa6b9b 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs @@ -63,6 +63,7 @@ public class EfRepositoryListBuilder : ISpecimenBuilder where T : BaseEntityF fixture.Customize(x => x.FromFactory(() => new MapperConfiguration(cfg => { + cfg.AddProfile(); cfg.AddProfile(); cfg.AddProfile(); cfg.AddProfile(); diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/AuthRequestRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Repositories/AuthRequestRepositoryTests.cs new file mode 100644 index 0000000000..994c8dca83 --- /dev/null +++ b/test/Infrastructure.EFIntegration.Test/Repositories/AuthRequestRepositoryTests.cs @@ -0,0 +1,50 @@ +using Bit.Core.Entities; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Infrastructure.EFIntegration.Test.AutoFixture; +using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; +using Xunit; +using EfRepo = Bit.Infrastructure.EntityFramework.Repositories; +using SqlRepo = Bit.Infrastructure.Dapper.Repositories; + +namespace Bit.Infrastructure.EFIntegration.Test.Repositories; + +public class AuthRequestRepositoryTests +{ + [CiSkippedTheory, EfAuthRequestAutoData] + public async void CreateAsync_Works_DataMatches( + AuthRequest authRequest, + AuthRequestCompare equalityComparer, + List suts, + SqlRepo.AuthRequestRepository sqlAuthRequestRepo, + User user, + List efUserRepos, + SqlRepo.UserRepository sqlUserRepo + ) + { + authRequest.ResponseDeviceId = null; + var savedAuthRequests = new List(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efUser = await efUserRepos[i].CreateAsync(user); + sut.ClearChangeTracking(); + authRequest.UserId = efUser.Id; + + var postEfAuthRequest = await sut.CreateAsync(authRequest); + sut.ClearChangeTracking(); + + var savedAuthRequest = await sut.GetByIdAsync(postEfAuthRequest.Id); + savedAuthRequests.Add(savedAuthRequest); + } + + var sqlUser = await sqlUserRepo.CreateAsync(user); + authRequest.UserId = sqlUser.Id; + var sqlAuthRequest = await sqlAuthRequestRepo.CreateAsync(authRequest); + var savedSqlAuthRequest = await sqlAuthRequestRepo.GetByIdAsync(sqlAuthRequest.Id); + savedAuthRequests.Add(savedSqlAuthRequest); + + var distinctItems = savedAuthRequests.Distinct(equalityComparer); + Assert.True(!distinctItems.Skip(1).Any()); + } +} diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/AuthRequestCompare.cs b/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/AuthRequestCompare.cs new file mode 100644 index 0000000000..bbe2b14e88 --- /dev/null +++ b/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/AuthRequestCompare.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; +using Bit.Core.Entities; + +namespace Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; + +public class AuthRequestCompare : IEqualityComparer +{ + public bool Equals(AuthRequest x, AuthRequest y) + { + return x.AccessCode == y.AccessCode && + x.MasterPasswordHash == y.MasterPasswordHash && + x.PublicKey == y.PublicKey && + x.RequestDeviceIdentifier == y.RequestDeviceIdentifier && + x.RequestDeviceType == y.RequestDeviceType && + x.RequestIpAddress == y.RequestIpAddress && + x.RequestFingerprint == y.RequestFingerprint; + } + + public int GetHashCode([DisallowNull] AuthRequest obj) + { + return base.GetHashCode(); + } +} diff --git a/util/Migrator/DbScripts/2022-09-12_00_AuthRequestInit.sql b/util/Migrator/DbScripts/2022-09-12_00_AuthRequestInit.sql new file mode 100644 index 0000000000..7541e45962 --- /dev/null +++ b/util/Migrator/DbScripts/2022-09-12_00_AuthRequestInit.sql @@ -0,0 +1,218 @@ +-- Create Auth Request table +IF OBJECT_ID('[dbo].[AuthRequest]') IS NOT NULL +BEGIN + DROP TABLE [dbo].[AuthRequest] +END + +IF OBJECT_ID('[dbo].[AuthRequest]') IS NULL +BEGIN +CREATE TABLE [dbo].[AuthRequest] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [Type] SMALLINT NOT NULL, + [RequestDeviceIdentifier] NVARCHAR(50) NOT NULL, + [RequestDeviceType] SMALLINT NOT NULL, + [RequestIpAddress] VARCHAR(50) NOT NULL, + [RequestFingerprint] VARCHAR(MAX) NOT NULL, + [ResponseDeviceId] UNIQUEIDENTIFIER NULL, + [AccessCode] VARCHAR(25) NOT NULL, + [PublicKey] VARCHAR(MAX) NOT NULL, + [Key] VARCHAR(MAX) NULL, + [MasterPasswordHash] VARCHAR(MAX) NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [ResponseDate] DATETIME2 (7) NULL, + [AuthenticationDate] DATETIME2 (7) NULL, + CONSTRAINT [PK_AuthRequest] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_AuthRequest_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]), + CONSTRAINT [FK_AuthRequest_ResponseDevice] FOREIGN KEY ([ResponseDeviceId]) REFERENCES [dbo].[Device] ([Id]) +); +END +GO + +-- Create View +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'AuthRequestView') +BEGIN + DROP VIEW [dbo].[AuthRequestView] +END +GO + +CREATE VIEW [dbo].[AuthRequestView] +AS +SELECT + * +FROM + [dbo].[AuthRequest] +GO + +-- Auth Request CRUD sprocs +IF OBJECT_ID('[dbo].[AuthRequest_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[AuthRequest_Create] +END +GO + +CREATE PROCEDURE [dbo].[AuthRequest_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @Type TINYINT, + @RequestDeviceIdentifier NVARCHAR(50), + @RequestDeviceType TINYINT, + @RequestIpAddress VARCHAR(50), + @RequestFingerprint VARCHAR(MAX), + @ResponseDeviceId UNIQUEIDENTIFIER, + @AccessCode VARCHAR(25), + @PublicKey VARCHAR(MAX), + @Key VARCHAR(MAX), + @MasterPasswordHash VARCHAR(MAX), + @CreationDate DATETIME2(7), + @ResponseDate DATETIME2(7), + @AuthenticationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[AuthRequest] + ( + [Id], + [UserId], + [Type], + [RequestDeviceIdentifier], + [RequestDeviceType], + [RequestIpAddress], + [RequestFingerprint], + [ResponseDeviceId], + [AccessCode], + [PublicKey], + [Key], + [MasterPasswordHash], + [CreationDate], + [ResponseDate], + [AuthenticationDate] + ) + VALUES + ( + @Id, + @UserId, + @Type, + @RequestDeviceIdentifier, + @RequestDeviceType, + @RequestIpAddress, + @RequestFingerprint, + @ResponseDeviceId, + @AccessCode, + @PublicKey, + @Key, + @MasterPasswordHash, + @CreationDate, + @ResponseDate, + @AuthenticationDate + ) +END +GO + +IF OBJECT_ID('[dbo].[AuthRequest_Update]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[AuthRequest_Update] +END +GO + +CREATE PROCEDURE [dbo].[AuthRequest_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @ResponseDeviceId UNIQUEIDENTIFIER, + @Key VARCHAR(MAX), + @MasterPasswordHash VARCHAR(MAX), + @ResponseDate DATETIME2(7), + @AuthenticationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[AuthRequest] + SET + [ResponseDeviceId] = @ResponseDeviceId, + [Key] = @Key, + [MasterPasswordHash] = @MasterPasswordHash, + [ResponseDate] = @ResponseDate, + [AuthenticationDate] = @AuthenticationDate + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[AuthRequest_ReadById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[AuthRequest_ReadById] +END +GO + +CREATE PROCEDURE [dbo].[AuthRequest_ReadById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[AuthRequestView] + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[AuthRequest_DeleteById]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[AuthRequest_DeleteById] +END +GO + +CREATE PROCEDURE [dbo].[AuthRequest_DeleteById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[AuthRequest] + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[AuthRequest_DeleteIfExpired]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[AuthRequest_DeleteIfExpired] +END +GO + +CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired] +AS +BEGIN + SET NOCOUNT OFF + DELETE FROM [dbo].[AuthRequest] WHERE [CreationDate] < DATEADD(minute, -15, GETUTCDATE()); +END +GO + +IF OBJECT_ID('[dbo].[AuthRequest_ReadByUserId]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[AuthRequest_ReadByUserId] +END +GO + +CREATE PROCEDURE [dbo].[AuthRequest_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + + SELECT + * + FROM + [dbo].[AuthRequestView] + WHERE + [UserId] = @UserId +END +GO diff --git a/util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.Designer.cs b/util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.Designer.cs new file mode 100644 index 0000000000..866620693c --- /dev/null +++ b/util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.Designer.cs @@ -0,0 +1,1672 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20220912144222_PasswordlessAuthRequests")] + partial class PasswordlessAuthRequests + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.cs b/util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.cs new file mode 100644 index 0000000000..d74294e220 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20220912144222_PasswordlessAuthRequests.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class PasswordlessAuthRequests : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AuthRequest", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Type = table.Column(type: "tinyint unsigned", nullable: false), + RequestDeviceIdentifier = table.Column(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + RequestDeviceType = table.Column(type: "tinyint unsigned", nullable: false), + RequestIpAddress = table.Column(type: "varchar(50)", maxLength: 50, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + RequestFingerprint = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ResponseDeviceId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + AccessCode = table.Column(type: "varchar(25)", maxLength: 25, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PublicKey = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Key = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + MasterPasswordHash = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false), + ResponseDate = table.Column(type: "datetime(6)", nullable: true), + AuthenticationDate = table.Column(type: "datetime(6)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthRequest", x => x.Id); + table.ForeignKey( + name: "FK_AuthRequest_Device_ResponseDeviceId", + column: x => x.ResponseDeviceId, + principalTable: "Device", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_AuthRequest_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_AuthRequest_ResponseDeviceId", + table: "AuthRequest", + column: "ResponseDeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_AuthRequest_UserId", + table: "AuthRequest", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuthRequest"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 471b285a44..59ca6e5a20 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -19,6 +19,65 @@ namespace Bit.MySqlMigrations.Migrations .HasAnnotation("ProductVersion", "6.0.4") .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => { b.Property("Id") @@ -1208,6 +1267,23 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("User", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") diff --git a/util/MySqlMigrations/Scripts/2022-09-12_00_PasswordlessAuth.sql b/util/MySqlMigrations/Scripts/2022-09-12_00_PasswordlessAuth.sql new file mode 100644 index 0000000000..9959606f1a --- /dev/null +++ b/util/MySqlMigrations/Scripts/2022-09-12_00_PasswordlessAuth.sql @@ -0,0 +1,31 @@ +START TRANSACTION; + +CREATE TABLE `AuthRequest` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `UserId` char(36) COLLATE ascii_general_ci NOT NULL, + `Type` tinyint unsigned NOT NULL, + `RequestDeviceIdentifier` varchar(50) CHARACTER SET utf8mb4 NULL, + `RequestDeviceType` tinyint unsigned NOT NULL, + `RequestIpAddress` varchar(50) CHARACTER SET utf8mb4 NULL, + `RequestFingerprint` longtext CHARACTER SET utf8mb4 NULL, + `ResponseDeviceId` char(36) COLLATE ascii_general_ci NULL, + `AccessCode` varchar(25) CHARACTER SET utf8mb4 NULL, + `PublicKey` longtext CHARACTER SET utf8mb4 NULL, + `Key` longtext CHARACTER SET utf8mb4 NULL, + `MasterPasswordHash` longtext CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `ResponseDate` datetime(6) NULL, + `AuthenticationDate` datetime(6) NULL, + CONSTRAINT `PK_AuthRequest` PRIMARY KEY (`Id`), + CONSTRAINT `FK_AuthRequest_Device_ResponseDeviceId` FOREIGN KEY (`ResponseDeviceId`) REFERENCES `Device` (`Id`), + CONSTRAINT `FK_AuthRequest_User_UserId` FOREIGN KEY (`UserId`) REFERENCES `User` (`Id`) ON DELETE CASCADE +) CHARACTER SET=utf8mb4; + +CREATE INDEX `IX_AuthRequest_ResponseDeviceId` ON `AuthRequest` (`ResponseDeviceId`); + +CREATE INDEX `IX_AuthRequest_UserId` ON `AuthRequest` (`UserId`); + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20220912144222_PasswordlessAuthRequests', '6.0.4'); + +COMMIT; diff --git a/util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.Designer.cs b/util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.Designer.cs new file mode 100644 index 0000000000..ad123ddd66 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.Designer.cs @@ -0,0 +1,1680 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20220830163921_PasswordlessAuthRequests")] + partial class PasswordlessAuthRequests + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasColumnType("text"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasColumnType("text"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasColumnType("text"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.cs b/util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.cs new file mode 100644 index 0000000000..cef759de8c --- /dev/null +++ b/util/PostgresMigrations/Migrations/20220830163921_PasswordlessAuthRequests.cs @@ -0,0 +1,905 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class PasswordlessAuthRequests : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "User", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RenewalReminderDate", + table: "User", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "PremiumExpirationDate", + table: "User", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastFailedLoginDate", + table: "User", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "User", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "AccountRevisionDate", + table: "User", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Transaction", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "SsoUser", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "SsoConfig", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "SsoConfig", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Send", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "ExpirationDate", + table: "Send", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DeletionDate", + table: "Send", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Send", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "ProviderUser", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "ProviderUser", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "ProviderOrganization", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "ProviderOrganization", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Provider", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Provider", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Policy", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Policy", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "OrganizationUser", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "OrganizationUser", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "ValidUntil", + table: "OrganizationSponsorship", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastSyncDate", + table: "OrganizationSponsorship", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "OrganizationApiKey", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Organization", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "OwnersNotifiedOfAutoscaling", + table: "Organization", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ExpirationDate", + table: "Organization", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Organization", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Installation", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Group", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Group", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "ExpirationDate", + table: "Grant", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Grant", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "ConsumedDate", + table: "Grant", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Folder", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Folder", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "Date", + table: "Event", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "EmergencyAccess", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RecoveryInitiatedDate", + table: "EmergencyAccess", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastNotificationDate", + table: "EmergencyAccess", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "EmergencyAccess", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Device", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Device", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Collection", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Collection", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Cipher", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "DeletedDate", + table: "Cipher", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Cipher", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.CreateTable( + name: "AuthRequest", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + Type = table.Column(type: "smallint", nullable: false), + RequestDeviceIdentifier = table.Column(type: "text", nullable: true), + RequestDeviceType = table.Column(type: "smallint", nullable: false), + RequestIpAddress = table.Column(type: "text", nullable: true), + RequestFingerprint = table.Column(type: "text", nullable: true), + ResponseDeviceId = table.Column(type: "uuid", nullable: true), + AccessCode = table.Column(type: "text", nullable: true), + PublicKey = table.Column(type: "text", nullable: true), + Key = table.Column(type: "text", nullable: true), + MasterPasswordHash = table.Column(type: "text", nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false), + ResponseDate = table.Column(type: "timestamp with time zone", nullable: true), + AuthenticationDate = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthRequest", x => x.Id); + table.ForeignKey( + name: "FK_AuthRequest_Device_ResponseDeviceId", + column: x => x.ResponseDeviceId, + principalTable: "Device", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_AuthRequest_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AuthRequest_ResponseDeviceId", + table: "AuthRequest", + column: "ResponseDeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_AuthRequest_UserId", + table: "AuthRequest", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuthRequest"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "User", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RenewalReminderDate", + table: "User", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "PremiumExpirationDate", + table: "User", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastFailedLoginDate", + table: "User", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "User", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "AccountRevisionDate", + table: "User", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Transaction", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "SsoUser", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "SsoConfig", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "SsoConfig", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Send", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "ExpirationDate", + table: "Send", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DeletionDate", + table: "Send", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Send", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "ProviderUser", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "ProviderUser", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "ProviderOrganization", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "ProviderOrganization", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Provider", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Provider", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Policy", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Policy", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "OrganizationUser", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "OrganizationUser", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "ValidUntil", + table: "OrganizationSponsorship", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastSyncDate", + table: "OrganizationSponsorship", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "OrganizationApiKey", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Organization", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "OwnersNotifiedOfAutoscaling", + table: "Organization", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ExpirationDate", + table: "Organization", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Organization", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Installation", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Group", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Group", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "ExpirationDate", + table: "Grant", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Grant", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "ConsumedDate", + table: "Grant", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Folder", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Folder", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "Date", + table: "Event", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "EmergencyAccess", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RecoveryInitiatedDate", + table: "EmergencyAccess", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastNotificationDate", + table: "EmergencyAccess", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "EmergencyAccess", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Device", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Device", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Collection", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Collection", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RevisionDate", + table: "Cipher", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "DeletedDate", + table: "Cipher", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreationDate", + table: "Cipher", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index cdef656acc..c81a0201b0 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -23,6 +23,62 @@ namespace Bit.PostgresMigrations.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasColumnType("text"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasColumnType("text"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasColumnType("text"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => { b.Property("Id") @@ -32,13 +88,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); b.Property("DeletedDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Favorites") .HasColumnType("text"); @@ -53,7 +109,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("smallint"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -76,7 +132,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ExternalId") .HasMaxLength(300) @@ -89,7 +145,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -167,7 +223,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Identifier") .HasMaxLength(50) @@ -182,7 +238,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(255)"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -203,7 +259,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasMaxLength(256) @@ -219,13 +275,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("LastNotificationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("RecoveryInitiatedDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -260,7 +316,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("Date") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("DeviceType") .HasColumnType("smallint"); @@ -310,13 +366,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Name") .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserId") .HasColumnType("uuid"); @@ -339,10 +395,10 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(200)"); b.Property("ConsumedDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); @@ -352,7 +408,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(200)"); b.Property("ExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("SessionId") .HasMaxLength(100) @@ -380,7 +436,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ExternalId") .HasMaxLength(300) @@ -394,7 +450,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -429,7 +485,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasMaxLength(256) @@ -481,13 +537,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(30)"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Enabled") .HasColumnType("boolean"); b.Property("ExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Gateway") .HasColumnType("smallint"); @@ -523,7 +579,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(50)"); b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Plan") .HasMaxLength(50) @@ -542,7 +598,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Seats") .HasColumnType("integer"); @@ -610,7 +666,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -656,7 +712,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(256)"); b.Property("LastSyncDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("OfferedToEmail") .HasMaxLength(256) @@ -678,7 +734,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("ValidUntil") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -698,7 +754,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasMaxLength(256) @@ -721,7 +777,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -747,7 +803,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); @@ -759,7 +815,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -798,7 +854,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Enabled") .HasColumnType("boolean"); @@ -807,7 +863,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -826,7 +882,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Key") .HasColumnType("text"); @@ -838,7 +894,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Settings") .HasColumnType("text"); @@ -858,7 +914,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasColumnType("text"); @@ -873,7 +929,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -902,19 +958,19 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("integer"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); b.Property("DeletionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Disabled") .HasColumnType("boolean"); b.Property("ExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("HideEmail") .HasColumnType("boolean"); @@ -933,7 +989,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(300)"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -959,7 +1015,7 @@ namespace Bit.PostgresMigrations.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); @@ -971,7 +1027,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -989,7 +1045,7 @@ namespace Bit.PostgresMigrations.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ExternalId") .HasMaxLength(50) @@ -1049,7 +1105,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("numeric"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Details") .HasMaxLength(100) @@ -1095,7 +1151,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("AccountRevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ApiKey") .IsRequired() @@ -1103,7 +1159,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(30)"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Culture") .HasMaxLength(10) @@ -1151,7 +1207,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("LastFailedLoginDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("LicenseKey") .HasMaxLength(100) @@ -1176,7 +1232,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("PremiumExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("PrivateKey") .HasColumnType("text"); @@ -1188,10 +1244,10 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RenewalReminderDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("SecurityStamp") .IsRequired() @@ -1219,6 +1275,23 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("User", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") diff --git a/util/PostgresMigrations/Scripts/2022-09-12_00_PasswordlessAuthRequests.psql b/util/PostgresMigrations/Scripts/2022-09-12_00_PasswordlessAuthRequests.psql new file mode 100644 index 0000000000..0db38abc0f --- /dev/null +++ b/util/PostgresMigrations/Scripts/2022-09-12_00_PasswordlessAuthRequests.psql @@ -0,0 +1,133 @@ +START TRANSACTION; + +ALTER TABLE "User" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "User" ALTER COLUMN "RenewalReminderDate" TYPE timestamp with time zone; + +ALTER TABLE "User" ALTER COLUMN "PremiumExpirationDate" TYPE timestamp with time zone; + +ALTER TABLE "User" ALTER COLUMN "LastFailedLoginDate" TYPE timestamp with time zone; + +ALTER TABLE "User" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "User" ALTER COLUMN "AccountRevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Transaction" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "SsoUser" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "SsoConfig" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "SsoConfig" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Send" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Send" ALTER COLUMN "ExpirationDate" TYPE timestamp with time zone; + +ALTER TABLE "Send" ALTER COLUMN "DeletionDate" TYPE timestamp with time zone; + +ALTER TABLE "Send" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "ProviderUser" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "ProviderUser" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "ProviderOrganization" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "ProviderOrganization" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Provider" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Provider" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Policy" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Policy" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "OrganizationUser" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "OrganizationUser" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "OrganizationSponsorship" ALTER COLUMN "ValidUntil" TYPE timestamp with time zone; + +ALTER TABLE "OrganizationSponsorship" ALTER COLUMN "LastSyncDate" TYPE timestamp with time zone; + +ALTER TABLE "OrganizationApiKey" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Organization" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Organization" ALTER COLUMN "OwnersNotifiedOfAutoscaling" TYPE timestamp with time zone; + +ALTER TABLE "Organization" ALTER COLUMN "ExpirationDate" TYPE timestamp with time zone; + +ALTER TABLE "Organization" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Installation" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Group" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Group" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Grant" ALTER COLUMN "ExpirationDate" TYPE timestamp with time zone; + +ALTER TABLE "Grant" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Grant" ALTER COLUMN "ConsumedDate" TYPE timestamp with time zone; + +ALTER TABLE "Folder" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Folder" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Event" ALTER COLUMN "Date" TYPE timestamp with time zone; + +ALTER TABLE "EmergencyAccess" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "EmergencyAccess" ALTER COLUMN "RecoveryInitiatedDate" TYPE timestamp with time zone; + +ALTER TABLE "EmergencyAccess" ALTER COLUMN "LastNotificationDate" TYPE timestamp with time zone; + +ALTER TABLE "EmergencyAccess" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Device" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Device" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Collection" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Collection" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +ALTER TABLE "Cipher" ALTER COLUMN "RevisionDate" TYPE timestamp with time zone; + +ALTER TABLE "Cipher" ALTER COLUMN "DeletedDate" TYPE timestamp with time zone; + +ALTER TABLE "Cipher" ALTER COLUMN "CreationDate" TYPE timestamp with time zone; + +CREATE TABLE "AuthRequest" ( + "Id" uuid NOT NULL, + "UserId" uuid NOT NULL, + "Type" smallint NOT NULL, + "RequestDeviceIdentifier" text NULL, + "RequestDeviceType" smallint NOT NULL, + "RequestIpAddress" text NULL, + "RequestFingerprint" text NULL, + "ResponseDeviceId" uuid NULL, + "AccessCode" text NULL, + "PublicKey" text NULL, + "Key" text NULL, + "MasterPasswordHash" text NULL, + "CreationDate" timestamp with time zone NOT NULL, + "ResponseDate" timestamp with time zone NULL, + "AuthenticationDate" timestamp with time zone NULL, + CONSTRAINT "PK_AuthRequest" PRIMARY KEY ("Id"), + CONSTRAINT "FK_AuthRequest_Device_ResponseDeviceId" FOREIGN KEY ("ResponseDeviceId") REFERENCES "Device" ("Id"), + CONSTRAINT "FK_AuthRequest_User_UserId" FOREIGN KEY ("UserId") REFERENCES "User" ("Id") ON DELETE CASCADE +); + +CREATE INDEX "IX_AuthRequest_ResponseDeviceId" ON "AuthRequest" ("ResponseDeviceId"); + +CREATE INDEX "IX_AuthRequest_UserId" ON "AuthRequest" ("UserId"); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20220830163921_PasswordlessAuthRequests', '6.0.4'); + +COMMIT;