Files
server/src/Api/Auth/Controllers/AuthRequestsController.cs
rr-bw d2c2ae5b4d fix(invalid-auth-request-approvals): Auth/[PM-3387] Better Error Handling for Invalid Auth Request Approval (#6264)
If a user approves an invalid auth request, on the Requesting Device they currently they get stuck on the `LoginViaAuthRequestComponent` with a spinning wheel.

This PR makes it so that when an Approving Device attempts to approve an invalid auth request, the Approving Device receives an error toast and the `UpdateAuthRequestAsync()` operation is blocked.
2025-09-18 17:30:05 -07:00

139 lines
5.5 KiB
C#

// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Api.Auth.Models.Response;
using Bit.Api.Models.Response;
using Bit.Core;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
using Bit.Core.Auth.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Auth.Controllers;
[Route("auth-requests")]
[Authorize("Application")]
public class AuthRequestsController(
IUserService userService,
IAuthRequestRepository authRequestRepository,
IGlobalSettings globalSettings,
IAuthRequestService authRequestService) : Controller
{
private readonly IUserService _userService = userService;
private readonly IAuthRequestRepository _authRequestRepository = authRequestRepository;
private readonly IGlobalSettings _globalSettings = globalSettings;
private readonly IAuthRequestService _authRequestService = authRequestService;
[HttpGet("")]
public async Task<ListResponseModel<AuthRequestResponseModel>> GetAll()
{
var userId = _userService.GetProperUserId(User).Value;
var authRequests = await _authRequestRepository.GetManyByUserIdAsync(userId);
var responses = authRequests.Select(a => new AuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault));
return new ListResponseModel<AuthRequestResponseModel>(responses);
}
[HttpGet("{id}")]
public async Task<AuthRequestResponseModel> Get(Guid id)
{
var userId = _userService.GetProperUserId(User).Value;
var authRequest = await _authRequestService.GetAuthRequestAsync(id, userId);
if (authRequest == null)
{
throw new NotFoundException();
}
return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
}
[HttpGet("pending")]
[RequireFeature(FeatureFlagKeys.BrowserExtensionLoginApproval)]
public async Task<ListResponseModel<PendingAuthRequestResponseModel>> GetPendingAuthRequestsAsync()
{
var userId = _userService.GetProperUserId(User).Value;
var rawResponse = await _authRequestRepository.GetManyPendingAuthRequestByUserId(userId);
var responses = rawResponse.Select(a => new PendingAuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault));
return new ListResponseModel<PendingAuthRequestResponseModel>(responses);
}
[HttpGet("{id}/response")]
[AllowAnonymous]
public async Task<AuthRequestResponseModel> GetResponse(Guid id, [FromQuery] string code)
{
var authRequest = await _authRequestService.GetValidatedAuthRequestAsync(id, code);
if (authRequest == null)
{
throw new NotFoundException();
}
return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
}
[HttpPost("")]
[AllowAnonymous]
public async Task<AuthRequestResponseModel> Post([FromBody] AuthRequestCreateRequestModel model)
{
if (model.Type == AuthRequestType.AdminApproval)
{
throw new BadRequestException("You must be authenticated to create a request of that type.");
}
var authRequest = await _authRequestService.CreateAuthRequestAsync(model);
var r = new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
return r;
}
[HttpPost("admin-request")]
public async Task<AuthRequestResponseModel> PostAdminRequest([FromBody] AuthRequestCreateRequestModel model)
{
var authRequest = await _authRequestService.CreateAuthRequestAsync(model);
var r = new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
return r;
}
[HttpPut("{id}")]
public async Task<AuthRequestResponseModel> Put(Guid id, [FromBody] AuthRequestUpdateRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
// If the Approving Device is attempting to approve a request, validate the approval
if (model.RequestApproved == true)
{
await ValidateApprovalOfMostRecentAuthRequest(id, userId);
}
var authRequest = await _authRequestService.UpdateAuthRequestAsync(id, userId, model);
return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault);
}
private async Task ValidateApprovalOfMostRecentAuthRequest(Guid id, Guid userId)
{
// Get the current auth request to find the device identifier
var currentAuthRequest = await _authRequestService.GetAuthRequestAsync(id, userId);
if (currentAuthRequest == null)
{
throw new NotFoundException();
}
// Get all pending auth requests for this user (returns most recent per device)
var pendingRequests = await _authRequestRepository.GetManyPendingAuthRequestByUserId(userId);
// Find the most recent request for the same device
var mostRecentForDevice = pendingRequests
.FirstOrDefault(pendingRequest => pendingRequest.RequestDeviceIdentifier == currentAuthRequest.RequestDeviceIdentifier);
var isMostRecentRequestForDevice = mostRecentForDevice?.Id == id;
if (!isMostRecentRequestForDevice)
{
throw new BadRequestException("This request is no longer valid. Make sure to approve the most recent request.");
}
}
}