Compare commits

..

4 Commits

Author SHA1 Message Date
Developer 02
dd62af5ada feat(DIExtensions): Hinzufügen einer Ausnahmemeldung. 2025-03-11 16:32:56 +01:00
Developer 02
b4068eff8e chore(client): Hochgestuft auf 1.2.0 2025-03-11 15:58:50 +01:00
Developer 02
3b0428130a feat(AuthClient): implementiert IHostedService.
- Aktualisiert um AuthClient zu den Diensten als Hosted Service hinzuzufügen.
 - Der zugehörige Unit-Test wurde aktualisiert, um IHost anstelle von IServiceProvider zu verwenden, um gehostete Dienste testen zu können.
2025-03-11 15:53:37 +01:00
Developer 02
4ccf7a20b3 feat(AuthClient): try-start entfernen und Abbruch-Token hinzufügen, um asynchron zu starten 2025-03-11 14:50:10 +01:00
7 changed files with 70 additions and 56 deletions

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Description>DigitalData.Auth.Abstractions defines lightweight interfaces for sending and receiving authentication keys in .NET applications. It provides a unified IAuthClient for managing connections and errors, enabling seamless integration with authentication systems.</Description> <Description>DigitalData.Auth.Abstractions defines lightweight interfaces for sending and receiving authentication keys in .NET applications. It provides a unified IAuthClient for managing connections and errors, enabling seamless integration with authentication systems.</Description>
<PackageId>DigitalData.Auth.Abstractions</PackageId> <PackageId>DigitalData.Auth.Abstractions</PackageId>
<Version>1.1.0</Version> <Version>1.2.0</Version>
<Company>Digital Data GmbH</Company> <Company>Digital Data GmbH</Company>
<Product>Digital Data GmbH</Product> <Product>Digital Data GmbH</Product>
<Copyright>Copyright 2025</Copyright> <Copyright>Copyright 2025</Copyright>
@@ -14,8 +14,8 @@
<PackageIcon>auth_icon.png</PackageIcon> <PackageIcon>auth_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/DigitalData.Auth</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/DigitalData.Auth</RepositoryUrl>
<PackageTags>Digital Data Auth Authorization Authentication Abstractions</PackageTags> <PackageTags>Digital Data Auth Authorization Authentication Abstractions</PackageTags>
<AssemblyVersion>1.1.0</AssemblyVersion> <AssemblyVersion>1.2.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion> <FileVersion>1.2.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -4,7 +4,5 @@ public interface IAuthClient : IAuthListenHandler, IAuthSenderHandler
{ {
bool IsConnected { get; } bool IsConnected { get; }
Task StartAsync(); Task StartAsync(CancellationToken cancellationToken = default);
Task<bool> TryStartAsync();
} }

View File

@@ -1,11 +1,12 @@
using DigitalData.Auth.Abstractions; using DigitalData.Auth.Abstractions;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace DigitalData.Auth.Client; namespace DigitalData.Auth.Client;
public class AuthClient : IAuthClient, IAsyncDisposable public class AuthClient : IAuthClient, IHostedService
{ {
private readonly HubConnection _connection; private readonly HubConnection _connection;
@@ -36,25 +37,17 @@ public class AuthClient : IAuthClient, IAsyncDisposable
public IEnumerable<ClientPublicKey> PublicKeys => _params.PublicKeys; public IEnumerable<ClientPublicKey> PublicKeys => _params.PublicKeys;
public async Task StartAsync() public async Task StartAsync(CancellationToken cancellationToken = default)
{ {
await _connection.StartAsync(); await _connection.StartAsync(cancellationToken);
IsConnected = true; IsConnected = true;
await GetAllPublicKeysAsync(); await GetAllPublicKeysAsync();
} }
public async Task<bool> TryStartAsync() public async Task StopAsync(CancellationToken cancellationToken)
{ {
try await _connection.StopAsync(cancellationToken);
{ IsConnected = false;
await StartAsync();
return true;
}
catch(Exception ex)
{
_logger?.LogError(ex, "{message}", ex.Message);
return false;
}
} }
public Task ReceivePublicKeyAsync(string issuer, string audience, string message) => Task.Run(() => _params.TriggerOnPublicReceivedEvent(this, issuer, audience, message, _logger)); public Task ReceivePublicKeyAsync(string issuer, string audience, string message) => Task.Run(() => _params.TriggerOnPublicReceivedEvent(this, issuer, audience, message, _logger));
@@ -68,11 +61,4 @@ public class AuthClient : IAuthClient, IAsyncDisposable
foreach (var publicKey in PublicKeys) foreach (var publicKey in PublicKeys)
await GetPublicKeyAsync(publicKey.Issuer, publicKey.Audience); await GetPublicKeyAsync(publicKey.Issuer, publicKey.Audience);
} }
public virtual async ValueTask DisposeAsync()
{
await _connection.StopAsync();
await _connection.DisposeAsync();
GC.SuppressFinalize(this);
}
} }

