Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ public static DeviceAuthRequestResponseModel From(DeviceAuthDetails deviceAuthDe
EncryptedUserKey = deviceAuthDetails.EncryptedUserKey
};

if (deviceAuthDetails.AuthRequestId != null && deviceAuthDetails.AuthRequestCreatedAt != null)
if (deviceAuthDetails.AuthRequestId != null && deviceAuthDetails.AuthRequestCreationDate != null)
{
converted.DevicePendingAuthRequest = new PendingAuthRequest
{
Id = (Guid)deviceAuthDetails.AuthRequestId,
CreationDate = (DateTime)deviceAuthDetails.AuthRequestCreatedAt
CreationDate = (DateTime)deviceAuthDetails.AuthRequestCreationDate
};
}

Expand Down
68 changes: 14 additions & 54 deletions src/Core/Auth/Models/Data/DeviceAuthDetails.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
ο»Ώusing Bit.Core.Auth.Utilities;
using Bit.Core.Entities;
using Bit.Core.Enums;

namespace Bit.Core.Auth.Models.Data;

public class DeviceAuthDetails : Device
{
public bool IsTrusted { get; set; }
public bool IsTrusted => DeviceExtensions.IsTrusted(this);
public Guid? AuthRequestId { get; set; }
public DateTime? AuthRequestCreatedAt { get; set; }
public DateTime? AuthRequestCreationDate { get; set; }

/**
* Parameterless constructor for Dapper name-based mapping.
*/
public DeviceAuthDetails() { }

/**
* Constructor for EF response.
Expand All @@ -24,62 +28,18 @@ public DeviceAuthDetails(
}

Id = device.Id;
UserId = device.UserId;
Name = device.Name;
Type = device.Type;
Identifier = device.Identifier;
PushToken = device.PushToken;
CreationDate = device.CreationDate;
IsTrusted = device.IsTrusted();
EncryptedPublicKey = device.EncryptedPublicKey;
RevisionDate = device.RevisionDate;
EncryptedUserKey = device.EncryptedUserKey;
EncryptedPublicKey = device.EncryptedPublicKey;
EncryptedPrivateKey = device.EncryptedPrivateKey;
Active = device.Active;
AuthRequestId = authRequestId;
AuthRequestCreatedAt = authRequestCreationDate;
}

/**
* Constructor for dapper response.
* Note: if the authRequestId or authRequestCreationDate is null it comes back as
* an empty guid and a min value for datetime. That could change if the stored
* procedure runs on a different kind of db.
*/
public DeviceAuthDetails(
Guid id,
Guid userId,
string name,
short type,
string identifier,
string pushToken,
DateTime creationDate,
DateTime revisionDate,
string encryptedUserKey,
string encryptedPublicKey,
string encryptedPrivateKey,
bool active,
Guid authRequestId,
DateTime authRequestCreationDate)
{
Id = id;
Name = name;
Type = (DeviceType)type;
Identifier = identifier;
CreationDate = creationDate;
IsTrusted = new Device
{
Id = id,
UserId = userId,
Name = name,
Type = (DeviceType)type,
Identifier = identifier,
PushToken = pushToken,
RevisionDate = revisionDate,
EncryptedUserKey = encryptedUserKey,
EncryptedPublicKey = encryptedPublicKey,
EncryptedPrivateKey = encryptedPrivateKey,
Active = active
}.IsTrusted();
EncryptedPublicKey = encryptedPublicKey;
EncryptedUserKey = encryptedUserKey;
AuthRequestId = authRequestId != Guid.Empty ? authRequestId : null;
AuthRequestCreatedAt =
authRequestCreationDate != DateTime.MinValue ? authRequestCreationDate : null;
AuthRequestCreationDate = authRequestCreationDate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,38 @@ BEGIN
SET NOCOUNT ON;

SELECT
D.*,
AR.Id as AuthRequestId,
AR.CreationDate as AuthRequestCreationDate
FROM dbo.DeviceView D
LEFT JOIN (
SELECT
Id,
CreationDate,
RequestDeviceIdentifier,
Approved,
ROW_NUMBER() OVER (PARTITION BY RequestDeviceIdentifier ORDER BY CreationDate DESC) as rn
FROM dbo.AuthRequestView
WHERE Type IN (0, 1) -- AuthenticateAndUnlock and Unlock types only
AND CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired
AND UserId = @UserId -- Requests for this user only
) AR -- This join will get the most recent request per device, regardless of approval status
ON D.Identifier = AR.RequestDeviceIdentifier AND AR.rn = 1 AND AR.Approved IS NULL -- Get only the most recent unapproved request per device
D.[Id],
D.[UserId],
D.[Name],
D.[Type],
D.[Identifier],
D.[PushToken],
D.[CreationDate],
D.[RevisionDate],
D.[EncryptedUserKey],
D.[EncryptedPublicKey],
D.[EncryptedPrivateKey],
D.[Active],
AR.[Id] AS [AuthRequestId],
AR.[CreationDate] AS [AuthRequestCreationDate]
FROM
[dbo].[DeviceView] D
LEFT OUTER JOIN (
SELECT
[Id],
[CreationDate],
[RequestDeviceIdentifier],
[Approved],
ROW_NUMBER() OVER (PARTITION BY [RequestDeviceIdentifier] ORDER BY [CreationDate] DESC) AS rn
FROM
[dbo].[AuthRequestView]
WHERE
[Type] IN (0,1) -- AuthenticateAndUnlock and Unlock types only
AND [CreationDate] >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired
AND [UserId] = @UserId -- Requests for this user only
) AR -- This join will get the most recent request per device, regardless of approval status
ON D.[Identifier] = AR.[RequestDeviceIdentifier] AND AR.[rn] = 1 AND AR.[Approved] IS NULL -- Get only the most recent unapproved request per device
WHERE
D.UserId = @UserId -- Include only devices for this user
AND D.Active = 1; -- Include only active devices
D.[UserId] = @UserId -- Include only devices for this user
AND D.[Active] = 1; -- Include only active devices
END;

Loading
Loading