Skip to content

fix(native): surface terminal auth connect errors#1649

Merged
kixelated merged 5 commits into
moq-dev:mainfrom
Qizot:fix/auth-connect-errors
Jun 12, 2026
Merged

fix(native): surface terminal auth connect errors#1649
kixelated merged 5 commits into
moq-dev:mainfrom
Qizot:fix/auth-connect-errors

Conversation

@Qizot

@Qizot Qizot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Preserve unauthorized and forbidden connection failures as typed errors through native reconnect, libmoq, and UniFFI so clients can distinguish auth failures from retryable transport failures.

Previously an auth error would not be recognized since the client would fallback to connecting to e.g. websocket. If both transports failed we were left with generic error that completely stripped the unauthorized information.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds terminal authentication error handling by introducing a ConnectError enum (Unauthorized, Forbidden) with status-code mapping, wiring it into moq-native, classifying transport-layer failures (noq, quinn, quiche, websocket), centralizing QUIC-vs-WebSocket racing to short-circuit on auth failures, making reconnect loops terminate on auth errors, and exposing Unauthorized/Forbidden on FFI surfaces (libmoq/moq-ffi) with distinct return codes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: surfacing terminal auth connect errors (401/403) as typed errors through the native layer.
Description check ✅ Passed The description is clearly related to the changeset, explaining the problem (auth errors being lost during transport fallback) and the solution (preserving them as typed errors).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
rs/moq-native/tests/broadcast.rs (1)

786-857: ⚡ Quick win

Add a fallback-path auth regression test.

Both new auth tests dial ws://, so they only exercise the pure WebSocket connector. The regression called out in this PR was losing Unauthorized once another transport also failed, and these cases won't catch that. Please add a sibling case that hits http://localhost:{port} (with only the WebSocket TCP listener bound) and asserts client.connect(...) / client.reconnect(...) still surface ConnectError::Unauthorized while QUIC fails in parallel.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-native/tests/broadcast.rs` around lines 786 - 857, Duplicate each
failing-websocket test as a sibling that uses an http:// URL to exercise the
fallback path: add two new tests (e.g.,
websocket_unauthorized_handshake_is_explicit_http_fallback and
reconnect_stops_on_websocket_unauthorized_http_fallback) that copy the existing
test bodies but parse url::Url from format!("http://{addr}") instead of
"ws://{addr}", call client.connect(url) / client.reconnect(url) respectively,
and assert the same expect_connect_err/
assert_connect_error(moq_native::ConnectError::Unauthorized) results; keep the
same TCP listener that only serves an unauthorized HTTP response so QUIC (other
transports) will fail in parallel and the fallback path is exercised.
rs/moq-native/src/connect.rs (1)

26-29: ⚡ Quick win

Call ConnectError::is_auth() when classifying terminal auth failures.

downcast_ref::<ConnectError>().is_some() makes every future ConnectError variant terminal because the enum is already #[non_exhaustive]. The first non-auth variant added here would silently stop reconnect retries.

