feat(auth): typed ApiKeyPepperUnavailableException for missing API-key pepper; bump to 0.4.2 (scadaproj#23)

Derives from InvalidOperationException so existing catches keep working.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 12:30:04 -05:00
parent 8bb2679e43
commit 243c3fa619
5 changed files with 88 additions and 4 deletions
@@ -285,7 +285,7 @@ public sealed class ApiKeyAdminCommands
string? pepper = _pepperProvider.GetPepper();
if (string.IsNullOrWhiteSpace(pepper))
{
throw new InvalidOperationException("pepper unavailable");
throw new ApiKeyPepperUnavailableException();
}
return pepper;
@@ -0,0 +1,34 @@
namespace ZB.MOM.WW.Auth.ApiKeys;
/// <summary>
/// Thrown when an API-key operation needs the secret pepper but
/// <see cref="IApiKeyPepperProvider.GetPepper"/> returned <c>null</c>, empty or whitespace
/// (create and rotate in <c>ApiKeyAdminCommands</c>).
/// </summary>
/// <remarks>
/// Derives from <see cref="InvalidOperationException"/>, which is what these paths threw before
/// this type existed, so existing <c>catch (InvalidOperationException)</c> blocks keep working.
/// The message stays <c>"pepper unavailable"</c>. Consumers can catch this type instead of
/// pre-checking <see cref="IApiKeyPepperProvider"/> themselves or matching the message. The verifier
/// does not throw it: a missing pepper surfaces there as <c>ApiKeyFailure.PepperUnavailable</c>.
/// </remarks>
public sealed class ApiKeyPepperUnavailableException : InvalidOperationException
{
/// <summary>Creates the exception with the default <c>"pepper unavailable"</c> message.</summary>
public ApiKeyPepperUnavailableException()
: base("pepper unavailable")
{
}
/// <summary>Creates the exception with a custom message.</summary>
public ApiKeyPepperUnavailableException(string message)
: base(message)
{
}
/// <summary>Creates the exception with a custom message and inner exception.</summary>
public ApiKeyPepperUnavailableException(string message, Exception innerException)
: base(message, innerException)
{
}
}