Verify mailer credentials against ProcessWeb API before showing E-Mail-Dienst as healthy
Playwright Tests / test (push) Canceled after 0s
Playwright Tests / test (push) Canceled after 0s
The Admin status pill previously turned green whenever the mailer was enabled in config, without proving the account/token actually work. Add IComService.VerifyCredentialsAsync (GET /mailer/fc?fn=verify, also passing serverid when configured so the API confirms this server belongs to the account) and wire it into a new "email" system-status probe, so the pill only goes green once the Mailer API itself confirms the credentials. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f36f65789e
commit
107b7f54f1
@@ -27,4 +27,30 @@ public interface IComService
|
||||
/// <param name="message">Text message body.</param>
|
||||
/// <returns><c>true</c> when the SMS was accepted successfully.</returns>
|
||||
Task<bool> SendSmsAsync(string mobile, string message);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the configured account/token against the Mailer API's credential-check
|
||||
/// endpoint (GET {BaseUrl}/mailer/fc?fn=verify) without sending anything.
|
||||
/// </summary>
|
||||
Task<MailerCredentialsCheckResult> VerifyCredentialsAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>Outcome of a Mailer API credential verification (see <see cref="IComService.VerifyCredentialsAsync"/>).</summary>
|
||||
public sealed class MailerCredentialsCheckResult
|
||||
{
|
||||
/// <summary><c>true</c> only when the Mailer API confirmed the credentials (HTTP 200, <c>ok:true</c>).</summary>
|
||||
public bool Ok { get; init; }
|
||||
|
||||
/// <summary>Account name the Mailer API echoed back, when verified.</summary>
|
||||
public string? Account { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Server ID the Mailer API echoed back. Only present when a <c>serverid</c> was sent with
|
||||
/// the request (i.e. <see cref="ProcessWebComSettings.ServerId"/> is configured); in that
|
||||
/// case <see cref="Ok"/> is only <c>true</c> when this server belongs to the account too.
|
||||
/// </summary>
|
||||
public string? ServerId { get; init; }
|
||||
|
||||
/// <summary>Human-readable failure detail (status code / body / exception message), never the token.</summary>
|
||||
public string? Detail { get; init; }
|
||||
}
|
||||
|
||||
@@ -199,6 +199,64 @@ public class ProcessWebComService : IComService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MailerCredentialsCheckResult> VerifyCredentialsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_settings.AccountId) || string.IsNullOrWhiteSpace(_settings.Token))
|
||||
return new MailerCredentialsCheckResult { Ok = false, Detail = "Konto-ID oder Token nicht konfiguriert." };
|
||||
|
||||
var client = _httpClientFactory.CreateClient("ProcessWebMailer");
|
||||
string credentials = Convert.ToBase64String(
|
||||
Encoding.UTF8.GetBytes($"{_settings.AccountId}:{_settings.Token}"));
|
||||
|
||||
// Passing serverid makes the Mailer API also confirm that THIS server is one of the
|
||||
// account's usable servers, not just that the account/token pair is valid.
|
||||
string url = $"{_settings.BaseUrl}/mailer/fc?fn=verify";
|
||||
if (!string.IsNullOrWhiteSpace(_settings.ServerId))
|
||||
url += $"&serverid={Uri.EscapeDataString(_settings.ServerId)}";
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
|
||||
|
||||
try
|
||||
{
|
||||
using var response = await client.SendAsync(request, cancellationToken);
|
||||
string body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return new MailerCredentialsCheckResult
|
||||
{
|
||||
Ok = false,
|
||||
Detail = $"API-Fehler {(int)response.StatusCode} ({response.StatusCode}): {body}",
|
||||
};
|
||||
|
||||
var payload = JsonConvert.DeserializeObject<MailerVerifyResponse>(body);
|
||||
bool ok = payload?.Ok == true;
|
||||
string? detail = ok ? null
|
||||
: (!string.IsNullOrWhiteSpace(payload?.ServerId)
|
||||
? $"Server-ID '{payload!.ServerId}' gehört nicht zu diesem Mailer-Konto."
|
||||
: "Mailer API meldete ungültige Zugangsdaten.");
|
||||
return new MailerCredentialsCheckResult
|
||||
{
|
||||
Ok = ok,
|
||||
Account = payload?.Account,
|
||||
ServerId = payload?.ServerId,
|
||||
Detail = detail,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "VerifyCredentialsAsync failed");
|
||||
return new MailerCredentialsCheckResult { Ok = false, Detail = ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MailerVerifyResponse
|
||||
{
|
||||
public bool Ok { get; set; }
|
||||
public string? Account { get; set; }
|
||||
public string? ServerId { get; set; }
|
||||
}
|
||||
|
||||
// ── Private helpers ────────────────────────────────────────────────────────
|
||||
|
||||
private async Task<(bool ok, System.Net.HttpStatusCode status, string body)> PostToApiAsync(string reference, string comtype, object communication)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Fuchs.Services;
|
||||
/// </summary>
|
||||
public sealed class SystemProbeResult
|
||||
{
|
||||
/// <summary>Probe identifier: <c>database</c>, <c>keyvault</c>, <c>blob</c> or <c>mfr</c>.</summary>
|
||||
/// <summary>Probe identifier: <c>database</c>, <c>email</c>, <c>keyvault</c>, <c>blob</c> or <c>mfr</c>.</summary>
|
||||
public string Component { get; init; } = "";
|
||||
|
||||
/// <summary><c>ok</c> | <c>error</c> | <c>disabled</c> | <c>unconfigured</c>.</summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Fuchs.Services;
|
||||
/// </summary>
|
||||
public sealed class SystemStatusService : ISystemStatusService
|
||||
{
|
||||
private static readonly string[] Components = { "database", "keyvault", "blob", "mfr" };
|
||||
private static readonly string[] Components = { "database", "email", "keyvault", "blob", "mfr" };
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IHostEnvironment _environment;
|
||||
@@ -177,6 +177,7 @@ public sealed class SystemStatusService : ISystemStatusService
|
||||
result = component switch
|
||||
{
|
||||
"database" => await ProbeDatabaseAsync(cancellationToken),
|
||||
"email" => await ProbeEmailAsync(cancellationToken),
|
||||
"keyvault" => await ProbeKeyVaultAsync(cancellationToken),
|
||||
"blob" => await ProbeBlobAsync(cancellationToken),
|
||||
"mfr" => await ProbeMfrAsync(cancellationToken),
|
||||
@@ -268,6 +269,32 @@ public sealed class SystemStatusService : ISystemStatusService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SystemProbeResult> ProbeEmailAsync(CancellationToken ct)
|
||||
{
|
||||
if (!_mailer.Enabled)
|
||||
return SystemProbeResult.Disabled("email", "Mailer ist deaktiviert (Fuchs:Mailer:Enabled=false).");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_mailer.AccountId) || string.IsNullOrWhiteSpace(_mailer.Token))
|
||||
return SystemProbeResult.Unconfigured("email", "Konto-ID oder Token nicht konfiguriert.");
|
||||
|
||||
MailerCredentialsCheckResult check = await _comService.VerifyCredentialsAsync(ct);
|
||||
bool checkedServerId = !string.IsNullOrWhiteSpace(_mailer.ServerId);
|
||||
string okMessage = checkedServerId
|
||||
? "Zugangsdaten und Server-ID beim Mailer verifiziert."
|
||||
: "Zugangsdaten beim Mailer verifiziert.";
|
||||
string? okDetail = checkedServerId && !string.IsNullOrWhiteSpace(check.ServerId)
|
||||
? $"{check.Account} (Server: {check.ServerId})"
|
||||
: check.Account;
|
||||
return new SystemProbeResult
|
||||
{
|
||||
Component = "email",
|
||||
Status = check.Ok ? "ok" : "error",
|
||||
Ok = check.Ok,
|
||||
Message = check.Ok ? okMessage : "Zugangsdaten konnten nicht verifiziert werden.",
|
||||
Detail = check.Ok ? okDetail : check.Detail,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<SystemProbeResult> ProbeKeyVaultAsync(CancellationToken ct)
|
||||
{
|
||||
string appName = _configuration["SecretManagement:AppName"] ?? "";
|
||||
|
||||
Reference in New Issue
Block a user