Import functionality for wino accounts, calendar sync UI, bunch of shell improvements
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Google;
|
||||
using Serilog;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
|
||||
namespace Wino.Core.Synchronizers.Errors.Gmail;
|
||||
|
||||
public class GmailAuthenticationFailedHandler : ISynchronizerErrorHandler
|
||||
{
|
||||
private readonly ILogger _logger = Log.ForContext<GmailAuthenticationFailedHandler>();
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public GmailAuthenticationFailedHandler(IAccountService accountService)
|
||||
{
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
public bool CanHandle(SynchronizerErrorContext error)
|
||||
{
|
||||
if (error.Exception is not GoogleApiException googleEx)
|
||||
return false;
|
||||
|
||||
var reason = googleEx.Error?.Errors?.FirstOrDefault()?.Reason?.ToLowerInvariant() ?? string.Empty;
|
||||
var message = googleEx.Message?.ToLowerInvariant() ?? string.Empty;
|
||||
|
||||
return googleEx.HttpStatusCode == HttpStatusCode.Unauthorized ||
|
||||
(googleEx.HttpStatusCode == HttpStatusCode.Forbidden &&
|
||||
(reason.Contains("auth") ||
|
||||
reason.Contains("credential") ||
|
||||
message.Contains("invalid credentials") ||
|
||||
message.Contains("insufficient authentication") ||
|
||||
message.Contains("login required")));
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(SynchronizerErrorContext error)
|
||||
{
|
||||
_logger.Warning(error.Exception,
|
||||
"Gmail authentication failed for account {AccountName} ({AccountId}). User intervention is required.",
|
||||
error.Account?.Name, error.Account?.Id);
|
||||
|
||||
if (error.Account != null)
|
||||
{
|
||||
await PersistInvalidCredentialAttentionAsync(error.Account).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
error.Severity = SynchronizerErrorSeverity.AuthRequired;
|
||||
error.Category = SynchronizerErrorCategory.Authentication;
|
||||
error.RetryDelay = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task PersistInvalidCredentialAttentionAsync(MailAccount account)
|
||||
{
|
||||
var persistedAccount = await _accountService.GetAccountAsync(account.Id).ConfigureAwait(false);
|
||||
|
||||
if (persistedAccount == null || persistedAccount.AttentionReason == AccountAttentionReason.InvalidCredentials)
|
||||
return;
|
||||
|
||||
persistedAccount.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
||||
await _accountService.UpdateAccountAsync(persistedAccount).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using MailKit.Security;
|
||||
using Serilog;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -14,6 +15,12 @@ namespace Wino.Core.Synchronizers.Errors.Imap;
|
||||
public class ImapAuthenticationFailedHandler : ISynchronizerErrorHandler
|
||||
{
|
||||
private readonly ILogger _logger = Log.ForContext<ImapAuthenticationFailedHandler>();
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public ImapAuthenticationFailedHandler(IAccountService accountService)
|
||||
{
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
public bool CanHandle(SynchronizerErrorContext error)
|
||||
{
|
||||
@@ -22,12 +29,17 @@ public class ImapAuthenticationFailedHandler : ISynchronizerErrorHandler
|
||||
(error.ErrorMessage?.Contains("authentication", System.StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
public Task<bool> HandleAsync(SynchronizerErrorContext error)
|
||||
public async Task<bool> HandleAsync(SynchronizerErrorContext error)
|
||||
{
|
||||
_logger.Warning(error.Exception,
|
||||
"IMAP authentication failed for account {AccountName} ({AccountId}). User needs to re-authenticate.",
|
||||
error.Account?.Name, error.Account?.Id);
|
||||
|
||||
if (error.Account != null)
|
||||
{
|
||||
await PersistInvalidCredentialAttentionAsync(error.Account).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Mark as requiring authentication - this will stop sync and notify user
|
||||
error.Severity = SynchronizerErrorSeverity.AuthRequired;
|
||||
error.Category = SynchronizerErrorCategory.Authentication;
|
||||
@@ -35,6 +47,20 @@ public class ImapAuthenticationFailedHandler : ISynchronizerErrorHandler
|
||||
// No point in retrying auth failures - credentials need to be updated
|
||||
error.RetryDelay = null;
|
||||
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task PersistInvalidCredentialAttentionAsync(MailAccount account)
|
||||
{
|
||||
var persistedAccount = await _accountService.GetAccountAsync(account.Id).ConfigureAwait(false);
|
||||
|
||||
if (persistedAccount == null)
|
||||
return;
|
||||
|
||||
if (persistedAccount.AttentionReason == AccountAttentionReason.InvalidCredentials)
|
||||
return;
|
||||
|
||||
persistedAccount.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
||||
await _accountService.UpdateAccountAsync(persistedAccount).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Graph.Models.ODataErrors;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using Serilog;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
|
||||
namespace Wino.Core.Synchronizers.Errors.Outlook;
|
||||
|
||||
public class OutlookAuthenticationFailedHandler : ISynchronizerErrorHandler
|
||||
{
|
||||
private readonly ILogger _logger = Log.ForContext<OutlookAuthenticationFailedHandler>();
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public OutlookAuthenticationFailedHandler(IAccountService accountService)
|
||||
{
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
public bool CanHandle(SynchronizerErrorContext error)
|
||||
{
|
||||
if (error.Exception is ApiException apiException)
|
||||
{
|
||||
if (apiException.ResponseStatusCode == 401)
|
||||
return true;
|
||||
|
||||
if (apiException.ResponseStatusCode == 403)
|
||||
{
|
||||
var message = apiException.Message?.ToLowerInvariant() ?? string.Empty;
|
||||
return message.Contains("access denied") || message.Contains("authentication");
|
||||
}
|
||||
}
|
||||
|
||||
if (error.Exception is ODataError oDataError)
|
||||
{
|
||||
if (oDataError.ResponseStatusCode == 401)
|
||||
return true;
|
||||
|
||||
var code = oDataError.Error?.Code?.ToLowerInvariant() ?? string.Empty;
|
||||
var message = oDataError.Error?.Message?.ToLowerInvariant() ?? string.Empty;
|
||||
|
||||
return code.Contains("invalidauthenticationtoken") ||
|
||||
code.Contains("invalidgrant") ||
|
||||
code.Contains("token") ||
|
||||
message.Contains("access token") ||
|
||||
message.Contains("authentication");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(SynchronizerErrorContext error)
|
||||
{
|
||||
_logger.Warning(error.Exception,
|
||||
"Outlook authentication failed for account {AccountName} ({AccountId}). User intervention is required.",
|
||||
error.Account?.Name, error.Account?.Id);
|
||||
|
||||
if (error.Account != null)
|
||||
{
|
||||
await PersistInvalidCredentialAttentionAsync(error.Account).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
error.Severity = SynchronizerErrorSeverity.AuthRequired;
|
||||
error.Category = SynchronizerErrorCategory.Authentication;
|
||||
error.RetryDelay = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task PersistInvalidCredentialAttentionAsync(MailAccount account)
|
||||
{
|
||||
var persistedAccount = await _accountService.GetAccountAsync(account.Id).ConfigureAwait(false);
|
||||
|
||||
if (persistedAccount == null || persistedAccount.AttentionReason == AccountAttentionReason.InvalidCredentials)
|
||||
return;
|
||||
|
||||
persistedAccount.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
||||
await _accountService.UpdateAccountAsync(persistedAccount).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user