You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
7.9 KiB
254 lines
7.9 KiB
using System; |
|
using System.Net; |
|
using System.Threading.Tasks; |
|
using Microsoft.Extensions.Logging; |
|
using Octokit; |
|
using StabilityMatrix.Core.Api; |
|
using StabilityMatrix.Core.Attributes; |
|
using StabilityMatrix.Core.Models; |
|
using StabilityMatrix.Core.Models.Api; |
|
using StabilityMatrix.Core.Models.Api.CivitTRPC; |
|
using StabilityMatrix.Core.Models.Api.Lykos; |
|
using StabilityMatrix.Core.Services; |
|
using ApiException = Refit.ApiException; |
|
|
|
namespace StabilityMatrix.Avalonia.Services; |
|
|
|
[Singleton(typeof(IAccountsService))] |
|
public class AccountsService : IAccountsService |
|
{ |
|
private readonly ILogger<AccountsService> logger; |
|
private readonly ISecretsManager secretsManager; |
|
private readonly ILykosAuthApi lykosAuthApi; |
|
private readonly ICivitTRPCApi civitTRPCApi; |
|
|
|
/// <inheritdoc /> |
|
public event EventHandler<LykosAccountStatusUpdateEventArgs>? LykosAccountStatusUpdate; |
|
|
|
/// <inheritdoc /> |
|
public event EventHandler<CivitAccountStatusUpdateEventArgs>? CivitAccountStatusUpdate; |
|
|
|
public LykosAccountStatusUpdateEventArgs? LykosStatus { get; private set; } |
|
|
|
public CivitAccountStatusUpdateEventArgs? CivitStatus { get; private set; } |
|
|
|
public AccountsService( |
|
ILogger<AccountsService> logger, |
|
ISecretsManager secretsManager, |
|
ILykosAuthApi lykosAuthApi, |
|
ICivitTRPCApi civitTRPCApi |
|
) |
|
{ |
|
this.logger = logger; |
|
this.secretsManager = secretsManager; |
|
this.lykosAuthApi = lykosAuthApi; |
|
this.civitTRPCApi = civitTRPCApi; |
|
|
|
// Update our own status when the Lykos account status changes |
|
LykosAccountStatusUpdate += (_, args) => LykosStatus = args; |
|
} |
|
|
|
public async Task LykosLoginAsync(string email, string password) |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
|
|
var tokens = await lykosAuthApi.PostLogin(new PostLoginRequest(email, password)); |
|
|
|
secrets = secrets with { LykosAccount = tokens }; |
|
|
|
await secretsManager.SaveAsync(secrets); |
|
|
|
await RefreshLykosAsync(secrets); |
|
} |
|
|
|
public async Task LykosSignupAsync(string email, string password, string username) |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
|
|
var tokens = await lykosAuthApi.PostAccount( |
|
new PostAccountRequest(email, password, password, username) |
|
); |
|
|
|
secrets = secrets with { LykosAccount = tokens }; |
|
|
|
await secretsManager.SaveAsync(secrets); |
|
|
|
await RefreshLykosAsync(secrets); |
|
} |
|
|
|
public async Task LykosLogoutAsync() |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
await secretsManager.SaveAsync(secrets with { LykosAccount = null }); |
|
|
|
OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs.Disconnected); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public async Task LykosPatreonOAuthLogoutAsync() |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
if (secrets.LykosAccount is null) |
|
{ |
|
throw new InvalidOperationException( |
|
"Lykos account must be connected in to manage OAuth connections" |
|
); |
|
} |
|
|
|
await lykosAuthApi.DeletePatreonOAuth(); |
|
|
|
await RefreshLykosAsync(secrets); |
|
} |
|
|
|
public async Task CivitLoginAsync(string apiToken) |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
|
|
// Get id first using the api token |
|
var userAccount = await civitTRPCApi.GetUserAccountDefault(apiToken); |
|
var id = userAccount.Result.Data.Json.Id; |
|
|
|
// Then get the username using the id |
|
var account = await civitTRPCApi.GetUserById( |
|
new CivitGetUserByIdRequest { Id = id }, |
|
apiToken |
|
); |
|
var username = account.Result.Data.Json.Username; |
|
|
|
secrets = secrets with { CivitApi = new CivitApiTokens(apiToken, username) }; |
|
|
|
await secretsManager.SaveAsync(secrets); |
|
|
|
await RefreshCivitAsync(secrets); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public async Task CivitLogoutAsync() |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
await secretsManager.SaveAsync(secrets with { CivitApi = null }); |
|
|
|
OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs.Disconnected); |
|
} |
|
|
|
public async Task RefreshAsync() |
|
{ |
|
var secrets = await secretsManager.SafeLoadAsync(); |
|
|
|
await RefreshLykosAsync(secrets); |
|
await RefreshCivitAsync(secrets); |
|
} |
|
|
|
private async Task RefreshLykosAsync(Secrets secrets) |
|
{ |
|
if ( |
|
secrets.LykosAccount is not null |
|
&& !string.IsNullOrWhiteSpace(secrets.LykosAccount?.RefreshToken) |
|
&& !string.IsNullOrWhiteSpace(secrets.LykosAccount?.AccessToken) |
|
) |
|
{ |
|
try |
|
{ |
|
var user = await lykosAuthApi.GetUserSelf(); |
|
|
|
OnLykosAccountStatusUpdate( |
|
new LykosAccountStatusUpdateEventArgs { IsConnected = true, User = user } |
|
); |
|
|
|
return; |
|
} |
|
catch (OperationCanceledException) |
|
{ |
|
logger.LogWarning("Timed out while fetching Lykos Auth user info"); |
|
} |
|
catch (InvalidOperationException e) |
|
{ |
|
logger.LogWarning(e, "Failed to get authentication token"); |
|
} |
|
catch (ApiException e) |
|
{ |
|
if (e.StatusCode is HttpStatusCode.Unauthorized) { } |
|
else |
|
{ |
|
logger.LogWarning(e, "Failed to get user info from Lykos"); |
|
} |
|
} |
|
catch (Exception e) |
|
{ |
|
logger.LogError(e, "Unknown error while refreshing Lykos account status"); |
|
} |
|
} |
|
|
|
OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs.Disconnected); |
|
} |
|
|
|
private async Task RefreshCivitAsync(Secrets secrets) |
|
{ |
|
if (secrets.CivitApi is not null) |
|
{ |
|
try |
|
{ |
|
var user = await civitTRPCApi.GetUserProfile( |
|
new CivitUserProfileRequest { Username = secrets.CivitApi.Username }, |
|
secrets.CivitApi.ApiToken |
|
); |
|
|
|
OnCivitAccountStatusUpdate( |
|
new CivitAccountStatusUpdateEventArgs { IsConnected = true, UserProfile = user } |
|
); |
|
|
|
return; |
|
} |
|
catch (OperationCanceledException) |
|
{ |
|
logger.LogWarning("Timed out while fetching Civit Auth user info"); |
|
} |
|
catch (ApiException e) |
|
{ |
|
if (e.StatusCode is HttpStatusCode.Unauthorized) { } |
|
else |
|
{ |
|
logger.LogWarning(e, "Failed to get user info from Civit"); |
|
} |
|
} |
|
} |
|
|
|
OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs.Disconnected); |
|
} |
|
|
|
private void OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs e) |
|
{ |
|
if (!e.IsConnected && LykosStatus?.IsConnected == true) |
|
{ |
|
logger.LogInformation("Lykos account disconnected"); |
|
} |
|
else if (e.IsConnected && LykosStatus?.IsConnected == false) |
|
{ |
|
logger.LogInformation( |
|
"Lykos account connected: {Id} ({Username})", |
|
e.User?.Id, |
|
e.User?.Account.Name |
|
); |
|
} |
|
|
|
LykosAccountStatusUpdate?.Invoke(this, e); |
|
} |
|
|
|
private void OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs e) |
|
{ |
|
if (!e.IsConnected && CivitStatus?.IsConnected == true) |
|
{ |
|
logger.LogInformation("Civit account disconnected"); |
|
} |
|
else if (e.IsConnected && CivitStatus?.IsConnected == false) |
|
{ |
|
logger.LogInformation( |
|
"Civit account connected: {Id} ({Username})", |
|
e.UserProfile?.UserId, |
|
e.UserProfile?.Username |
|
); |
|
} |
|
|
|
CivitAccountStatusUpdate?.Invoke(this, e); |
|
} |
|
}
|
|
|