1use affinidi_tdk::secrets_resolver::errors::SecretsResolverError;
2use axum::http::StatusCode;
3use axum::response::{IntoResponse, Response};
4use tracing::{debug, warn};
5
6#[derive(Debug, thiserror::Error)]
7pub enum AppError {
8 #[error("configuration error: {0}")]
9 Config(String),
10
11 #[error("io error: {0}")]
12 Io(#[from] std::io::Error),
13
14 #[error("store error: {0}")]
15 Store(#[from] fjall::Error),
16
17 #[error("serialization error: {0}")]
18 Serialization(#[from] serde_json::Error),
19
20 #[error("internal error: {0}")]
21 Internal(String),
22
23 #[error("secret store error: {0}")]
24 SecretStore(String),
25
26 #[error("not found: {0}")]
27 NotFound(String),
28
29 #[error("conflict: {0}")]
30 Conflict(String),
31
32 #[error("secrets error: {0}")]
33 Secrets(#[from] SecretsResolverError),
34
35 #[error("authentication error: {0}")]
36 Authentication(String),
37
38 #[error("unauthorized: {0}")]
39 Unauthorized(String),
40
41 #[error("forbidden: {0}")]
42 Forbidden(String),
43
44 #[error("step-up required: {0}")]
56 StepUpRequired(String),
57
58 #[error("validation error: {0}")]
59 Validation(String),
60
61 #[error("request is missing required Trust-Task header")]
66 TrustTaskMissing,
67
68 #[error("Trust-Task header does not match handler (expected {expected})")]
73 TrustTaskMismatch {
74 expected: String,
75 received: Option<String>,
76 },
77
78 #[error("malformed Trust-Task identifier: {0}")]
82 TrustTaskMalformed(String),
83
84 #[error("Idempotency-Key conflict: same key, different request body")]
90 IdempotencyKeyConflict,
91
92 #[error("invalid pagination cursor")]
99 InvalidCursor,
100
101 #[error("resource limit exceeded: {0}")]
109 ResourceExhausted(String),
110
111 #[error("{message}")]
114 ServiceError { status: StatusCode, message: String },
115
116 #[error("{operation} failed: {source}")]
122 Vsock {
123 operation: &'static str,
124 #[source]
125 source: std::io::Error,
126 },
127}
128
129impl AppError {
130 pub fn vsock(operation: &'static str) -> impl FnOnce(std::io::Error) -> AppError {
135 move |source| AppError::Vsock { operation, source }
136 }
137}
138
139impl From<crate::auth::backend::AuthError> for AppError {
156 fn from(e: crate::auth::backend::AuthError) -> Self {
157 use crate::auth::backend::AuthError as A;
158 match e {
159 A::Forbidden | A::DidMethodRejected => AppError::Forbidden(e.to_string()),
160 A::PendingChallengeLimitReached => AppError::Validation(e.to_string()),
161 A::SessionNotFound
162 | A::SessionStateMismatch
163 | A::ChallengeMismatch
164 | A::ChallengeExpired
165 | A::SignerMismatch
166 | A::StaleMessage
167 | A::RefreshTokenInvalid
168 | A::RefreshTokenExpired => AppError::Authentication(e.to_string()),
169 A::AttestationFailed(msg) => AppError::Internal(format!("tee attestation: {msg}")),
170 A::Internal(msg) => AppError::Internal(msg),
171 }
172 }
173}
174
175impl IntoResponse for AppError {
176 fn into_response(self) -> Response {
177 let status = match &self {
178 AppError::Config(_) => StatusCode::INTERNAL_SERVER_ERROR,
179 AppError::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
180 AppError::Store(_) => StatusCode::INTERNAL_SERVER_ERROR,
181 AppError::Serialization(_) => StatusCode::INTERNAL_SERVER_ERROR,
182 AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
183 AppError::SecretStore(_) => StatusCode::INTERNAL_SERVER_ERROR,
184 AppError::NotFound(_) => StatusCode::NOT_FOUND,
185 AppError::Conflict(_) => StatusCode::CONFLICT,
186 AppError::Secrets(_) => StatusCode::INTERNAL_SERVER_ERROR,
187 AppError::Authentication(_) => StatusCode::UNAUTHORIZED,
188 AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
189 AppError::Forbidden(_) => StatusCode::FORBIDDEN,
190 AppError::StepUpRequired(_) => StatusCode::FORBIDDEN,
191 AppError::Validation(_) => StatusCode::BAD_REQUEST,
192 AppError::TrustTaskMissing => StatusCode::BAD_REQUEST,
193 AppError::TrustTaskMismatch { .. } => StatusCode::UNSUPPORTED_MEDIA_TYPE,
194 AppError::TrustTaskMalformed(_) => StatusCode::BAD_REQUEST,
195 AppError::IdempotencyKeyConflict => StatusCode::UNPROCESSABLE_ENTITY,
196 AppError::InvalidCursor => StatusCode::BAD_REQUEST,
197 AppError::ResourceExhausted(_) => StatusCode::SERVICE_UNAVAILABLE,
198 AppError::ServiceError { status, .. } => *status,
199 AppError::Vsock { .. } => StatusCode::INTERNAL_SERVER_ERROR,
200 };
201
202 if status.is_server_error() {
203 warn!(status = %status.as_u16(), error = %self, "server error");
204 } else {
205 debug!(status = %status.as_u16(), error = %self, "client error");
206 }
207
208 let body = match &self {
213 AppError::TrustTaskMissing => serde_json::json!({
214 "error": "TrustTaskMissing",
215 "message": self.to_string(),
216 }),
217 AppError::TrustTaskMismatch { expected, received } => serde_json::json!({
218 "error": "TrustTaskMismatch",
219 "message": self.to_string(),
220 "expected": expected,
221 "received": received,
222 }),
223 AppError::TrustTaskMalformed(value) => serde_json::json!({
224 "error": "TrustTaskMalformed",
225 "message": self.to_string(),
226 "received": value,
227 }),
228 AppError::IdempotencyKeyConflict => serde_json::json!({
229 "error": "IdempotencyKeyConflict",
230 "message": self.to_string(),
231 }),
232 AppError::StepUpRequired(msg) => serde_json::json!({
233 "error": "step_up_required",
234 "message": msg,
235 "requiredAcr": "aal2",
236 }),
237 _ => serde_json::json!({ "error": self.to_string() }),
238 };
239 (status, axum::Json(body)).into_response()
240 }
241}
242
243pub fn key_derivation_error(msg: impl Into<String>) -> AppError {
245 AppError::ServiceError {
246 status: StatusCode::BAD_REQUEST,
247 message: format!("key derivation error: {}", msg.into()),
248 }
249}
250
251pub fn bad_gateway_error(msg: impl Into<String>) -> AppError {
253 AppError::ServiceError {
254 status: StatusCode::BAD_GATEWAY,
255 message: format!("bad gateway: {}", msg.into()),
256 }
257}
258
259pub fn tee_attestation_error(msg: impl Into<String>) -> AppError {
261 AppError::ServiceError {
262 status: StatusCode::SERVICE_UNAVAILABLE,
263 message: format!("TEE attestation error: {}", msg.into()),
264 }
265}