| File: DevTunnelLoginManager.cs | Web Access |
| Project: src\src\Aspire.Hosting.DevTunnels\Aspire.Hosting.DevTunnels.csproj (Aspire.Hosting.DevTunnels) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.Utils; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.DevTunnels; internal sealed class DevTunnelLoginManager( IDevTunnelClient devTunnelClient, IInteractionService interactionService, IConfiguration configuration, ILogger<DevTunnelLoginManager> logger) : CoalescingAsyncOperation { private const string PreferredAuthProviderKey = "ASPIRE_DEVTUNNELS_AUTH_PROVIDER"; private readonly IDevTunnelClient _devTunnelClient = devTunnelClient; private readonly IInteractionService _interactionService = interactionService; private readonly IConfiguration _configuration = configuration; private readonly ILogger<DevTunnelLoginManager> _logger = logger; public Task EnsureUserLoggedInAsync(CancellationToken cancellationToken = default) => RunAsync(cancellationToken); protected override async Task ExecuteCoreAsync(CancellationToken cancellationToken) { while (true) { _logger.LogDebug("Checking dev tunnel login status"); var loginStatus = await _devTunnelClient.GetUserLoginStatusAsync(_logger, cancellationToken).ConfigureAwait(false); if (loginStatus.IsLoggedIn) { _logger.LogDebug("User already logged in to dev tunnel service as {Username} with {Provider}", loginStatus.Username, loginStatus.Provider); // Already logged in break; } else { var selectedProvider = LoginProvider.Microsoft; if (_configuration[PreferredAuthProviderKey] is string preferredProvider) { if (Enum.TryParse<LoginProvider>(preferredProvider, ignoreCase: true, out var provider)) { selectedProvider = provider; } else { _logger.LogWarning($"Invalid {PreferredAuthProviderKey} value '{{PreferredProvider}}', defaulting to {{SelectedProvider}}", preferredProvider, selectedProvider); } } if (_interactionService.IsAvailable) { // Not logged in, prompt the user to login _logger.LogDebug("Prompting user to login to dev tunnel service"); var result = await _interactionService.PromptNotificationAsync( "Dev tunnels", Resources.MessageStrings.AuthenticationRequiredNotification, new() { Intent = MessageIntent.Warning, PrimaryButtonText = Resources.MessageStrings.LoginWithMicrosoft, SecondaryButtonText = Resources.MessageStrings.LoginWithGitHub, ShowSecondaryButton = true, ShowDismiss = false }, cancellationToken).ConfigureAwait(false); selectedProvider = result.Data ? LoginProvider.Microsoft : LoginProvider.GitHub; _logger.LogDebug("User selected {LoginProvider} for dev tunnel login", selectedProvider); // Check again in case they logged in from another window while we were prompting loginStatus = await _devTunnelClient.GetUserLoginStatusAsync(_logger, cancellationToken).ConfigureAwait(false); } if (!loginStatus.IsLoggedIn || loginStatus.Provider != selectedProvider) { // Trigger the login flow _logger.LogInformation("Initiating dev tunnel login via {LoginProvider}", selectedProvider); loginStatus = await _devTunnelClient.UserLoginAsync(selectedProvider, _logger, cancellationToken).ConfigureAwait(false); if (loginStatus.IsLoggedIn) { // Successfully logged in _logger.LogInformation("User logged in to dev tunnel service as {Username} with {Provider}", loginStatus.Username, loginStatus.Provider); break; } } else { // Logged in from another window while we were prompting _logger.LogDebug("User already logged in to dev tunnel service as {Username} with {Provider}", loginStatus.Username, loginStatus.Provider); break; } _logger.LogDebug("User login to dev tunnel service failed, retrying login prompt"); } } } }