using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Entities; using Bit.Core.Services; using Bit.Core.Tokens; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Duo = DuoUniversal; namespace Bit.Core.Auth.Identity.TokenProviders; public class DuoUniversalTokenProvider( IServiceProvider serviceProvider, IDataProtectorTokenFactory tokenDataFactory, IDuoUniversalTokenService duoUniversalTokenService) : IUserTwoFactorTokenProvider { /// /// We need the IServiceProvider to resolve the . There is a complex dependency dance /// occurring between , which extends the , and the usage /// of the within this class. Trying to resolve the using /// the DI pipeline will not allow the server to start and it will hang and give no helpful indication as to the /// problem. /// private readonly IServiceProvider _serviceProvider = serviceProvider; private readonly IDataProtectorTokenFactory _tokenDataFactory = tokenDataFactory; private readonly IDuoUniversalTokenService _duoUniversalTokenService = duoUniversalTokenService; public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { var userService = _serviceProvider.GetRequiredService(); var duoUniversalTokenProvider = await GetDuoTwoFactorProvider(user, userService); if (duoUniversalTokenProvider == null) { return false; } return duoUniversalTokenProvider.Enabled; } public async Task GenerateAsync(string purpose, UserManager manager, User user) { var duoClient = await GetDuoClientAsync(user); if (duoClient == null) { return null; } return _duoUniversalTokenService.GenerateAuthUrl(duoClient, _tokenDataFactory, user); } public async Task ValidateAsync(string purpose, string token, UserManager manager, User user) { var duoClient = await GetDuoClientAsync(user); if (duoClient == null) { return false; } return await _duoUniversalTokenService.RequestDuoValidationAsync(duoClient, _tokenDataFactory, user, token); } /// /// Get the Duo Two Factor Provider for the user if they have premium access to Duo /// /// Active User /// null or Duo TwoFactorProvider private async Task GetDuoTwoFactorProvider(User user, IUserService userService) { if (!await userService.CanAccessPremium(user)) { return null; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); if (!_duoUniversalTokenService.HasProperDuoMetadata(provider)) { return null; } return provider; } /// /// Uses the User to fetch a valid TwoFactorProvider and use it to create a Duo.Client /// /// active user /// null or Duo TwoFactorProvider private async Task GetDuoClientAsync(User user) { var userService = _serviceProvider.GetRequiredService(); var provider = await GetDuoTwoFactorProvider(user, userService); if (provider == null) { return null; } var duoClient = await _duoUniversalTokenService.BuildDuoTwoFactorClientAsync(provider); if (duoClient == null) { return null; } return duoClient; } }