Compare commits
4 Commits
29ad0554bc
...
dd62af5ada
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd62af5ada | ||
|
|
b4068eff8e | ||
|
|
3b0428130a | ||
|
|
4ccf7a20b3 |
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user