using OTSSignsOrchestrator.Server.Clients; using OTSSignsOrchestrator.Server.Data.Entities; namespace OTSSignsOrchestrator.Server.Health.Checks; /// /// Verifies the OAuth2 app in can still authenticate /// by testing a client_credentials flow against the Xibo CMS instance. /// AutoRemediate=false — credential rotation requires a separate job. /// public sealed class OauthAppHealthCheck : IHealthCheck { private readonly XiboClientFactory _clientFactory; private readonly IServiceProvider _services; private readonly ILogger _logger; public string CheckName => "OauthApp"; public bool AutoRemediate => false; public OauthAppHealthCheck( XiboClientFactory clientFactory, IServiceProvider services, ILogger logger) { _clientFactory = clientFactory; _services = services; _logger = logger; } public async Task RunAsync(Instance instance, CancellationToken ct) { var oauthApp = instance.OauthAppRegistries.FirstOrDefault(); if (oauthApp is null) return new HealthCheckResult(HealthStatus.Critical, "No OAuth app registered"); var abbrev = instance.Customer.Abbreviation; var settings = _services.GetRequiredService(); var secret = await settings.GetAsync(Core.Services.SettingsService.InstanceOAuthSecretId(abbrev)); if (string.IsNullOrEmpty(secret)) return new HealthCheckResult(HealthStatus.Critical, "OAuth client secret not found in Bitwarden — cannot authenticate"); try { // Attempt to create a client (which fetches a token via client_credentials) var client = await _clientFactory.CreateAsync(instance.XiboUrl, oauthApp.ClientId, secret); // If we got here, the token was obtained successfully return new HealthCheckResult(HealthStatus.Healthy, "OAuth2 client_credentials flow successful"); } catch (Exception ex) { return new HealthCheckResult(HealthStatus.Critical, $"OAuth2 authentication failed: {ex.Message}", "Credential rotation job may be required"); } } }