diff --git a/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs b/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
index 51f1fd80..cb8a12e8 100644
--- a/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
+++ b/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
@@ -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 { }
}
diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json
index 43c44b18..0dce80f3 100644
--- a/Wino.Core.Domain/Translations/en_US/resources.json
+++ b/Wino.Core.Domain/Translations/en_US/resources.json
@@ -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",
diff --git a/Wino.Core.Domain/Translator.Designer.cs b/Wino.Core.Domain/Translator.Designer.cs
index 480c4ddb..e526cf8e 100644
--- a/Wino.Core.Domain/Translator.Designer.cs
+++ b/Wino.Core.Domain/Translator.Designer.cs
@@ -43,6 +43,26 @@ namespace Wino.Core.Domain
///
public static string AccountCreationDialog_FetchingProfileInformation => Resources.GetTranslatedString(@"AccountCreationDialog_FetchingProfileInformation");
+ ///
+ /// If your browser did not launch automatically to complete authentication:
+ ///
+ public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row0 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row0");
+
+ ///
+ /// 1) Click the button below to copy the authentication address
+ ///
+ public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row1 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row1");
+
+ ///
+ /// 2) Launch your web browser (Edge, Chrome, Firefox etc...)
+ ///
+ public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row2 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row2");
+
+ ///
+ /// 3) Paste the copied address and go to the website to complete authentication manually.
+ ///
+ public static string AccountCreationDialog_GoogleAuthHelpClipboardText_Row3 => Resources.GetTranslatedString(@"AccountCreationDialog_GoogleAuthHelpClipboardText_Row3");
+
///
/// Account Name
///
diff --git a/Wino.Core.UWP/Services/NativeAppService.cs b/Wino.Core.UWP/Services/NativeAppService.cs
index 2d8089ca..00e59663 100644
--- a/Wino.Core.UWP/Services/NativeAppService.cs
+++ b/Wino.Core.UWP/Services/NativeAppService.cs
@@ -156,7 +156,9 @@ namespace Wino.Services
await taskbarManager.RequestPinCurrentAppAsync();
}
- public async Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri, CancellationToken cancellationToken = default)
+ public async Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator,
+ string authorizationUri,
+ CancellationToken cancellationToken = default)
{
if (authorizationCompletedTaskSource != null)
{
diff --git a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs
index 6a36e33a..4a7666c3 100644
--- a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs
+++ b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs
@@ -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(messageJson));
break;
+ case nameof(CopyAuthURLRequested):
+ WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize(messageJson));
+ break;
default:
throw new Exception("Invalid data type name passed to client.");
}
diff --git a/Wino.Core/Authenticators/GmailAuthenticator.cs b/Wino.Core/Authenticators/GmailAuthenticator.cs
index faee8d43..9fe3aaa3 100644
--- a/Wino.Core/Authenticators/GmailAuthenticator.cs
+++ b/Wino.Core/Authenticators/GmailAuthenticator.cs
@@ -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);
diff --git a/Wino.Mail.ViewModels/AccountManagementViewModel.cs b/Wino.Mail.ViewModels/AccountManagementViewModel.cs
index 78ee7f71..a402f259 100644
--- a/Wino.Mail.ViewModels/AccountManagementViewModel.cs
+++ b/Wino.Mail.ViewModels/AccountManagementViewModel.cs
@@ -196,7 +196,8 @@ namespace Wino.Mail.ViewModels
var tokenInformationResponse = await _winoServerConnectionManager
.GetResponseAsync(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
- createdAccount), accountCreationCancellationTokenSource.Token);
+ createdAccount,
+ createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
if (creationDialog.State == AccountCreationDialogState.Canceled)
throw new AccountSetupCanceledException();
diff --git a/Wino.Mail/Dialogs/AccountCreationDialog.xaml b/Wino.Mail/Dialogs/AccountCreationDialog.xaml
index cb0220e7..351cf0ae 100644
--- a/Wino.Mail/Dialogs/AccountCreationDialog.xaml
+++ b/Wino.Mail/Dialogs/AccountCreationDialog.xaml
@@ -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">
@@ -31,15 +32,56 @@
Text="{x:Bind domain:Translator.AccountCreationDialog_Initializing}"
TextWrapping="Wrap" />
+
+
-
+
diff --git a/Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs b/Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs
index 6ab48b1e..cf043a9b 100644
--- a/Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs
+++ b/Wino.Mail/Dialogs/AccountCreationDialog.xaml.cs
@@ -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
{
+ 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();
+ await clipboardService.CopyClipboardAsync(copyClipboardURL);
}
}
}
diff --git a/Wino.Mail/Dialogs/BaseAccountCreationDialog.cs b/Wino.Mail/Dialogs/BaseAccountCreationDialog.cs
index b31203c2..bb56381c 100644
--- a/Wino.Mail/Dialogs/BaseAccountCreationDialog.cs
+++ b/Wino.Mail/Dialogs/BaseAccountCreationDialog.cs
@@ -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)
{
diff --git a/Wino.Mail/Dialogs/NewAccountDialog.xaml b/Wino.Mail/Dialogs/NewAccountDialog.xaml
index 7d3d1d02..6bbe5e35 100644
--- a/Wino.Mail/Dialogs/NewAccountDialog.xaml
+++ b/Wino.Mail/Dialogs/NewAccountDialog.xaml
@@ -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">
-
+
@@ -31,10 +31,10 @@
Height="35"
Source="{x:Bind ProviderImage}" />
+ VerticalAlignment="Center"
+ Spacing="2">
@@ -71,7 +71,6 @@
/// For delegating authentication/authorization to the server app.
///
- public record AuthorizationRequested(MailProviderType MailProviderType, MailAccount CreatedAccount) : IClientMessage;
+ public record AuthorizationRequested(MailProviderType MailProviderType, MailAccount CreatedAccount, bool ProposeCopyAuthorizationURL) : IClientMessage;
}
diff --git a/Wino.Messages/Server/CopyAuthURLRequested.cs b/Wino.Messages/Server/CopyAuthURLRequested.cs
new file mode 100644
index 00000000..75979881
--- /dev/null
+++ b/Wino.Messages/Server/CopyAuthURLRequested.cs
@@ -0,0 +1,10 @@
+using Wino.Messaging.UI;
+
+namespace Wino.Messaging.Server
+{
+ ///
+ /// When authenticators are proposed to copy the auth URL on the UI.
+ ///
+ /// URL to be copied to clipboard.
+ public record CopyAuthURLRequested(string AuthURL) : UIMessageBase;
+}
diff --git a/Wino.Server/MessageHandlers/AuthenticationHandler.cs b/Wino.Server/MessageHandlers/AuthenticationHandler.cs
index aea5b7d1..4707abc5 100644
--- a/Wino.Server/MessageHandlers/AuthenticationHandler.cs
+++ b/Wino.Server/MessageHandlers/AuthenticationHandler.cs
@@ -21,10 +21,21 @@ namespace Wino.Server.MessageHandlers
_authenticationProvider = authenticationProvider;
}
- protected override async Task> HandleAsync(AuthorizationRequested message, CancellationToken cancellationToken = default)
+ protected override async Task> 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);
diff --git a/Wino.Server/ServerContext.cs b/Wino.Server/ServerContext.cs
index 31bd0808..7d44119a 100644
--- a/Wino.Server/ServerContext.cs
+++ b/Wino.Server/ServerContext.cs
@@ -42,7 +42,8 @@ namespace Wino.Server
IRecipient,
IRecipient,
IRecipient,
- IRecipient
+ IRecipient,
+ IRecipient
{
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()