1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

Auth/pm 2996/add auth request data to devices response model (#5152)

fix(auth): [PM-2996] Add Pending Auth Request Data to Devices Response
- New stored procedure to fetch the appropriate data.
- Updated devices controller to respond with the new data.
- Tests written at the controller and repository level.
Resolves PM-2996
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-01-07 15:52:53 -05:00
committed by GitHub
parent 5ae232e336
commit cc96e35072
21 changed files with 620 additions and 30 deletions

View File

@ -8,9 +8,9 @@ namespace Bit.Infrastructure.IntegrationTest.Auth.Repositories;
public class AuthRequestRepositoryTests
{
private readonly static TimeSpan _userRequestExpiration = TimeSpan.FromMinutes(15);
private readonly static TimeSpan _adminRequestExpiration = TimeSpan.FromDays(6);
private readonly static TimeSpan _afterAdminApprovalExpiration = TimeSpan.FromHours(12);
private static readonly TimeSpan _userRequestExpiration = TimeSpan.FromMinutes(15);
private static readonly TimeSpan _adminRequestExpiration = TimeSpan.FromDays(6);
private static readonly TimeSpan _afterAdminApprovalExpiration = TimeSpan.FromHours(12);
[DatabaseTheory, DatabaseData]
public async Task DeleteExpiredAsync_Works(
@ -25,11 +25,11 @@ public class AuthRequestRepositoryTests
SecurityStamp = "stamp",
});
// A user auth request type that has passed it's expiration time, should be deleted.
// A user auth request type that has passed its expiration time, should be deleted.
var userExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AuthenticateAndUnlock, CreateExpiredDate(_userRequestExpiration)));
// An AdminApproval request that hasn't had any action taken on it and has passed it's expiration time, should be deleted.
// An AdminApproval request that hasn't had any action taken on it and has passed its expiration time, should be deleted.
var adminApprovalExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, CreateExpiredDate(_adminRequestExpiration)));
@ -37,7 +37,7 @@ public class AuthRequestRepositoryTests
var adminApprovedExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.AddDays(-6), true, CreateExpiredDate(_afterAdminApprovalExpiration)));
// An AdminApproval request that was rejected within it's allowed lifetime but has no gone past it's expiration time, should be deleted.
// An AdminApproval request that was rejected within its allowed lifetime but has not gone past its expiration time, should be deleted.
var adminRejectedExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, CreateExpiredDate(_adminRequestExpiration), false, DateTime.UtcNow.AddHours(-1)));
@ -45,7 +45,7 @@ public class AuthRequestRepositoryTests
var notExpiredUserAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.Unlock, DateTime.UtcNow.AddMinutes(-1)));
// An AdminApproval AuthRequest that was create 6 days 23 hours 59 minutes 59 seconds ago which is right on the edge of still being valid
// An AdminApproval AuthRequest that was created 6 days 23 hours 59 minutes 59 seconds ago which is right on the edge of still being valid
var notExpiredAdminApprovalRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.Add(new TimeSpan(days: 6, hours: 23, minutes: 59, seconds: 59))));

View File

