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    /// The identity is already registered at the target registry.
138    #[error("identity already registered at this registry")]
139    AlreadyRegistered,
140
141    /// The registration rate limit has been exceeded.
142    #[error("registration quota exceeded — try again later")]
143    QuotaExceeded,
144
145    /// A network error occurred during registration.
146    #[error("network error: {0}")]
147    NetworkError(#[source] auths_core::ports::network::NetworkError),
148
149    /// The local DID format is invalid.
150    #[error("invalid DID format: {did}")]
151    InvalidDidFormat {
152        /// The DID that failed validation.
153        did: String,
154    },
155
156    /// Loading the local identity failed.
157    #[error("identity load error: {0}")]
158    IdentityLoadError(#[source] auths_id::error::StorageError),
159
160    /// Reading from the local registry failed.
161    #[error("registry read error: {0}")]
162    RegistryReadError(#[source] auths_id::storage::registry::backend::RegistryError),
163
164    /// Serialization of identity data failed.
165    #[error("serialization error: {0}")]
166    SerializationError(#[source] serde_json::Error),
167}
168
169impl From<auths_core::AgentError> for SetupError {
170    fn from(err: auths_core::AgentError) -> Self {
171        SetupError::CryptoError(err)
172    }
173}
174
175impl From<RegistrationError> for SetupError {
176    fn from(err: RegistrationError) -> Self {
177        SetupError::RegistrationFailed(err)
178    }
179}
180
181impl From<auths_core::ports::network::NetworkError> for RegistrationError {
182    fn from(err: auths_core::ports::network::NetworkError) -> Self {
183        RegistrationError::NetworkError(err)
184    }
185}
186
187impl AuthsErrorInfo for SetupError {
188    fn error_code(&self) -> &'static str {
189        match self {
190            Self::IdentityAlreadyExists { .. } => "AUTHS-E5001",
191            Self::KeychainUnavailable { .. } => "AUTHS-E5002",
192            Self::CryptoError(e) => e.error_code(),
193            Self::WeakPassphrase { .. } => "AUTHS-E5008",
194            Self::StorageError(e) => e.error_code(),
195            Self::GitConfigError(_) => "AUTHS-E5004",
196            Self::InvalidSetupConfig(_) => "AUTHS-E5007",
197            Self::RegistrationFailed(e) => e.error_code(),
198            Self::PlatformVerificationFailed(_) => "AUTHS-E5006",
199        }
200    }
201
202    fn suggestion(&self) -> Option<&'static str> {
203        match self {
204            Self::IdentityAlreadyExists { .. } => {
205                Some("Use `auths id show` to inspect the existing identity")
206            }
207            Self::KeychainUnavailable { .. } => {
208                Some("Run `auths doctor` to diagnose keychain issues")
209            }
210            Self::CryptoError(e) => e.suggestion(),
211            Self::WeakPassphrase { .. } => Some(
212                "Use at least 12 characters with 3 of 4 character classes (lowercase, uppercase, digit, symbol)",
213            ),
214            Self::StorageError(e) => e.suggestion(),
215            Self::GitConfigError(_) => {
216                Some("Ensure Git is configured: git config --global user.name/email")
217            }
218            // The variant message carries the specific guidance; a generic
219            // fix-line under it would only dilute it.
220            Self::InvalidSetupConfig(_) => None,
221            Self::RegistrationFailed(e) => e.suggestion(),
222            Self::PlatformVerificationFailed(_) => Some(
223                "Platform identity verification failed; check your platform credentials and network connectivity",
224            ),
225        }
226    }
227}
228
229impl AuthsErrorInfo for RotationError {
230    fn error_code(&self) -> &'static str {
231        match self {
232            Self::IdentityNotFound { .. } => "AUTHS-E5301",
233            Self::KeyNotFound(_) => "AUTHS-E5302",
234            Self::KeyDecryptionFailed(_) => "AUTHS-E5303",
235            Self::KelHistoryFailed(_) => "AUTHS-E5304",
236            Self::RotationFailed(_) => "AUTHS-E5305",
237            Self::PartialRotation(_) => "AUTHS-E5306",
238            Self::HardwareKeyNotRotatable { .. } => "AUTHS-E5307",
239        }
240    }
241
242    fn suggestion(&self) -> Option<&'static str> {
243        match self {
244            Self::IdentityNotFound { .. } => Some("Run `auths init` to create an identity first"),
245            Self::KeyNotFound(_) => Some("Run `auths key list` to see available keys"),
246            Self::KeyDecryptionFailed(_) => Some("Check your passphrase and try again"),
247            Self::KelHistoryFailed(_) => Some("Run `auths doctor` to check KEL integrity"),
248            Self::RotationFailed(_) => Some(
249                "Key rotation failed; verify your current key is accessible with `auths key list`",
250            ),
251            Self::PartialRotation(_) => {
252                Some("Re-run the rotation with the same new key to complete the keychain write")
253            }
254            Self::HardwareKeyNotRotatable { .. } => Some(
255                "Hardware-backed keys (Secure Enclave / HSM) cannot be rotated in-place; provision a software-backed identity or rotate by creating a new identity",
256            ),
257        }
258    }
259}
260
261impl AuthsErrorInfo for RegistrationError {
262    fn error_code(&self) -> &'static str {
263        match self {
264            Self::AlreadyRegistered => "AUTHS-E5401",
265            Self::QuotaExceeded => "AUTHS-E5402",
266            Self::NetworkError(e) => e.error_code(),
267            Self::InvalidDidFormat { .. } => "AUTHS-E5403",
268            Self::IdentityLoadError(_) => "AUTHS-E5404",
269            Self::RegistryReadError(_) => "AUTHS-E5405",
270            Self::SerializationError(_) => "AUTHS-E5406",
271        }
272    }
273
274    fn suggestion(&self) -> Option<&'static str> {
275        match self {
276            Self::AlreadyRegistered => Some(
277                "This identity is already registered; use `auths id show` to see registration details",
278            ),
279            Self::QuotaExceeded => Some("Wait a few minutes and try again"),
280            Self::NetworkError(e) => e.suggestion(),
281            Self::InvalidDidFormat { .. } => {
282                Some("Run `auths doctor` to check local identity data")
283            }
284            Self::IdentityLoadError(_) => Some("Run `auths doctor` to check local identity data"),
285            Self::RegistryReadError(_) => Some("Run `auths doctor` to check local identity data"),
286            Self::SerializationError(_) => Some("Run `auths doctor` to check local identity data"),
287        }
288    }
289}