64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
79 lines
2.9 KiB
C#
79 lines
2.9 KiB
C#
using System.Net.Http.Json;
|
|
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.Components.Authorization;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Security.Blazor;
|
|
|
|
/// <summary>
|
|
/// Blazor Server <see cref="AuthenticationStateProvider"/> that snapshots the cookie-backed
|
|
/// principal supplied at circuit boot and polls <c>/auth/ping</c> every 60 seconds to detect
|
|
/// expiry. Mirrors ScadaLink's CentralUI implementation.
|
|
/// </summary>
|
|
public sealed class CookieAuthenticationStateProvider : AuthenticationStateProvider, IAsyncDisposable
|
|
{
|
|
private static readonly TimeSpan PingInterval = TimeSpan.FromSeconds(60);
|
|
|
|
private readonly HttpClient _http;
|
|
private readonly ILogger<CookieAuthenticationStateProvider> _logger;
|
|
private readonly CancellationTokenSource _cts = new();
|
|
private ClaimsPrincipal _current;
|
|
private Task? _pingLoop;
|
|
|
|
/// <summary>Initializes a new instance of the CookieAuthenticationStateProvider class.</summary>
|
|
/// <param name="initial">The initial claims principal from circuit boot.</param>
|
|
/// <param name="http">The HTTP client for authentication ping requests.</param>
|
|
/// <param name="logger">The logger for diagnostic messages.</param>
|
|
public CookieAuthenticationStateProvider(
|
|
ClaimsPrincipal initial,
|
|
HttpClient http,
|
|
ILogger<CookieAuthenticationStateProvider> logger)
|
|
{
|
|
_current = initial;
|
|
_http = http;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override Task<AuthenticationState> GetAuthenticationStateAsync()
|
|
{
|
|
_pingLoop ??= Task.Run(() => PingLoopAsync(_cts.Token));
|
|
return Task.FromResult(new AuthenticationState(_current));
|
|
}
|
|
|
|
private async Task PingLoopAsync(CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
while (!ct.IsCancellationRequested)
|
|
{
|
|
await Task.Delay(PingInterval, ct).ConfigureAwait(false);
|
|
var resp = await _http.GetAsync("/auth/ping", ct).ConfigureAwait(false);
|
|
if (!resp.IsSuccessStatusCode && _current.Identity?.IsAuthenticated == true)
|
|
{
|
|
_logger.LogInformation("/auth/ping returned {Code}; notifying circuit", (int)resp.StatusCode);
|
|
_current = new ClaimsPrincipal(new ClaimsIdentity());
|
|
NotifyAuthenticationStateChanged(
|
|
Task.FromResult(new AuthenticationState(_current)));
|
|
}
|
|
}
|
|
}
|
|
catch (OperationCanceledException) { /* expected on shutdown */ }
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Auth ping loop terminated unexpectedly");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_cts.Cancel();
|
|
if (_pingLoop is not null)
|
|
{
|
|
try { await _pingLoop.ConfigureAwait(false); } catch { /* swallow shutdown errors */ }
|
|
}
|
|
_cts.Dispose();
|
|
}
|
|
}
|