@ -0,0 +1,191 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.Auth.Repositories;
public class DeviceRepositoryTests
{
[DatabaseTheory]
[DatabaseData]
public async Task GetManyByUserIdWithDeviceAuth_Works_ReturnsExpectedResults(
IDeviceRepository sutRepository,
IUserRepository userRepository,
IAuthRequestRepository authRequestRepository)
{
// Arrange
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var device = await sutRepository.CreateAsync(new Device
{
Active = true,
Name = "chrome-test",
UserId = user.Id,
Type = DeviceType.ChromeBrowser,
Identifier = Guid.NewGuid().ToString(),
});
var staleAuthRequest = await authRequestRepository.CreateAsync(new AuthRequest
{
ResponseDeviceId = null,
Approved = null,
Type = AuthRequestType.AuthenticateAndUnlock,
OrganizationId = null,
UserId = user.Id,
RequestIpAddress = ":1",
RequestDeviceIdentifier = device.Identifier,
AccessCode = "AccessCode_1234",
PublicKey = "PublicKey_1234"
});
staleAuthRequest.CreationDate = DateTime.UtcNow.AddMinutes(-10);
await authRequestRepository.ReplaceAsync(staleAuthRequest);
var freshAuthRequest = await authRequestRepository.CreateAsync(new AuthRequest
{
ResponseDeviceId = null,
Approved = null,
Type = AuthRequestType.AuthenticateAndUnlock,
OrganizationId = null,
UserId = user.Id,
RequestIpAddress = ":1",
RequestDeviceIdentifier = device.Identifier,
AccessCode = "AccessCode_1234",
PublicKey = "PublicKey_1234",
Key = "Key_1234",
MasterPasswordHash = "MasterPasswordHash_1234"
});
// Act
var response = await sutRepository.GetManyByUserIdWithDeviceAuth(user.Id);
// Assert
Assert.NotNull(response.First().AuthRequestId);
Assert.NotNull(response.First().AuthRequestCreatedAt);
Assert.Equal(response.First().AuthRequestId, freshAuthRequest.Id);
}
[DatabaseTheory]
[DatabaseData]
public async Task GetManyByUserIdWithDeviceAuth_WorksWithNoAuthRequestAndMultipleDevices_ReturnsExpectedResults(
IDeviceRepository sutRepository,
IUserRepository userRepository)
{
// Arrange
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
await sutRepository.CreateAsync(new Device
{
Active = true,
Name = "chrome-test",
UserId = user.Id,
Type = DeviceType.ChromeBrowser,
Identifier = Guid.NewGuid().ToString(),
});
await sutRepository.CreateAsync(new Device
{
Active = true,
Name = "macos-test",
UserId = user.Id,
Type = DeviceType.MacOsDesktop,
Identifier = Guid.NewGuid().ToString(),
});
// Act
var response = await sutRepository.GetManyByUserIdWithDeviceAuth(user.Id);
// Assert
Assert.NotNull(response.First());
Assert.Null(response.First().AuthRequestId);
Assert.True(response.Count == 2);
}
[DatabaseTheory]
[DatabaseData]
public async Task GetManyByUserIdWithDeviceAuth_FailsToRespondWithAnyAuthData_ReturnsExpectedResults(
IDeviceRepository sutRepository,
IUserRepository userRepository,
IAuthRequestRepository authRequestRepository)
{
var casesThatCauseNoAuthDataInResponse = new[]
{
new
{
authRequestType = AuthRequestType.AdminApproval, // Device typing is wrong
authRequestApproved = (bool?)null,
expirey = DateTime.UtcNow.AddMinutes(0),
},
new
{
authRequestType = AuthRequestType.AuthenticateAndUnlock,
authRequestApproved = (bool?)true, // Auth request is already approved
expirey = DateTime.UtcNow.AddMinutes(0),
},
new
{
authRequestType = AuthRequestType.AuthenticateAndUnlock,
authRequestApproved = (bool?)null,
expirey = DateTime.UtcNow.AddMinutes(-30), // Past the point of expiring
}
};
foreach (var testCase in casesThatCauseNoAuthDataInResponse)
{
// Arrange
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var device = await sutRepository.CreateAsync(new Device
{
Active = true,
Name = "chrome-test",
UserId = user.Id,
Type = DeviceType.ChromeBrowser,
Identifier = Guid.NewGuid().ToString(),
});
var authRequest = await authRequestRepository.CreateAsync(new AuthRequest
{
ResponseDeviceId = null,
Approved = testCase.authRequestApproved,
Type = testCase.authRequestType,
OrganizationId = null,
UserId = user.Id,
RequestIpAddress = ":1",
RequestDeviceIdentifier = device.Identifier,
AccessCode = "AccessCode_1234",
PublicKey = "PublicKey_1234"
});
authRequest.CreationDate = testCase.expirey;
await authRequestRepository.ReplaceAsync(authRequest);
// Act
var response = await sutRepository.GetManyByUserIdWithDeviceAuth(user.Id);
// Assert
Assert.Null(response.First().AuthRequestId);
Assert.Null(response.First().AuthRequestCreatedAt);
}
}
}