feat: add Bearer token authentication support#227
Conversation
Review Summary by QodoAdd Bearer token authentication support to KurrentDB Rust client
WalkthroughsDescription• Add Authentication enum supporting Basic and Bearer token variants • Implement Bearer token authentication for OAuth/OIDC integration • Maintain backward compatibility with existing Credentials API • Update HTTP and gRPC request metadata to handle both auth types • Add comprehensive unit tests for auth header construction Diagramflowchart LR
A["Authentication Enum<br/>Basic/Bearer"] --> B["build_authorization_header<br/>gRPC metadata"]
A --> C["http_configure_auth<br/>HTTP requests"]
D["Credentials"] --> A
E[".authenticated() builder"] --> A
B --> F["Authorization header<br/>Basic/Bearer format"]
C --> F
File Changes1. kurrentdb/src/types.rs
|
Code Review by Qodo
1.
|
The bridge work in DEV-1644 needs Bearer auth on the wire, but the Rust client only knows how to send Basic. This adds an Authentication enum alongside the existing Credentials struct, with Basic and Bearer variants, so per-call options can carry either flavor. The migration is intentionally non-breaking. Credentials and its constructor are unchanged, From<Credentials> for Authentication is provided, and .authenticated() now takes impl Into<Authentication> so existing callsites keep compiling without changes. Bumps the crate to 1.1.0. Closes DEV-1643.
http_configure_auth was reading login/password/token bytes through from_utf8_unchecked, but Credentials::new and Authentication::bearer both accept arbitrary Into<Bytes>, so a perfectly safe caller could hand in non-UTF-8 and trigger UB. Swapped to from_utf8_lossy, which is what request.rs already does for the gRPC path. reqwest's basic_auth and bearer_auth take impl Display, and Cow<str> implements Display, so it slots in directly.
build_authorization_header used .expect() on MetadataValue::try_from. For Bearer that's risky: the token goes onto the wire verbatim, and HTTP/2 rejects control chars (NUL, LF, CR, others below 0x20, DEL), so a token someone read from a file or env var without trimming the trailing newline would crash the client. Now the function returns Option<MetadataValue<Ascii>>. On Err it emits a tracing::warn! tagged with the auth kind (basic/bearer, never the token itself) and returns None. build_request_metadata skips the authorization header when None, the server replies AccessDenied, and the warn log gives operators a clear hint about what went wrong. Worth flagging: tonic's MetadataValue<Ascii> is more permissive than the name implies. It accepts bytes 0x80 through 0xFF, full UTF-8, and U+FFFD. What actually breaks is the control-char set above; the new tests cover that.
A pass of small cleanups after running the diff through code review. Authentication grew a kind() helper so the tracing labels aren't typed out as raw "basic"/"bearer" strings each time. The nested if-let in build_request_metadata flattens to a single .as_deref().and_then(...) chain. The three near-duplicate "bearer with invalid char" tests collapse into one parameterized loop. settings_from moves to the top of the test module so it isn't buried mid-file. Comment-wise: trimmed narrative explanations down to the WHY, and added a brief note on why http_configure_auth uses from_utf8_lossy.
CI bumped to 1.95 and three new lints fired on pre-existing code, none of it from this PR. Silenced dead_code on the generated common.rs module (unused proto types are noise from prost-build), collapsed the nested if-let in the connection-id selector with a &&-let, and rewrote two `% 2 == 0` checks in the random node tiebreak as .is_multiple_of(2).
CI's `cargo fmt --check` caught a few line-wraps in request.rs that I missed locally.
The auth code had accumulated a lot of comments that just restated the type or function name. Stripped the Basic variant doc, the basic/bearer constructor docs, and the resolve_authentication docstring. Removed the narrative explanation of the from_utf8_lossy choice; the change reads clearly without it. Trimmed the panic-prevention comment to one line and tightened the .authenticated() rustdoc and the parameterized test's rationale comment. Net is -11 lines of comments. Kept only the WHYs that aren't obvious from the code: the From<Credentials> migration path on the enum, "sent verbatim" on the Bearer variant, base64 hints in the tests, the security intent of not logging tokens, and the panic-vector context.
1da8ffd to
a4fa77c
Compare
Add unit tests for http_configure_auth and resolve_authentication, the HTTP auth surface used by gossip and persistent subscriptions, which had no coverage. Drop three gRPC tests whose behavior is already exercised elsewhere (From<Credentials> via the .authenticated() builder, the bearer builder path via the override test, and the private builder's invalid-char handling via the public metadata test).
The options! macro was changed in #227 (credentials -> authentication) without bumping eventstore-macros, so cargo publish verification of kurrentdb pulled the old 0.0.1 from crates.io and failed to compile.
The options! macro was changed in #227 (credentials -> authentication) without bumping eventstore-macros, so cargo publish verification of kurrentdb pulled the old 0.0.1 from crates.io and failed to compile.
Adds Bearer token authentication to the Rust client. Until now requests could only carry HTTP Basic credentials. This PR adds a parallel path so callers can send
Authorization: Bearer <token>for OAuth/OIDC flows. The bridge work in DEV-1644 depends on it.The change is fully additive: nothing existing breaks, no migrations needed.
What's new
Authenticationenum withBasic(Credentials)andBearer(Bytes)variants.Authentication::basic(login, password)andAuthentication::bearer(token)constructors.impl From<Credentials> for Authenticationso the existing API surface relaxes without breaking callers.build_request_metadataandhttp_configure_authnow match on the variant and emit the rightAuthorizationheader.How to use it
If you're already using
Credentials::new(...), nothing changes:Backward compatibility
Note
No existing code needs to change.
Credentials, its constructor, connection-string parsing, and the TOML/JSON serde format for credentials are all unchanged..authenticated()was relaxed from(Credentials)to(impl Into<Authentication>). Calls passing aCredentialsvalue continue to compile because of the newFromimpl. Per cargo's semver rules this counts as a minor change.Closes DEV-1643.
Required by DEV-1644.