use affinidi_tdk::secrets_resolver::errors::SecretsResolverError;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use tracing::{debug, warn};
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("configuration error: {0}")]
Config(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("store error: {0}")]
Store(#[from] fjall::Error),
#[error("serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("internal error: {0}")]
Internal(String),
#[error("secret store error: {0}")]
SecretStore(String),
#[error("not found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("secrets error: {0}")]
Secrets(#[from] SecretsResolverError),
#[error("authentication error: {0}")]
Authentication(String),
#[error("unauthorized: {0}")]
Unauthorized(String),
#[error("forbidden: {0}")]
Forbidden(String),
#[error("step-up required: {0}")]
StepUpRequired(String),
#[error("validation error: {0}")]
Validation(String),
#[error("request is missing required Trust-Task header")]
TrustTaskMissing,
#[error("Trust-Task header does not match handler (expected {expected})")]
TrustTaskMismatch {
expected: String,
received: Option<String>,
},
#[error("malformed Trust-Task identifier: {0}")]
TrustTaskMalformed(String),
#[error("Idempotency-Key conflict: same key, different request body")]
IdempotencyKeyConflict,
#[error("invalid pagination cursor")]
InvalidCursor,
#[error("{message}")]
ServiceError { status: StatusCode, message: String },
#[error("{operation} failed: {source}")]
Vsock {
operation: &'static str,
#[source]
source: std::io::Error,
},
}
impl AppError {
pub fn vsock(operation: &'static str) -> impl FnOnce(std::io::Error) -> AppError {
move |source| AppError::Vsock { operation, source }
}
}
impl From<crate::auth::backend::AuthError> for AppError {
fn from(e: crate::auth::backend::AuthError) -> Self {
use crate::auth::backend::AuthError as A;
match e {
A::Forbidden | A::DidMethodRejected => AppError::Forbidden(e.to_string()),
A::PendingChallengeLimitReached => AppError::Validation(e.to_string()),
A::SessionNotFound
| A::SessionStateMismatch
| A::ChallengeMismatch
| A::ChallengeExpired
| A::SignerMismatch
| A::StaleMessage
| A::RefreshTokenInvalid
| A::RefreshTokenExpired => AppError::Authentication(e.to_string()),
A::AttestationFailed(msg) => AppError::Internal(format!("tee attestation: {msg}")),
A::Internal(msg) => AppError::Internal(msg),
}
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let status = match &self {
AppError::Config(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::Store(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::Serialization(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::SecretStore(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::NotFound(_) => StatusCode::NOT_FOUND,
AppError::Conflict(_) => StatusCode::CONFLICT,
AppError::Secrets(_) => StatusCode::INTERNAL_SERVER_ERROR,
AppError::Authentication(_) => StatusCode::UNAUTHORIZED,
AppError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
AppError::Forbidden(_) => StatusCode::FORBIDDEN,
AppError::StepUpRequired(_) => StatusCode::FORBIDDEN,
AppError::Validation(_) => StatusCode::BAD_REQUEST,
AppError::TrustTaskMissing => StatusCode::BAD_REQUEST,
AppError::TrustTaskMismatch { .. } => StatusCode::UNSUPPORTED_MEDIA_TYPE,
AppError::TrustTaskMalformed(_) => StatusCode::BAD_REQUEST,
AppError::IdempotencyKeyConflict => StatusCode::UNPROCESSABLE_ENTITY,
AppError::InvalidCursor => StatusCode::BAD_REQUEST,
AppError::ServiceError { status, .. } => *status,
AppError::Vsock { .. } => StatusCode::INTERNAL_SERVER_ERROR,
};
if status.is_server_error() {
warn!(status = %status.as_u16(), error = %self, "server error");
} else {
debug!(status = %status.as_u16(), error = %self, "client error");
}
let body = match &self {
AppError::TrustTaskMissing => serde_json::json!({
"error": "TrustTaskMissing",
"message": self.to_string(),
}),
AppError::TrustTaskMismatch { expected, received } => serde_json::json!({
"error": "TrustTaskMismatch",
"message": self.to_string(),
"expected": expected,
"received": received,
}),
AppError::TrustTaskMalformed(value) => serde_json::json!({
"error": "TrustTaskMalformed",
"message": self.to_string(),
"received": value,
}),
AppError::IdempotencyKeyConflict => serde_json::json!({
"error": "IdempotencyKeyConflict",
"message": self.to_string(),
}),
AppError::StepUpRequired(msg) => serde_json::json!({
"error": "step_up_required",
"message": msg,
"requiredAcr": "aal2",
}),
_ => serde_json::json!({ "error": self.to_string() }),
};
(status, axum::Json(body)).into_response()
}
}
pub fn key_derivation_error(msg: impl Into<String>) -> AppError {
AppError::ServiceError {
status: StatusCode::BAD_REQUEST,
message: format!("key derivation error: {}", msg.into()),
}
}
pub fn bad_gateway_error(msg: impl Into<String>) -> AppError {
AppError::ServiceError {
status: StatusCode::BAD_GATEWAY,
message: format!("bad gateway: {}", msg.into()),
}
}
pub fn tee_attestation_error(msg: impl Into<String>) -> AppError {
AppError::ServiceError {
status: StatusCode::SERVICE_UNAVAILABLE,
message: format!("TEE attestation error: {}", msg.into()),
}
}