View File

@@ -18,6 +18,18 @@ public static class DIExtensions
.AddSingleton<IAuthClient, AuthClient>() .AddSingleton<IAuthClient, AuthClient>()
.TryAddSingleton<HubConnectionBuilder>(); .TryAddSingleton<HubConnectionBuilder>();
services.AddHostedService(sp =>
{
var client = sp.GetRequiredService<IAuthClient>() as AuthClient;
if (client is not null)
return client;
else throw new InvalidOperationException(
"IAuthClient instance could not be resolved from the service provider. " +
"This may indicate that the 'AddAuthHubClient' extension method was not called " +
"or there was an issue with the dependency registration process."
);
});
return services; return services;
} }
} }

View File

@@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PackageId>DigitalData.Auth.Client</PackageId> <PackageId>DigitalData.Auth.Client</PackageId>
<Version>1.1.5</Version> <Version>1.2.1.1</Version>
<Description>DigitalData.Auth.Client is a SignalR-based authentication client that enables applications to connect to a central authentication hub for real-time message exchange. It provides seamless connection management, automatic reconnection (RetryPolicy), and event-driven communication (ClientEvents). The package includes dependency injection support via DIExtensions, allowing easy integration into ASP.NET Core applications. With built-in retry policies and secure message handling, it ensures a reliable and scalable authentication client for real-time authentication workflows.</Description> <Description>DigitalData.Auth.Client is a SignalR-based authentication client that enables applications to connect to a central authentication hub for real-time message exchange. It provides seamless connection management, automatic reconnection (RetryPolicy), and event-driven communication (ClientEvents). The package includes dependency injection support via DIExtensions, allowing easy integration into ASP.NET Core applications. With built-in retry policies and secure message handling, it ensures a reliable and scalable authentication client for real-time authentication workflows.</Description>
<Company>Digital Data GmbH</Company> <Company>Digital Data GmbH</Company>
<Product>Digital Data GmbH</Product> <Product>Digital Data GmbH</Product>
@@ -14,8 +14,8 @@
<PackageIcon>auth_icon.png</PackageIcon> <PackageIcon>auth_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/DigitalData.Auth</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/DigitalData.Auth</RepositoryUrl>
<PackageTags>Digital Data Auth Authorization Authentication</PackageTags> <PackageTags>Digital Data Auth Authorization Authentication</PackageTags>
<AssemblyVersion>1.1.5</AssemblyVersion> <AssemblyVersion>1.2.1.1</AssemblyVersion>
<FileVersion>1.1.5</FileVersion> <FileVersion>1.2.1.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -29,6 +29,7 @@
<PackageReference Include="DigitalData.Core.Abstractions" Version="3.3.0" /> <PackageReference Include="DigitalData.Core.Abstractions" Version="3.3.0" />
<PackageReference Include="DigitalData.Core.Security" Version="1.0.0" /> <PackageReference Include="DigitalData.Core.Security" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -18,7 +18,7 @@ public class AuthClientTests
{ {
private string _hubUrl; private string _hubUrl;
private Func<Action<ClientParams>, ServiceProvider> Build; private Func<Action<ClientParams>, IHost> Build;
private WebApplication? _app; private WebApplication? _app;
@@ -84,13 +84,18 @@ public class AuthClientTests
{ {
Build = options => Build = options =>
{ {
var provider = new ServiceCollection() var host = Host.CreateDefaultBuilder()
.AddAuthHubClient(options: options) .ConfigureServices(services =>
.BuildServiceProvider(); {
services.AddAuthHubClient(options: options)
.BuildServiceProvider();
})
.Build();
_disposableAsync.Enqueue(provider); if(host is IAsyncDisposable disposable)
_disposableAsync.Enqueue(disposable);
return provider; return host;
}; };
// Create and run test server // Create and run test server
@@ -105,25 +110,37 @@ public class AuthClientTests
if (_app is not null) if (_app is not null)
{ {
await _app.StopAsync(); await _app.StopAsync();
await _app.DisposeAsync();
Console.WriteLine("Test server stopped."); Console.WriteLine("Test server stopped.");
} }
while (_disposableAsync.Count > 0) while (_disposableAsync.Count > 0)
await _disposableAsync.Dequeue().DisposeAsync(); {
var disposable = _disposableAsync.Dequeue();
if (disposable is IHost host)
await host.StopAsync();
await disposable.DisposeAsync();
}
} }
[Test] [Test]
public async Task StartAsync_ShouldConnectSuccessfully() [TestCase(true, false, true, TestName = "ShouldStart_WhenHostStartsEvenIfClientDoesNot")]
[TestCase(false, true, true, TestName = "ShouldStart_WhenClientStartsEvenIfHostDoesNot")]
public async Task StartAsync_ShouldConnectSuccessfully(bool startHost, bool startClient, bool expectedIsConnected)
{ {
// Arrange // Arrange
var provider = Build(opt => opt.Url = _hubUrl); var host = Build(opt => opt.Url = _hubUrl);
var client = provider.GetRequiredService<IAuthClient>(); var client = host.Services.GetRequiredService<IAuthClient>();
// Act // Act
await client.StartAsync(); if (startHost)
await host.StartAsync();
if (startClient)
await client.StartAsync();
// Assert // Assert
Assert.That(client.IsConnected); Assert.That(client.IsConnected, Is.EqualTo(expectedIsConnected));
} }
[Test] [Test]
@@ -135,12 +152,12 @@ public class AuthClientTests
string rcv_key = string.Empty; string rcv_key = string.Empty;
// Sender client // Sender client
var provider_sender = Build(opt => opt.Url = _hubUrl); var sender_host = Build(opt => opt.Url = _hubUrl);
var sender_client = provider_sender.GetRequiredService<IAuthClient>(); var sender_client = sender_host.Services.GetRequiredService<IAuthClient>();
await sender_client.StartAsync(); await sender_client.StartAsync();
// Receiver client // Receiver client
var provider_receiver = Build(opt => var receiver_host = Build(opt =>
{ {
opt.Url = _hubUrl; opt.Url = _hubUrl;
opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) => opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) =>
@@ -150,7 +167,7 @@ public class AuthClientTests
rcv_key = key; rcv_key = key;
}; };
}); });
var client_receiver = provider_receiver.GetRequiredService<IAuthClient>(); var client_receiver = receiver_host.Services.GetRequiredService<IAuthClient>();
await client_receiver.StartAsync(); await client_receiver.StartAsync();
string issuer = "issuer"; string issuer = "issuer";
@@ -177,12 +194,12 @@ public class AuthClientTests
{ {
// Arrange // Arrange
string? publicKey = null; string? publicKey = null;
var provider = Build(opt => var host = Build(opt =>
{ {
opt.Url = _hubUrl; opt.Url = _hubUrl;
opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) => publicKey = key; opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) => publicKey = key;
}); });
var client = provider.GetRequiredService<IAuthClient>(); var client = host.Services.GetRequiredService<IAuthClient>();
await client.StartAsync(); await client.StartAsync();
@@ -207,12 +224,12 @@ public class AuthClientTests
{ {
// Arrange // Arrange
var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" }; var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" };
var provider = Build(opt => var host = Build(opt =>
{ {
opt.Url = _hubUrl; opt.Url = _hubUrl;
opt.PublicKeys.Add(publicKey); opt.PublicKeys.Add(publicKey);
}); });
var client = provider.GetRequiredService<IAuthClient>(); var client = host.Services.GetRequiredService<IAuthClient>();
await client.StartAsync(); await client.StartAsync();
// Act // Act
@@ -230,18 +247,18 @@ public class AuthClientTests
{ {
// Arrange // Arrange
var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" }; var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" };
var provider = Build(opt => var host = Build(opt =>
{ {
opt.Url = _hubUrl; opt.Url = _hubUrl;
opt.PublicKeys.Add(publicKey); opt.PublicKeys.Add(publicKey);
opt.RetryDelay = new TimeSpan(0, 0, 1); opt.RetryDelay = new TimeSpan(0, 0, 1);
}); });
var client = provider.GetRequiredService<IAuthClient>(); var client = host.Services.GetRequiredService<IAuthClient>();
await client.StartAsync(); await client.StartAsync();
// Act // Act
CancellationToken cancellationToken = default; CancellationToken cancellationToken = default;
await _app.StopAsync(cancellationToken); await _app!.StopAsync(cancellationToken);
_app = null; _app = null;
var newApp = CreateWebApplication(_port); var newApp = CreateWebApplication(_port);

View File

@@ -12,7 +12,7 @@
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "http://localhost:5075", "applicationUrl": "http://localhost:5075",
"environmentVariables": { "environmentVariables": {
@@ -22,7 +22,7 @@
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "https://localhost:7192;http://localhost:5075", "applicationUrl": "https://localhost:7192;http://localhost:5075",
"environmentVariables": { "environmentVariables": {
@@ -31,7 +31,7 @@
}, },
"IIS Express": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",
"launchBrowser": true, "launchBrowser": false,
"launchUrl": "swagger", "launchUrl": "swagger",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"