Skip to main content

auths_sdk/domains/identity/
error.rs

1use auths_core::error::AuthsErrorInfo;
2use thiserror::Error;
3
4/// Errors from identity setup operations (developer, CI, agent).
5///
6/// Usage:
7/// ```ignore
8/// match sdk_result {
9///     Err(SetupError::IdentityAlreadyExists { did }) => { /* reuse or abort */ }
10///     Err(e) => return Err(e.into()),
11///     Ok(result) => { /* success */ }
12/// }
13/// ```
14#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum SetupError {
17    /// An identity already exists at the configured path.
18    #[error("identity already exists: {did}")]
19    IdentityAlreadyExists {
20        /// The DID of the existing identity.
21        did: String,
22    },
23
24    /// The platform keychain is unavailable or inaccessible.
25    #[error("keychain unavailable ({backend}): {reason}")]
26    KeychainUnavailable {
27        /// The keychain backend name (e.g. "macOS Keychain").
28        backend: String,
29        /// The reason the keychain is unavailable.
30        reason: String,
31    },
32
33    /// A cryptographic operation failed.
34    #[error("crypto error: {0}")]
35    CryptoError(#[source] auths_core::AgentError),
36
37    /// The supplied passphrase fails the strength policy. Carries which input
38    /// supplied it so the user knows exactly what to fix — vital in
39    /// `--non-interactive` runs where there is no prompt fallback.
40    #[error("passphrase from {source_name} is too weak: {reason}")]
41    WeakPassphrase {
42        /// The input that supplied the passphrase (e.g. "the AUTHS_PASSPHRASE
43        /// environment variable").
44        source_name: String,
45        /// The policy violation, rendered for display.
46        reason: String,
47    },
48
49    /// A storage operation failed.
50    #[error("storage error: {0}")]
51    StorageError(#[source] crate::error::SdkStorageError),
52
53    /// Setting a git configuration key failed.
54    #[error("git config error: {0}")]
55    GitConfigError(#[source] crate::ports::git_config::GitConfigError),
56
57    /// Setup configuration parameters are invalid.
58    #[error("invalid setup config: {0}")]
59    InvalidSetupConfig(String),
60
61    /// Remote registry registration failed.
62    #[error("registration failed: {0}")]
63    RegistrationFailed(#[source] RegistrationError),
64
65    /// Platform identity verification failed.
66    #[error("platform verification failed: {0}")]
67    PlatformVerificationFailed(String),
68}
69
70/// Errors from identity rotation operations.
71///
72/// Usage:
73/// ```ignore
74/// match rotate_result {
75///     Err(RotationError::KelHistoryFailed(msg)) => { /* no prior events */ }
76///     Err(e) => return Err(e.into()),
77///     Ok(result) => { /* success */ }
78/// }
79/// ```
80#[derive(Debug, Error)]
81#[non_exhaustive]
82pub enum RotationError {
83    /// The identity was not found at the expected path.
84    #[error("identity not found at {path}")]
85    IdentityNotFound {
86        /// The filesystem path where the identity was expected.
87        path: std::path::PathBuf,
88    },
89
90    /// The requested key alias was not found in the keychain.
91    #[error("key not found: {0}")]
92    KeyNotFound(String),
93
94    /// Decrypting the key material failed (e.g. wrong passphrase).
95    #[error("key decryption failed: {0}")]
96    KeyDecryptionFailed(String),
97
98    /// Reading or validating the KEL history failed.
99    #[error("KEL history error: {0}")]
100    KelHistoryFailed(String),
101
102    /// The rotation operation failed.
103    #[error("rotation failed: {0}")]
104    RotationFailed(String),
105
106    /// KEL event was written but the new key could not be persisted to the keychain.
107    /// Recovery: re-run rotation with the same new key to replay the keychain write.
108    #[error(
109        "rotation event committed to KEL but keychain write failed — manual recovery required: {0}"
110    )]
111    PartialRotation(String),
112
113    /// Rotation requires a software-backed key but the current key is hardware-backed (SE/HSM).
114    #[error(
115        "rotation requires a software-backed key; alias '{alias}' is hardware-backed (Secure Enclave) and cannot export the raw key material rotation needs"
116    )]
117    HardwareKeyNotRotatable {
118        /// The alias whose backend refused to export.
119        alias: String,
120    },
121}
122
123/// Errors from remote registry operations.
124///
125/// Usage:
126/// ```ignore
127/// match register_result {
128///     Err(RegistrationError::AlreadyRegistered) => { /* skip */ }
129///     Err(RegistrationError::QuotaExceeded) => { /* retry later */ }
130///     Err(e) => return Err(e.into()),
131///     Ok(outcome) => { /* success */ }
132/// }
133/// ```
134#[derive(Debug, Error)]
135#[non_exhaustive]
136pub enum RegistrationError {
137    /// A registry-dependent verb was invoked with no registry configured.
138    ///
139    /// There is no default: auths is offline-first and every core workflow —
140    /// identity, signing, verification — works without a registry.
141    #[error(
142        "no registry configured. Auths needs no registry: identity, signing and verification are local and git-native, and a signer's KEL reaches a verifier over the same git remote as the code. Pass --registry <url> or set AUTHS_REGISTRY_URL only if you are running one."
143    )]
144    NoRegistryConfigured,
145
146    /// The identity is already registered at the target registry.
147    #[error("identity already registered at this registry")]
148    AlreadyRegistered,
149
150    /// The registration rate limit has been exceeded.
151    #[error("registration quota exceeded — try again later")]
152    QuotaExceeded,
153
154    /// A network error occurred during registration.
155    #[error("network error: {0}")]
156    NetworkError(#[source] auths_core::ports::network::NetworkError),
157
158    /// The local DID format is invalid.
159    #[error("invalid DID format: {did}")]
160    InvalidDidFormat {
161        /// The DID that failed validation.
162        did: String,
163    },
164
165    /// Loading the local identity failed.
166    #[error("identity load error: {0}")]
167    IdentityLoadError(#[source] auths_id::error::StorageError),
168
169    /// Reading from the local registry failed.
170    #[error("registry read error: {0}")]
171    RegistryReadError(#[source] auths_id::storage::registry::backend::RegistryError),
172
173    /// Serialization of identity data failed.
174    #[error("serialization error: {0}")]
175    SerializationError(#[source] serde_json::Error),
176}
177
178impl From<auths_core::AgentError> for SetupError {
179    fn from(err: auths_core::AgentError) -> Self {
180        SetupError::CryptoError(err)
181    }
182}
183
184impl From<RegistrationError> for SetupError {
185    fn from(err: RegistrationError) -> Self {
186        SetupError::RegistrationFailed(err)
187    }
188}
189
190impl From<auths_core::ports::network::NetworkError> for RegistrationError {
191    fn from(err: auths_core::ports::network::NetworkError) -> Self {
192        RegistrationError::NetworkError(err)
193    }
194}
195
196impl AuthsErrorInfo for SetupError {
197    fn error_code(&self) -> &'static str {
198        match self {
199            Self::IdentityAlreadyExists { .. } => "AUTHS-E5001",
200            Self::KeychainUnavailable { .. } => "AUTHS-E5002",
201            Self::CryptoError(e) => e.error_code(),
202            Self::WeakPassphrase { .. } => "AUTHS-E5008",
203            Self::StorageError(e) => e.error_code(),
204            Self::GitConfigError(_) => "AUTHS-E5004",
205            Self::InvalidSetupConfig(_) => "AUTHS-E5007",
206            Self::RegistrationFailed(e) => e.error_code(),
207            Self::PlatformVerificationFailed(_) => "AUTHS-E5006",
208        }
209    }
210
211    fn suggestion(&self) -> Option<&'static str> {
212        match self {
213            Self::IdentityAlreadyExists { .. } => {
214                Some("Use `auths id show` to inspect the existing identity")
215            }
216            Self::KeychainUnavailable { .. } => {
217                Some("Run `auths doctor` to diagnose keychain issues")
218            }
219            Self::CryptoError(e) => e.suggestion(),
220            Self::WeakPassphrase { .. } => Some(
221                "Use at least 12 characters with 3 of 4 character classes (lowercase, uppercase, digit, symbol)",
222            ),
223            Self::StorageError(e) => e.suggestion(),
224            Self::GitConfigError(_) => {
225                Some("Ensure Git is configured: git config --global user.name/email")
226            }
227            // The variant message carries the specific guidance; a generic
228            // fix-line under it would only dilute it.
229            Self::InvalidSetupConfig(_) => None,
230            Self::RegistrationFailed(e) => e.suggestion(),
231            Self::PlatformVerificationFailed(_) => Some(
232                "Platform identity verification failed; check your platform credentials and network connectivity",
233            ),
234        }
235    }
236}
237
238impl AuthsErrorInfo for RotationError {
239    fn error_code(&self) -> &'static str {
240        match self {
241            Self::IdentityNotFound { .. } => "AUTHS-E5301",
242            Self::KeyNotFound(_) => "AUTHS-E5302",
243            Self::KeyDecryptionFailed(_) => "AUTHS-E5303",
244            Self::KelHistoryFailed(_) => "AUTHS-E5304",
245            Self::RotationFailed(_) => "AUTHS-E5305",
246            Self::PartialRotation(_) => "AUTHS-E5306",
247            Self::HardwareKeyNotRotatable { .. } => "AUTHS-E5307",
248        }
249    }
250
251    fn suggestion(&self) -> Option<&'static str> {
252        match self {
253            Self::IdentityNotFound { .. } => Some("Run `auths init` to create an identity first"),
254            Self::KeyNotFound(_) => Some("Run `auths key list` to see available keys"),
255            Self::KeyDecryptionFailed(_) => Some("Check your passphrase and try again"),
256            Self::KelHistoryFailed(_) => Some("Run `auths doctor` to check KEL integrity"),
257            Self::RotationFailed(_) => Some(
258                "Key rotation failed; verify your current key is accessible with `auths key list`",
259            ),
260            Self::PartialRotation(_) => {
261                Some("Re-run the rotation with the same new key to complete the keychain write")
262            }
263            Self::HardwareKeyNotRotatable { .. } => Some(
264                "Hardware-backed keys (Secure Enclave / HSM) cannot be rotated in-place; provision a software-backed identity or rotate by creating a new identity",
265            ),
266        }
267    }
268}
269
270impl AuthsErrorInfo for RegistrationError {
271    fn error_code(&self) -> &'static str {
272        match self {
273            Self::NoRegistryConfigured => "AUTHS-E5400",
274            Self::AlreadyRegistered => "AUTHS-E5401",
275            Self::QuotaExceeded => "AUTHS-E5402",
276            Self::NetworkError(e) => e.error_code(),
277            Self::InvalidDidFormat { .. } => "AUTHS-E5403",
278            Self::IdentityLoadError(_) => "AUTHS-E5404",
279            Self::RegistryReadError(_) => "AUTHS-E5405",
280            Self::SerializationError(_) => "AUTHS-E5406",
281        }
282    }
283
284    fn suggestion(&self) -> Option<&'static str> {
285        match self {
286            Self::NoRegistryConfigured => Some(
287                "Registration is optional — signing and verification need no registry. Pass --registry <url> or set AUTHS_REGISTRY_URL only if you run one",
288            ),
289            Self::AlreadyRegistered => Some(
290                "This identity is already registered; use `auths id show` to see registration details",
291            ),
292            Self::QuotaExceeded => Some("Wait a few minutes and try again"),
293            Self::NetworkError(e) => e.suggestion(),
294            Self::InvalidDidFormat { .. } => {
295                Some("Run `auths doctor` to check local identity data")
296            }
297            Self::IdentityLoadError(_) => Some("Run `auths doctor` to check local identity data"),
298            Self::RegistryReadError(_) => Some("Run `auths doctor` to check local identity data"),
299            Self::SerializationError(_) => Some("Run `auths doctor` to check local identity data"),
300        }
301    }
302}