diff --git a/src/Core/Enums/CipherRepromptType.cs b/src/Core/Enums/CipherRepromptType.cs
new file mode 100644
index 0000000000..0e5b60ff20
--- /dev/null
+++ b/src/Core/Enums/CipherRepromptType.cs
@@ -0,0 +1,8 @@
+namespace Bit.Core.Enums
+{
+ public enum CipherRepromptType : byte
+ {
+ None = 0,
+ Password = 1,
+ }
+}
diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs
index 4c6a4217c4..2c22a94f72 100644
--- a/src/Core/Enums/EventType.cs
+++ b/src/Core/Enums/EventType.cs
@@ -28,6 +28,7 @@
Cipher_ClientAutofilled = 1114,
Cipher_SoftDeleted = 1115,
Cipher_Restored = 1116,
+ Cipher_ClientToggledCardNumberVisible = 1117,
Collection_Created = 1300,
Collection_Updated = 1301,
diff --git a/src/Core/Models/Api/Request/CipherRequestModel.cs b/src/Core/Models/Api/Request/CipherRequestModel.cs
index d1db599e71..e4d1f4fc57 100644
--- a/src/Core/Models/Api/Request/CipherRequestModel.cs
+++ b/src/Core/Models/Api/Request/CipherRequestModel.cs
@@ -20,6 +20,7 @@ namespace Bit.Core.Models.Api
public string OrganizationId { get; set; }
public string FolderId { get; set; }
public bool Favorite { get; set; }
+ public CipherRepromptType Reprompt { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
@@ -59,6 +60,7 @@ namespace Bit.Core.Models.Api
{
existingCipher.FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : (Guid?)new Guid(FolderId);
existingCipher.Favorite = Favorite;
+ existingCipher.Reprompt = Reprompt;
ToCipher(existingCipher);
return existingCipher;
}
diff --git a/src/Core/Models/Api/Response/CipherResponseModel.cs b/src/Core/Models/Api/Response/CipherResponseModel.cs
index 9d7e718fc2..88f236d7ea 100644
--- a/src/Core/Models/Api/Response/CipherResponseModel.cs
+++ b/src/Core/Models/Api/Response/CipherResponseModel.cs
@@ -6,6 +6,7 @@ using System.Linq;
using Newtonsoft.Json;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
+using Bit.Core.Enums;
namespace Bit.Core.Models.Api
{
@@ -91,12 +92,14 @@ namespace Bit.Core.Models.Api
Favorite = cipher.Favorite;
Edit = cipher.Edit;
ViewPassword = cipher.ViewPassword;
+ Reprompt = cipher.Reprompt.GetValueOrDefault(CipherRepromptType.None);
}
public string FolderId { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool ViewPassword { get; set; }
+ public CipherRepromptType Reprompt { get; set; }
}
public class CipherDetailsResponseModel : CipherResponseModel
diff --git a/src/Core/Models/Table/Cipher.cs b/src/Core/Models/Table/Cipher.cs
index 0eb3c6bf32..cf9ba9a7e5 100644
--- a/src/Core/Models/Table/Cipher.cs
+++ b/src/Core/Models/Table/Cipher.cs
@@ -21,6 +21,7 @@ namespace Bit.Core.Models.Table
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
public DateTime? DeletedDate { get; internal set; }
+ public Enums.CipherRepromptType? Reprompt { get; set; }
public void SetNewId()
{
diff --git a/src/Sql/dbo/Functions/CipherDetails.sql b/src/Sql/dbo/Functions/CipherDetails.sql
index 3d10be774e..f32e4bdfc0 100644
--- a/src/Sql/dbo/Functions/CipherDetails.sql
+++ b/src/Sql/dbo/Functions/CipherDetails.sql
@@ -25,6 +25,7 @@ SELECT
THEN NULL
ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
END [FolderId],
- C.[DeletedDate]
+ C.[DeletedDate],
+ C.[Reprompt]
FROM
[dbo].[Cipher] C
\ No newline at end of file
diff --git a/src/Sql/dbo/Stored Procedures/CipherDetails_Create.sql b/src/Sql/dbo/Stored Procedures/CipherDetails_Create.sql
index bd4c575534..0aaef08ff2 100644
--- a/src/Sql/dbo/Stored Procedures/CipherDetails_Create.sql
+++ b/src/Sql/dbo/Stored Procedures/CipherDetails_Create.sql
@@ -14,7 +14,8 @@
@Edit BIT, -- not used
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
- @DeletedDate DATETIME2(7)
+ @DeletedDate DATETIME2(7),
+ @Reprompt TINYINT
AS
BEGIN
SET NOCOUNT ON
@@ -33,7 +34,8 @@ BEGIN
[Folders],
[CreationDate],
[RevisionDate],
- [DeletedDate]
+ [DeletedDate],
+ [Reprompt]
)
VALUES
(
@@ -46,7 +48,8 @@ BEGIN
CASE WHEN @FolderId IS NOT NULL THEN CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}') ELSE NULL END,
@CreationDate,
@RevisionDate,
- @DeletedDate
+ @DeletedDate,
+ @Reprompt
)
IF @OrganizationId IS NOT NULL
diff --git a/src/Sql/dbo/Stored Procedures/CipherDetails_Update.sql b/src/Sql/dbo/Stored Procedures/CipherDetails_Update.sql
index bb1f006f6e..7ec2ae6fa3 100644
--- a/src/Sql/dbo/Stored Procedures/CipherDetails_Update.sql
+++ b/src/Sql/dbo/Stored Procedures/CipherDetails_Update.sql
@@ -14,7 +14,8 @@
@Edit BIT, -- not used
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
- @DeletedDate DATETIME2(2)
+ @DeletedDate DATETIME2(2),
+ @Reprompt TINYINT
AS
BEGIN
SET NOCOUNT ON
@@ -47,6 +48,7 @@ BEGIN
ELSE
JSON_MODIFY([Favorites], @UserIdPath, NULL)
END,
+ [Reprompt] = @Reprompt,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate
diff --git a/src/Sql/dbo/Tables/Cipher.sql b/src/Sql/dbo/Tables/Cipher.sql
index f10be7bb7b..3cea623a8a 100644
--- a/src/Sql/dbo/Tables/Cipher.sql
+++ b/src/Sql/dbo/Tables/Cipher.sql
@@ -1,4 +1,5 @@
-CREATE TABLE [dbo].[Cipher] (
+
+CREATE TABLE [dbo].[Cipher] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[UserId] UNIQUEIDENTIFIER NULL,
[OrganizationId] UNIQUEIDENTIFIER NULL,
@@ -10,6 +11,7 @@
[CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NOT NULL,
[DeletedDate] DATETIME2 (7) NULL,
+ [Reprompt] TINYINT NULL,
CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
diff --git a/util/Migrator/DbScripts/2021-04-13_00_CipherPasswordPrompt.sql b/util/Migrator/DbScripts/2021-04-13_00_CipherPasswordPrompt.sql
new file mode 100644
index 0000000000..fb483c645d
--- /dev/null
+++ b/util/Migrator/DbScripts/2021-04-13_00_CipherPasswordPrompt.sql
@@ -0,0 +1,196 @@
+IF COL_LENGTH('[dbo].[Cipher]', 'Reprompt') IS NULL
+BEGIN
+ ALTER TABLE [dbo].[Cipher]
+ ADD [Reprompt] TINYINT NULL;
+END
+GO
+
+IF OBJECT_ID('[dbo].[CipherDetails]') IS NOT NULL
+BEGIN
+ DROP FUNCTION [dbo].[CipherDetails]
+END
+GO
+
+CREATE FUNCTION [dbo].[CipherDetails](@UserId UNIQUEIDENTIFIER)
+RETURNS TABLE
+AS RETURN
+SELECT
+ C.[Id],
+ C.[UserId],
+ C.[OrganizationId],
+ C.[Type],
+ C.[Data],
+ C.[Attachments],
+ C.[CreationDate],
+ C.[RevisionDate],
+ CASE
+ WHEN
+ @UserId IS NULL
+ OR C.[Favorites] IS NULL
+ OR JSON_VALUE(C.[Favorites], CONCAT('$."', @UserId, '"')) IS NULL
+ THEN 0
+ ELSE 1
+ END [Favorite],
+ CASE
+ WHEN
+ @UserId IS NULL
+ OR C.[Folders] IS NULL
+ THEN NULL
+ ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
+ END [FolderId],
+ C.[DeletedDate],
+ C.[Reprompt]
+FROM
+ [dbo].[Cipher] C
+GO
+
+IF OBJECT_ID('[dbo].[UserCipherDetails]') IS NOT NULL
+BEGIN
+ EXECUTE sp_refreshsqlmodule N'[dbo].[UserCipherDetails]';
+END
+GO
+
+IF OBJECT_ID('[dbo].[CipherDetails_Create]') IS NOT NULL
+BEGIN
+ DROP PROCEDURE [dbo].[CipherDetails_Create]
+END
+GO
+
+CREATE PROCEDURE [dbo].[CipherDetails_Create]
+ @Id UNIQUEIDENTIFIER,
+ @UserId UNIQUEIDENTIFIER,
+ @OrganizationId UNIQUEIDENTIFIER,
+ @Type TINYINT,
+ @Data NVARCHAR(MAX),
+ @Favorites NVARCHAR(MAX), -- not used
+ @Folders NVARCHAR(MAX), -- not used
+ @Attachments NVARCHAR(MAX), -- not used
+ @CreationDate DATETIME2(7),
+ @RevisionDate DATETIME2(7),
+ @FolderId UNIQUEIDENTIFIER,
+ @Favorite BIT,
+ @Edit BIT, -- not used
+ @ViewPassword BIT, -- not used
+ @OrganizationUseTotp BIT, -- not used
+ @DeletedDate DATETIME2(7),
+ @Reprompt TINYINT
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
+ DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
+
+ INSERT INTO [dbo].[Cipher]
+ (
+ [Id],
+ [UserId],
+ [OrganizationId],
+ [Type],
+ [Data],
+ [Favorites],
+ [Folders],
+ [CreationDate],
+ [RevisionDate],
+ [DeletedDate],
+ [Reprompt]
+ )
+ VALUES
+ (
+ @Id,
+ CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
+ @OrganizationId,
+ @Type,
+ @Data,
+ CASE WHEN @Favorite = 1 THEN CONCAT('{', @UserIdKey, ':true}') ELSE NULL END,
+ CASE WHEN @FolderId IS NOT NULL THEN CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}') ELSE NULL END,
+ @CreationDate,
+ @RevisionDate,
+ @DeletedDate,
+ @Reprompt
+ )
+
+ IF @OrganizationId IS NOT NULL
+ BEGIN
+ EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
+ END
+ ELSE IF @UserId IS NOT NULL
+ BEGIN
+ EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
+ END
+END
+GO
+
+IF OBJECT_ID('[dbo].[CipherDetails_Update]') IS NOT NULL
+BEGIN
+ DROP PROCEDURE [dbo].[CipherDetails_Update]
+END
+GO
+
+CREATE PROCEDURE [dbo].[CipherDetails_Update]
+ @Id UNIQUEIDENTIFIER,
+ @UserId UNIQUEIDENTIFIER,
+ @OrganizationId UNIQUEIDENTIFIER,
+ @Type TINYINT,
+ @Data NVARCHAR(MAX),
+ @Favorites NVARCHAR(MAX), -- not used
+ @Folders NVARCHAR(MAX), -- not used
+ @Attachments NVARCHAR(MAX), -- not used
+ @CreationDate DATETIME2(7),
+ @RevisionDate DATETIME2(7),
+ @FolderId UNIQUEIDENTIFIER,
+ @Favorite BIT,
+ @Edit BIT, -- not used
+ @ViewPassword BIT, -- not used
+ @OrganizationUseTotp BIT, -- not used
+ @DeletedDate DATETIME2(2),
+ @Reprompt TINYINT
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
+ DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
+
+ UPDATE
+ [dbo].[Cipher]
+ SET
+ [UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
+ [OrganizationId] = @OrganizationId,
+ [Type] = @Type,
+ [Data] = @Data,
+ [Folders] =
+ CASE
+ WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN
+ CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}')
+ WHEN @FolderId IS NOT NULL THEN
+ JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50)))
+ ELSE
+ JSON_MODIFY([Folders], @UserIdPath, NULL)
+ END,
+ [Favorites] =
+ CASE
+ WHEN @Favorite = 1 AND [Favorites] IS NULL THEN
+ CONCAT('{', @UserIdKey, ':true}')
+ WHEN @Favorite = 1 THEN
+ JSON_MODIFY([Favorites], @UserIdPath, CAST(1 AS BIT))
+ ELSE
+ JSON_MODIFY([Favorites], @UserIdPath, NULL)
+ END,
+ [Reprompt] = @Reprompt,
+ [CreationDate] = @CreationDate,
+ [RevisionDate] = @RevisionDate,
+ [DeletedDate] = @DeletedDate
+ WHERE
+ [Id] = @Id
+
+ IF @OrganizationId IS NOT NULL
+ BEGIN
+ EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
+ END
+ ELSE IF @UserId IS NOT NULL
+ BEGIN
+ EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
+ END
+END
+GO
diff --git a/util/Migrator/Migrator.csproj b/util/Migrator/Migrator.csproj
index 34653b3473..55d674aa44 100644
--- a/util/Migrator/Migrator.csproj
+++ b/util/Migrator/Migrator.csproj
@@ -9,6 +9,10 @@
+
+
+
+