diff --git a/Wino.Authentication/BaseAuthenticator.cs b/Wino.Authentication/BaseAuthenticator.cs
new file mode 100644
index 00000000..4e3664df
--- /dev/null
+++ b/Wino.Authentication/BaseAuthenticator.cs
@@ -0,0 +1,17 @@
+using Wino.Core.Domain.Enums;
+using Wino.Core.Domain.Interfaces;
+
+namespace Wino.Authentication
+{
+ public abstract class BaseAuthenticator
+ {
+ public abstract MailProviderType ProviderType { get; }
+ protected IAuthenticatorConfig AuthenticatorConfig { get; }
+
+ protected BaseAuthenticator(IAuthenticatorConfig authenticatorConfig)
+ {
+
+ AuthenticatorConfig = authenticatorConfig;
+ }
+ }
+}
diff --git a/Wino.Authentication/GmailAuthenticator.cs b/Wino.Authentication/GmailAuthenticator.cs
new file mode 100644
index 00000000..54445e3f
--- /dev/null
+++ b/Wino.Authentication/GmailAuthenticator.cs
@@ -0,0 +1,53 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Google.Apis.Auth.OAuth2;
+using Google.Apis.Util.Store;
+using Wino.Core.Domain.Entities.Shared;
+using Wino.Core.Domain.Enums;
+using Wino.Core.Domain.Interfaces;
+using Wino.Core.Domain.Models.Authentication;
+
+namespace Wino.Authentication
+{
+ public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
+ {
+ private const string FileDataStoreFolder = "WinoGmailStore";
+
+ public GmailAuthenticator(IAuthenticatorConfig authConfig) : base(authConfig)
+ {
+ }
+
+ public string ClientId => AuthenticatorConfig.GmailAuthenticatorClientId;
+ public bool ProposeCopyAuthURL { get; set; }
+
+ public override MailProviderType ProviderType => MailProviderType.Gmail;
+
+ ///
+ /// Generates the token information for the given account.
+ /// For gmail, interactivity is automatically handled when you get the token.
+ ///
+ /// Account to get token for.
+ public Task GenerateTokenInformationAsync(MailAccount account)
+ => GetTokenInformationAsync(account);
+
+ public async Task GetTokenInformationAsync(MailAccount account)
+ {
+ var userCredential = await GetGoogleUserCredentialAsync(account);
+
+ if (userCredential.Token.IsStale)
+ {
+ await userCredential.RefreshTokenAsync(CancellationToken.None);
+ }
+
+ return new TokenInformationEx(userCredential.Token.AccessToken, account.Address);
+ }
+
+ private Task GetGoogleUserCredentialAsync(MailAccount account)
+ {
+ return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
+ {
+ ClientId = ClientId
+ }, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(FileDataStoreFolder));
+ }
+ }
+}
diff --git a/Wino.Authentication/Office365Authenticator.cs b/Wino.Authentication/Office365Authenticator.cs
new file mode 100644
index 00000000..7bb1017a
--- /dev/null
+++ b/Wino.Authentication/Office365Authenticator.cs
@@ -0,0 +1,16 @@
+using Wino.Core.Domain.Enums;
+using Wino.Core.Domain.Interfaces;
+
+namespace Wino.Authentication
+{
+ public class Office365Authenticator : OutlookAuthenticator
+ {
+ public Office365Authenticator(INativeAppService nativeAppService,
+ IApplicationConfiguration applicationConfiguration,
+ IAuthenticatorConfig authenticatorConfig) : base(nativeAppService, applicationConfiguration, authenticatorConfig)
+ {
+ }
+
+ public override MailProviderType ProviderType => MailProviderType.Office365;
+ }
+}
diff --git a/Wino.Core/Authenticators/Mail/OutlookAuthenticator.cs b/Wino.Authentication/OutlookAuthenticator.cs
similarity index 64%
rename from Wino.Core/Authenticators/Mail/OutlookAuthenticator.cs
rename to Wino.Authentication/OutlookAuthenticator.cs
index e4b8fe9d..8bdcfb27 100644
--- a/Wino.Core/Authenticators/Mail/OutlookAuthenticator.cs
+++ b/Wino.Authentication/OutlookAuthenticator.cs
@@ -4,22 +4,16 @@ using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Identity.Client.Extensions.Msal;
-using Wino.Core.Authenticators.Base;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
-using Wino.Core.Extensions;
-using Wino.Core.Services;
+using Wino.Core.Domain.Models.Authentication;
-namespace Wino.Core.Authenticators.Mail
+namespace Wino.Authentication
{
- ///
- /// Authenticator for Outlook Mail provider.
- /// Token cache is managed by MSAL, not by Wino.
- ///
- public class OutlookAuthenticator : OutlookAuthenticatorBase
+ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
{
private const string TokenCacheFileName = "OutlookCache.bin";
private bool isTokenCacheAttached = false;
@@ -27,27 +21,14 @@ namespace Wino.Core.Authenticators.Mail
// Outlook
private const string Authority = "https://login.microsoftonline.com/common";
- public override string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
-
- private readonly string[] MailScope =
- [
- "email",
- "mail.readwrite",
- "offline_access",
- "mail.send",
- "Mail.Send.Shared",
- "Mail.ReadWrite.Shared",
- "User.Read"
- ];
-
public override MailProviderType ProviderType => MailProviderType.Outlook;
private readonly IPublicClientApplication _publicClientApplication;
private readonly IApplicationConfiguration _applicationConfiguration;
- public OutlookAuthenticator(ITokenService tokenService,
- INativeAppService nativeAppService,
- IApplicationConfiguration applicationConfiguration) : base(tokenService)
+ public OutlookAuthenticator(INativeAppService nativeAppService,
+ IApplicationConfiguration applicationConfiguration,
+ IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig)
{
_applicationConfiguration = applicationConfiguration;
@@ -59,7 +40,7 @@ namespace Wino.Core.Authenticators.Mail
ListOperatingSystemAccounts = true,
};
- var outlookAppBuilder = PublicClientApplicationBuilder.Create(ClientId)
+ var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId)
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
.WithBroker(options)
.WithDefaultRedirectUri()
@@ -68,7 +49,9 @@ namespace Wino.Core.Authenticators.Mail
_publicClientApplication = outlookAppBuilder.Build();
}
- public override async Task GetTokenAsync(MailAccount account)
+ public string[] Scope => AuthenticatorConfig.OutlookScope;
+
+ private async Task EnsureTokenCacheAttachedAsync()
{
if (!isTokenCacheAttached)
{
@@ -78,23 +61,29 @@ namespace Wino.Core.Authenticators.Mail
isTokenCacheAttached = true;
}
+ }
+
+ public async Task GetTokenInformationAsync(MailAccount account)
+ {
+ await EnsureTokenCacheAttachedAsync();
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
- // TODO: Handle it from the server.
- if (storedAccount == null) throw new AuthenticationAttentionException(account);
+ if (storedAccount == null)
+ return await GenerateTokenInformationAsync(account);
try
{
- var authResult = await _publicClientApplication.AcquireTokenSilent(MailScope, storedAccount).ExecuteAsync();
+ var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync();
- return authResult.CreateTokenInformation() ?? throw new Exception("Failed to get Outlook token.");
+ return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
}
catch (MsalUiRequiredException)
{
// Somehow MSAL is not able to refresh the token silently.
// Force interactive login.
- return await GenerateTokenAsync(account, true);
+
+ return await GenerateTokenInformationAsync(account);
}
catch (Exception)
{
@@ -102,22 +91,26 @@ namespace Wino.Core.Authenticators.Mail
}
}
- public override async Task GenerateTokenAsync(MailAccount account, bool saveToken)
+ public async Task GenerateTokenInformationAsync(MailAccount account)
{
try
{
+ await EnsureTokenCacheAttachedAsync();
+
var authResult = await _publicClientApplication
- .AcquireTokenInteractive(MailScope)
+ .AcquireTokenInteractive(Scope)
.ExecuteAsync();
- var tokenInformation = authResult.CreateTokenInformation();
+ // If the account is null, it means it's the initial creation of it.
+ // If not, make sure the authenticated user address matches the username.
+ // When people refresh their token, accounts must match.
- if (saveToken)
+ if (account?.Address != null && !account.Address.Equals(authResult.Account.Username, StringComparison.OrdinalIgnoreCase))
{
- await SaveTokenInternalAsync(account, tokenInformation);
+ throw new AuthenticationException("Authenticated address does not match with your account address.");
}
- return tokenInformation;
+ return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
}
catch (MsalClientException msalClientException)
{
diff --git a/Wino.Authentication/Wino.Authentication.csproj b/Wino.Authentication/Wino.Authentication.csproj
new file mode 100644
index 00000000..09b5a3d2
--- /dev/null
+++ b/Wino.Authentication/Wino.Authentication.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netstandard2.0
+ Wino.Authentication
+ Debug;Release
+ 12
+ AnyCPU;x64;x86
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Wino.Core.Domain/Entities/Shared/TokenInformation.cs b/Wino.Core.Domain/Entities/Shared/TokenInformation.cs
deleted file mode 100644
index e3204f9c..00000000
--- a/Wino.Core.Domain/Entities/Shared/TokenInformation.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using SQLite;
-using Wino.Core.Domain.Models.Authentication;
-
-namespace Wino.Core.Domain.Entities.Shared
-{
- public class TokenInformation : TokenInformationBase
- {
- [PrimaryKey]
- public Guid Id { get; set; }
-
- public Guid AccountId { get; set; }
-
- ///
- /// Unique object storage for authenticators if needed.
- ///
- public string UniqueId { get; set; }
- public string Address { get; set; }
-
- public void RefreshTokens(TokenInformationBase tokenInformationBase)
- {
- if (tokenInformationBase == null)
- throw new ArgumentNullException(nameof(tokenInformationBase));
-
- AccessToken = tokenInformationBase.AccessToken;
- RefreshToken = tokenInformationBase.RefreshToken;
- ExpiresAt = tokenInformationBase.ExpiresAt;
- }
- }
-}
diff --git a/Wino.Core.Domain/Interfaces/IAccountService.cs b/Wino.Core.Domain/Interfaces/IAccountService.cs
index 5eb83845..aac9b19b 100644
--- a/Wino.Core.Domain/Interfaces/IAccountService.cs
+++ b/Wino.Core.Domain/Interfaces/IAccountService.cs
@@ -48,7 +48,7 @@ namespace Wino.Core.Domain.Interfaces
/// Creates new account with the given server information if any.
/// Also sets the account as Startup account if there are no accounts.
///
- Task CreateAccountAsync(MailAccount account, TokenInformation tokenInformation, CustomServerInformation customServerInformation);
+ Task CreateAccountAsync(MailAccount account, CustomServerInformation customServerInformation);
///
/// Fixed authentication errors for account by forcing interactive login.
diff --git a/Wino.Core.Domain/Interfaces/IAuthenticator.cs b/Wino.Core.Domain/Interfaces/IAuthenticator.cs
index 93dad514..86d2f8b4 100644
--- a/Wino.Core.Domain/Interfaces/IAuthenticator.cs
+++ b/Wino.Core.Domain/Interfaces/IAuthenticator.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
+using Wino.Core.Domain.Models.Authentication;
namespace Wino.Core.Domain.Interfaces
{
@@ -11,26 +12,28 @@ namespace Wino.Core.Domain.Interfaces
///
MailProviderType ProviderType { get; }
- ///
- /// Gets the token from the cache if exists.
- /// If the token is expired, tries to refresh.
- /// This can throw AuthenticationAttentionException if silent refresh fails.
- ///
- /// Account to get token for.
- /// Valid token info to be used in integrators.
- Task GetTokenAsync(MailAccount account);
+ Task GetTokenInformationAsync(MailAccount account);
- ///
- /// Initial creation of token. Requires user interaction.
- /// This will cache the token but still returns for account creation
- /// since account address is required.
- ///
- /// Freshly created TokenInformation..
- Task GenerateTokenAsync(MailAccount account, bool saveToken);
+ Task GenerateTokenInformationAsync(MailAccount account);
- ///
- /// ClientId in case of needed for authorization/authentication.
- ///
- string ClientId { get; }
+ /////
+ ///// Gets the token for the given account from the cache.
+ ///// Forces interactive login if the token is not found.
+ /////
+ ///// Account to get access token for.
+ ///// Access token
+ //Task GetTokenAsync(MailAccount account);
+
+ /////
+ ///// Forces an interactive login to get the token for the given account.
+ /////
+ ///// Account to get access token for.
+ ///// Access token
+ //// Task GenerateTokenAsync(MailAccount account);
+
+ /////
+ ///// ClientId in case of needed for authorization/authentication.
+ /////
+ //string ClientId { get; }
}
}
diff --git a/Wino.Core.Domain/Interfaces/IAuthenticatorConfig.cs b/Wino.Core.Domain/Interfaces/IAuthenticatorConfig.cs
new file mode 100644
index 00000000..435e4489
--- /dev/null
+++ b/Wino.Core.Domain/Interfaces/IAuthenticatorConfig.cs
@@ -0,0 +1,10 @@
+namespace Wino.Core.Domain.Interfaces
+{
+ public interface IAuthenticatorConfig
+ {
+ string OutlookAuthenticatorClientId { get; }
+ string[] OutlookScope { get; }
+ string GmailAuthenticatorClientId { get; }
+ string[] GmailScope { get; }
+ }
+}
diff --git a/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs b/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
index cb8a12e8..ef842706 100644
--- a/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
+++ b/Wino.Core.Domain/Interfaces/IAuthenticatorTypes.cs
@@ -5,5 +5,6 @@
{
bool ProposeCopyAuthURL { get; set; }
}
+
public interface IImapAuthenticator : IAuthenticator { }
}
diff --git a/Wino.Core.Domain/Interfaces/INativeAppService.cs b/Wino.Core.Domain/Interfaces/INativeAppService.cs
index ff933592..19eca7da 100644
--- a/Wino.Core.Domain/Interfaces/INativeAppService.cs
+++ b/Wino.Core.Domain/Interfaces/INativeAppService.cs
@@ -1,7 +1,5 @@
using System;
-using System.Threading;
using System.Threading.Tasks;
-using Wino.Core.Domain.Models.Authorization;
namespace Wino.Core.Domain.Interfaces
{
@@ -13,13 +11,6 @@ namespace Wino.Core.Domain.Interfaces
Task LaunchFileAsync(string filePath);
Task LaunchUriAsync(Uri uri);
- ///
- /// Launches the default browser with the specified uri and waits for protocol activation to finish.
- ///
- ///
- /// Response callback from the browser.
- Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri, CancellationToken cancellationToken = default);
-
///
/// Finalizes GetAuthorizationResponseUriAsync for current IAuthenticator.
///
@@ -32,11 +23,6 @@ namespace Wino.Core.Domain.Interfaces
Task PinAppToTaskbarAsync();
- ///
- /// Some cryptographic shit is needed for requesting Google authentication in UWP.
- ///
- GoogleAuthorizationRequest GetGoogleAuthorizationRequest();
-
///
/// Gets or sets the function that returns a pointer for main window hwnd for UWP.
/// This is used to display WAM broker dialog on running UWP app called by a windowless server code.
diff --git a/Wino.Core.Domain/Models/Accounts/ProfileInformation.cs b/Wino.Core.Domain/Models/Accounts/ProfileInformation.cs
index b9fb5677..c0d38911 100644
--- a/Wino.Core.Domain/Models/Accounts/ProfileInformation.cs
+++ b/Wino.Core.Domain/Models/Accounts/ProfileInformation.cs
@@ -5,5 +5,6 @@
///
/// Display sender name for the account.
/// Base 64 encoded profile picture data of the account. Thumbnail size.
- public record ProfileInformation(string SenderName, string Base64ProfilePictureData);
+ /// Address of the profile.
+ public record ProfileInformation(string SenderName, string Base64ProfilePictureData, string AccountAddress);
}
diff --git a/Wino.Core.Domain/Models/Authentication/TokenInformationBase.cs b/Wino.Core.Domain/Models/Authentication/TokenInformationBase.cs
deleted file mode 100644
index 27f69abf..00000000
--- a/Wino.Core.Domain/Models/Authentication/TokenInformationBase.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-
-namespace Wino.Core.Domain.Models.Authentication
-{
- public class TokenInformationBase
- {
- public string AccessToken { get; set; }
- public string RefreshToken { get; set; }
-
- ///
- /// UTC date for token expiration.
- ///
- public DateTime ExpiresAt { get; set; }
-
- ///
- /// Gets the value indicating whether the token is expired or not.
- ///
- public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
- }
-}
diff --git a/Wino.Core.Domain/Models/Authentication/TokenInformationEx.cs b/Wino.Core.Domain/Models/Authentication/TokenInformationEx.cs
new file mode 100644
index 00000000..270d9af7
--- /dev/null
+++ b/Wino.Core.Domain/Models/Authentication/TokenInformationEx.cs
@@ -0,0 +1,11 @@
+namespace Wino.Core.Domain.Models.Authentication
+{
+ ///
+ /// Previously known as TokenInformation.
+ /// We used to store this model in the database.
+ /// Now we store it in the memory.
+ ///
+ /// Access token/
+ /// Address of the authenticated user.
+ public record TokenInformationEx(string AccessToken, string AccountAddress);
+}
diff --git a/Wino.Core.UWP/Services/NativeAppService.cs b/Wino.Core.UWP/Services/NativeAppService.cs
index 00e59663..9e0e977b 100644
--- a/Wino.Core.UWP/Services/NativeAppService.cs
+++ b/Wino.Core.UWP/Services/NativeAppService.cs
@@ -1,7 +1,5 @@
using System;
-using System.Threading;
using System.Threading.Tasks;
-using Nito.AsyncEx;
using Windows.ApplicationModel;
using Windows.Foundation.Metadata;
using Windows.Security.Authentication.Web;
@@ -11,8 +9,6 @@ using Windows.Storage;
using Windows.Storage.Streams;
using Windows.System;
using Windows.UI.Shell;
-using Wino.Core.Domain;
-using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Authorization;
@@ -156,26 +152,6 @@ namespace Wino.Services
await taskbarManager.RequestPinCurrentAppAsync();
}
- public async Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator,
- string authorizationUri,
- CancellationToken cancellationToken = default)
- {
- if (authorizationCompletedTaskSource != null)
- {
- authorizationCompletedTaskSource.TrySetException(new AuthenticationException(Translator.Exception_AuthenticationCanceled));
- authorizationCompletedTaskSource = null;
- }
-
- authorizationCompletedTaskSource = new TaskCompletionSource();
-
- bool isLaunched = await Launcher.LaunchUriAsync(new Uri(authorizationUri)).AsTask(cancellationToken);
-
- if (!isLaunched)
- throw new WinoServerException("Failed to launch Google Authentication dialog.");
-
- return await authorizationCompletedTaskSource.Task.WaitAsync(cancellationToken);
- }
-
public void ContinueAuthorization(Uri authorizationResponseUri)
{
if (authorizationCompletedTaskSource != null)
diff --git a/Wino.Core.UWP/Styles/DataTemplates.xaml b/Wino.Core.UWP/Styles/DataTemplates.xaml
index 0191001c..1c5ca6fe 100644
--- a/Wino.Core.UWP/Styles/DataTemplates.xaml
+++ b/Wino.Core.UWP/Styles/DataTemplates.xaml
@@ -4,9 +4,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
- xmlns:helpers="using:Wino.Helpers"
xmlns:coreControls="using:Wino.Core.UWP.Controls"
xmlns:domain="using:Wino.Core.Domain"
+ xmlns:helpers="using:Wino.Helpers"
xmlns:local="using:Wino.Core.UWP.Styles"
xmlns:menu="using:Wino.Core.MenuItems"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
diff --git a/Wino.Core/Authenticators/Base/BaseAuthenticator.cs b/Wino.Core/Authenticators/Base/BaseAuthenticator.cs
deleted file mode 100644
index 9da7a83e..00000000
--- a/Wino.Core/Authenticators/Base/BaseAuthenticator.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System.Threading.Tasks;
-using Wino.Core.Domain.Entities.Shared;
-using Wino.Core.Domain.Enums;
-using Wino.Core.Services;
-
-namespace Wino.Core.Authenticators.Base
-{
- public abstract class BaseAuthenticator
- {
- public abstract MailProviderType ProviderType { get; }
-
- protected ITokenService TokenService { get; }
-
- protected BaseAuthenticator(ITokenService tokenService)
- {
- TokenService = tokenService;
- }
-
- internal Task SaveTokenInternalAsync(MailAccount account, TokenInformation tokenInformation)
- => TokenService.SaveTokenInformationAsync(account.Id, tokenInformation);
- }
-}
diff --git a/Wino.Core/Authenticators/Base/GmailAuthenticatorBase.cs b/Wino.Core/Authenticators/Base/GmailAuthenticatorBase.cs
deleted file mode 100644
index c6e79d71..00000000
--- a/Wino.Core/Authenticators/Base/GmailAuthenticatorBase.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Threading.Tasks;
-using Wino.Core.Domain.Entities.Shared;
-using Wino.Core.Domain.Interfaces;
-using Wino.Core.Services;
-
-namespace Wino.Core.Authenticators.Base
-{
- public abstract class GmailAuthenticatorBase : BaseAuthenticator, IGmailAuthenticator
- {
- protected GmailAuthenticatorBase(ITokenService tokenService) : base(tokenService)
- {
- }
-
- public abstract string ClientId { get; }
- public bool ProposeCopyAuthURL { get; set; }
-
- public abstract Task GenerateTokenAsync(MailAccount account, bool saveToken);
-
- public abstract Task GetTokenAsync(MailAccount account);
- }
-}
diff --git a/Wino.Core/Authenticators/Base/OutlookAuthenticatorBase.cs b/Wino.Core/Authenticators/Base/OutlookAuthenticatorBase.cs
deleted file mode 100644
index b38982f3..00000000
--- a/Wino.Core/Authenticators/Base/OutlookAuthenticatorBase.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Threading.Tasks;
-using Wino.Core.Domain.Entities.Shared;
-using Wino.Core.Domain.Interfaces;
-using Wino.Core.Services;
-
-namespace Wino.Core.Authenticators.Base
-{
- public abstract class OutlookAuthenticatorBase : BaseAuthenticator, IOutlookAuthenticator
- {
- protected OutlookAuthenticatorBase(ITokenService tokenService) : base(tokenService)
- {
- }
-
- public abstract string ClientId { get; }
-
- public abstract Task GenerateTokenAsync(MailAccount account, bool saveToken);
-
- public abstract Task GetTokenAsync(MailAccount account);
- }
-}
diff --git a/Wino.Core/Authenticators/Calendar/OutlookAuthenticator.cs b/Wino.Core/Authenticators/Calendar/OutlookAuthenticator.cs
deleted file mode 100644
index 3d3f2658..00000000
--- a/Wino.Core/Authenticators/Calendar/OutlookAuthenticator.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Wino.Core.Authenticators.Base;
-using Wino.Core.Domain.Entities.Shared;
-using Wino.Core.Domain.Enums;
-using Wino.Core.Services;
-
-namespace Wino.Core.Authenticators.Calendar
-{
- public class OutlookAuthenticator : OutlookAuthenticatorBase
- {
- public OutlookAuthenticator(ITokenService tokenService) : base(tokenService)
- {
- }
-
- public override string ClientId => throw new NotImplementedException();
-
- public override MailProviderType ProviderType => MailProviderType.Outlook;
-
- public override Task GenerateTokenAsync(MailAccount account, bool saveToken)
- {
- throw new NotImplementedException();
- }
-
- public override Task GetTokenAsync(MailAccount account)
- {
- throw new NotImplementedException();
- }
- }
-}
diff --git a/Wino.Core/Authenticators/Mail/GmailAuthenticator.cs b/Wino.Core/Authenticators/Mail/GmailAuthenticator.cs
deleted file mode 100644
index 127d2910..00000000
--- a/Wino.Core/Authenticators/Mail/GmailAuthenticator.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-using System;
-using System.Net.Http;
-using System.Text;
-using System.Text.Json.Nodes;
-using System.Threading.Tasks;
-using CommunityToolkit.Mvvm.Messaging;
-using Wino.Core.Authenticators.Base;
-using Wino.Core.Domain;
-using Wino.Core.Domain.Entities.Shared;
-using Wino.Core.Domain.Enums;
-using Wino.Core.Domain.Exceptions;
-using Wino.Core.Domain.Interfaces;
-using Wino.Core.Domain.Models.Authentication;
-using Wino.Core.Domain.Models.Authorization;
-using Wino.Core.Services;
-using Wino.Messaging.UI;
-
-namespace Wino.Core.Authenticators.Mail
-{
- public class GmailAuthenticator : GmailAuthenticatorBase
- {
- public override string ClientId { get; } = "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
-
- private const string TokenEndpoint = "https://www.googleapis.com/oauth2/v4/token";
- private const string RefreshTokenEndpoint = "https://oauth2.googleapis.com/token";
- private const string UserInfoEndpoint = "https://gmail.googleapis.com/gmail/v1/users/me/profile";
-
- public override MailProviderType ProviderType => MailProviderType.Gmail;
-
- private readonly INativeAppService _nativeAppService;
-
- public GmailAuthenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService)
- {
- _nativeAppService = nativeAppService;
- }
-
- ///
- /// Performs tokenization code exchange and retrieves the actual Access - Refresh tokens from Google
- /// after redirect uri returns from browser.
- ///
- /// Tokenization request.
- /// In case of network or parsing related error.
- private async Task PerformCodeExchangeAsync(GoogleTokenizationRequest tokenizationRequest)
- {
- var uri = tokenizationRequest.BuildRequest();
-
- var content = new StringContent(uri, Encoding.UTF8, "application/x-www-form-urlencoded");
-
- var handler = new HttpClientHandler()
- {
- AllowAutoRedirect = true
- };
-
- var client = new HttpClient(handler);
-
- var response = await client.PostAsync(TokenEndpoint, content);
- string responseString = await response.Content.ReadAsStringAsync();
-
- if (!response.IsSuccessStatusCode)
- throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthorizationCodeExchangeFailed);
-
- var parsed = JsonNode.Parse(responseString).AsObject();
-
- if (parsed.ContainsKey("error"))
- throw new GoogleAuthenticationException(parsed["error"]["message"].GetValue());
-
- var accessToken = parsed["access_token"].GetValue();
- var refreshToken = parsed["refresh_token"].GetValue();
- var expiresIn = parsed["expires_in"].GetValue();
-
- var expirationDate = DateTime.UtcNow.AddSeconds(expiresIn);
-
- client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
-
- // Get basic user info for UserName.
-
- var userinfoResponse = await client.GetAsync(UserInfoEndpoint);
- string userinfoResponseContent = await userinfoResponse.Content.ReadAsStringAsync();
-
- var parsedUserInfo = JsonNode.Parse(userinfoResponseContent).AsObject();
-
- if (parsedUserInfo.ContainsKey("error"))
- throw new GoogleAuthenticationException(parsedUserInfo["error"]["message"].GetValue());
-
- var username = parsedUserInfo["emailAddress"].GetValue();
-
- return new TokenInformation()
- {
- Id = Guid.NewGuid(),
- Address = username,
- AccessToken = accessToken,
- RefreshToken = refreshToken,
- ExpiresAt = expirationDate
- };
- }
-
- public async override Task GetTokenAsync(MailAccount account)
- {
- var cachedToken = await TokenService.GetTokenInformationAsync(account.Id)
- ?? throw new AuthenticationAttentionException(account);
-
- if (cachedToken.IsExpired)
- {
- // Refresh token with new exchanges.
- // No need to check Username for account.
-
- var refreshedTokenInfoBase = await RefreshTokenAsync(cachedToken.RefreshToken);
-
- cachedToken.RefreshTokens(refreshedTokenInfoBase);
-
- // Save new token and return.
- await SaveTokenInternalAsync(account, cachedToken);
- }
-
- return cachedToken;
- }
-
-
- public async override Task GenerateTokenAsync(MailAccount account, bool saveToken)
- {
- var authRequest = _nativeAppService.GetGoogleAuthorizationRequest();
-
- var authorizationUri = authRequest.BuildRequest(ClientId);
-
- Uri responseRedirectUri = null;
-
- if (ProposeCopyAuthURL)
- {
- WeakReferenceMessenger.Default.Send(new CopyAuthURLRequested(authorizationUri));
- }
-
- try
- {
- responseRedirectUri = await _nativeAppService.GetAuthorizationResponseUriAsync(this, authorizationUri);
- }
- catch (Exception)
- {
- throw new AuthenticationException(Translator.Exception_AuthenticationCanceled);
- }
-
- authRequest.ValidateAuthorizationCode(responseRedirectUri);
-
- // Start tokenization.
- var tokenizationRequest = new GoogleTokenizationRequest(authRequest);
- var tokenInformation = await PerformCodeExchangeAsync(tokenizationRequest);
-
- if (saveToken)
- {
- await SaveTokenInternalAsync(account, tokenInformation);
- }
-
- return tokenInformation;
- }
-
- ///
- /// Internally exchanges refresh token with a new access token and returns new TokenInformation.
- ///
- /// Token to be used in refreshing.
- /// New TokenInformationBase that has new tokens and expiration date without a username. This token is not saved to database after returned.
- private async Task RefreshTokenAsync(string refresh_token)
- {
- // TODO: This doesn't work.
- var refreshUri = string.Format("client_id={0}&refresh_token={1}&grant_type=refresh_token", ClientId, refresh_token);
-
- //Uri.EscapeDataString(refreshUri);
- var content = new StringContent(refreshUri, Encoding.UTF8, "application/x-www-form-urlencoded");
-
- var client = new HttpClient();
-
- var response = await client.PostAsync(RefreshTokenEndpoint, content);
-
- string responseString = await response.Content.ReadAsStringAsync();
-
- var parsed = JsonNode.Parse(responseString).AsObject();
-
- // TODO: Error parsing is incorrect.
- if (parsed.ContainsKey("error"))
- throw new GoogleAuthenticationException(parsed["error_description"].GetValue());
-
- var accessToken = parsed["access_token"].GetValue();
-
- string activeRefreshToken = refresh_token;
-
- // Refresh token might not be returned.
- // In this case older refresh token is still available for new refreshes.
- // Only change if provided.
-
- if (parsed.ContainsKey("refresh_token"))
- {
- activeRefreshToken = parsed["refresh_token"].GetValue();
- }
-
- var expiresIn = parsed["expires_in"].GetValue();
- var expirationDate = DateTime.UtcNow.AddSeconds(expiresIn);
-
- return new TokenInformationBase()
- {
- AccessToken = accessToken,
- ExpiresAt = expirationDate,
- RefreshToken = activeRefreshToken
- };
- }
- }
-}
diff --git a/Wino.Core/Authenticators/Mail/Office365Authenticator.cs b/Wino.Core/Authenticators/Mail/Office365Authenticator.cs
deleted file mode 100644
index 35bf51ed..00000000
--- a/Wino.Core/Authenticators/Mail/Office365Authenticator.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Wino.Core.Domain.Enums;
-using Wino.Core.Domain.Interfaces;
-using Wino.Core.Services;
-
-namespace Wino.Core.Authenticators.Mail
-{
- public class Office365Authenticator : OutlookAuthenticator
- {
- public Office365Authenticator(ITokenService tokenService, INativeAppService nativeAppService, IApplicationConfiguration applicationConfiguration) : base(tokenService, nativeAppService, applicationConfiguration) { }
-
- public override MailProviderType ProviderType => MailProviderType.Office365;
- }
-}
diff --git a/Wino.Core/CoreContainerSetup.cs b/Wino.Core/CoreContainerSetup.cs
index 4c6d7fe2..de6da3cf 100644
--- a/Wino.Core/CoreContainerSetup.cs
+++ b/Wino.Core/CoreContainerSetup.cs
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Serilog.Core;
-using Wino.Core.Authenticators.Mail;
+using Wino.Authentication;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Integration.Processors;
using Wino.Core.Integration.Threading;
@@ -28,7 +28,6 @@ namespace Wino.Core
services.AddTransient();
services.AddTransient();
- services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
diff --git a/Wino.Core/Extensions/TokenizationExtensions.cs b/Wino.Core/Extensions/TokenizationExtensions.cs
deleted file mode 100644
index 46442d94..00000000
--- a/Wino.Core/Extensions/TokenizationExtensions.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using Microsoft.Identity.Client;
-using Wino.Core.Domain.Entities.Shared;
-
-namespace Wino.Core.Extensions
-{
- public static class TokenizationExtensions
- {
- public static TokenInformation CreateTokenInformation(this AuthenticationResult clientBuilderResult)
- {
- // Plain access token info is not stored for Outlook in Wino's database.
- // Here we store UniqueId and Access Token in memory only to compare the UniqueId returned from MSAL auth result.
-
- var tokenInfo = new TokenInformation()
- {
- Address = clientBuilderResult.Account.Username,
- Id = Guid.NewGuid(),
- UniqueId = clientBuilderResult.UniqueId,
- AccessToken = clientBuilderResult.AccessToken
- };
-
- return tokenInfo;
- }
- }
-}
diff --git a/Wino.Core/Http/GmailClientMessageHandler.cs b/Wino.Core/Http/GmailClientMessageHandler.cs
index f591c4b3..238c2f23 100644
--- a/Wino.Core/Http/GmailClientMessageHandler.cs
+++ b/Wino.Core/Http/GmailClientMessageHandler.cs
@@ -1,25 +1,29 @@
-using System;
-using System.Net.Http;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Http;
using Wino.Core.Domain.Entities.Shared;
+using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Http
{
internal class GmailClientMessageHandler : ConfigurableMessageHandler
{
- public Func> TokenRetrieveDelegate { get; }
+ private readonly IGmailAuthenticator _gmailAuthenticator;
+ private readonly MailAccount _mailAccount;
- public GmailClientMessageHandler(Func> tokenRetrieveDelegate) : base(new HttpClientHandler())
+ public GmailClientMessageHandler(IGmailAuthenticator gmailAuthenticator, MailAccount mailAccount) : base(new HttpClientHandler())
{
- TokenRetrieveDelegate = tokenRetrieveDelegate;
+ _gmailAuthenticator = gmailAuthenticator;
+ _mailAccount = mailAccount;
}
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
- var tokenizationTask = TokenRetrieveDelegate.Invoke();
- var tokenInformation = await tokenizationTask;
+ // This call here will automatically trigger Google Auth's interactive login if the token is not found.
+ // or refresh the token based on the FileDataStore.
+
+ var tokenInformation = await _gmailAuthenticator.GetTokenInformationAsync(_mailAccount);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenInformation.AccessToken);
diff --git a/Wino.Core/Http/MicrosoftTokenProvider.cs b/Wino.Core/Http/MicrosoftTokenProvider.cs
index ec373e1e..88df01b0 100644
--- a/Wino.Core/Http/MicrosoftTokenProvider.cs
+++ b/Wino.Core/Http/MicrosoftTokenProvider.cs
@@ -22,12 +22,12 @@ namespace Wino.Core.Http
public AllowedHostsValidator AllowedHostsValidator { get; }
public async Task GetAuthorizationTokenAsync(Uri uri,
- Dictionary additionalAuthenticationContext = null,
- CancellationToken cancellationToken = default)
+ Dictionary additionalAuthenticationContext = null,
+ CancellationToken cancellationToken = default)
{
- var token = await _authenticator.GetTokenAsync(_account).ConfigureAwait(false);
+ var tokenInfo = await _authenticator.GetTokenInformationAsync(_account);
- return token?.AccessToken;
+ return tokenInfo.AccessToken;
}
}
}
diff --git a/Wino.Core/Services/AccountService.cs b/Wino.Core/Services/AccountService.cs
index 630dd3bf..aa07b63f 100644
--- a/Wino.Core/Services/AccountService.cs
+++ b/Wino.Core/Services/AccountService.cs
@@ -206,8 +206,9 @@ namespace Wino.Core.Services
var authenticator = _authenticationProvider.GetAuthenticator(account.ProviderType);
// This will re-generate token.
- var token = await authenticator.GenerateTokenAsync(account, true);
+ var token = await authenticator.GenerateTokenInformationAsync(account);
+ // TODO: Rest?
Guard.IsNotNull(token);
}
@@ -267,10 +268,10 @@ namespace Wino.Core.Services
public async Task DeleteAccountAsync(MailAccount account)
{
// TODO: Delete mime messages and attachments.
+ // TODO: Delete token cache by underlying provider.
await Connection.ExecuteAsync("DELETE FROM MailCopy WHERE Id IN(SELECT Id FROM MailCopy WHERE FolderId IN (SELECT Id from MailItemFolder WHERE MailAccountId == ?))", account.Id);
- await Connection.Table().Where(a => a.AccountId == account.Id).DeleteAsync();
await Connection.Table().DeleteAsync(a => a.MailAccountId == account.Id);
await Connection.Table().DeleteAsync(a => a.MailAccountId == account.Id);
await Connection.Table().DeleteAsync(a => a.AccountId == account.Id);
@@ -333,6 +334,10 @@ namespace Wino.Core.Services
account.SenderName = profileInformation.SenderName;
account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
+ if (string.IsNullOrEmpty(account.Address))
+ {
+ account.Address = profileInformation.AccountAddress;
+ }
// Forcefully add or update a contact data with the provided information.
var accountContact = new AccountContact()
@@ -469,7 +474,7 @@ namespace Wino.Core.Services
await Connection.ExecuteAsync(query.GetRawQuery()).ConfigureAwait(false);
}
- public async Task CreateAccountAsync(MailAccount account, TokenInformation tokenInformation, CustomServerInformation customServerInformation)
+ public async Task CreateAccountAsync(MailAccount account, CustomServerInformation customServerInformation)
{
Guard.IsNotNull(account);
@@ -518,12 +523,6 @@ namespace Wino.Core.Services
if (customServerInformation != null)
await Connection.InsertAsync(customServerInformation);
-
- // Outlook token cache is managed by MSAL.
- // Don't save it to database.
-
- if (tokenInformation != null && (account.ProviderType != MailProviderType.Outlook || account.ProviderType == MailProviderType.Office365))
- await Connection.InsertAsync(tokenInformation);
}
public async Task UpdateSynchronizationIdentifierAsync(Guid accountId, string newIdentifier)
diff --git a/Wino.Core/Services/AuthenticationProvider.cs b/Wino.Core/Services/AuthenticationProvider.cs
index 57b1a64b..42a7d8b8 100644
--- a/Wino.Core/Services/AuthenticationProvider.cs
+++ b/Wino.Core/Services/AuthenticationProvider.cs
@@ -1,5 +1,5 @@
using System;
-using Wino.Core.Authenticators.Mail;
+using Wino.Authentication;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
@@ -10,14 +10,16 @@ namespace Wino.Core.Services
public class AuthenticationProvider : IAuthenticationProvider
{
private readonly INativeAppService _nativeAppService;
- private readonly ITokenService _tokenService;
private readonly IApplicationConfiguration _applicationConfiguration;
+ private readonly IAuthenticatorConfig _authenticatorConfig;
- public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService, IApplicationConfiguration applicationConfiguration)
+ public AuthenticationProvider(INativeAppService nativeAppService,
+ IApplicationConfiguration applicationConfiguration,
+ IAuthenticatorConfig authenticatorConfig)
{
_nativeAppService = nativeAppService;
- _tokenService = tokenService;
_applicationConfiguration = applicationConfiguration;
+ _authenticatorConfig = authenticatorConfig;
}
public IAuthenticator GetAuthenticator(MailProviderType providerType)
@@ -25,9 +27,9 @@ namespace Wino.Core.Services
// TODO: Move DI
return providerType switch
{
- MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService, _applicationConfiguration),
- MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService, _applicationConfiguration),
- MailProviderType.Gmail => new GmailAuthenticator(_tokenService, _nativeAppService),
+ MailProviderType.Outlook => new OutlookAuthenticator(_nativeAppService, _applicationConfiguration, _authenticatorConfig),
+ MailProviderType.Office365 => new Office365Authenticator(_nativeAppService, _applicationConfiguration, _authenticatorConfig),
+ MailProviderType.Gmail => new GmailAuthenticator(_authenticatorConfig),
_ => throw new ArgumentException(Translator.Exception_UnsupportedProvider),
};
}
diff --git a/Wino.Core/Services/DatabaseService.cs b/Wino.Core/Services/DatabaseService.cs
index 294736c6..4e368aea 100644
--- a/Wino.Core/Services/DatabaseService.cs
+++ b/Wino.Core/Services/DatabaseService.cs
@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System.IO;
using System.Threading.Tasks;
using SQLite;
using Wino.Core.Domain.Entities.Mail;
@@ -35,16 +34,7 @@ namespace Wino.Core.Services
var publisherCacheFolder = _folderConfiguration.PublisherSharedFolderPath;
var databaseFileName = Path.Combine(publisherCacheFolder, DatabaseName);
- Connection = new SQLiteAsyncConnection(databaseFileName)
- {
- // Enable for debugging sqlite.
- Trace = true,
- Tracer = new Action((t) =>
- {
- // Debug.WriteLine(t);
- // Log.Debug(t);
- })
- };
+ Connection = new SQLiteAsyncConnection(databaseFileName);
await CreateTablesAsync();
@@ -57,7 +47,6 @@ namespace Wino.Core.Services
typeof(MailCopy),
typeof(MailItemFolder),
typeof(MailAccount),
- typeof(TokenInformation),
typeof(AccountContact),
typeof(CustomServerInformation),
typeof(AccountSignature),
diff --git a/Wino.Core/Services/TokenService.cs b/Wino.Core/Services/TokenService.cs
deleted file mode 100644
index a425e8e0..00000000
--- a/Wino.Core/Services/TokenService.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Wino.Core.Domain.Entities.Shared;
-
-namespace Wino.Core.Services
-{
- public interface ITokenService
- {
- Task GetTokenInformationAsync(Guid accountId);
- Task SaveTokenInformationAsync(Guid accountId, TokenInformation tokenInformation);
- }
-
- public class TokenService : BaseDatabaseService, ITokenService
- {
- public TokenService(IDatabaseService databaseService) : base(databaseService) { }
-
- public Task GetTokenInformationAsync(Guid accountId)
- => Connection.Table().FirstOrDefaultAsync(a => a.AccountId == accountId);
-
- public async Task SaveTokenInformationAsync(Guid accountId, TokenInformation tokenInformation)
- {
- // Delete all tokens for this account.
- await Connection.Table().DeleteAsync(a => a.AccountId == accountId);
-
- // Save new token info to the account.
- tokenInformation.AccountId = accountId;
-
- await Connection.InsertOrReplaceAsync(tokenInformation);
- }
- }
-}
diff --git a/Wino.Core/Synchronizers/BaseMailSynchronizer.cs b/Wino.Core/Synchronizers/BaseMailSynchronizer.cs
index 1843a035..0f876dfc 100644
--- a/Wino.Core/Synchronizers/BaseMailSynchronizer.cs
+++ b/Wino.Core/Synchronizers/BaseMailSynchronizer.cs
@@ -119,6 +119,11 @@ namespace Wino.Core.Synchronizers
{
Account.SenderName = profileInformation.SenderName;
Account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
+
+ if (!string.IsNullOrEmpty(profileInformation.AccountAddress))
+ {
+ Account.Address = profileInformation.AccountAddress;
+ }
}
return profileInformation;
diff --git a/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs b/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs
index 74afc361..15a8966f 100644
--- a/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs
+++ b/Wino.Core/Synchronizers/Mail/GmailSynchronizer.cs
@@ -46,15 +46,15 @@ namespace Wino.Core.Synchronizers.Mail
private readonly GmailService _gmailService;
private readonly PeopleServiceService _peopleService;
- private readonly IAuthenticator _authenticator;
+ private readonly IGmailAuthenticator _authenticator;
private readonly IGmailChangeProcessor _gmailChangeProcessor;
private readonly ILogger _logger = Log.ForContext();
public GmailSynchronizer(MailAccount account,
- IAuthenticator authenticator,
+ IGmailAuthenticator authenticator,
IGmailChangeProcessor gmailChangeProcessor) : base(account)
{
- var messageHandler = new GmailClientMessageHandler(() => _authenticator.GetTokenAsync(Account));
+ var messageHandler = new GmailClientMessageHandler(authenticator, account);
var initializer = new BaseClientService.Initializer()
{
@@ -77,8 +77,12 @@ namespace Wino.Core.Synchronizers.Mail
var profileRequest = _peopleService.People.Get("people/me");
profileRequest.PersonFields = "names,photos";
- string senderName = string.Empty, base64ProfilePicture = string.Empty;
+ string senderName = string.Empty, base64ProfilePicture = string.Empty, address = string.Empty;
+ var gmailUserData = _gmailService.Users.GetProfile("me");
+ var gmailProfile = await gmailUserData.ExecuteAsync();
+
+ address = gmailProfile.EmailAddress;
var userProfile = await profileRequest.ExecuteAsync();
senderName = userProfile.Names?.FirstOrDefault()?.DisplayName ?? Account.SenderName;
@@ -90,7 +94,7 @@ namespace Wino.Core.Synchronizers.Mail
base64ProfilePicture = await GetProfilePictureBase64EncodedAsync(profilePicture).ConfigureAwait(false);
}
- return new ProfileInformation(senderName, base64ProfilePicture);
+ return new ProfileInformation(senderName, base64ProfilePicture, address);
}
protected override async Task SynchronizeAliasesAsync()
diff --git a/Wino.Core/Synchronizers/Mail/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/Mail/OutlookSynchronizer.cs
index 58efc6f3..3cc8e583 100644
--- a/Wino.Core/Synchronizers/Mail/OutlookSynchronizer.cs
+++ b/Wino.Core/Synchronizers/Mail/OutlookSynchronizer.cs
@@ -533,20 +533,20 @@ namespace Wino.Core.Synchronizers.Mail
///
/// Get the user's display name.
///
- /// Display name of the user.
- private async Task GetSenderNameAsync()
+ /// Display name and address of the user.
+ private async Task> GetDisplayNameAndAddressAsync()
{
var userInfo = await _graphClient.Me.GetAsync();
- return userInfo.DisplayName;
+ return new Tuple(userInfo.DisplayName, userInfo.Mail);
}
public override async Task GetProfileInformationAsync()
{
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
- var senderName = await GetSenderNameAsync().ConfigureAwait(false);
+ var displayNameAndAddress = await GetDisplayNameAndAddressAsync().ConfigureAwait(false);
- return new ProfileInformation(senderName, profilePictureData);
+ return new ProfileInformation(displayNameAndAddress.Item1, profilePictureData, displayNameAndAddress.Item2);
}
///
diff --git a/Wino.Core/Wino.Core.csproj b/Wino.Core/Wino.Core.csproj
index 20fad4b2..19b1a7ae 100644
--- a/Wino.Core/Wino.Core.csproj
+++ b/Wino.Core/Wino.Core.csproj
@@ -43,6 +43,7 @@
+
diff --git a/Wino.Mail.ViewModels/AccountManagementViewModel.cs b/Wino.Mail.ViewModels/AccountManagementViewModel.cs
index 86b296d9..8cab69a8 100644
--- a/Wino.Mail.ViewModels/AccountManagementViewModel.cs
+++ b/Wino.Mail.ViewModels/AccountManagementViewModel.cs
@@ -14,6 +14,7 @@ using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
+using Wino.Core.Domain.Models.Authentication;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.ViewModels;
@@ -107,7 +108,7 @@ namespace Wino.Mail.ViewModels
creationDialog.ShowDialog(accountCreationCancellationTokenSource);
creationDialog.State = AccountCreationDialogState.SigningIn;
- TokenInformation tokenInformation = null;
+ string tokenInformation = string.Empty;
// Custom server implementation requires more async waiting.
if (creationDialog is ICustomServerAccountCreationDialog customServerDialog)
@@ -129,24 +130,26 @@ namespace Wino.Mail.ViewModels
}
else
{
- // For OAuth authentications, we just generate token and assign it to the MailAccount.
+ // OAuth authentication is handled here.
+ // Server authenticates, returns the token info here.
var tokenInformationResponse = await WinoServerConnectionManager
- .GetResponseAsync(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
+ .GetResponseAsync(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
createdAccount,
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
if (creationDialog.State == AccountCreationDialogState.Canceled)
throw new AccountSetupCanceledException();
- tokenInformationResponse.ThrowIfFailed();
+ createdAccount.Address = tokenInformationResponse.Data.AccountAddress;
- tokenInformation = tokenInformationResponse.Data;
- createdAccount.Address = tokenInformation.Address;
- tokenInformation.AccountId = createdAccount.Id;
+ tokenInformationResponse.ThrowIfFailed();
}
- await AccountService.CreateAccountAsync(createdAccount, tokenInformation, customServerInformation);
+ // Address is still doesn't have a value for API synchronizers.
+ // It'll be synchronized with profile information.
+
+ await AccountService.CreateAccountAsync(createdAccount, customServerInformation);
// Local account has been created.
@@ -172,6 +175,11 @@ namespace Wino.Mail.ViewModels
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
+ if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress))
+ {
+ createdAccount.Address = profileSynchronizationResult.ProfileInformation.AccountAddress;
+ }
+
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
}
diff --git a/Wino.Mail/App.xaml.cs b/Wino.Mail/App.xaml.cs
index ec6c6997..c7e10e0e 100644
--- a/Wino.Mail/App.xaml.cs
+++ b/Wino.Mail/App.xaml.cs
@@ -96,7 +96,7 @@ namespace Wino
services.AddSingleton();
services.AddTransient();
services.AddTransient();
-
+ services.AddSingleton();
}
private void RegisterViewModels(IServiceCollection services)
diff --git a/Wino.Mail/Package.appxmanifest b/Wino.Mail/Package.appxmanifest
index c0457ee6..5407eb55 100644
--- a/Wino.Mail/Package.appxmanifest
+++ b/Wino.Mail/Package.appxmanifest
@@ -42,7 +42,7 @@
+ EntryPoint="Wino.Mail.App">
"b19c2035-d740-49ff-b297-de6ec561b208";
+
+ public string[] OutlookScope => new string[]
+ {
+ "email",
+ "mail.readwrite",
+ "offline_access",
+ "mail.send",
+ "Mail.Send.Shared",
+ "Mail.ReadWrite.Shared",
+ "User.Read"
+ };
+
+ public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
+
+ public string[] GmailScope => new string[]
+ {
+ "https://mail.google.com/",
+ "https://www.googleapis.com/auth/userinfo.profile",
+ "https://www.googleapis.com/auth/gmail.labels"
+ };
+ }
+}
diff --git a/Wino.Mail/Wino.Mail.csproj b/Wino.Mail/Wino.Mail.csproj
index f854920a..1ba4ef5e 100644
--- a/Wino.Mail/Wino.Mail.csproj
+++ b/Wino.Mail/Wino.Mail.csproj
@@ -14,8 +14,8 @@
{68A432B8-C1B7-494C-8D6D-230788EA683E}
AppContainerExe
Properties
- Wino
- Wino
+ Wino.Mail
+ Wino.Mail
en-US
UAP
10.0.22621.0
@@ -259,6 +259,7 @@
+
@@ -565,7 +566,6 @@
-
@@ -623,9 +623,7 @@
Windows Desktop Extensions for the UWP
-
-
-
+
14.0
diff --git a/Wino.Server/App.xaml.cs b/Wino.Server/App.xaml.cs
index 024dd156..5768fece 100644
--- a/Wino.Server/App.xaml.cs
+++ b/Wino.Server/App.xaml.cs
@@ -83,6 +83,19 @@ namespace Wino.Server
services.AddSingleton(serverMessageHandlerFactory);
+ // Server type related services.
+ // TODO: Better abstraction.
+
+ if (WinoServerType == WinoAppType.Mail)
+ {
+ services.AddSingleton();
+ }
+ else
+ {
+ // TODO: Calendar config will be added here.
+ }
+
+
return services.BuildServiceProvider();
}
@@ -135,6 +148,8 @@ namespace Wino.Server
{
string processName = WinoServerType == WinoAppType.Mail ? "Wino.Mail" : "Wino.Calendar";
+ var processs = Process.GetProcesses();
+
var proc = Process.GetProcessesByName(processName).FirstOrDefault() ?? throw new Exception($"{processName} client is not running.");
for (IntPtr appWindow = FindWindowEx(IntPtr.Zero, IntPtr.Zero, FRAME_WINDOW, null); appWindow != IntPtr.Zero;
diff --git a/Wino.Server/MessageHandlers/AuthenticationHandler.cs b/Wino.Server/MessageHandlers/AuthenticationHandler.cs
index 19afd5e0..fd9c5394 100644
--- a/Wino.Server/MessageHandlers/AuthenticationHandler.cs
+++ b/Wino.Server/MessageHandlers/AuthenticationHandler.cs
@@ -1,27 +1,27 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Interfaces;
+using Wino.Core.Domain.Models.Authentication;
using Wino.Core.Domain.Models.Server;
using Wino.Messaging.Server;
using Wino.Server.Core;
namespace Wino.Server.MessageHandlers
{
- public class AuthenticationHandler : ServerMessageHandler
+ public class AuthenticationHandler : ServerMessageHandler
{
private readonly IAuthenticationProvider _authenticationProvider;
- public override WinoServerResponse FailureDefaultResponse(Exception ex)
- => WinoServerResponse.CreateErrorResponse(ex.Message);
+ public override WinoServerResponse FailureDefaultResponse(Exception ex)
+ => WinoServerResponse.CreateErrorResponse(ex.Message);
public AuthenticationHandler(IAuthenticationProvider authenticationProvider)
{
_authenticationProvider = authenticationProvider;
}
- protected override async Task> HandleAsync(AuthorizationRequested message,
+ protected override async Task> HandleAsync(AuthorizationRequested message,
CancellationToken cancellationToken = default)
{
var authenticator = _authenticationProvider.GetAuthenticator(message.MailProviderType);
@@ -36,10 +36,21 @@ namespace Wino.Server.MessageHandlers
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);
+ TokenInformationEx generatedToken = null;
- return WinoServerResponse.CreateSuccessResponse(generatedToken);
+ if (message.CreatedAccount != null)
+ {
+ generatedToken = await authenticator.GetTokenInformationAsync(message.CreatedAccount);
+ }
+ else
+ {
+ // Initial authentication request.
+ // There is no account to get token for.
+
+ generatedToken = await authenticator.GenerateTokenInformationAsync(message.CreatedAccount);
+ }
+
+ return WinoServerResponse.CreateSuccessResponse(generatedToken);
}
}
}
diff --git a/Wino.Server/ServerViewModel.cs b/Wino.Server/ServerViewModel.cs
index bb5899f2..2f7c7803 100644
--- a/Wino.Server/ServerViewModel.cs
+++ b/Wino.Server/ServerViewModel.cs
@@ -26,22 +26,7 @@ namespace Wino.Server
[RelayCommand]
public Task LaunchWinoAsync()
{
- //var opt = new SynchronizationOptions()
- //{
- // Type = Wino.Core.Domain.Enums.SynchronizationType.Full,
- // AccountId = Guid.Parse("b3620ce7-8a69-4d81-83d5-a94bbe177431")
- //};
-
- //var req = new NewSynchronizationRequested(opt, Wino.Core.Domain.Enums.SynchronizationSource.Server);
- //WeakReferenceMessenger.Default.Send(req);
-
- // return Task.CompletedTask;
-
return Launcher.LaunchUriAsync(new Uri($"{App.WinoMailLaunchProtocol}:")).AsTask();
- //await _notificationBuilder.CreateNotificationsAsync(Guid.Empty, new List()
- //{
- // new MailCopy(){ UniqueId = Guid.Parse("8f25d2a0-4448-4fee-96a9-c9b25a19e866")}
- //});
}
///
diff --git a/Wino.Server/Wino.Server.csproj b/Wino.Server/Wino.Server.csproj
index 7f9edb8b..971e6885 100644
--- a/Wino.Server/Wino.Server.csproj
+++ b/Wino.Server/Wino.Server.csproj
@@ -18,6 +18,7 @@
+
@@ -36,6 +37,7 @@
+
diff --git a/Wino.sln b/Wino.sln
index 61fc80b4..28c61a9b 100644
--- a/Wino.sln
+++ b/Wino.sln
@@ -32,6 +32,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Core.ViewModels", "Win
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Calendar.ViewModels", "Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj", "{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Authentication", "Wino.Authentication\Wino.Authentication.csproj", "{A4DBA01A-F315-49E0-8428-BB99D32B20F9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -306,6 +308,26 @@ Global
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x64.Build.0 = Release|Any CPU
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x86.ActiveCfg = Release|Any CPU
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x86.Build.0 = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|ARM.Build.0 = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x64.Build.0 = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x86.Build.0 = Debug|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|ARM.ActiveCfg = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|ARM.Build.0 = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|ARM64.Build.0 = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x64.ActiveCfg = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x64.Build.0 = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x86.ActiveCfg = Release|Any CPU
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -320,6 +342,7 @@ Global
{D4919A19-E70F-4916-83D2-5D5F87BEB949} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
+ {A4DBA01A-F315-49E0-8428-BB99D32B20F9} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {721F946E-F69F-4987-823A-D084B436FC1E}