dotnet-cqrs/Svrnty.CQRS.Altcha.Grpc/AltchaCallCredentials.cs
Mathias Beaulieu-Duncan 4446288bb6 feat(altcha): add Svrnty.CQRS.Altcha.Grpc with default verifier + proto
The default transport for IAltchaVerifier / IAltchaChallengeProvider —
calls a self-hosted altcha service over gRPC.

Wire contract
- Protos/altcha.proto defines svrnty.cqrs.altcha.v1.AltchaService with
  CreateChallenge + VerifyChallenge RPCs. Shipped in this package as
  source-of-truth; Go (and other) implementations vendor a copy.
- Challenge.challenge_hash is named (not "challenge") to avoid a C#
  property/class name collision; the MinimalApi widget JSON remaps.

Runtime
- AltchaGrpcVerifier maps RpcException → AltchaVerifyResult.Fail with
  a diagnostic reason ("verify-timeout", "service-unavailable", etc.)
  so the auth check surfaces a clean Unauthorized without leaking
  transport detail.
- AltchaGrpcChallengeProvider lets create-challenge failures bubble
  (challenge endpoint should 5xx if altcha is down — clients retry).
- AltchaGrpcOptions.TokenProvider hook for consumer-supplied HMAC
  service-token minting (plan-b will plug in ServiceTokenIssuer).
- AddGrpcClient<AltchaServiceClient> registered with HttpClientFactory.

AddSvrntyAltchaGrpcVerifier(Action<...>) and overload binding from
IConfiguration cover both wiring styles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:25:59 -04:00

28 lines
809 B
C#

using Grpc.Core;
namespace Svrnty.CQRS.Altcha.Grpc;
/// <summary>
/// Helper that builds gRPC call metadata (an <c>Authorization</c> header)
/// from <see cref="AltchaGrpcOptions.TokenProvider"/>. Kept as a separate
/// shared helper so the verifier and challenge provider apply identical
/// rules.
/// </summary>
internal static class AltchaCallCredentials
{
public static async Task<Metadata?> BuildMetadataAsync(AltchaGrpcOptions options, CancellationToken cancellationToken)
{
if (options.TokenProvider is null)
return null;
var token = await options.TokenProvider(cancellationToken);
if (string.IsNullOrWhiteSpace(token))
return null;
return new Metadata
{
{ "Authorization", $"Bearer {token}" }
};
}
}