Ability to copying authorization URL for Gmail (#375)
* Implemented copying auth URL for Gmail authentication. * Update Button icon and add row spacing in Flyout grid The icon used in the Button.Content has been updated to a new design and is now wrapped inside a Viewbox with a width of 20 to ensure proper scaling. Additionally, the Grid inside the Flyout now includes RowSpacing="12" to improve visual separation between rows.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
namespace Wino.Core.Domain.Interfaces
|
||||
{
|
||||
public interface IOutlookAuthenticator : IAuthenticator { }
|
||||
public interface IGmailAuthenticator : IAuthenticator { }
|
||||
public interface IGmailAuthenticator : IAuthenticator
|
||||
{
|
||||
bool ProposeCopyAuthURL { get; set; }
|
||||
}
|
||||
public interface IImapAuthenticator : IAuthenticator { }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"AccountCreationDialog_PreparingFolders": "We are getting folder information at the moment.",
|
||||
"AccountCreationDialog_SigninIn": "Account information is being saved.",
|
||||
"AccountCreationDialog_FetchingProfileInformation": "Fetching profile details.",
|
||||
"AccountCreationDialog_GoogleAuthHelpClipboardText_Row0": "If your browser did not launch automatically to complete authentication:",
|
||||
"AccountCreationDialog_GoogleAuthHelpClipboardText_Row1": "1) Click the button below to copy the authentication address",
|
||||
"AccountCreationDialog_GoogleAuthHelpClipboardText_Row2": "2) Launch your web browser (Edge, Chrome, Firefox etc...)",
|
||||
"AccountCreationDialog_GoogleAuthHelpClipboardText_Row3": "3) Paste the copied address and go to the website to complete authentication manually.",
|
||||
"AccountEditDialog_Message": "Account Name",
|
||||
"AccountEditDialog_Title": "Edit Account",
|
||||
"AccountPickerDialog_Title": "Pick an account",
|
||||
|
||||
20
Wino.Core.Domain/Translator.Designer.cs
generated
20
Wino.Core.Domain/Translator.Designer.cs
generated
@@ -43,6 +43,26 @@ namespace Wino.Core.Domain
|
||||
/// </summary>
|
||||
public static string AccountCreationDialog_FetchingProfileInformation => Resources.GetTranslatedString(@"AccountCreationDialog_FetchingProfileInformation");
|
||||
|
||||
/// <summary>
|
||||
/// If your browser did not launch automatically to complete authentication:
|
||||
/// </summary>
|
||||
public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row0 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row0");
|
||||
|
||||
/// <summary>
|
||||
/// 1) Click the button below to copy the authentication address
|
||||
/// </summary>
|
||||
public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row1 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row1");
|
||||
|
||||
/// <summary>
|
||||
/// 2) Launch your web browser (Edge, Chrome, Firefox etc...)
|
||||
/// </summary>
|
||||
public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row2 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row2");
|
||||
|
||||
/// <summary>
|
||||
/// 3) Paste the copied address and go to the website to complete authentication manually.
|
||||
/// </summary>
|
||||
public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row3 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row3");
|
||||
|
||||
/// <summary>
|
||||
/// Account Name
|
||||
/// </summary>
|
||||
|
||||
@@ -156,7 +156,9 @@ namespace Wino.Services
|
||||
await taskbarManager.RequestPinCurrentAppAsync();
|
||||
}
|
||||
|
||||
public async Task<Uri> GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri, CancellationToken cancellationToken = default)
|
||||
public async Task<Uri> GetAuthorizationResponseUriAsync(IAuthenticator authenticator,
|
||||
string authorizationUri,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (authorizationCompletedTaskSource != null)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ using Wino.Core.Integration.Json;
|
||||
using Wino.Messaging;
|
||||
using Wino.Messaging.Client.Connection;
|
||||
using Wino.Messaging.Enums;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
@@ -253,6 +254,9 @@ namespace Wino.Core.UWP.Services
|
||||
case nameof(AccountFolderConfigurationUpdated):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountFolderConfigurationUpdated>(messageJson));
|
||||
break;
|
||||
case nameof(CopyAuthURLRequested):
|
||||
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<CopyAuthURLRequested>(messageJson));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid data type name passed to client.");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -11,6 +12,7 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
using Wino.Core.Domain.Models.Authorization;
|
||||
using Wino.Core.Services;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Core.Authenticators
|
||||
{
|
||||
@@ -24,6 +26,8 @@ namespace Wino.Core.Authenticators
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
||||
|
||||
public bool ProposeCopyAuthURL { get; set; }
|
||||
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
|
||||
public GmailAuthenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService)
|
||||
@@ -121,6 +125,11 @@ namespace Wino.Core.Authenticators
|
||||
|
||||
Uri responseRedirectUri = null;
|
||||
|
||||
if (ProposeCopyAuthURL)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new CopyAuthURLRequested(authorizationUri));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
responseRedirectUri = await _nativeAppService.GetAuthorizationResponseUriAsync(this, authorizationUri);
|
||||
|
||||
@@ -196,7 +196,8 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
var tokenInformationResponse = await _winoServerConnectionManager
|
||||
.GetResponseAsync<TokenInformation, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
|
||||
createdAccount), accountCreationCancellationTokenSource.Token);
|
||||
createdAccount,
|
||||
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
|
||||
|
||||
if (creationDialog.State == AccountCreationDialogState.Canceled)
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
x:Name="Root"
|
||||
Closing="DialogClosing"
|
||||
CornerRadius="8"
|
||||
mc:Ignorable="d">
|
||||
<Grid x:Name="RootGrid" RowSpacing="10">
|
||||
@@ -31,15 +32,56 @@
|
||||
Text="{x:Bind domain:Translator.AccountCreationDialog_Initializing}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<Button
|
||||
x:Name="AuthHelpDialogButton"
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="Collapsed">
|
||||
<Button.Content>
|
||||
<Viewbox Width="20">
|
||||
<PathIcon Data="M960 4q132 0 254 34t228 96 194 150 149 193 97 229 34 254q0 132-34 254t-96 228-150 194-193 149-229 97-254 34q-132 0-254-34t-228-96-194-150-149-193-97-229T4 960q0-132 34-254t96-228 150-194 193-149 229-97T960 4zm0 1792q115 0 222-30t200-84 169-131 130-169 85-200 30-222q0-115-30-222t-84-200-131-169-169-130-200-85-222-30q-115 0-222 30t-200 84-169 131-130 169-85 200-30 222q0 115 30 222t84 200 131 169 169 130 200 85 222 30zm-64-388h128v128H896v-128zm64-960q66 0 124 25t101 69 69 102 26 124q0 60-19 104t-47 81-62 65-61 59-48 63-19 76v64H896v-64q0-60 19-104t47-81 62-65 61-59 48-63 19-76q0-40-15-75t-41-61-61-41-75-15q-40 0-75 15t-61 41-41 61-15 75H640q0-66 25-124t68-101 102-69 125-26z" />
|
||||
</Viewbox>
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<Grid MaxWidth="400" RowSpacing="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Foreground="Yellow" TextWrapping="WrapWholeWords">
|
||||
<Run FontWeight="SemiBold" Text="{x:Bind domain:Translator.AccountCreationDialog_GoogleAuthHelpClipboardText_Row0}" />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Run Text="{x:Bind domain:Translator.AccountCreationDialog_GoogleAuthHelpClipboardText_Row1}" />
|
||||
<LineBreak />
|
||||
<Run Text="{x:Bind domain:Translator.AccountCreationDialog_GoogleAuthHelpClipboardText_Row2}" />
|
||||
<LineBreak />
|
||||
<Run Text="{x:Bind domain:Translator.AccountCreationDialog_GoogleAuthHelpClipboardText_Row3}" />
|
||||
</TextBlock>
|
||||
|
||||
<Button
|
||||
x:Name="CopyClipboard"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Click="CopyClicked"
|
||||
Content="{x:Bind domain:Translator.Buttons_Copy}" />
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
x:Name="CancelButton"
|
||||
Grid.Row="3"
|
||||
HorizontalAlignment="Center"
|
||||
Click="CancelClicked"
|
||||
VerticalAlignment="Bottom"
|
||||
Click="CancelClicked"
|
||||
Content="{x:Bind domain:Translator.Buttons_Cancel}" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="DialogStates">
|
||||
<VisualState x:Name="PreparingFolders">
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
namespace Wino.Dialogs
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
{
|
||||
public sealed partial class AccountCreationDialog : BaseAccountCreationDialog
|
||||
public sealed partial class AccountCreationDialog : BaseAccountCreationDialog, IRecipient<CopyAuthURLRequested>
|
||||
{
|
||||
private string copyClipboardURL;
|
||||
public AccountCreationDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
}
|
||||
|
||||
private void CancelClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
public async void Receive(CopyAuthURLRequested message)
|
||||
{
|
||||
Complete(true);
|
||||
copyClipboardURL = message.AuthURL;
|
||||
|
||||
await Task.Delay(2000);
|
||||
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
AuthHelpDialogButton.Visibility = Windows.UI.Xaml.Visibility.Visible;
|
||||
});
|
||||
}
|
||||
|
||||
private void CancelClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e) => Complete(true);
|
||||
|
||||
private async void CopyClicked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(copyClipboardURL)) return;
|
||||
|
||||
var clipboardService = App.Current.Services.GetService<IClipboardService>();
|
||||
await clipboardService.CopyClipboardAsync(copyClipboardURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Wino.Dialogs
|
||||
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(AccountCreationDialogState), typeof(BaseAccountCreationDialog), new PropertyMetadata(AccountCreationDialogState.Idle));
|
||||
|
||||
// Prevent users from dismissing it by ESC key.
|
||||
private void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
|
||||
public void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
|
||||
{
|
||||
if (args.Result == ContentDialogResult.None)
|
||||
{
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
x:Class="Wino.Dialogs.NewAccountDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:accounts="using:Wino.Core.Domain.Models.Accounts"
|
||||
Title="{x:Bind domain:Translator.NewAccountDialog_Title}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
Title="{x:Bind domain:Translator.NewAccountDialog_Title}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
DefaultButton="Primary"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
Opened="DialogOpened"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
PrimaryButtonClick="CreateClicked"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_CreateAccount}"
|
||||
SecondaryButtonClick="CancelClicked"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<DataTemplate x:Key="NewMailProviderTemplate" x:DataType="accounts:ProviderDetail">
|
||||
<Grid Padding="6" Margin="0,8">
|
||||
<Grid Margin="0,8" Padding="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -31,10 +31,10 @@
|
||||
Height="35"
|
||||
Source="{x:Bind ProviderImage}" />
|
||||
<StackPanel
|
||||
Spacing="2"
|
||||
Grid.Column="1"
|
||||
Margin="12,0"
|
||||
VerticalAlignment="Center">
|
||||
VerticalAlignment="Center"
|
||||
Spacing="2">
|
||||
<TextBlock FontWeight="Bold" Text="{x:Bind Name}" />
|
||||
<TextBlock Text="{x:Bind Description}" />
|
||||
</StackPanel>
|
||||
@@ -71,7 +71,6 @@
|
||||
|
||||
<ListView
|
||||
Grid.Row="2"
|
||||
|
||||
Padding="0"
|
||||
ItemTemplate="{StaticResource NewMailProviderTemplate}"
|
||||
ItemsSource="{x:Bind Providers}"
|
||||
|
||||
@@ -7,5 +7,5 @@ namespace Wino.Messaging.Server
|
||||
/// <summary>
|
||||
/// For delegating authentication/authorization to the server app.
|
||||
/// </summary>
|
||||
public record AuthorizationRequested(MailProviderType MailProviderType, MailAccount CreatedAccount) : IClientMessage;
|
||||
public record AuthorizationRequested(MailProviderType MailProviderType, MailAccount CreatedAccount, bool ProposeCopyAuthorizationURL) : IClientMessage;
|
||||
}
|
||||
|
||||
10
Wino.Messages/Server/CopyAuthURLRequested.cs
Normal file
10
Wino.Messages/Server/CopyAuthURLRequested.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Messaging.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// When authenticators are proposed to copy the auth URL on the UI.
|
||||
/// </summary>
|
||||
/// <param name="AuthURL">URL to be copied to clipboard.</param>
|
||||
public record CopyAuthURLRequested(string AuthURL) : UIMessageBase<CopyAuthURLRequested>;
|
||||
}
|
||||
@@ -21,10 +21,21 @@ namespace Wino.Server.MessageHandlers
|
||||
_authenticationProvider = authenticationProvider;
|
||||
}
|
||||
|
||||
protected override async Task<WinoServerResponse<TokenInformation>> HandleAsync(AuthorizationRequested message, CancellationToken cancellationToken = default)
|
||||
protected override async Task<WinoServerResponse<TokenInformation>> HandleAsync(AuthorizationRequested message,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var authenticator = _authenticationProvider.GetAuthenticator(message.MailProviderType);
|
||||
|
||||
// Some users are having issues with Gmail authentication.
|
||||
// Their browsers may never launch to complete authentication.
|
||||
// Offer to copy auth url for them to complete it manually.
|
||||
// Redirection will occur to the app and the token will be saved.
|
||||
|
||||
if (message.ProposeCopyAuthorizationURL && authenticator is IGmailAuthenticator gmailAuthenticator)
|
||||
{
|
||||
gmailAuthenticator.ProposeCopyAuthURL = true;
|
||||
}
|
||||
|
||||
// Do not save the token here. Call is coming from account creation and things are atomic there.
|
||||
var generatedToken = await authenticator.GenerateTokenAsync(message.CreatedAccount, saveToken: false);
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ namespace Wino.Server
|
||||
IRecipient<RefreshUnreadCountsMessage>,
|
||||
IRecipient<ServerTerminationModeChanged>,
|
||||
IRecipient<AccountSynchronizationProgressUpdatedMessage>,
|
||||
IRecipient<AccountFolderConfigurationUpdated>
|
||||
IRecipient<AccountFolderConfigurationUpdated>,
|
||||
IRecipient<CopyAuthURLRequested>
|
||||
{
|
||||
private readonly System.Timers.Timer _timer;
|
||||
private static object connectionLock = new object();
|
||||
@@ -139,6 +140,8 @@ namespace Wino.Server
|
||||
|
||||
public async void Receive(AccountFolderConfigurationUpdated message) => await SendMessageAsync(MessageType.UIMessage, message);
|
||||
|
||||
public async void Receive(CopyAuthURLRequested message) => await SendMessageAsync(MessageType.UIMessage, message);
|
||||
|
||||
#endregion
|
||||
|
||||
private string GetAppPackagFamilyName()
|
||||
|
||||
Reference in New Issue
Block a user