feat(client-go): typed Unauthenticated vs PermissionDenied errors (#198)

Every RPC failure surfaced as a GatewayError with no way to tell a rejected
key from a missing scope, though docs/ClientLibrariesDesign.md requires the
two be treated distinctly. GatewayError now matches the new sentinels
ErrUnauthenticated and ErrPermissionDenied via errors.Is (by gRPC status code
only, so it works through the secret-redaction wrapper) and exposes Code().

Tests: errors_auth_test.go — sentinel matching per code, Code(), and a bufconn
AcknowledgeAlarm round trip proving the match survives the wire and the
password stays redacted (both behavioural tests fail without GatewayError.Is).
This commit is contained in:
Joseph Doherty
2026-11-06 16:08:51 -05:00
parent b9096f7c57
commit 9351ec1d2c
4 changed files with 138 additions and 3 deletions
+7 -1
View File
@@ -92,7 +92,13 @@ returned subscription owns cancellation and exposes `Close` for deterministic
goroutine cleanup. Raw protobuf messages remain available through the
`mxgateway` package aliases and the `Raw` helper methods. Typed errors support
`errors.As` for `GatewayError`, `CommandError`, and `MxAccessError`; command
errors preserve the raw reply.
errors preserve the raw reply. Authorization failures are distinct:
`errors.Is(err, mxgateway.ErrUnauthenticated)` matches a rejected API key
(gRPC `Unauthenticated` — fix the key) and `errors.Is(err,
mxgateway.ErrPermissionDenied)` matches a valid key missing a scope or another
key's session (gRPC `PermissionDenied` — grant the scope). `GatewayError.Code()`
returns the raw gRPC status code. The match reads only the status code, so it
works through the credential-redaction wrapper.
`EnsureMxAccessSuccess` follows COM semantics: only a **negative** HRESULT is a
failure, so positive success codes such as `S_FALSE` (1) pass. `StatusSucceeded`