mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
docs(auth): annotate AuthError variants with WIRE class — doc-at-source for P1+P2
Pairs with c31307d40 (the audit branch's wire mapper + property tests) and Eva's [13] ruling: one wire categorization lives in buzz-relay::auth_wire, and AuthError variants carry doc annotations pointing at it. Doc-only diff; no behavior change. The enum-level docstring spells out the wire-mapping contract — the four AuthErrorWireCategory targets, what each invariant guards (byte-identity on verification-class collapses an existence oracle; Internal must never stringify on the wire), and a pointer at the compile-time exhaustiveness fence in auth_error_wire's match that catches variant-add-then-forget. Per-variant: each variant carries a "WIRE class:" line naming its AuthErrorWireCategory target. Nip98Replay calls out the byte-identity requirement against Nip98Invalid explicitly (community-scoped seen-set presence oracle). Internal calls out the no-stringify rule explicitly (community-prefixed Redis keys can ride the inner String). No intra-doc link to AuthErrorWireCategory: buzz-auth cannot depend on buzz-relay (would cycle), so the type is referenced by name in prose. Validation: - cargo fmt -p buzz-auth --check ✅ - cargo test -p buzz-auth ✅ (40 passed) - cargo clippy -p buzz-auth --no-deps --lib --tests ✅ (one pre-existing warning at nip98_replay.rs:162, not from this diff) - cargo doc -p buzz-auth --no-deps ✅ (zero new doc warnings; two pre-existing broken links in rate_limit.rs/nip98_replay.rs unchanged) - cargo test -p buzz-relay ✅ in isolation; one known-flaky Redis presence test (pubsub_fanout::global_presence_*) intermittently fails in full-suite runs and passes when run alone — same flake Eva flagged on ed33878b7's push. Doc-only diff cannot affect Redis fanout timing. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
co-authored by
Tyler Longwell
parent
7fc43fb391
commit
41602ab4d2
@@ -5,21 +5,53 @@
|
||||
/// Variants are designed to be safe to return to callers without leaking
|
||||
/// internal implementation details. Do **not** include raw token values,
|
||||
/// database contents, or stack traces in error messages.
|
||||
///
|
||||
/// # Wire-mapping contract
|
||||
///
|
||||
/// This enum describes **what happened**; how it renders on the wire is the
|
||||
/// relay's concern. The single sanctioned conversion lives in
|
||||
/// `buzz_relay::auth_wire::auth_error_wire`, which collapses every variant
|
||||
/// into one of four `AuthErrorWireCategory` values:
|
||||
///
|
||||
/// - `AuthFailed` — all verification-class variants (`InvalidSignature`,
|
||||
/// `ChallengeMismatch`, `RelayUrlMismatch`, `EventExpired`, `Nip98Invalid`,
|
||||
/// `Nip98Replay`, `PubkeyMismatch`) MUST be byte-indistinguishable on the
|
||||
/// wire. Distinguishing any pair turns the community-scoped replay seen-set
|
||||
/// or membership state into a presence oracle. See the audit note at
|
||||
/// `RESEARCH/RELAY_REWRITE_AUTH_ERROR_ORACLE_AUDIT.md` (policies P1–P5).
|
||||
/// - `InsufficientScope` / `ChannelAccessDenied` — authorization class,
|
||||
/// remediation-distinct, carry no tenant-scoped detail.
|
||||
/// - `InternalRedacted` — `Internal(_)` MUST NEVER stringify on the wire; the
|
||||
/// inner string can carry community-prefixed Redis keys (existence oracle).
|
||||
/// The construction site logs detail via `tracing::warn!`; the wire sees
|
||||
/// the category only.
|
||||
///
|
||||
/// The exhaustive match in `auth_error_wire` (no wildcard arm) is the
|
||||
/// compile-time fence: adding any variant here fails to compile in
|
||||
/// `buzz-relay` until the wire-class decision is made.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthError {
|
||||
/// The NIP-42 event signature is invalid or the event is structurally malformed.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. Byte-identical with all other verification-class variants.
|
||||
#[error("invalid signature or malformed auth event")]
|
||||
InvalidSignature,
|
||||
|
||||
/// The `challenge` tag in the AUTH event does not match the relay's issued challenge.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. Byte-identical with all other verification-class variants.
|
||||
#[error("challenge mismatch")]
|
||||
ChallengeMismatch,
|
||||
|
||||
/// The `relay` tag in the AUTH event does not match this relay's URL.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. Byte-identical with all other verification-class variants.
|
||||
#[error("relay url mismatch")]
|
||||
RelayUrlMismatch,
|
||||
|
||||
/// The AUTH event's `created_at` timestamp is more than ±60 seconds from now.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. Byte-identical with all other verification-class variants.
|
||||
#[error("auth event timestamp outside ±60s window")]
|
||||
EventExpired,
|
||||
|
||||
@@ -27,20 +59,32 @@ pub enum AuthError {
|
||||
///
|
||||
/// The inner string describes the specific failure (signature, timestamp, URL, etc.)
|
||||
/// and is safe to include in server logs. Do **not** forward raw event content to clients.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. Byte-identical with all other verification-class variants;
|
||||
/// the inner string is log-only.
|
||||
#[error("NIP-98 HTTP Auth verification failed: {0}")]
|
||||
Nip98Invalid(String),
|
||||
|
||||
/// A NIP-98 event with the same id has already been observed within the
|
||||
/// replay-prevention window. The event itself was structurally valid; the
|
||||
/// rejection is on freshness, not validity.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. MUST be byte-indistinguishable from
|
||||
/// `Nip98Invalid` on the wire — distinguishing them turns the
|
||||
/// community-scoped seen-set into a presence oracle on event ids.
|
||||
#[error("NIP-98 replay: event id already seen within window")]
|
||||
Nip98Replay,
|
||||
|
||||
/// The pubkey in the auth event does not match the expected identity.
|
||||
///
|
||||
/// **WIRE class:** `AuthFailed`. Byte-identical with all other verification-class variants.
|
||||
#[error("pubkey mismatch: event pubkey does not match authenticated identity")]
|
||||
PubkeyMismatch,
|
||||
|
||||
/// The authenticated context does not have the required scope for this operation.
|
||||
///
|
||||
/// **WIRE class:** `InsufficientScope`. Authorization-class, remediation-distinct;
|
||||
/// the `required`/`have` fields carry no tenant-scoped detail and are safe on the wire.
|
||||
#[error("insufficient scope: required {required}, have {have:?}")]
|
||||
InsufficientScope {
|
||||
/// The scope that was required.
|
||||
@@ -50,10 +94,17 @@ pub enum AuthError {
|
||||
},
|
||||
|
||||
/// The authenticated user is not a member of the requested channel.
|
||||
///
|
||||
/// **WIRE class:** `ChannelAccessDenied`. Authorization-class, remediation-distinct.
|
||||
#[error("channel access denied")]
|
||||
ChannelAccessDenied,
|
||||
|
||||
/// An unexpected internal error occurred (e.g. a `spawn_blocking` panic).
|
||||
///
|
||||
/// **WIRE class:** `InternalRedacted`. The inner string MUST NEVER appear on the wire —
|
||||
/// it can carry community-prefixed Redis keys, downstream error chains, or other
|
||||
/// existence-oracle surfaces. Log detail at the construction site; the wire sees the
|
||||
/// category only.
|
||||
#[error("internal auth error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user