using OTSSignsOrchestrator.Server.Clients; using OTSSignsOrchestrator.Server.Data.Entities; namespace OTSSignsOrchestrator.Server.Health.Checks; /// /// Verifies the invite-{abbrev} flow exists in Authentik by searching for it /// in the invitation stages list. /// public sealed class InvitationFlowHealthCheck : IHealthCheck { private readonly IAuthentikClient _authentikClient; private readonly ILogger _logger; public string CheckName => "InvitationFlow"; public bool AutoRemediate => false; public InvitationFlowHealthCheck( IAuthentikClient authentikClient, ILogger logger) { _authentikClient = authentikClient; _logger = logger; } public async Task RunAsync(Instance instance, CancellationToken ct) { var abbrev = instance.Customer.Abbreviation; var expectedName = $"invite-{abbrev}"; try { // Search Authentik groups for evidence of the invitation flow // The invitation is created as a stage invitation; we verify via the // Authentik API by searching for it by name. var groupResponse = await _authentikClient.ListGroupsAsync(expectedName); if (groupResponse.IsSuccessStatusCode && groupResponse.Content?.Results is { Count: > 0 }) { var found = groupResponse.Content.Results.Any(g => g.TryGetValue("name", out var n) && string.Equals(n?.ToString(), expectedName, StringComparison.OrdinalIgnoreCase)); if (found) { return new HealthCheckResult(HealthStatus.Healthy, $"Invitation flow '{expectedName}' exists in Authentik"); } } // If groups don't show it, it's still possible the invitation was created // as a separate stage object. Log as degraded since we can't fully confirm. return new HealthCheckResult(HealthStatus.Degraded, $"Invitation flow '{expectedName}' not found in Authentik", "The invitation may exist but could not be verified via group search"); } catch (Exception ex) { return new HealthCheckResult(HealthStatus.Critical, $"Failed to check invitation flow: {ex.Message}"); } } }