2024-04-18 01:44:37 +02:00
using System ;
using System.Linq ;
using System.Threading.Tasks ;
using Microsoft.Identity.Client ;
2024-08-05 00:36:26 +02:00
using Microsoft.Identity.Client.Broker ;
using Microsoft.Identity.Client.Extensions.Msal ;
2024-04-18 01:44:37 +02:00
using Wino.Core.Domain ;
2024-11-10 23:28:25 +01:00
using Wino.Core.Domain.Entities.Shared ;
2024-04-18 01:44:37 +02:00
using Wino.Core.Domain.Enums ;
using Wino.Core.Domain.Exceptions ;
using Wino.Core.Domain.Interfaces ;
2024-11-20 01:45:48 +01:00
using Wino.Core.Domain.Models.Authentication ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
namespace Wino.Authentication ;
public class OutlookAuthenticator : BaseAuthenticator , IOutlookAuthenticator
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
private const string TokenCacheFileName = "OutlookCache.bin" ;
private bool isTokenCacheAttached = false ;
2024-08-05 00:36:26 +02:00
2025-02-16 11:54:23 +01:00
// Outlook
private const string Authority = "https://login.microsoftonline.com/common" ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public override MailProviderType ProviderType = > MailProviderType . Outlook ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
private readonly IPublicClientApplication _publicClientApplication ;
private readonly IApplicationConfiguration _applicationConfiguration ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public OutlookAuthenticator ( INativeAppService nativeAppService ,
IApplicationConfiguration applicationConfiguration ,
IAuthenticatorConfig authenticatorConfig ) : base ( authenticatorConfig )
{
_applicationConfiguration = applicationConfiguration ;
2024-08-05 00:36:26 +02:00
2025-02-16 11:54:23 +01:00
var authenticationRedirectUri = nativeAppService . GetWebAuthenticationBrokerUri ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var options = new BrokerOptions ( BrokerOptions . OperatingSystems . Windows )
{
Title = "Wino Mail" ,
ListOperatingSystemAccounts = true ,
} ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var outlookAppBuilder = PublicClientApplicationBuilder . Create ( AuthenticatorConfig . OutlookAuthenticatorClientId )
. WithParentActivityOrWindow ( nativeAppService . GetCoreWindowHwnd )
. WithBroker ( options )
. WithDefaultRedirectUri ( )
. WithAuthority ( Authority ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
_publicClientApplication = outlookAppBuilder . Build ( ) ;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public string [ ] Scope = > AuthenticatorConfig . OutlookScope ;
2024-11-20 01:45:48 +01:00
2025-02-16 11:54:23 +01:00
private async Task EnsureTokenCacheAttachedAsync ( )
{
if ( ! isTokenCacheAttached )
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
var storageProperties = new StorageCreationPropertiesBuilder ( TokenCacheFileName , _applicationConfiguration . PublisherSharedFolderPath ) . Build ( ) ;
var msalcachehelper = await MsalCacheHelper . CreateAsync ( storageProperties ) ;
msalcachehelper . RegisterCache ( _publicClientApplication . UserTokenCache ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
isTokenCacheAttached = true ;
2024-11-20 01:45:48 +01:00
}
2025-02-16 11:54:23 +01:00
}
2024-11-20 01:45:48 +01:00
2025-02-16 11:54:23 +01:00
public async Task < TokenInformationEx > GetTokenInformationAsync ( MailAccount account )
{
await EnsureTokenCacheAttachedAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var storedAccount = ( await _publicClientApplication . GetAccountsAsync ( ) ) . FirstOrDefault ( a = > a . Username = = account . Address ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if ( storedAccount = = null )
return await GenerateTokenInformationAsync ( account ) ;
2024-11-20 01:45:48 +01:00
2025-02-16 11:54:23 +01:00
try
{
var authResult = await _publicClientApplication . AcquireTokenSilent ( Scope , storedAccount ) . ExecuteAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new TokenInformationEx ( authResult . AccessToken , authResult . Account . Username ) ;
2025-02-16 11:35:43 +01:00
}
2025-02-16 11:54:23 +01:00
catch ( MsalUiRequiredException )
{
// Somehow MSAL is not able to refresh the token silently.
// Force interactive login.
2024-11-20 01:45:48 +01:00
2025-02-16 11:54:23 +01:00
return await GenerateTokenInformationAsync ( account ) ;
}
catch ( Exception )
2025-02-16 11:35:43 +01:00
{
2025-02-16 11:54:23 +01:00
throw ;
}
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task < TokenInformationEx > GenerateTokenInformationAsync ( MailAccount account )
{
try
{
await EnsureTokenCacheAttachedAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var authResult = await _publicClientApplication
. AcquireTokenInteractive ( Scope )
. ExecuteAsync ( ) ;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// 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.
2025-02-16 11:43:30 +01:00
2025-02-16 11:54:23 +01:00
if ( account ? . Address ! = null & & ! account . Address . Equals ( authResult . Account . Username , StringComparison . OrdinalIgnoreCase ) )
2025-02-16 11:43:30 +01:00
{
2025-04-26 12:25:34 +02:00
throw new AuthenticationException ( "Authenticated address does not match with your account address. If you are signing with a Office365, it is not officially supported yet." ) ;
2025-02-16 11:43:30 +01:00
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
return new TokenInformationEx ( authResult . AccessToken , authResult . Account . Username ) ;
2025-02-16 11:35:43 +01:00
}
2025-02-16 11:54:23 +01:00
catch ( MsalClientException msalClientException )
{
if ( msalClientException . ErrorCode = = "authentication_canceled" | | msalClientException . ErrorCode = = "access_denied" )
throw new AccountSetupCanceledException ( ) ;
throw ;
}
throw new AuthenticationException ( Translator . Exception_UnknowErrorDuringAuthentication , new Exception ( Translator . Exception_TokenGenerationFailed ) ) ;
2024-04-18 01:44:37 +02:00
}
}