Add authentication and token services.

This commit is contained in:
2025-11-16 19:59:05 +00:00
parent 20216bc1d6
commit 869e675ee3
4 changed files with 192 additions and 0 deletions

View File

@@ -2,9 +2,11 @@ using System.Net.Http;
using Marechai.App.Presentation.ViewModels;
using Marechai.App.Presentation.Views;
using Marechai.App.Services;
using Marechai.App.Services.Authentication;
using Marechai.App.Services.Caching;
using Microsoft.UI.Xaml;
using Uno.Extensions;
using Uno.Extensions.Authentication;
using Uno.Extensions.Configuration;
using Uno.Extensions.Hosting;
using Uno.Extensions.Http;
@@ -94,6 +96,8 @@ public partial class App : Application
.UseLocalization()
.UseHttp((context, services) =>
{
services.AddTransient<DelegatingHandler,
HttpAuthHandler>();
#if DEBUG
// DelegatingHandler will be automatically injected
@@ -119,6 +123,11 @@ public partial class App : Application
.AddSingleton<IColorThemeService,
ColorThemeService>();
services
.AddSingleton<IAuthenticationService,
AuthService>();
services.AddSingleton<ITokenService, TokenService>();
services.AddSingleton<FlagCache>();
services.AddSingleton<CompanyLogoCache>();
services.AddSingleton<MachinePhotoCache>();

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Refit;
using Uno.Extensions;
using Uno.Extensions.Authentication;
namespace Marechai.App.Services.Authentication;
public sealed class AuthService
(ApiClient client, ITokenService tokenService, IStringLocalizer stringLocalizer) : IAuthenticationService
{
/// <inheritdoc />
public async ValueTask<bool> LoginAsync(IDispatcher? dispatcher, IDictionary<string, string>? credentials = null,
string? provider = null, CancellationToken? cancellationToken = null)
{
if(credentials is null) return false;
string? email = credentials.FirstOrDefault(x => x.Key == "Email").Value;
string? password = credentials.FirstOrDefault(x => x.Key == "Password").Value;
if(email is null)
{
credentials["error"] = stringLocalizer["Auth.EmailIsRequired"];
return false;
}
var loginModel = new AuthRequest
{
Email = email,
Password = password
};
AuthResponse? authResponse;
try
{
tokenService.RemoveToken();
authResponse = await client.Auth.Login.PostAsync(loginModel);
}
catch(ValidationApiException ex)
{
switch(ex.StatusCode)
{
case HttpStatusCode.BadRequest:
if(ex.Content is {} problemDetails)
{
if(problemDetails.Errors.Count > 0)
{
credentials["error"] = problemDetails.Errors.FirstOrDefault().Value?.FirstOrDefault() ??
stringLocalizer["Http.BadRequest"];
return false;
}
credentials["error"] = stringLocalizer["Http.BadRequest"];
return false;
}
break;
}
credentials["error"] = stringLocalizer["Http.BadRequest"];
return false;
}
catch(ApiException ex)
{
switch(ex.StatusCode)
{
case HttpStatusCode.Unauthorized:
credentials["error"] = stringLocalizer["Auth.InvalidCredentials"];
return false;
}
credentials["error"] = stringLocalizer["Http.BadRequest"];
return false;
}
catch(Exception ex)
{
#pragma warning disable EPC12
credentials["error"] = ex.Message;
#pragma warning restore EPC12
return false;
}
if(string.IsNullOrWhiteSpace(authResponse?.Token)) return false;
tokenService.SetToken(authResponse.Token);
return true;
}
/// <inheritdoc />
public ValueTask<bool> RefreshAsync(CancellationToken? cancellationToken = null) =>
IsAuthenticated(cancellationToken);
/// <inheritdoc />
public async ValueTask<bool> LogoutAsync(IDispatcher? dispatcher, CancellationToken? cancellationToken = null)
{
tokenService.RemoveToken();
LoggedOut?.Invoke(this, EventArgs.Empty);
return true;
}
/// <inheritdoc />
public async ValueTask<bool> IsAuthenticated(CancellationToken? cancellationToken = null)
{
string token = tokenService.GetToken();
// TODO: Check token validity
return !string.IsNullOrWhiteSpace(token);
}
/// <inheritdoc />
public string[] Providers { get; } = [];
/// <inheritdoc />
public event EventHandler? LoggedOut;
}

View File

@@ -0,0 +1,32 @@
using Windows.Storage;
namespace Marechai.App.Services.Authentication;
public interface ITokenService
{
string GetToken();
void RemoveToken();
void SetToken(string token);
}
public sealed class TokenService : ITokenService
{
readonly ApplicationDataContainer _settings = ApplicationData.Current.LocalSettings;
/// <inheritdoc />
public string GetToken() => (string)_settings.Values["token"];
/// <inheritdoc />
public void RemoveToken()
{
_settings.Values.Remove("token");
}
/// <inheritdoc />
public void SetToken(string token)
{
_settings.Values["token"] = token;
}
}

View File

@@ -0,0 +1,22 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Marechai.App.Services.Authentication;
namespace Marechai.App.Services.Endpoints;
public sealed class HttpAuthHandler(ITokenService tokenService) : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
string token = tokenService.GetToken();
if(!string.IsNullOrEmpty(token)) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}