diff --git a/quest/m1/auth/README.md b/quest/m1/auth/README.md index e5f120c0d75..6953e0699e5 100644 --- a/quest/m1/auth/README.md +++ b/quest/m1/auth/README.md @@ -23,6 +23,17 @@ that predates the stream. Node-bound peer grants for direct sessions belong to Decisions settled while planning, recorded so review does not relitigate them: +- **Per-request boundary (2026-10-12).** Request tokens are an experimental + Rust interoperability hook with application-owned verification, retaining + renewal. They are not a complete authentication scheme or a relay feature. + Local session permission may admit an incoming request; grants received + from the peer may not. The shared acceptor queue distinguishes session and + request events with separate acceptance handles. This is an intentional + Rust API change, not an optional scope field on the existing acceptor. + Per-request relay integration, JS APIs, CLI, and bindings are deferred; + session AUTH work continues. Document the hook only in minimal API comments, + with no feature page or guide. The relay request-lease quest is related + future work, no longer required to complete this epic. - **One AUTH stream per token, opened by whoever presents it.** Either side of a session may open one; the acceptor answers with the grant that token earns. A moq-net session opens one on both sides right after SETUP with an @@ -88,8 +99,9 @@ Decisions settled while planning, recorded so review does not relitigate them: SUBSCRIBE_OK. Rust is `moq_net::auth` with `auth::Grant`, `auth::Handle`, `auth::Token`, and `auth::Request`; JS mirrors as `connection.auth`. -Everything here is additive: `Session::auth()` is new, and the relay derives -the grant from the origin handles it already scopes. Decided 2026-10-05: wire +`Session::auth()` is additive, while the request-token hook intentionally +changes the Rust acceptor API as described above. The relay derives the grant +from the origin handles it already scopes. Decided 2026-10-05: wire work targets the wip lite version until the maintainer cuts it, never a published version in place, so AUTH lands in `moq-lite-07-wip` (or whichever version is wip then), not lite-06. The UNAUTHORIZED stream code stays on lite-06 @@ -102,17 +114,10 @@ because `main` assigned 0x3A to NOT_FETCHABLE first. - [AUTH violations](/quest/m1/auth/violations.md) - every AUTH protocol violation closes the session in Rust and JS, lite and IETF - [Relay tokens](/quest/m1/auth/relay-refresh.md) - the relay verifies tokens sent in band, unions their grants, and cancels only work that loses access -- [Request-token decode](/quest/m1/auth/request-token-decode.md) - an - `AUTHORIZATION TOKEN` on a moq-transport request decodes by the draft's - rules, closing the session on the forms the draft forbids -- [Setup extensions](/quest/m1/auth/extensions.md) - a side declares which - Setup extensions it offers with one `Extensions` struct -- [Request tokens](/quest/m1/auth/request-token.md) - a token on a - moq-transport request authorizes that request and renews it in band +- [Request tokens](/quest/m1/auth/request-token.md) - an experimental Rust + verifier hook confines a token's authority to its request and renews it - [Request gate](/quest/m1/auth/request-gate.md) - every incoming request is refused up front and held to the grant through a gate its dispatcher owns -- [Request leases](/quest/m1/auth/request-lease.md) - moq-relay honors a - request token with a lease of its own - [Expired token error](/quest/m1/auth/expired-error.md) - an expired token reports `Error::Expired`, not `Unauthorized`, in Rust, JS, and the bindings - [Bindings](/quest/m1/auth/bindings.md) - grants and tokens reach every @@ -123,6 +128,12 @@ because `main` assigned 0x3A to NOT_FETCHABLE first. ## Related +- [Request leases](/quest/m3/auth-request-lease.md) - deferred relay + integration, to revisit when a consumer needs per-request verification +- [#5107](https://github.com/moq-dev/moq/pull/5107) - landed request-token + decoding, including session-fatal REQUEST_UPDATE decode failures +- [#5106](https://github.com/moq-dev/moq/pull/5106) - landed setup extension + configuration - [Malformed grant](https://github.com/moq-dev/moq/pull/5256) - a malformed or non-canonical grant pattern, or an out-of-range `Expires`, closes the session with PROTOCOL_VIOLATION in Rust and JS diff --git a/quest/m1/auth/bindings.md b/quest/m1/auth/bindings.md index 41755f0c86e..072d5145126 100644 --- a/quest/m1/auth/bindings.md +++ b/quest/m1/auth/bindings.md @@ -10,6 +10,10 @@ one without the plugin reconnecting. ## Plan +This quest exposes session AUTH only. Per-request setters and verifier APIs +remain deferred until a consumer needs them (confirmed 2026-10-12); do not +export the experimental Rust request hook through the bindings here. + - `MoqSession` in `rs/moq-ffi/src/session.rs` gains `auth()` returning an `Arc` object, the shape `publisher()` uses, with `grant() -> Option` (a record: publish patterns, subscribe patterns, expiry diff --git a/quest/m1/auth/extensions.md b/quest/m1/auth/extensions.md deleted file mode 100644 index c06628fe89f..00000000000 --- a/quest/m1/auth/extensions.md +++ /dev/null @@ -1,32 +0,0 @@ -# [S] A side declares which Setup extensions it offers - -## Goal - -A moq-net client or server, moq-tokio's dial and listen `Config`, and js/net's -`connect` and `accept` declare which Setup extensions they offer with one -positive `#[non_exhaustive] setup::Extensions { auth, solicit }`, all on by -default, so an application can decline the AUTH or solicit extension. Later -extensions join the same struct. - -## Plan - -Decided 2026-10-01 (Q2 on [#4675](https://github.com/moq-dev/moq/pull/4675)), -which built it. [#5106](https://github.com/moq-dev/moq/pull/5106) carves it -out so it lands before -[Request tokens](/quest/m1/auth/request-token.md). - -- `setup::Extensions` in `rs/moq-net/src/setup.rs`, set through - `Client::with_extensions` and `Server::with_extensions`. It folds in the - private bools `run_setup` takes in `rs/moq-net/src/ietf/session.rs`. -- moq-tokio: an `extensions` field on `connect::Config` and `listen::Config`, - serde, omitted when default. -- js/net: `extensions` on `ConnectProps` and `AcceptProps`. -- A side serves an inbound AUTH only when it offered AUTH itself. -- Wire: a draft-17+ server now omits AUTH from its SETUP when the client did - not offer it, where it always sent it before. List this in the PR. -- Tests, Rust and JS: declining each extension keeps it out of SETUP, and an - inbound AUTH on a side that declined it is refused. - -Public API: additive, `setup::Extensions`, `Client::with_extensions`, -`Server::with_extensions`, the moq-tokio `extensions` fields, and JS -`Extensions`. diff --git a/quest/m1/auth/relay-refresh.md b/quest/m1/auth/relay-refresh.md index f9bba19257e..c43a34163c1 100644 --- a/quest/m1/auth/relay-refresh.md +++ b/quest/m1/auth/relay-refresh.md @@ -12,6 +12,14 @@ expiry, an expiry that leaves the union intact ends only that token, and ## Plan +- Coordinate shared plumbing with [Request tokens](/quest/m1/auth/request-token.md) + and #5148. The 2026-10-12 request-token plan chooses one queue with explicit + session/request event variants and distinct acceptance handles. The relay + accepts session events here and explicitly refuses request verification + as `Unsupported`; it must never attach a request token as a session grant. + Share cancellation, pending-work bounds, and persistent connection handles + whichever implementation lands first. #5282 already implements this shared + plumbing; its separate open API and limit-policy decisions remain open. - `Connection::run` in `rs/moq-relay/src/connection.rs` takes `requests()` from `moq_net::server::Handshake::auth()` before `.ok()`, so the relay owns the initial empty AUTH too and the driver's fallback never races it. @@ -71,9 +79,11 @@ expiry, an expiry that leaves the union intact ends only that token, and - Bound pending tokens: `auth::Handle::requests()` hands out an unbounded queue today, and a closed AUTH stream leaves its request queued, so a peer that finishes AUTH streams faster than the relay verifies them grows it - without limit (Codex security review on #4039). This quest is the first - consumer, so bound it per session here and refuse what overflows; dropping a - `Request` already refuses its token. + without limit (Codex security review on #4039). Bound it per session and + refuse what overflows; share the bound and withdrawal support with the + request-token hook whichever lands first. Neither consumer may land with + unbounded pending verification. Dropping a `Request` already refuses its + token. - Docs: `doc/bin/relay/auth.md` gains an "in-band tokens" section beside revalidation stating that grants union, that a token needs the admitted root, what an expiry does to the union today, and that the grant's expiry diff --git a/quest/m1/auth/request-gate.md b/quest/m1/auth/request-gate.md index 40829eb7525..64791085c18 100644 --- a/quest/m1/auth/request-gate.md +++ b/quest/m1/auth/request-gate.md @@ -23,7 +23,9 @@ Decisions settled while planning (2026-10-09): was rejected because it loses those graceful refusals. - **Rust and JS together**, so the shape and names stay mirrored. - **Effective authorization.** Land after request-token support. Dispatch - uses session-first admission or the admitted request grant, preserving + uses independently verified local session permission first, then the + admitted request grant; peer-issued AUTH_OK grants cannot authorize incoming + requests. Preserve renewal, revocation, expiry and the session ceiling. Do not reject a valid token-only request merely because it is outside the session union. Joining FETCH follows the named subscription's live authorization and ends with @@ -32,6 +34,8 @@ Decisions settled while planning (2026-10-09): request-token setter/acceptor. - **Structure only.** Finding new gaps is not the goal, but any request type the refactor shows was ungated gets a regression test in the same PR. + Initial verification already consumes request capacity and observes + cancellation in the request-token hook; those fixes must not wait here. Today `auth::Gate` is built inside each handler: the IETF publisher's SUBSCRIBE, standalone FETCH, and TRACK_STATUS; the IETF subscriber; and the diff --git a/quest/m1/auth/request-token-decode.md b/quest/m1/auth/request-token-decode.md deleted file mode 100644 index 8f84b8a2b8e..00000000000 --- a/quest/m1/auth/request-token-decode.md +++ /dev/null @@ -1,63 +0,0 @@ -# [S] A token on a request decodes by the draft's rules - -## Goal - -The `AUTHORIZATION TOKEN` parameter (`0x03`) on a moq-transport request -(SUBSCRIBE, REQUEST_UPDATE, PUBLISH, FETCH, PUBLISH_NAMESPACE, -SUBSCRIBE_NAMESPACE, TRACK_STATUS, or any other request that carries -parameters) decodes with the same structure and rules as the SETUP option. -A token the draft says must close the session closes it with the draft's -code; a `USE_VALUE` token is read and, for now, grants nothing, so a request -outside the session grant is still refused `UNAUTHORIZED`. Today every -request decodes the key and ignores it. - -## Plan - -[#5107](https://github.com/moq-dev/moq/pull/5107) carves this from -[#4675](https://github.com/moq-dev/moq/pull/4675) so it lands first; [Request tokens](/quest/m1/auth/request-token.md) then gives a -`USE_VALUE` token meaning. The decode needs neither [Relay -tokens](/quest/m1/auth/relay-refresh.md) nor a lease. - -- Decode with the SETUP option's structure and rules - (`rs/moq-net/src/ietf/token.rs`, `js/net/src/ietf/token.ts`): `USE_VALUE` yields the token. `REGISTER` closes the session with - `AUTH_TOKEN_CACHE_OVERFLOW` (0x13): we - advertise no `MAX_AUTH_TOKEN_CACHE_SIZE`, so the limit is 0, and draft-21 - §8.9 says overflowing it MUST close the session. Only SETUP falls back to - USE_VALUE (§9.1.4). `DELETE` or `USE_ALIAS` closes with - `UNKNOWN_AUTH_TOKEN_ALIAS` (0x17): a cache size of 0 "prohibits the use - of token Aliases" (§9.1.3), so no alias is ever registered. §8.9 says to - reject the message, but 0x17 exists only as a Session Termination Code - (§12.2), not a Request Error Code. Record that close as a deviation - under "moq-transport" in `doc/concept/standard.md`. A token structure that does not - decode closes with `KEY_VALUE_FORMATTING_ERROR`. Paul Gregoire's - validator checks the REGISTER and malformed-token codes (decided - 2026-10-04; aliases decided 2026-10-05). Both decoder families change: the strict - `decode_params!` path, where each request reads the repeatable key into an - ignored `Vec`, and draft-14's `Parameters::skip`, which consumes it - unread. `js/net` keeps every instance in `Parameters` and reads none. - Every instance decodes by these rules, and a repeat stays accepted, as - the drafts allow; decoding 0x03 as an `Option` would refuse it. -- The SETUP option's `DELETE` and `USE_ALIAS` move from `PROTOCOL_VIOLATION` - to `UNKNOWN_AUTH_TOKEN_ALIAS` too, so both paths close the same way. -- Build on #5028's per-draft parameter decode (merged), which reworked the - same per-message decode. -- 0x13 and 0x17 join the shared session registry: `SessionError` - (`rs/moq-net/src/error.rs`) and `SessionCode` (`js/net/src/error.ts`). - That registry is moq-lite's too, and lite codes below 32 carry - moq-transport's meaning, so add both rows to the Session Error Codes table - in `drafts/draft-lcurley-moq-lite.md` and to the - `session_codes_round_trip` test. No per-version mapping. That adds two - codes to moq-lite's wire registry. -- `js/net` mirrors the decode. -- Tests, one legacy and one strict draft, Rust and JS: a `REGISTER` token on - a request closes with `AUTH_TOKEN_CACHE_OVERFLOW`, a `DELETE` or - `USE_ALIAS` one with `UNKNOWN_AUTH_TOKEN_ALIAS`, a malformed request token - with `KEY_VALUE_FORMATTING_ERROR`, and a `USE_VALUE` token on a request - outside the session grant is still refused `UNAUTHORIZED`. - -Public API: none beyond the two session codes. Wire: session codes 0x13 and -0x17 are named; the parameter already exists in every supported draft. - -## Related - -- [Request tokens](/quest/m1/auth/request-token.md) - gives a decoded request token its grant diff --git a/quest/m1/auth/request-token.md b/quest/m1/auth/request-token.md index b75969c524f..99ca31f5701 100644 --- a/quest/m1/auth/request-token.md +++ b/quest/m1/auth/request-token.md @@ -1,119 +1,124 @@ -# [L] A token on a request authorizes that request +# [L] An experimental Rust hook verifies a request token ## Goal -A moq-transport peer authorizes a SUBSCRIBE or PUBLISH_NAMESPACE with the -`AUTHORIZATION TOKEN` parameter (`0x03`) on that request, and refreshes it in -band with the draft's update: SUBSCRIBE_UPDATE for a subscription on drafts 14 -and 15, which cannot update a namespace, and REQUEST_UPDATE for either from -16. A request is authorized -by the session's grant first; when that does not cover it, by the token on the -request; with neither it is refused `UNAUTHORIZED`. The token's grant covers -only the request it rode on, never joins the session union, and ends with that -request. A joining FETCH can use its named subscription's authorization; -it does not independently admit a token. On other requests the token is ignored. +Rust applications can opt into verifying a moq-transport token on SUBSCRIBE +or PUBLISH_NAMESPACE and renewing that request's grant in band. This is a +temporary interoperability hook, not a complete authentication system or +relay feature. The application supplies verification and policy. No request +token becomes session authority, and a peer's AUTH_OK cannot authorize its +incoming requests. -## Plan - -[#5148](https://github.com/moq-dev/moq/pull/5148) builds this, carrying -Kyle Sletmoe's [#4675](https://github.com/moq-dev/moq/pull/4675) forward on a -branch maintainers can push to. Decided 2026-10-09: an external moq-transport -deployment needs per-request tokens with in-band renewal, so this quest keeps -its full scope, reversing the 2026-10-08 shrink. The pieces that stand alone -land first as the quests under Required; #5148 stacks on both. - -Decided, so review does not relitigate them: - -- **Admission.** A request the session grant covers is served without - verifying its token. Otherwise the token becomes an `auth::Request` on the - session's `auth::Handle`, marked with the request's path and kind, and the - acceptor's `Grant` is checked against that request alone. With no - `requests()` consumer a non-empty token is refused `Unsupported`. -- **Scope.** Honored on SUBSCRIBE, PUBLISH_NAMESPACE, and an update renewing - one, SUBSCRIBE_UPDATE included; ignored on every other request (2026-10-05). -- **Joining FETCH** (decided 2026-10-10). A join inherits its named - subscription's effective authorization, confined to the pinned track and - allowed range. Observe live renewal, revoke, expiry and the session ceiling; - a dependent join ends when its subscription ends and cannot prolong that - grant. Do not clone a one-time grant snapshot or add it to the session union. - Standalone FETCH tokens remain ignored. Test token-only joins on drafts - 14-19 and their termination, plus native fill on newer drafts. This fixes - #5148's saved-namespace join constructing a session-only gate after a - request-token-only SUBSCRIBE succeeds. -- **Refused renewal** follows drafts 16 section 9.11.1 and 18 section 10.9.1: - REQUEST_ERROR ends only that request, with PUBLISH_DONE `UPDATE_FAILED` for - a subscription. A namespace ends with a closed stream from 17, and with - PUBLISH_NAMESPACE_CANCEL on 16, where it shares the control stream (16 - section 9.24). The session stays up and the old grant does not survive. A - lapse or acceptor revoke also ends only that request (2026-10-05). -- **Client credential.** `auth::Handle::set_request_token`, beside session - tokens, with no `Client` methods. moq-tokio's `Connection` owns the token - across reconnects: `Connection::auth()` renews it on a live connection and - `connect::Config::with_request_token` seeds it. Setting a new token - re-presents it on live requests. This owns the request-token slice of - `Connection::auth()`, so it does not wait on - [Relay tokens](/quest/m1/auth/relay-refresh.md): whichever lands first adds - the handle (2026-10-01 Q1 and Q3, 2026-10-05). -- **Update credit.** Draft-19+ advertises and enforces MAX_REQUEST_UPDATES, - closing with TOO_MANY_REQUEST_UPDATES (0x1B); earlier drafts keep a local - guard that ends only the request. 0x1B joins the shared session registry as - [Request-token decode](/quest/m1/auth/request-token-decode.md) does for 0x13 - and 0x17: `SessionCode` in `js/net`, moq-lite's Session Error Codes table, - and `session_codes_round_trip`. The sender keeps one renewal in flight per - subscription on drafts that answer an update. Draft-14 never answers an - accepted SUBSCRIBE_UPDATE, so its sender does not wait for one; test two - successive replacements with no answers. -- **Drafts.** Renewal works on every supported draft, 14 through 16 included - (2026-10-09), tested with SUBSCRIBE_UPDATE on 14 and 15. A namespace renews - from 16, whose REQUEST_UPDATE covers PUBLISH_NAMESPACE. -- `EXPIRED_AUTH_TOKEN` and `MALFORMED_AUTH_TOKEN` are not this quest's; they - land with [Expired token error](/quest/m1/auth/expired-error.md), which - does not block it (2026-10-01 Q4). - -Of what a 2026-10-09 read of #4675 found, #5148 fixes updates answered out of -order behind a pending renewal, a repeated 0x03 refused, duplicated admission -and renewal logic, three parallel `Option`s on `auth::Request`, a request token -silently ignored on moq-lite, an outbound update sniffer, unread -`max_request_updates` and `decode_value`, and a refused renewal overloading -`Error::Unsupported`. Still open in #5148, to fix before it merges: - -- A namespace renews only from draft 17; a token-bearing draft-16 - REQUEST_UPDATE for a PUBLISH_NAMESPACE is still ignored. Implement it - (decided 2026-10-09: draft-16 allows it, and the Drafts decision covers 16), - with a test that a refused one sends PUBLISH_NAMESPACE_CANCEL. Its tokens - also decode by the request rules, in Rust and JS: REGISTER, an alias, or a - malformed structure closes the session, and a repeat is refused - MALFORMED_AUTH_TOKEN (deferred from #5107). +The hook retains renewal, expiry, revocation, and dependent joining FETCH. +Per-request relay integration, JS setters and acceptors, CLI flags, and +bindings are deferred until a consumer needs them. Session AUTH work remains +separate. -Decided 2026-10-09: `set_request_token` keeps the typed `setup::Token` and -the encoder writes only `USE_VALUE`. Alias forms wait for a consumer that -needs them, rather than an untyped bytes API now. - -Open for review: one `requests()` consumer receives both session and request -tokens, so an acceptor written for session tokens also answers request tokens. - -Public API: additive. moq-net gains `auth::RequestKind`, `auth::Scope`, -`auth::Request::scope`, `auth::Handle::set_request_token`, and -`SessionError::TooManyRequestUpdates`, mirrored as a `js/net` `SessionCode`. -moq-tokio gains `Auth`, -`Connection::auth`, `connect::Config::with_request_token`, and -`server::Request::auth`. Wire: MAX_REQUEST_UPDATES (0x08) on draft-19+ and -TOO_MANY_REQUEST_UPDATES (0x1B), which also joins moq-lite's registry; the -parameter already exists in every -supported draft. - -Follow-ups, planned when a consumer needs them: a JS request-token setter and -accept-side `requests()`, a moq-cli `--request-token`, and the setter through -moq-ffi in [Bindings](/quest/m1/auth/bindings.md). - -## Required +## Plan -- [Request-token decode](/quest/m1/auth/request-token-decode.md) - a request - token decodes by the draft's rules and its forbidden forms close the session -- [Setup extensions](/quest/m1/auth/extensions.md) - a side declares which - Setup extensions it offers with one `Extensions` struct +Continue [#5148](https://github.com/moq-dev/moq/pull/5148), which carries +[#4675](https://github.com/moq-dev/moq/pull/4675) forward. Decisions confirmed +2026-10-12: + +- **Scope.** Keep the Rust hook and renewal instead of reducing the work to + decoding or expanding it into relay integration. Interoperability is the + reason to expose it; carrying a token alone does not authenticate it. +- **Verifier API.** Keep one event queue with explicit session/request + variants and distinct acceptance handles. A session-token consumer must + consciously handle or refuse the request variant; an optional `scope()` + on a shared `accept()` is insufficient. The types must distinguish a + session grant advertised to a peer from a locally enforced request grant. + Update existing consumers together, including the relay's explicit refusal + of request verification. This intentionally changes the acceptor API so + existing session handlers cannot silently acquire new security semantics. +- **Admission.** Independently verified local session permission takes + precedence. Only when it does not cover the request is the request token + verified, with the accepted grant checked against that request's path and + kind. Grants received in AUTH_OK describe what the peer allows us to do; + they never skip verification of what the peer asks us to do. Without local + permission or a verified request grant, refuse `UNAUTHORIZED`; without an + application request verifier, refuse a token requiring verification as + `Unsupported`. Malformed token encodings still fail before admission. +- **Lifetime and bounds.** Pending initial verification consumes request + capacity before awaiting the application and observes request/session + cancellation. Withdrawal must cancel pending work and make late answers + ineffective; any verifier work still in flight remains counted until it + ends. Renewal has the same ownership discipline. Coordinate queue + bounds and cancellation with [Relay tokens](/quest/m1/auth/relay-refresh.md) + and #5282, which already adds shared pending-AUTH support; do not defer the + initial-admission fix to the later request-gate refactor. Reuse shared + primitives whichever PR lands first, without approving #5282's separate + API or limit-policy decisions here. +- **Documentation.** Minimal Rust API comments only: experimental hook, + application verification required, no per-request relay integration, and + setter success is not peer authorization. No feature page, guide, or guide + quest. Keep required wire registries synchronized with changed codes. + +Retained decisions: + +- **Wire scope.** Honor tokens on SUBSCRIBE, unsolicited PUBLISH_NAMESPACE, + and their renewal updates; other request tokens grant no authority. The + solicited NAMESPACE response has no request-token slot. Preserve the + decoder rules landed by [#5107](https://github.com/moq-dev/moq/pull/5107), + including session-fatal malformed/forbidden forms and request-level + refusal of repeated identical tokens. SETUP retains its own alias rules. +- **Renewal across drafts.** SUBSCRIBE_UPDATE renews subscriptions on drafts + 14 and 15; REQUEST_UPDATE renews subscriptions and namespaces from 16. + Fix #5148's missing draft-16 namespace renewal, including Rust/JS decoding + and refusal of malformed, forbidden, or repeated tokens on that path. +- **Request grants stay local.** A grant covers only its request, never + joins a session union, and ends with the request. A refused renewal ends + only that request and does not retain the old grant: REQUEST_ERROR and + PUBLISH_DONE `UPDATE_FAILED` for a subscription, a closed namespace stream + from draft 17, or PUBLISH_NAMESPACE_CANCEL on draft 16. Expiry and revocation + also end only the affected request. +- **Joining FETCH.** Inherit the named subscription's live authorization, + confined to its pinned track and allowed range, observing renewal, + revocation, expiry, and the session ceiling. End with that subscription; + neither a cached namespace nor a grant snapshot prolongs authority. + Standalone FETCH gains no request-token authorization. +- **Client credential.** Keep `auth::Handle::set_request_token` and typed + `setup::Token`, encoding only `USE_VALUE`; defer aliases. It is a default + for outgoing requests on the connection, not a token chosen individually + for each request. Replacement renews live eligible requests, and success + means local configuration accepted, not authentication acknowledged. + `Connection::auth()` owns it across reconnects and + `connect::Config::with_request_token` seeds it. Share the persistent handle + and server passthrough with #5282; add no parallel `Client` methods. +- **Update credit.** Advertise and enforce MAX_REQUEST_UPDATES on draft 19+, + with TOO_MANY_REQUEST_UPDATES (0x1B) in the shared Rust/JS session registry + and moq-lite's registry. Earlier drafts keep a local guard that ends only + the request. Keep one renewal in flight where updates receive answers; + draft 14 must permit successive replacements without waiting for answers. +- Typed `EXPIRED_AUTH_TOKEN` and received `MALFORMED_AUTH_TOKEN` errors remain + with [Expired token error](/quest/m1/auth/expired-error.md); preserving the + existing decoder's emitted refusal does not wait on that quest. + +Verification belongs in existing CI suites. Reproduce the audit's covering +peer AUTH_OK with a local verifier that refuses: the verifier must run and +the incoming request must fail. Cover both request kinds and the independent +local-session permission case. Exercise stalled initial verification at the +existing request cap, cancellation, late completion, and capacity release. +Preserve tests for scoped grants, renewal refusal, expiry/revocation, two +draft-14 replacements, draft-16 namespace renewal, and token-only joining +FETCH on drafts 14-19 plus native fill on newer drafts. Keep unsupported +protocols explicit. Run scoped checks, wire-draft validation when applicable, +and cross-language interop for the wire changes. + +Public API: a breaking Rust acceptor event/type distinction, plus the +request-token setter and persistent connection integration. Export only the +types consumers need; do not keep the optional-scope compatibility shape. +JS receives necessary decoder/registry parity, not a request-token API. +Wire: existing 0x03 tokens and draft-specific renewal messages, plus +MAX_REQUEST_UPDATES (0x08) and TOO_MANY_REQUEST_UPDATES (0x1B) for draft 19+; +0x1B also joins moq-lite's registry. No complete authentication scheme is +promised by these mechanisms. ## Related -- [Request leases](/quest/m1/auth/request-lease.md) - moq-relay honors a - request token with a lease of its own +- [#5106](https://github.com/moq-dev/moq/pull/5106) - landed setup extension + configuration; no remaining prerequisite for starting this hook +- [Relay tokens](/quest/m1/auth/relay-refresh.md) - shared pending-AUTH and + persistent connection plumbing, with session verification kept separate +- [Request leases](/quest/m3/auth-request-lease.md) - deferred relay + verification, not a prerequisite for this experimental hook diff --git a/quest/m1/ietf-epochs.md b/quest/m1/ietf-epochs.md index 9a0fa70c822..a59017af587 100644 --- a/quest/m1/ietf-epochs.md +++ b/quest/m1/ietf-epochs.md @@ -73,12 +73,9 @@ Public API: expose negotiation through the existing extension configuration and reuse the epoch model in Rust and JS. Wire: an opt-in IETF identity extension; no changes to unnegotiated published versions or broadcast paths. -## Required - -- [Setup extensions](/quest/m1/auth/extensions.md) - the common configuration for offering extensions - ## Related +- [#5106](https://github.com/moq-dev/moq/pull/5106) - landed the common configuration for offering extensions - [Broadcast epochs](/quest/m0/broadcast-epoch/README.md) - current lite-07 identity and legacy restart behavior - [Announcement takeover](/quest/m1/announce-takeover.md) - shared continuity model and lite-07 operation - [IETF takeover](/quest/m1/ietf-takeover.md) - independently advertised continuation of a claim-served instance diff --git a/quest/m2/ietf-malformed-close.md b/quest/m2/ietf-malformed-close.md index fded000f6bf..69795632217 100644 --- a/quest/m2/ietf-malformed-close.md +++ b/quest/m2/ietf-malformed-close.md @@ -53,8 +53,8 @@ Decided with the maintainer on 2026-10-04 and 2026-10-05: in drafts 18 and 21. That registry is moq-lite's too, and lite codes below 32 carry moq-transport's meaning, so add the rows to the Session Error Codes table in `drafts/draft-lcurley-moq-lite.md` and to - `session_codes_round_trip`, as [Request-token - decode](/quest/m1/auth/request-token-decode.md) does for 0x13 and 0x17. No + `session_codes_round_trip`, as the merged + [#5107](https://github.com/moq-dev/moq/pull/5107) did for 0x13 and 0x17. No per-version mapping. Where a code is a real burden, the PROTOCOL_VIOLATION fallback in the Goal applies. - **Regression tests, not a CI job for the validator.** Add one session or @@ -72,8 +72,8 @@ Decided with the maintainer on 2026-10-04 and 2026-10-05: now closes the session. - **Mirror in `js/net/src/ietf`** in the same PR. - Request tokens are out of scope here. AUTH_TOKEN_CACHE_OVERFLOW and the - request-token decode belong to [Request-token - decode](/quest/m1/auth/request-token-decode.md). + request-token decode landed in + [#5107](https://github.com/moq-dev/moq/pull/5107). Where each case lives, mapped on 2026-10-04 (paths under `rs/moq-net/src/`): @@ -126,5 +126,5 @@ behaviour moves closer to the drafts. ## Related -- [Request-token decode](/quest/m1/auth/request-token-decode.md) - owns request-token decode and AUTH_TOKEN_CACHE_OVERFLOW +- [#5107](https://github.com/moq-dev/moq/pull/5107) - landed request-token decode and AUTH_TOKEN_CACHE_OVERFLOW - [moq-transport request codes](/quest/m2/ietf-request-codes.md) - the request-level half of the same validator report diff --git a/quest/m3/README.md b/quest/m3/README.md index 1e0f42fbd3e..862d9368bab 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -42,6 +42,7 @@ m4's upstream waits in here and moved m2 work with no consumer. - [Conan remote](/quest/m3/cpp-conan.md) - a remote we own serves the same tarball to `conan install` - [Linux OBS GPU input](/quest/m3/obs-linux-gpu.md) - publish OBS compositor frames without CPU readback on a validated Linux graphics/encoder combination - [Routing cost domains](/quest/m3/routing-cost-domains.md) - design operator boundaries and policy without adding incomparable costs +- [Relay request tokens](/quest/m3/auth-request-lease.md) - verify a request token with its own lease when a consumer needs relay integration - [moq-transport cluster peers](/quest/m3/ietf-cluster-peers.md) - the cluster draft carries the route layer, so moq-transport relays peer again, once a moq-transport relay operator asks - [The moq.pro mesh runs lite-07](/quest/m3/lite07-mesh.md) - the deployment that makes the exemption dead code - [Drop the hidden cluster exemption](/quest/m3/hidden-exemption.md) - relays stop forcing hidden broadcasts on cluster peers once every peer opts in on the wire diff --git a/quest/m1/auth/request-lease.md b/quest/m3/auth-request-lease.md similarity index 71% rename from quest/m1/auth/request-lease.md rename to quest/m3/auth-request-lease.md index e7b88055d95..e5b75dda5e9 100644 --- a/quest/m1/auth/request-lease.md +++ b/quest/m3/auth-request-lease.md @@ -4,14 +4,22 @@ moq-relay verifies a moq-transport request token through `moq_auth::Client`, each request getting a lease of its own that is never counted as a session -grant and ends when the request ends. Until this lands the relay has no -`requests()` consumer, so it refuses every request token `Unsupported`, even -once [Request tokens](/quest/m1/auth/request-token.md) merges. +grant and ends when the request ends. Until this lands the relay explicitly +refuses request-token verification as `Unsupported`, even if it consumes +session-token events and once [Request tokens](/quest/m1/auth/request-token.md) +merges. Requests independently covered by locally verified session permission +do not require request-token verification. ## Plan +Deferred on 2026-10-12 until a consumer needs relay request verification. +This is not required for the experimental Rust hook or the session AUTH epic. +Revisit the design against moq-transport's authentication model at that point; +the lease integration below is future work, not a capability of the hook. + An application acceptor built on moq-net answers request tokens through -`requests()` itself and does not need this; it is the relay's half of +the explicit request variant of `requests()` itself and does not need this; +it is the relay's half of [Request tokens](/quest/m1/auth/request-token.md). - A per-request call on `moq_auth::Client`, not the `Client::attach` that