Skip to content

fix(net): end a lapsed request token's requests with EXPIRED_AUTH_TOKEN - #7703

Merged
kixelated merged 6 commits into
mainfrom
quest/m1/auth-expiry-reset
Nov 3, 2026
Merged

kixelated merged 6 commits into
mainfrom
quest/m1/auth-expiry-reset

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Implements quest/m1/auth-expiry-reset.md, point 1 of moq-dev/moq#7646. The issue's other points landed in moq-dev/moq#7211, which this builds on. With the FETCH and announce cases below also covered, this finishes the issue.

Closes #7646

Summary

When a moq-transport request token's grant lapses, the requests it admitted now tell the peer so with the draft's EXPIRED_AUTH_TOKEN. Before, they ended with codes indistinguishable from a denial (or with a plain FIN). This lets a client tell "renew and retry" from "denied".

  • Code: EXPIRED_AUTH_TOKEN is stream reset code 0x7 in drafts 18 (§3.3.3/§15.10.4), 20 (§3.3.4/§15.11.4), and 22 (§12.5/§16.11.4). 0x5 is the REQUEST_ERROR code moq-net already sends. That is a separate registry; on streams, 0x5 means TOO_FAR_BEHIND.

What a lapsed token now sends

Request Before Now
SUBSCRIBE: open group streams, fill and END_OF_TRACK streams CANCELLED (0x1) 0x7. PUBLISH_DONE still says UNAUTHORIZED ("token expired")
FETCH: data stream after FETCH_OK CANCELLED 0x7
FETCH: request stream after FETCH_OK INTERNAL_ERROR 0x7
FETCH: refused before FETCH_OK (preparing, or resolving through Gate::guard) REQUEST_ERROR UNAUTHORIZED REQUEST_ERROR EXPIRED_AUTH_TOKEN (0x5), as SUBSCRIBE already did
PUBLISH_NAMESPACE accepted, after its OK FIN RESET_STREAM and STOP_SENDING with 0x7: a request cancel per draft-22 §6.4.2.3, with expiry as the reason to re-announce (§9.14)

A revoked or narrowed token ends all of these as before. The quest described the subscription case as INTERNAL_ERROR, but on main its streams actually reset with CANCELLED: auth::until dropped the serve future, so each stream fell back to Writer's Drop.

How

  • Which ends count: ietf::error::token_expired is the one check. It is true for the deadline passing, or the acceptor revoking with SessionError::Expired, and it already decided PUBLISH_DONE's "token expired".
  • Subscriptions and FETCHes: a Lapse flag (ietf/publisher.rs) is set where the grant end is seen, just before the request's serving future drops. Group streams read it in a GroupServe Drop; fill, END_OF_TRACK and FETCH data streams opt in through Ranked. The FETCH request stream goes through Lapse::abort.
  • Announces: a lapsed PUBLISH_NAMESPACE uses the new Reader::stop and Writer::reset. Both are pub(crate), and both take a code the caller already took from the registry.
  • Decoding: reading 0x7 still gives StreamError::Unknown(0x7), and no StreamError encodes to it. So a relay passes an upstream's expiry on as INTERNAL_ERROR, rather than telling its own peer that their token lapsed.
  • Public API: no changes.
  • Docs: doc/bin/relay/auth.md describes what a lapse sends.
  • Quest: the quest file is deleted, along with its link in quest/m1/README.md.

Decisions

  • ✅ Cover FETCH here? A FETCH whose request-token grant lapses resets its data stream, and after FETCH_OK its request stream, with EXPIRED_AUTH_TOKEN. The lapse flag is attached in serve_fetch_stream. (Maintainer, 2026-11-03)
  • ✅ Cover the announce here? A PUBLISH_NAMESPACE we accepted whose token lapses after its OK is cancelled with a reset and STOP_SENDING carrying 0x7, not a FIN, per draft-22 §6.4.2.3 and §9.14. This PR then closes moq-dev/moq#7646. (Maintainer, 2026-11-03)

Choices made without asking

  • Revoked as expired: an acceptor revoking with SessionError::Expired counts as an expiry, matching the "token expired" PUBLISH_DONE reason it already got.
  • FETCH refusal before FETCH_OK: an ended token now refuses with the token's own error, as SUBSCRIBE does. That gives EXPIRED_AUTH_TOKEN for a lapse and UNAUTHORIZED otherwise (the wire code is unchanged for revocation). Gate::guard does the same, which also makes a TRACK_STATUS whose token lapses while it resolves refuse as EXPIRED_AUTH_TOKEN. The Result a FETCH returns now carries the acceptor's own code, as a subscription's already did. Three tests that pinned Error::Unauthorized were updated to match.
  • Unflushed OK on a lapsed announce: the reset takes an announce's OK with it if it is still buffered. The cancel is the whole message.
  • Lapsed-announce test: a_lapsed_announce_grant_retracts_before_the_peer_reads asserted that a lapse waits on the peer's FIN acknowledgement. A reset waits on nothing, so it is now a_lapsed_announce_grant_retracts_without_waiting_on_the_peer. The FIN-and-wait behavior keeps its revoke test (a_revoked_token_retracts_an_announce_whose_ok_is_blocked).
  • Decoding: 0x7 is not decoded into any local error (reasons above).

Checks

  • cargo nextest run --locked -p moq-net: 3352 passed, 13 skipped, on the final head.
  • cargo clippy --locked -p moq-net --all-targets -- -D warnings, with default and with no default features: clean.
  • cargo fmt: clean. quest check: ok.
  • New tests, on mocked time, for drafts 18, 20 and 22. Each covers a lapsed, a revoked-as-expired, and a revoked grant:
    • a_lapsed_token_resets_its_groups_as_expired: subscription group resets, PUBLISH_DONE, and stream order.
    • a_lapsed_token_ends_a_fetch_as_expired: lapse while resolving gives REQUEST_ERROR; lapse after FETCH_OK gives data and request stream resets.
    • a_lapsed_announce_token_cancels_its_request_as_expired: RESET_STREAM and STOP_SENDING with 0x7, or a FIN when revoked.
    • a_lapsed_subscriptions_ranked_streams_reset_as_expired: the fill and END_OF_TRACK path.
  • Not run: the docs site build (prose only), and other crates (only moq-net was built, as disk on the host is tight).

Review

  • Reviewer: quest review, Codex gpt-6.1-sol at medium effort.
  • Round 1 on head 6e9d61f6ce: no findings.
  • Since then, only a conflict-free merge of origin/main (moq-dev/moq#7681, which touches no rs/moq-net file). git diff origin/main...d12041da57 matches the reviewed diff except for one hunk offset in quest/m1/README.md.

Suggested follow-ups

  • Revoked announce after its OK: this still ends with a FIN, not a cancel. Draft-22 §6.4.2.3 withdraws a request by resetting it, so a FIN-only end is a gap for every non-expiry end too. The fix would be a reset with INTERNAL_ERROR, since the stream registry has no "unauthorized" code. That is a behavior change for revocations, outside this quest's expiry scope.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 3 commits November 2, 2026 23:53
…UTH_TOKEN

When a moq-transport request token's grant lapses, the subscription it
admitted is dropped from outside, so every open group stream fell back to
the writer's CANCELLED reset. The streams now share a Lapse flag, set just
before the drop when the grant ended by expiry (its deadline, or an
acceptor revoking it as expired), and reset with the draft's
EXPIRED_AUTH_TOKEN (0x7 in drafts 18, 20, and 22) instead: group streams
through a GroupServe Drop, the fill and END_OF_TRACK streams through
Ranked. A revoked or narrowed grant still cancels. PUBLISH_DONE stays
UNAUTHORIZED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title A lapsed request token resets its groups with EXPIRED_AUTH_TOKEN fix(net): reset a lapsed request token's group streams with EXPIRED_AUTH_TOKEN Nov 3, 2026
kixelated and others added 3 commits November 3, 2026 09:10
…OKEN

Per the maintainer's decisions on the PR:

- FETCH: once its request token lapses after FETCH_OK, the fetch's data
  stream and its request stream reset with EXPIRED_AUTH_TOKEN (0x7). A
  token end found anywhere in the fetch now carries the token's own
  refusal, so a lapse before FETCH_OK, including while resolving through
  Gate::guard, is refused with REQUEST_ERROR EXPIRED_AUTH_TOKEN.
- PUBLISH_NAMESPACE: an accepted announce whose token lapses after its OK
  is cancelled with RESET_STREAM and STOP_SENDING carrying 0x7 rather
  than finished (draft-22 sections 6.4.2.3 and 9.14).

A revoked or narrowed token ends both as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title fix(net): reset a lapsed request token's group streams with EXPIRED_AUTH_TOKEN fix(net): end a lapsed request token's requests with EXPIRED_AUTH_TOKEN Nov 3, 2026
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 17:51
@coderabbitai

coderabbitai Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 19 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e1abbcbc-cbf9-4870-99dc-7f466aa91fbe
Commits

Reviewing files that changed from the base of the PR and between 9ac02f9 and d12041d.

Files selected for processing: 9
  • doc/bin/relay/auth.md
  • quest/m1/README.md
  • quest/m1/auth-expiry-reset.md
  • rs/moq-net/src/auth.rs
  • rs/moq-net/src/coding/reader.rs
  • rs/moq-net/src/coding/writer.rs
  • rs/moq-net/src/ietf/error.rs
  • rs/moq-net/src/ietf/publisher.rs
  • rs/moq-net/src/ietf/subscriber.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed head d12041d. No actionable findings found in the expiry handling for subscriptions, FETCH, announces, and token gating.

Verification: GitHub-only static review; I did not run tests or independently verify the draft registries. The PR reports passing moq-net tests, clippy, and formatting checks.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes — the full 9-file diff: a moq-transport wire behavior change so a request whose own token lapsed ends distinguishably from a denial, plus its tests and docs.

  • EXPIRED_AUTH_TOKEN = 0x7 (ietf/error.rs): new stream reset code and a token_expired predicate (Error::Expired | Error::Session(SessionError::Expired)). Verified 0x7 is the registered EXPIRED_AUTH_TOKEN stream code in draft-ietf-moq-transport-18 §3.3.3 and -22 §12.5/§16.11.4; to_stream_code still never emits it and from_stream_code keeps it Unknown.
  • Lapse flag (ietf/publisher.rs): a shared Arc<AtomicBool> set where the grant end is observed, before the serving future drops. GroupServe::Drop, the Ranked::reset override (fill / END_OF_TRACK / FETCH data), and Lapse::abort (the answered request stream) read it. Ordering is sound: set() runs in the auth::until guard before serve is dropped.
  • FETCH coverage: token_grant now falls back to gate.token_guard(), so a standalone FETCH's own token is watched; lapse-before-OK refuses with REQUEST_ERROR 0x5, lapse-after-OK resets the data and request streams with 0x7.
  • Announce cancellation (subscriber.rs): an acked PUBLISH_NAMESPACE whose token lapsed is cancelled both ways with 0x7 per draft-22 §6.4.2.3, rather than FIN. The route is released first.
  • Gate::guard: polls poll_token_ended ahead of the gate it closes, so resolve-time lapse surfaces the token's own error.
  • Tests + docs: mocked-time tests across drafts 18/20/22 for lapsed, revoked-as-expired, and revoked; doc/bin/relay/auth.md updated; quest file deleted.

cargo check -p moq-net --lib passes on the head. Public API unchanged. No js/net mirror is needed: its ietf path only carries the SETUP-level AUTHORIZATION_TOKEN, not per-request token grants.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit fb9a4fb Nov 3, 2026
13 of 14 checks passed
@kixelated
kixelated deleted the quest/m1/auth-expiry-reset branch November 3, 2026 19:39
kixelated added a commit that referenced this pull request Nov 3, 2026
… quest

#7703 merged and deleted quest/m1/auth-expiry-reset.md, so the revoked-announce
quest is unblocked; #7703 stays cited as its source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

moq-net auth: expiry vs deny codes, renewal on session-authorized requests, tokenless PUBLISH_NAMESPACE, kind/path on auth::Request

1 participant