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("{message}")]
104 ServiceError { status: StatusCode, message: String },
105
106 #[error("{operation} failed: {source}")]
112 Vsock {
113 operation: &'static str,
114 #[source]
115 source: std::io::Error,
116 },
117}
118
119impl AppError {
120 pub fn vsock(operation: &'static str) -> impl FnOnce(std::io::Error) -> AppError {
125 move |source| AppError::Vsock { operation, source }
126 }
127}
128
129impl From<crate::auth::backend::AuthError> for AppError {
146 fn from(e: crate::auth::backend::AuthError) -> Self {
147 use crate::auth::backend::AuthError as A;
148 match e {
149 A::Forbidden | A::DidMethodRejected => AppError::Forbidden(e.to_string()),
150 A::PendingChallengeLimitReached => AppError::Validation(e.to_string()),
151 A::SessionNotFound
152 | A::SessionStateMismatch
153 | A::ChallengeMismatch
154 | A::ChallengeExpired
155 | A::SignerMismatch
156 | A::StaleMessage
157 | A::RefreshTokenInvalid
158 | A::RefreshTokenExpired => AppError::Authentication(e.to_string()),
159 A::AttestationFailed(msg) => AppError::Internal(format!("tee attestation: {msg}")),
160 A::Internal(msg) => AppError::Internal(msg),
161 }
162 }
163}
164
165impl IntoResponse for AppError {
166 fn into_response(self) -> Response {
167 let status = match &self {
168 AppError::Config(_) => StatusCode::INTERNAL_SERVER_ERROR,
169 AppError::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
170 AppError::Store(_) => StatusCode::INTERNAL_SERVER_ERROR,
171 AppError::Serialization(_) => StatusCode::INTERNAL_SERVER_ERROR,
172 AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
173 AppError::SecretStore(_) => StatusCode::INTERNAL_SERVER_ERROR,
174 AppError::NotFound(_) => StatusCode::NOT_FOUND,
175 AppError::Conflict(_) => StatusCode::CONFLICT,
176 AppError::Secrets(_) => StatusCode::INTERNAL_SERVER_ERROR,
177 AppError::Authentication(_) => StatusCode::UNAUTHORIZED,
178 AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
179 AppError::Forbidden(_) => StatusCode::FORBIDDEN,
180 AppError::StepUpRequired(_) => StatusCode::FORBIDDEN,
181 AppError::Validation(_) => StatusCode::BAD_REQUEST,
182 AppError::TrustTaskMissing => StatusCode::BAD_REQUEST,
183 AppError::TrustTaskMismatch { .. } => StatusCode::UNSUPPORTED_MEDIA_TYPE,
184 AppError::TrustTaskMalformed(_) => StatusCode::BAD_REQUEST,
185 AppError::IdempotencyKeyConflict => StatusCode::UNPROCESSABLE_ENTITY,
186 AppError::InvalidCursor => StatusCode::BAD_REQUEST,
187 AppError::ServiceError { status, .. } => *status,
188 AppError::Vsock { .. } => StatusCode::INTERNAL_SERVER_ERROR,
189 };
190
191 if status.is_server_error() {
192 warn!(status = %status.as_u16(), error = %self, "server error");
193 } else {
194 debug!(status = %status.as_u16(), error = %self, "client error");
195 }
196
197 let body = match &self {
202 AppError::TrustTaskMissing => serde_json::json!({
203 "error": "TrustTaskMissing",
204 "message": self.to_string(),
205 }),
206 AppError::TrustTaskMismatch { expected, received } => serde_json::json!({
207 "error": "TrustTaskMismatch",
208 "message": self.to_string(),
209 "expected": expected,
210 "received": received,
211 }),
212 AppError::TrustTaskMalformed(value) => serde_json::json!({
213 "error": "TrustTaskMalformed",
214 "message": self.to_string(),
215 "received": value,
216 }),
217 AppError::IdempotencyKeyConflict => serde_json::json!({
218 "error": "IdempotencyKeyConflict",
219 "message": self.to_string(),
220 }),
221 AppError::StepUpRequired(msg) => serde_json::json!({
222 "error": "step_up_required",
223 "message": msg,
224 "requiredAcr": "aal2",
225 }),
226 _ => serde_json::json!({ "error": self.to_string() }),
227 };
228 (status, axum::Json(body)).into_response()
229 }
230}
231
232pub fn key_derivation_error(msg: impl Into<String>) -> AppError {
234 AppError::ServiceError {
235 status: StatusCode::BAD_REQUEST,
236 message: format!("key derivation error: {}", msg.into()),
237 }
238}
239
240pub fn bad_gateway_error(msg: impl Into<String>) -> AppError {
242 AppError::ServiceError {
243 status: StatusCode::BAD_GATEWAY,
244 message: format!("bad gateway: {}", msg.into()),
245 }
246}
247
248pub fn tee_attestation_error(msg: impl Into<String>) -> AppError {
250 AppError::ServiceError {
251 status: StatusCode::SERVICE_UNAVAILABLE,
252 message: format!("TEE attestation error: {}", msg.into()),
253 }
254}