Suggested change
 pub(crate) fn is_auth_error(err: &anyhow::Error) -> bool {
-	err.downcast_ref::<ConnectError>().is_some()
+	err.downcast_ref::<ConnectError>()
+		.is_some_and(ConnectError::is_auth)
 		|| matches!(err.downcast_ref::<moq_net::Error>(), Some(moq_net::Error::Unauthorized))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-native/src/connect.rs` around lines 26 - 29, The current is_auth_error
function treats any ConnectError as terminal by using
downcast_ref::<ConnectError>().is_some(); change it to
downcast_ref::<ConnectError>().map_or(false, |e| e.is_auth()) so only auth
variants are considered terminal, and keep the existing check for
moq_net::Error::Unauthorized (matches!(err.downcast_ref::<moq_net::Error>(),
Some(moq_net::Error::Unauthorized))). Update the function to call
ConnectError::is_auth() on the downcasted value instead of just checking
presence.
rs/moq-ffi/src/session.rs (1)

54-69: ⚡ Quick win

Add test coverage for the moq_net::Error::Unauthorized mapping.

The test suite covers moq_native::ConnectError mappings but does not test the moq_net::Error::Unauthorized path handled at lines 47-49. For parity with rs/libmoq/src/session.rs (which includes this test case in context snippet 2), add:

assert!(matches!(
    map_connect_error(moq_net::Error::Unauthorized.into()),
    MoqError::Unauthorized
));

This ensures both FFI layers maintain consistent test coverage for all auth error paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-ffi/src/session.rs` around lines 54 - 69, Add a test assertion to
cover the moq_net authentication error mapping: in the tests module in
session.rs (inside the maps_native_auth_connect_errors test), call
map_connect_error with moq_net::Error::Unauthorized converted into the unified
error (moq_net::Error::Unauthorized.into()) and assert it matches
MoqError::Unauthorized; place this alongside the existing moq_native assertions
so both FFI layers (map_connect_error, MoqError) have parity in coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rs/moq-native/src/client.rs`:
- Around line 659-666: In the async test function
race_transport_connect_stops_on_quic_auth_error, call tokio::time::pause() at
the start of the test body (before any tokio::time::sleep is awaited) so the
sleep-based branch uses simulated time instead of real wall-clock time; locate
the test by the function name race_transport_connect_stops_on_quic_auth_error
and insert the single call to tokio::time::pause() as the first statement in the
async block to eliminate real-time delays in CI.

---

Nitpick comments:
In `@rs/moq-ffi/src/session.rs`:
- Around line 54-69: Add a test assertion to cover the moq_net authentication
error mapping: in the tests module in session.rs (inside the
maps_native_auth_connect_errors test), call map_connect_error with
moq_net::Error::Unauthorized converted into the unified error
(moq_net::Error::Unauthorized.into()) and assert it matches
MoqError::Unauthorized; place this alongside the existing moq_native assertions
so both FFI layers (map_connect_error, MoqError) have parity in coverage.

In `@rs/moq-native/src/connect.rs`:
- Around line 26-29: The current is_auth_error function treats any ConnectError
as terminal by using downcast_ref::<ConnectError>().is_some(); change it to
downcast_ref::<ConnectError>().map_or(false, |e| e.is_auth()) so only auth
variants are considered terminal, and keep the existing check for
moq_net::Error::Unauthorized (matches!(err.downcast_ref::<moq_net::Error>(),
Some(moq_net::Error::Unauthorized))). Update the function to call
ConnectError::is_auth() on the downcasted value instead of just checking
presence.

In `@rs/moq-native/tests/broadcast.rs`:
- Around line 786-857: Duplicate each failing-websocket test as a sibling that
uses an http:// URL to exercise the fallback path: add two new tests (e.g.,
websocket_unauthorized_handshake_is_explicit_http_fallback and
reconnect_stops_on_websocket_unauthorized_http_fallback) that copy the existing
test bodies but parse url::Url from format!("http://{addr}") instead of
"ws://{addr}", call client.connect(url) / client.reconnect(url) respectively,
and assert the same expect_connect_err/
assert_connect_error(moq_native::ConnectError::Unauthorized) results; keep the
same TCP listener that only serves an unauthorized HTTP response so QUIC (other
transports) will fail in parallel and the fallback path is exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 738c30cd-df3f-4036-b948-6d86ec568081

📥 Commits

Reviewing files that changed from the base of the PR and between cb0271c and 50d9a02.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • rs/libmoq/src/error.rs
  • rs/libmoq/src/session.rs
  • rs/moq-ffi/Cargo.toml
  • rs/moq-ffi/src/error.rs
  • rs/moq-ffi/src/session.rs
  • rs/moq-native/Cargo.toml
  • rs/moq-native/src/client.rs
  • rs/moq-native/src/connect.rs
  • rs/moq-native/src/lib.rs
  • rs/moq-native/src/noq.rs
  • rs/moq-native/src/quiche.rs
  • rs/moq-native/src/quinn.rs
  • rs/moq-native/src/reconnect.rs
  • rs/moq-native/src/websocket.rs
  • rs/moq-native/tests/broadcast.rs

Comment thread rs/moq-native/src/client.rs
@Qizot
Qizot marked this pull request as draft June 8, 2026 11:49
@Qizot
Qizot marked this pull request as ready for review June 8, 2026 13:13
Comment thread rs/libmoq/src/session.rs Outdated
Qizot added 2 commits June 9, 2026 07:51
Preserve unauthorized and forbidden connection failures as typed errors through native reconnect, libmoq, and UniFFI so clients can distinguish auth failures from retryable transport failures.
@Qizot
Qizot force-pushed the fix/auth-connect-errors branch from 79a261b to ecbf13e Compare June 9, 2026 05:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rs/libmoq/src/session.rs`:
- Around line 113-118: The map_connect_error function currently maps non-auth
connect failures to Error::Native(err); change it so only
moq_native::ConnectError::Unauthorized and ::Forbidden are special-cased and all
other connect errors fall back to the generic connect error variant (i.e.,
return the same Error::Connect-style variant used by the FFI surface) rather
than Error::Native; update the match in map_connect_error accordingly so
behavior matches rs/moq-ffi/src/session.rs:34-39 and only 401/403 are handled
specially.

In `@rs/moq-native/src/error.rs`:
- Around line 77-90: The connect_error() matcher currently ignores the
TransportRace variant, causing nested Unauthorized/Forbidden to be lost; update
connect_error() (in the Error impl) to handle the TransportRace branch by
checking both children (the TransportRace left/right fields) and returning the
first Some(crate::ConnectError) found (e.g., call connect_error() on each child
and propagate a non-None result), or alternatively ensure the TransportRace
construction never wraps auth failures — but the simpler fix is to add a
TransportRace arm that recurses into both children and returns any propagated
connect error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8b47cf4-13dc-4d90-a1b6-0a3301928a76

📥 Commits

Reviewing files that changed from the base of the PR and between 79a261b and ecbf13e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • rs/libmoq/src/error.rs
  • rs/libmoq/src/session.rs
  • rs/moq-ffi/src/error.rs
  • rs/moq-ffi/src/session.rs
  • rs/moq-native/Cargo.toml
  • rs/moq-native/src/client.rs
  • rs/moq-native/src/connect.rs
  • rs/moq-native/src/error.rs
  • rs/moq-native/src/lib.rs
  • rs/moq-native/src/noq.rs
  • rs/moq-native/src/quiche.rs
  • rs/moq-native/src/quinn.rs
  • rs/moq-native/src/reconnect.rs
  • rs/moq-native/src/websocket.rs
  • rs/moq-native/tests/broadcast.rs
✅ Files skipped from review due to trivial changes (2)
  • rs/moq-native/src/connect.rs
  • rs/moq-ffi/src/error.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • rs/moq-native/Cargo.toml
  • rs/moq-native/tests/broadcast.rs
  • rs/moq-native/src/websocket.rs
  • rs/libmoq/src/error.rs
  • rs/moq-native/src/client.rs
  • rs/moq-native/src/noq.rs

Comment thread rs/libmoq/src/session.rs
Comment thread rs/moq-native/src/error.rs
Keep libmoq connect failures on the generic connect error path while still mapping 401 and 403 explicitly.

Propagate nested connect errors out of TransportRace so auth failures are not lost when both transports fail.
@Qizot
Qizot requested a review from kixelated June 10, 2026 08:21
Appending the new auth codes instead of inserting them. Native stays -32
so existing C consumers keyed on that value don't silently misread.
Unauthorized and Forbidden take the next free codes (-33, -34).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kixelated
kixelated enabled auto-merge (squash) June 12, 2026 15:44
@kixelated

Copy link
Copy Markdown
Collaborator

Note, this disable automatic retries on auth failures, which I guess is the right thing to do?

@Qizot

Qizot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Note, this disable automatic retries on auth failures, which I guess is the right thing to do?

I would say so? Given the token doesn't change I wouldn't expect the relay to somehow start accepting it.

@kixelated
kixelated merged commit dad2e8d into moq-dev:main Jun 12, 2026
1 check passed
@moq-bot moq-bot Bot mentioned this pull request Jun 12, 2026
@moq-bot moq-bot Bot mentioned this pull request Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants