2025-09-22 00:29:51 -05:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Http;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using LANCommander.SDK.Abstractions;
|
|
|
|
|
using LANCommander.SDK.Exceptions;
|
|
|
|
|
using LANCommander.SDK.Extensions;
|
|
|
|
|
using LANCommander.SDK.Factories;
|
|
|
|
|
using LANCommander.SDK.Models;
|
2025-10-08 19:53:05 -05:00
|
|
|
using LANCommander.SDK.Providers;
|
2025-09-22 00:29:51 -05:00
|
|
|
using Microsoft.Extensions.Logging;
|
2025-10-08 19:53:05 -05:00
|
|
|
using AuthenticationProvider = LANCommander.SDK.Models.AuthenticationProvider;
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
namespace LANCommander.SDK.Services;
|
|
|
|
|
|
2025-09-24 20:58:23 -05:00
|
|
|
public class AuthenticationClient(
|
|
|
|
|
ILogger<AuthenticationClient> logger,
|
2025-09-22 00:29:51 -05:00
|
|
|
ITokenProvider tokenProvider,
|
2025-10-08 19:53:05 -05:00
|
|
|
IServerConfigurationRefresher configRefresher,
|
2025-11-07 20:19:13 -06:00
|
|
|
ISettingsProvider settingsProvider,
|
2025-09-22 00:29:51 -05:00
|
|
|
ApiRequestFactory apiRequestFactory,
|
2025-09-24 20:58:23 -05:00
|
|
|
IConnectionClient connectionClient)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
2025-10-01 18:45:30 -05:00
|
|
|
public async Task<AuthToken> AuthenticateAsync(string username, string password, Uri serverAddress)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-10-01 18:45:30 -05:00
|
|
|
var result = await apiRequestFactory
|
2025-09-22 00:29:51 -05:00
|
|
|
.Create()
|
2025-10-01 18:45:30 -05:00
|
|
|
.UseBaseAddress(serverAddress)
|
2025-09-22 00:29:51 -05:00
|
|
|
.UseRoute("/api/Auth/Login")
|
|
|
|
|
.UseMethod(HttpMethod.Post)
|
|
|
|
|
.AddBody(new AuthRequest
|
|
|
|
|
{
|
|
|
|
|
UserName = username,
|
|
|
|
|
Password = password,
|
|
|
|
|
})
|
|
|
|
|
.SendAsync<AuthToken>();
|
|
|
|
|
|
|
|
|
|
ErrorResponse errorResponse = null;
|
|
|
|
|
|
2025-10-01 18:45:30 -05:00
|
|
|
if (!result.Response.IsSuccessStatusCode)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
2025-10-01 18:45:30 -05:00
|
|
|
string message = result.Response.ReasonPhrase;
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
logger?.LogError("Authentication failed for user {UserName}: {Message}", username, message);
|
|
|
|
|
|
2025-10-01 18:45:30 -05:00
|
|
|
errorResponse = await ParseErrorResponseAsync(result.Response);
|
2025-09-22 00:29:51 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-01 18:45:30 -05:00
|
|
|
switch (result.Response.StatusCode)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
|
|
|
|
case HttpStatusCode.OK:
|
|
|
|
|
var token = new AuthToken
|
|
|
|
|
{
|
2025-10-01 18:45:30 -05:00
|
|
|
AccessToken = result.Data.AccessToken,
|
|
|
|
|
RefreshToken = result.Data.RefreshToken,
|
|
|
|
|
Expiration = result.Data.Expiration
|
2025-09-22 00:29:51 -05:00
|
|
|
};
|
|
|
|
|
|
2025-11-19 00:36:03 -06:00
|
|
|
tokenProvider.SetToken(token);
|
2025-09-22 00:29:51 -05:00
|
|
|
|
2025-10-08 19:53:05 -05:00
|
|
|
await configRefresher.RefreshAsync();
|
|
|
|
|
|
2025-09-22 00:29:51 -05:00
|
|
|
return token;
|
|
|
|
|
|
|
|
|
|
case HttpStatusCode.Forbidden:
|
|
|
|
|
case HttpStatusCode.BadRequest:
|
|
|
|
|
case HttpStatusCode.Unauthorized:
|
|
|
|
|
logger?.LogError("Authentication failed for user {UserName}: invalid username or password", username);
|
2025-09-23 00:57:48 -05:00
|
|
|
throw new AuthFailedException(AuthFailedException.AuthenticationErrorCode.InvalidCredentials, "Invalid username or password", errorData: errorResponse);
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
logger?.LogError("Authentication failed for user {UserName}: could not communicate with the server", username);
|
|
|
|
|
throw new WebException("Could not communicate with the server");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
// OnError?.Invoke(this, ex);
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task LogoutAsync()
|
|
|
|
|
{
|
2025-11-03 23:24:47 -06:00
|
|
|
try
|
|
|
|
|
{
|
2025-11-07 20:19:13 -06:00
|
|
|
await apiRequestFactory
|
2025-11-03 23:24:47 -06:00
|
|
|
.Create()
|
|
|
|
|
.UseRoute("/api/Auth/Logout")
|
|
|
|
|
.UseAuthenticationToken()
|
|
|
|
|
.PostAsync();
|
|
|
|
|
|
|
|
|
|
connectionClient.DisconnectAsync();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger.LogWarning("Could not logout, server inaccessible");
|
|
|
|
|
}
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
tokenProvider.SetToken(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task RegisterAsync(string username, string password, string passwordConfirmation)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-10-01 18:45:30 -05:00
|
|
|
var result = await apiRequestFactory
|
2025-09-22 00:29:51 -05:00
|
|
|
.Create()
|
|
|
|
|
.UseRoute("/api/Auth/Register")
|
|
|
|
|
.UseMethod(HttpMethod.Post)
|
|
|
|
|
.AddBody(new AuthRequest
|
|
|
|
|
{
|
|
|
|
|
UserName = username,
|
|
|
|
|
Password = password,
|
|
|
|
|
})
|
|
|
|
|
.SendAsync<AuthToken>();
|
|
|
|
|
|
|
|
|
|
ErrorResponse errorResponse = null;
|
|
|
|
|
|
2025-10-01 18:45:30 -05:00
|
|
|
if (!result.Response.IsSuccessStatusCode)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
2025-10-01 18:45:30 -05:00
|
|
|
logger?.LogError("Registration failed for user {UserName}: {Message}", username, result.ErrorMessage);
|
2025-09-22 00:29:51 -05:00
|
|
|
|
2025-10-01 18:45:30 -05:00
|
|
|
errorResponse = await ParseErrorResponseAsync(result.Response);
|
2025-09-22 00:29:51 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-01 18:45:30 -05:00
|
|
|
switch (result.Response.StatusCode)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
|
|
|
|
case HttpStatusCode.OK:
|
2025-11-19 00:36:03 -06:00
|
|
|
tokenProvider.SetToken(result.Data);
|
2025-09-22 00:29:51 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case HttpStatusCode.BadRequest:
|
|
|
|
|
case HttpStatusCode.Forbidden:
|
|
|
|
|
case HttpStatusCode.Unauthorized:
|
|
|
|
|
throw new RegisterFailedException("Could not register user", errorData: errorResponse);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
logger?.LogError("Registering failed for user {UserName}: could not communicate with the server", username);
|
|
|
|
|
throw new WebException("Could not communicate with the server");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
// OnError?.Invoke(this, ex);
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-24 20:58:23 -05:00
|
|
|
|
|
|
|
|
public async Task<bool> ValidateTokenAsync()
|
|
|
|
|
{
|
|
|
|
|
logger?.LogTrace("Validating token");
|
|
|
|
|
|
2025-11-19 00:36:03 -06:00
|
|
|
if (String.IsNullOrWhiteSpace(tokenProvider.GetToken()?.AccessToken))
|
2025-09-24 20:58:23 -05:00
|
|
|
{
|
|
|
|
|
logger?.LogError("Token is empty");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await apiRequestFactory
|
|
|
|
|
.Create()
|
|
|
|
|
.UseAuthenticationToken()
|
|
|
|
|
.UseRoute("/api/Auth/Validate")
|
2025-10-01 18:45:30 -05:00
|
|
|
.PostAsync();
|
2025-09-24 20:58:23 -05:00
|
|
|
|
|
|
|
|
logger?.LogTrace("Validated token successfully");
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger?.LogError("Validating token failed", ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
public async Task<IEnumerable<AuthenticationProvider>> GetAuthenticationProvidersAsync()
|
|
|
|
|
{
|
|
|
|
|
return await apiRequestFactory
|
|
|
|
|
.Create()
|
|
|
|
|
.UseRoute("/api/Auth/GetAuthenticationProviders")
|
|
|
|
|
.UseVersioning()
|
|
|
|
|
.GetAsync<IEnumerable<AuthenticationProvider>>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Uri GetAuthenticationProviderLoginUrl(string provider)
|
|
|
|
|
{
|
2025-09-24 20:58:23 -05:00
|
|
|
return connectionClient.GetServerAddress().Join($"api/Auth/Login?Provider={provider}");
|
2025-09-22 00:29:51 -05:00
|
|
|
}
|
|
|
|
|
|
2025-09-23 00:57:48 -05:00
|
|
|
internal async Task<ErrorResponse> ParseErrorResponseAsync(HttpResponseMessage response, bool defaultToGenericResponse = false)
|
2025-09-22 00:29:51 -05:00
|
|
|
{
|
|
|
|
|
ErrorResponse errorResponse = null;
|
|
|
|
|
|
|
|
|
|
// Try to deserialize the error response.
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-09-23 00:57:48 -05:00
|
|
|
errorResponse = JsonSerializer.Deserialize<ErrorResponse>(await response.Content.ReadAsStringAsync());
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
return errorResponse;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception deserializationEx)
|
|
|
|
|
{
|
|
|
|
|
// Log error and create a fallback message if deserialization fails.
|
|
|
|
|
if (defaultToGenericResponse)
|
|
|
|
|
{
|
2025-09-23 00:57:48 -05:00
|
|
|
logger?.LogError(deserializationEx, "Error deserializing error response");
|
2025-09-22 00:29:51 -05:00
|
|
|
|
|
|
|
|
errorResponse = new ErrorResponse
|
|
|
|
|
{
|
|
|
|
|
Message = "Could not process the server response."
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return errorResponse;
|
|
|
|
|
}
|
|
|
|
|
}
|