diff --git a/src/Core/Identity/U2fTokenProvider.cs b/src/Core/Identity/U2fTokenProvider.cs index 59bf3ce504..6f45682ac1 100644 --- a/src/Core/Identity/U2fTokenProvider.cs +++ b/src/Core/Identity/U2fTokenProvider.cs @@ -10,6 +10,7 @@ using System.Linq; using U2fLib = U2F.Core.Crypto.U2F; using U2F.Core.Models; using U2F.Core.Exceptions; +using System; namespace Bit.Core.Identity { @@ -80,7 +81,8 @@ namespace Bit.Core.Identity Challenge = auth.Challenge, KeyHandle = auth.KeyHandle, Version = auth.Version, - UserId = user.Id + UserId = user.Id, + CreationDate = DateTime.UtcNow }); challenges.Add(new diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 23ae1b362a..f08f4a4841 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -281,13 +281,14 @@ namespace Bit.Core.Services public async Task StartU2fRegistrationAsync(User user) { await _u2fRepository.DeleteManyByUserIdAsync(user.Id); - var reg = U2fLib.StartRegistration(Utilities.CoreHelpers.U2fAppIdUrl(_globalSettings)); + var reg = U2fLib.StartRegistration(CoreHelpers.U2fAppIdUrl(_globalSettings)); await _u2fRepository.CreateAsync(new U2f { AppId = reg.AppId, Challenge = reg.Challenge, Version = reg.Version, - UserId = user.Id + UserId = user.Id, + CreationDate = DateTime.UtcNow }); return new U2fRegistration diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index b467380cfe..ddb4c8226e 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -229,5 +229,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/U2f_DeleteOld.sql b/src/Sql/dbo/Stored Procedures/U2f_DeleteOld.sql new file mode 100644 index 0000000000..c4658e07e1 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/U2f_DeleteOld.sql @@ -0,0 +1,19 @@ +CREATE PROCEDURE [dbo].[U2f_DeleteOld] +AS +BEGIN + SET NOCOUNT ON + + DECLARE @BatchSize INT = 100 + DECLARE @Threshold DATETIME2(7) = DATEADD (day, -7, GETUTCDATE()) + + WHILE @BatchSize > 0 + BEGIN + DELETE TOP(@BatchSize) + FROM + [dbo].[U2f] + WHERE + [CreationDate] < @Threshold + + SET @BatchSize = @@ROWCOUNT + END +END \ No newline at end of file diff --git a/src/Sql/dbo/Tables/U2f.sql b/src/Sql/dbo/Tables/U2f.sql index 6abdf1f3cd..00d23f97a3 100644 --- a/src/Sql/dbo/Tables/U2f.sql +++ b/src/Sql/dbo/Tables/U2f.sql @@ -10,3 +10,13 @@ CONSTRAINT [FK_U2f_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ); + +GO +CREATE NONCLUSTERED INDEX [IX_U2f_CreationDate] + ON [dbo].[U2f]([CreationDate] ASC) + + +GO +CREATE NONCLUSTERED INDEX [IX_U2f_UserId] + ON [dbo].[U2f]([UserId] ASC); + diff --git a/util/Setup/DbScripts/2018-06-11_00_WebVaultUpdates.sql b/util/Setup/DbScripts/2018-06-11_00_WebVaultUpdates.sql index 46078876dd..e45688e074 100644 --- a/util/Setup/DbScripts/2018-06-11_00_WebVaultUpdates.sql +++ b/util/Setup/DbScripts/2018-06-11_00_WebVaultUpdates.sql @@ -27,6 +27,26 @@ BEGIN END GO +IF NOT EXISTS ( + SELECT * FROM sys.indexes WHERE [Name]='IX_U2f_CreationDate' + AND object_id = OBJECT_ID('[dbo].[U2f]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_U2f_CreationDate] + ON [dbo].[U2f]([CreationDate] ASC) +END +GO + +IF NOT EXISTS ( + SELECT * FROM sys.indexes WHERE [Name]='IX_U2f_UserId' + AND object_id = OBJECT_ID('[dbo].[U2f]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_U2f_UserId] + ON [dbo].[U2f]([UserId] ASC) +END +GO + IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'UserView') BEGIN DROP VIEW [dbo].[UserView] @@ -41,6 +61,33 @@ FROM [dbo].[User] GO +IF OBJECT_ID('[dbo].[U2f_DeleteOld]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[U2f_DeleteOld] +END +GO + +CREATE PROCEDURE [dbo].[U2f_DeleteOld] +AS +BEGIN + SET NOCOUNT ON + + DECLARE @BatchSize INT = 100 + DECLARE @Threshold DATETIME2(7) = DATEADD (day, -7, GETUTCDATE()) + + WHILE @BatchSize > 0 + BEGIN + DELETE TOP(@BatchSize) + FROM + [dbo].[U2f] + WHERE + [CreationDate] < @Threshold + + SET @BatchSize = @@ROWCOUNT + END +END +GO + IF OBJECT_ID('[dbo].[Grant_DeleteExpired]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[Grant_DeleteExpired]