use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; use miette::Diagnostic; use serde_json::json; use thiserror::Error; #[derive(Debug, Error, Diagnostic)] pub enum AuthzError { #[error("Failed to load policy file `{path}`")] #[diagnostic( code(barycenter::authz::policy_load), help("Check that the file exists and contains valid KDL syntax") )] PolicyLoadError { path: String, #[source] source: std::io::Error, }, #[error("Invalid policy: {0}")] #[diagnostic( code(barycenter::authz::invalid_policy), help("Each policy file must contain valid `resource`, `role`, `rule`, or `grant` KDL nodes") )] InvalidPolicy(String), #[error("Invalid grant: {0}")] #[diagnostic( code(barycenter::authz::invalid_grant), help("Grant syntax: grant \"relation\" on=\"type/id\" to=\"subject_type/id\" (optionally to=\"type/id#relation\")") )] InvalidGrant(String), #[error("Invalid condition expression: {0}")] #[diagnostic( code(barycenter::authz::invalid_condition), help("Supported operators: ==, !=, >, <, >=, <=, &&, ||, !, in. Paths use dot notation (e.g. request.ip)") )] InvalidCondition(String), #[error("Undefined resource type `{0}`")] #[diagnostic( code(barycenter::authz::undefined_resource), help("Define the resource type with: resource \"\" {{ relations {{ ... }} permissions {{ ... }} }}") )] UndefinedResourceType(String), #[error("Undefined role `{0}`")] #[diagnostic( code(barycenter::authz::undefined_role), help("Define the role with: role \"\" {{ permissions {{ ... }} }}") )] UndefinedRole(String), #[error("Cyclic role inheritance detected: {0}")] #[diagnostic( code(barycenter::authz::cyclic_roles), help("Check the `includes` lists in your role definitions for circular references") )] CyclicRoleInheritance(String), #[error("KDL parse error: {0}")] #[diagnostic( code(barycenter::authz::kdl_parse), help("Check your KDL file syntax — see https://kdl.dev for the specification") )] KdlParse(String), #[error("I/O error: {0}")] #[diagnostic(code(barycenter::authz::io))] Io(#[from] std::io::Error), } impl IntoResponse for AuthzError { fn into_response(self) -> Response { let (status, message) = match &self { AuthzError::InvalidPolicy(_) | AuthzError::InvalidGrant(_) | AuthzError::InvalidCondition(_) => (StatusCode::BAD_REQUEST, self.to_string()), _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()), }; let body = json!({ "error": message }); (status, Json(body)).into_response() } }