Skip to main content

openagent_auth_protocol/
error.rs

1//! Error types for the OpenAgent Core Protocol identity flow.
2//!
3//! Every fallible operation in this crate returns [`AuthProtocolError`].
4//! Variants are designed to be **actionable** — error messages name the
5//! operation, the field, and what went wrong.
6//!
7//! # History
8//!
9//! This crate previously carried two error enums (`AuthProtocolError` and
10//! `OaapError`) that disagreed with each other — which is part of how the
11//! crate came to not compile. There is one now.
12
13use thiserror::Error;
14
15/// Errors arising from Core Protocol handshake operations.
16#[derive(Debug, Error)]
17pub enum AuthProtocolError {
18    /// The protocol version in the received message is not supported.
19    #[error(
20        "unsupported protocol version {received}; this implementation supports version {supported}"
21    )]
22    UnsupportedVersion {
23        /// The version received from the peer.
24        received: u8,
25        /// The version this implementation supports.
26        supported: u8,
27    },
28
29    /// A challenge failed the Section 4 field constraints.
30    #[error("invalid challenge: {reason}")]
31    InvalidChallenge {
32        /// What constraint failed.
33        reason: String,
34    },
35
36    /// A proof failed the Section 15.3 shape checks.
37    #[error("invalid proof: {reason}")]
38    InvalidProof {
39        /// What constraint failed.
40        reason: String,
41    },
42
43    /// The nonce echoed in the proof does not match the challenge.
44    #[error("nonce mismatch: challenge carried {expected}, proof echoed {received}")]
45    NonceMismatch {
46        /// The nonce in the challenge.
47        expected: String,
48        /// The nonce echoed in the proof.
49        received: String,
50    },
51
52    /// The challenge nonce has expired (anti-replay protection).
53    #[error("challenge expired: issued at {issued}, expiry was {expiry}, current time is {now}")]
54    ChallengeExpired {
55        /// When the challenge was issued.
56        issued: String,
57        /// When it expired.
58        expiry: String,
59        /// Current timestamp.
60        now: String,
61    },
62
63    /// A nonce was reused (replay attack detected).
64    #[error("replay detected: nonce {nonce_hex} has already been used in a prior handshake")]
65    ReplayDetected {
66        /// Hex-encoded nonce that was replayed.
67        nonce_hex: String,
68    },
69
70    /// Signature verification failed against the trusted key set.
71    #[error("signature verification failed: {reason}")]
72    SignatureVerificationFailed {
73        /// Why verification failed.
74        reason: String,
75    },
76
77    /// The peer's DID document is structurally invalid or missing required fields.
78    #[error("invalid DID document for {did}: {reason}")]
79    InvalidDidDocument {
80        /// The DID whose document was invalid.
81        did: String,
82        /// What was wrong.
83        reason: String,
84    },
85
86    /// The transport layer failed to send or receive a message.
87    #[error("transport error: {reason}")]
88    TransportFailed {
89        /// Underlying transport error.
90        reason: String,
91    },
92
93    /// A timeout occurred waiting for a response from the peer.
94    #[error("timeout waiting for {step} response after {timeout_secs}s")]
95    Timeout {
96        /// Which protocol step timed out.
97        step: String,
98        /// How long we waited.
99        timeout_secs: u64,
100    },
101
102    /// Serialization or deserialization of a protocol message failed.
103    #[error("message serialization failed for {message_type}: {reason}")]
104    SerializationFailed {
105        /// The message type being (de)serialized.
106        message_type: String,
107        /// Underlying failure reason.
108        reason: String,
109    },
110
111    /// The challenge expiry duration exceeds the maximum allowed (300 seconds,
112    /// per Section 7 of the Core Protocol Specification).
113    #[error("challenge expiry {requested_secs}s exceeds maximum allowed {max_secs}s")]
114    ExpiryTooLong {
115        /// The requested expiry in seconds.
116        requested_secs: u64,
117        /// The maximum allowed.
118        max_secs: u64,
119    },
120
121    /// An internal failure that is not the peer's fault (e.g. a poisoned
122    /// lock in a session store). Surfaced rather than panicked, per the
123    /// workspace no-panic policy.
124    #[error("internal error: {0}")]
125    Internal(String),
126
127    /// A presented session token does not identify a live session.
128    #[error("session not found: {session_id}")]
129    SessionNotFound {
130        /// The token or identifier presented.
131        session_id: String,
132    },
133
134    /// A session exists but has expired and was removed.
135    #[error("session expired: {session_id}")]
136    SessionExpired {
137        /// The token or identifier presented.
138        session_id: String,
139    },
140}