diff --git a/senseicore/src/error.rs b/senseicore/src/error.rs index 5a7e43e..f12fa35 100644 --- a/senseicore/src/error.rs +++ b/senseicore/src/error.rs @@ -12,6 +12,8 @@ use std::{ io::ErrorKind, }; +use crate::services; + #[derive(Debug)] pub enum Error { Db(migration::DbErr), @@ -28,11 +30,14 @@ pub enum Error { LdkInvoiceParse(lightning_invoice::ParseOrSemanticError), InvalidSeedLength, FailedToWriteSeed, - Unauthenticated, + Unauthenticated(String), InvalidMacaroon, + AdminNodeNotFound, AdminNodeNotStarted, AdminNodeNotCreated, FundingGenerationNeverHappened, + AdminNodeService, + UnknownResponse, } impl Display for Error { @@ -52,13 +57,16 @@ impl Display for Error { Error::LdkInvoiceParse(e) => e.to_string(), Error::InvalidSeedLength => String::from("invalid seed length"), Error::FailedToWriteSeed => String::from("failed to write seed"), - Error::Unauthenticated => String::from("unauthenticated"), + Error::Unauthenticated(msg) => String::from(format!("unauthenticated: {}", msg)), Error::InvalidMacaroon => String::from("invalid macaroon"), + Error::AdminNodeNotFound => String::from("admin node not found"), Error::AdminNodeNotCreated => String::from("admin node not created"), Error::AdminNodeNotStarted => String::from("admin node not started"), Error::FundingGenerationNeverHappened => { String::from("funding generation for request never happened") } + Error::AdminNodeService => String::from("admin node service error"), + Error::UnknownResponse => String::from("unknown response"), }; write!(f, "{}", str) } @@ -136,6 +144,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: services::admin::Error) -> Self { + Error::AdminNodeService + } +} + impl From for std::io::Error { fn from(e: Error) -> std::io::Error { std::io::Error::new(ErrorKind::Other, e.to_string()) diff --git a/src/http/admin.rs b/src/http/admin.rs index 378c4cd..1afe562 100644 --- a/src/http/admin.rs +++ b/src/http/admin.rs @@ -7,10 +7,14 @@ // You may not use this file except in accordance with one or both of these // licenses. -use std::sync::Arc; +use std::{ + fmt::{self, Display}, + sync::Arc, +}; use axum::{ extract::{Extension, Query}, + response::{IntoResponse, Response}, routing::{delete, get, post}, Json, Router, }; @@ -21,6 +25,7 @@ use serde::Deserialize; use serde_json::{json, Value}; use senseicore::{ + error::Error as SenseiError, services::{ admin::{AdminRequest, AdminResponse, AdminService}, PaginationRequest, @@ -158,16 +163,82 @@ impl From for AdminRequest { } } +#[derive(Debug)] +pub enum HttpError { + Db(migration::DbErr), + SerdeJson(serde_json::Error), + Unauthenticated(String), + AdminNodeNotFound, + AdminNodeService, + UnknownResponse, +} + +impl Display for HttpError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let str = match self { + HttpError::Db(e) => e.to_string(), + HttpError::SerdeJson(e) => format!("invalid json: {}", e.to_string()), + HttpError::Unauthenticated(msg) => String::from(format!("unauthenticated: {}", msg)), + HttpError::AdminNodeNotFound => String::from("admin node not found"), + HttpError::AdminNodeService => String::from("admin node service error"), + HttpError::UnknownResponse => String::from("unknown response"), + }; + write!(f, "{}", str) + } +} + +impl IntoResponse for HttpError { + fn into_response(self) -> Response { + let status = StatusCode::BAD_REQUEST; + let (body, status) = match self { + HttpError::Db(e) => (e.to_string(), status), // SHOULD IT BE NOT FOUND STATUS??? + HttpError::SerdeJson(e) => (e.to_string(), StatusCode::UNPROCESSABLE_ENTITY), + HttpError::Unauthenticated(msg) => ( + format!("unauthenticated: {}", msg), + StatusCode::UNAUTHORIZED, + ), + HttpError::AdminNodeNotFound => { + (String::from("admin node not found"), StatusCode::NOT_FOUND) + } + HttpError::AdminNodeService => (String::from("admin node service error"), status), + HttpError::UnknownResponse => (String::from("unknown response"), status), + }; + + (status, body).into_response() + } +} + +impl From for HttpError { + fn from(err: SenseiError) -> Self { + let http_err = match err { + SenseiError::Db(e) => HttpError::Db(e), + SenseiError::AdminNodeService => HttpError::AdminNodeService, + _ => HttpError::UnknownResponse, + }; + http_err + } +} + +impl From for HttpError { + fn from(err: senseicore::services::admin::Error) -> Self { + let http_err = match err.into() { + SenseiError::Db(e) => HttpError::Db(e), + SenseiError::AdminNodeService => HttpError::AdminNodeService, + _ => HttpError::UnknownResponse, + }; + http_err + } +} + pub fn get_token_from_cookies_or_header( cookies: &Cookies, token: Option, -) -> Result { +) -> Result { match token { Some(token) => { - let res = token - .to_str() - .map(|str| str.to_string()) - .map_err(|_| StatusCode::UNAUTHORIZED); + let res = token.to_str().map(|str| str.to_string()).map_err(|e| { + HttpError::Unauthenticated(format!("token couldn't be converted to string: {}", e)) + }); res } None => match cookies.get("token") { @@ -175,7 +246,9 @@ pub fn get_token_from_cookies_or_header( let token_cookie_str = token_cookie.value().to_string(); Ok(token_cookie_str) } - None => Err(StatusCode::UNAUTHORIZED), + None => Err(HttpError::Unauthenticated(String::from( + "no token provided in header or cookies", + ))), }, } } @@ -185,14 +258,13 @@ pub async fn authenticate_request( scope: &str, cookies: &Cookies, token: Option, -) -> Result { +) -> Result { let token = get_token_from_cookies_or_header(cookies, token)?; let access_token = admin_service .database .get_access_token_by_token(token) - .await - .map_err(|_e| StatusCode::UNAUTHORIZED)?; + .await?; match access_token { Some(access_token) => { @@ -235,7 +307,7 @@ pub async fn list_tokens( cookies: Cookies, Query(pagination): Query, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "tokens/list", &cookies, token).await?; if authenticated { @@ -244,10 +316,10 @@ pub async fn list_tokens( .await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -256,24 +328,24 @@ pub async fn create_token( Json(payload): Json, cookies: Cookies, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "tokens/create", &cookies, token).await?; let request = { let params: Result = serde_json::from_value(payload); match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), } }?; if authenticated { match admin_service.call(request).await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -282,24 +354,24 @@ pub async fn delete_token( Json(payload): Json, cookies: Cookies, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "tokens/delete", &cookies, token).await?; let request = { let params: Result = serde_json::from_value(payload); match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), } }?; if authenticated { match admin_service.call(request).await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -308,7 +380,7 @@ pub async fn list_nodes( cookies: Cookies, Query(pagination): Query, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "nodes/list", &cookies, token).await?; if authenticated { match admin_service @@ -316,10 +388,10 @@ pub async fn list_nodes( .await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -327,15 +399,14 @@ pub async fn login( Extension(admin_service): Extension>, cookies: Cookies, Json(payload): Json, -) -> Result, StatusCode> { +) -> Result, HttpError> { let params: LoginNodeParams = - serde_json::from_value(payload).map_err(|_e| StatusCode::UNPROCESSABLE_ENTITY)?; + serde_json::from_value(payload).map_err(|e| HttpError::SerdeJson(e))?; let node = admin_service .database .get_node_by_username(¶ms.username) - .await - .map_err(|_e| StatusCode::UNPROCESSABLE_ENTITY)?; + .await?; match node { Some(node) => { @@ -384,12 +455,12 @@ pub async fn login( "token": token }))) } - _ => Err(StatusCode::UNPROCESSABLE_ENTITY), + _ => Err(HttpError::UnknownResponse), }, - Err(_err) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(_) => Err(HttpError::AdminNodeService), } } - None => Err(StatusCode::NOT_FOUND), + None => Err(HttpError::AdminNodeNotFound), } } @@ -403,11 +474,11 @@ pub async fn init_sensei( Extension(admin_service): Extension>, cookies: Cookies, Json(payload): Json, -) -> Result, StatusCode> { +) -> Result, HttpError> { let params: Result = serde_json::from_value(payload); let request = match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), }?; match admin_service.call(request).await { @@ -437,9 +508,9 @@ pub async fn init_sensei( token, })) } - _ => Err(StatusCode::UNPROCESSABLE_ENTITY), + _ => Err(HttpError::UnknownResponse), }, - Err(err) => Ok(Json(AdminResponse::Error(err))), + Err(err) => Err(err.into()), } } @@ -521,24 +592,25 @@ pub async fn create_node( Json(payload): Json, cookies: Cookies, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "nodes/create", &cookies, token).await?; + let request = { let params: Result = serde_json::from_value(payload); match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), } }?; if authenticated { match admin_service.call(request).await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -547,24 +619,24 @@ pub async fn start_node( Json(payload): Json, cookies: Cookies, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "nodes/start", &cookies, token).await?; let request = { let params: Result = serde_json::from_value(payload); match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), } }?; if authenticated { match admin_service.call(request).await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -573,23 +645,23 @@ pub async fn stop_node( Json(payload): Json, cookies: Cookies, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "nodes/stop", &cookies, token).await?; let request = { let params: Result = serde_json::from_value(payload); match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), } }?; if authenticated { match admin_service.call(request).await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } @@ -598,23 +670,23 @@ pub async fn delete_node( Json(payload): Json, cookies: Cookies, AuthHeader { macaroon: _, token }: AuthHeader, -) -> Result, StatusCode> { +) -> Result, HttpError> { let authenticated = authenticate_request(&admin_service, "nodes/delete", &cookies, token).await?; let request = { let params: Result = serde_json::from_value(payload); match params { Ok(params) => Ok(params.into()), - Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), + Err(err) => Err(HttpError::SerdeJson(err)), } }?; if authenticated { match admin_service.call(request).await { Ok(response) => Ok(Json(response)), - Err(_err) => Err(StatusCode::UNAUTHORIZED), + Err(err) => Err(err.into()), } } else { - Err(StatusCode::UNAUTHORIZED) + Err(HttpError::Unauthenticated("invalid token".to_string())) } } diff --git a/src/http/node.rs b/src/http/node.rs index 8127184..e4811ed 100644 --- a/src/http/node.rs +++ b/src/http/node.rs @@ -353,7 +353,7 @@ pub async fn handle_authenticated_request( Ok(Json(NodeResponse::StartNode {})) } _ => { - let err = senseicore::error::Error::Unauthenticated; + let err = senseicore::error::Error::Unauthenticated(String::from("")); let node_request_error: NodeRequestError = err.into(); Ok(Json(NodeResponse::Error(node_request_error))) }