using Grpc.Core; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Svrnty.CQRS.Altcha.Abstractions; namespace Svrnty.CQRS.Altcha.Grpc; /// /// Default backed by gRPC. Calls /// AltchaService.VerifyChallenge on the configured endpoint and /// maps any failure (transport error, deadline, server-reported failure) /// to . Verification failures are /// safe defaults — callers see an Unauthorized outcome from the /// auth check. /// public sealed class AltchaGrpcVerifier : IAltchaVerifier { private readonly AltchaService.AltchaServiceClient _client; private readonly IOptions _options; private readonly ILogger _logger; public AltchaGrpcVerifier( AltchaService.AltchaServiceClient client, IOptions options, ILogger logger) { _client = client; _options = options; _logger = logger; } public async Task VerifyAsync(string payload, CancellationToken cancellationToken = default) { var opts = _options.Value; try { var metadata = await AltchaCallCredentials.BuildMetadataAsync(opts, cancellationToken); var deadline = DateTime.UtcNow.Add(opts.CallTimeout); var response = await _client.VerifyChallengeAsync( new VerifyChallengeRequest { Payload = payload }, headers: metadata, deadline: deadline, cancellationToken: cancellationToken); return response.Ok ? AltchaVerifyResult.Success : AltchaVerifyResult.Fail(string.IsNullOrEmpty(response.Reason) ? "invalid" : response.Reason); } catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded) { _logger.LogWarning(ex, "Altcha verify timed out against {Endpoint}.", opts.Endpoint); return AltchaVerifyResult.Fail("verify-timeout"); } catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) { _logger.LogWarning(ex, "Altcha service unavailable at {Endpoint}.", opts.Endpoint); return AltchaVerifyResult.Fail("service-unavailable"); } catch (RpcException ex) { _logger.LogWarning(ex, "Altcha verify failed against {Endpoint}: {Status}", opts.Endpoint, ex.StatusCode); return AltchaVerifyResult.Fail("rpc-error"); } } }