From b22f0ecaa12552b15aa2b1bf08e3d4d71a74b4c9 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 14 Aug 2026 23:00:49 +0200 Subject: [PATCH] docs(swarm): trim the respond module doc under the 30-line block lint The discovery narrative belongs in the PR body, which carries it. What stays at the line is what the code cannot say: that issuer_account must be absent and why reaching for Token::new_user reintroduces the bug, the BASE32HEX-vs-BASE32 distinction, and that nats-jwt is a test oracle rather than a runtime dependency. --- swarm-nats-auth/src/respond.rs | 58 +++++++++++++--------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/swarm-nats-auth/src/respond.rs b/swarm-nats-auth/src/respond.rs index 8234991f..2d0162aa 100644 --- a/swarm-nats-auth/src/respond.rs +++ b/swarm-nats-auth/src/respond.rs @@ -1,44 +1,30 @@ //! Minting the reply the server expects on `msg.reply`. //! -//! Two JWTs are involved and **neither** can be produced by `nats-jwt`, for -//! the same underlying reason: its `Claims` struct has no `aud`, and both of -//! these need one. +//! Both JWTs are hand-built because neither is expressible through +//! `nats-jwt`: its `Claims` has no `aud`, which the `authorization_response` +//! wrapper needs (the asking server's id, so a captured reply cannot be +//! replayed elsewhere in the cluster) and the user token needs (the account +//! name). Its `IntoNatsClaims` also returns a closed +//! `NatsClaims { User, Account }`. //! -//! * the **`authorization_response`** wrapper — `aud` is the asking server's -//! id, so a captured reply cannot be replayed at a different server in the -//! cluster. Its `IntoNatsClaims` trait also returns the closed enum -//! `NatsClaims { User, Account }`, so the claim type is inexpressible -//! through it twice over. -//! * the **user JWT** it carries — in **server-config mode** (which is what -//! `nix/host-modules/swarm-nats.nix` renders: `accounts { AUTH, APP }`, -//! no operator), `aud` is the *account name* the admitted client lands in, -//! and `issuer_account` must be **absent**. `nats_jwt::Token::new_user` -//! always sets `issuer_account` and can express no `aud` at all. +//! ⛔ **`issuer_account` must be ABSENT from the user token.** It is an +//! operator-mode field, and `nix/host-modules/swarm-nats.nix` renders +//! *server-config* mode, where its mere presence makes the server refuse the +//! client — `Error non operator mode account "AUTH": attempted to use +//! issuer_account`, with the responder having answered `granted=true`. +//! `nats_jwt::Token::new_user` always sets it, which is why reaching for that +//! constructor again would reintroduce the bug. //! -//! 🩸 That second one was found by running the thing, not by reading it. The -//! responder answered `granted=true` and the server still refused the client, -//! logging `Error non operator mode account "AUTH": attempted to use -//! issuer_account`. Every unit test passed throughout: they assert the shape -//! this module *intends*, and the field that broke it was one nobody had -//! reason to assert was missing. `issuer_account` is an operator-mode field — -//! it names the account when a *signing* key rather than the account identity -//! key signed the token — and in config mode its mere presence is fatal. +//! Signing follows `nats-jwt`'s own `sign()`: serialise the claims with `jti` +//! **empty**, sha256 that, `BASE32HEX_NOPAD` the digest into `jti`, +//! re-serialise, sign `"."` with the account +//! key. ⚠️ `BASE32HEX`, not `BASE32` — one word, and the only symptom is a +//! token the server rejects without saying why. //! -//! So both are hand-built here, following the algorithm read out of -//! `nats-jwt`'s own `sign()`: serialise the claims with `jti` **empty**, -//! sha256 that, `BASE32HEX_NOPAD` the digest into `jti`, re-serialise, and -//! sign `"."` with the account key. -//! -//! ⚠️ `BASE32HEX`, not `BASE32`. That single word is the kind of thing that -//! produces a token the server rejects with no useful reason, and it is -//! copied from the reference implementation rather than from memory — which -//! is also why [`tests::hand_built_matches_the_reference`] exists: it builds -//! a *user* token both ways and asserts byte equality, so this encoder is -//! checked against `nats-jwt` on the shape that crate does model. Trusting it -//! on the shapes `nats-jwt` does **not** model has to rest on something -//! better than my reading of its source. `nats-jwt` is therefore a -//! **dev-dependency**: it is this module's test oracle, not part of the path -//! that runs in production. +//! `nats-jwt` is therefore a **dev-dependency, not a runtime one**: it is this +//! module's test oracle. [`tests::hand_built_matches_the_reference`] builds a +//! user token both ways and requires byte equality, so the encoder is checked +//! against the reference on the one shape that crate does model. use data_encoding::{BASE32HEX_NOPAD, BASE64URL_NOPAD}; use nkeys::KeyPair;