diff --git a/Cargo.lock b/Cargo.lock index 15bd7ba..ed77bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1941,6 +1941,7 @@ dependencies = [ "base64 0.21.0", "dotenvy", "log", + "paste", "rocket", "serde", "sqlx", diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index 9a0921c..ecbad68 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -13,4 +13,5 @@ sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] tokio = { version = "1", features = ["full"] } toml = "0.7.1" serde = "1.0.152" -dotenvy = "0.15.6" \ No newline at end of file +dotenvy = "0.15.6" +paste = "1.0.11" \ No newline at end of file diff --git a/trifid-api/src/format.rs b/trifid-api/src/format.rs index 9740222..30546c5 100644 --- a/trifid-api/src/format.rs +++ b/trifid-api/src/format.rs @@ -1,6 +1,4 @@ use std::fmt::{Display, Formatter}; -use base64::{decode, Engine}; -use rocket::info; use crate::format::PEMValidationError::{IncorrectSegmentLength, InvalidBase64Data, MissingStartSentinel}; use crate::util::base64decode; diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 0b4b142..92f02cb 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -3,107 +3,17 @@ use std::fs; use std::path::Path; use dotenvy::dotenv; use log::{error, info}; -use rocket::{routes, post, State, Rocket, Ignite}; -use rocket::{serde::Serialize}; -use rocket::http::{ContentType, Status}; -use rocket::serde::Deserialize; -use rocket::serde::json::Json; +use rocket::{catchers, routes}; use sqlx::migrate::Migrator; -use sqlx::PgPool; use sqlx::postgres::PgPoolOptions; use crate::config::TFConfig; -use crate::format::{validate_dh_pubkey_base64, validate_ed_pubkey_base64}; pub mod format; pub mod util; pub mod db; pub mod config; -#[derive(Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct EnrollRequest { - pub code: String, - #[serde(rename = "dhPubkey")] - pub dh_pubkey: String, - #[serde(rename = "edPubkey")] - pub ed_pubkey: String, - pub timestamp: String, -} - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct EnrollResponseMetadata {} - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct EnrollResponseOrganization { - pub id: String, - pub name: String, -} - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct EnrollResponseData { - pub config: String, - pub host_id: String, - pub counter: i64, - pub trusted_keys: String, - pub organization: EnrollResponseOrganization, -} - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct EnrollResponse { - pub data: EnrollResponseData, - pub metadata: EnrollResponseMetadata, -} - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct APIError { - errors: Vec -} -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct APIErrorSingular { - code: String, - message: String -} - -pub const ERR_MSG_MALFORMED_REQUEST: &str = "unable to parse the request body - is it valid JSON, using correct types?"; -pub const ERR_MSG_MALFORMED_REQUEST_CODE: &str = "ERR_MALFORMED_REQUEST"; - -#[post("/v2/enroll", data = "")] -fn enroll_endpoint(request: String, pool: &State) -> Result<(ContentType, Json), (Status, Json)> { - let request: EnrollRequest = match rocket::serde::json::from_str(request.as_str()) { - Ok(r) => r, - Err(e) => { - return Err((Status::BadRequest, Json(APIError { errors: vec![APIErrorSingular { code: ERR_MSG_MALFORMED_REQUEST_CODE.to_string(), message: format!("{} - {}", ERR_MSG_MALFORMED_REQUEST, e) }]}))) - } - }; - // validate request - if let Err(e) = validate_dh_pubkey_base64(request.dh_pubkey.as_str()) { - return Err((Status::BadRequest, Json(APIError { errors: vec![APIErrorSingular { code: ERR_MSG_MALFORMED_REQUEST_CODE.to_string(), message: format!("{} - invalid dhPubkey - {}", ERR_MSG_MALFORMED_REQUEST, e) }]}))) - } - if let Err(e) = validate_ed_pubkey_base64(request.ed_pubkey.as_str()) { - return Err((Status::BadRequest, Json(APIError { errors: vec![APIErrorSingular { code: ERR_MSG_MALFORMED_REQUEST_CODE.to_string(), message: format!("{} - invalid edPubkey - {}", ERR_MSG_MALFORMED_REQUEST, e) }]}))) - } - Ok((ContentType::JSON, Json(EnrollResponse { - data: EnrollResponseData { - config: "sdf".to_string(), - host_id: "sdf".to_string(), - counter: 0, - trusted_keys: "sdf".to_string(), - organization: EnrollResponseOrganization { id: "99s98d9878fds".to_string(), name: "e3team CA".to_string() }, - }, - metadata: EnrollResponseMetadata {}, - }))) -} - -#[post("/v1/dnclient")] -fn dnclient_endpoint() -> &'static str { - "DNClient functionality is not yet implemented" -} +pub mod routes; static MIGRATOR: Migrator = sqlx::migrate!(); @@ -165,9 +75,36 @@ async fn main() -> Result<(), Box> { info!("[tfapi] building rocket config"); let figment = rocket::Config::figment().merge(("port", config.listen_port)); +/* +error_handler!(400, "ERR_MALFORMED_REQUEST", "unable to parse the request body, is it properly formatted?"); +error_handler!(401, "ERR_AUTHENTICATION_REQUIRED", "this endpoint requires authentication but it was not provided"); +error_handler!(403, "ERR_UNAUTHORIZED", "authorization was provided but it is expired or invalid"); +error_handler!(404, "ERR_NOT_FOUND", "resource not found"); +error_handler!(405, "ERR_METHOD_NOT_ALLOWED", "method not allowed for this endpoint"); +error_handler!(500, "ERR_QL_QUERY_FAILED", "graphql query timed out"); +error_handler!(501, "ERR_NOT_IMPLEMENTED", "query not supported by this version of graphql"); +error_handler!(502, "ERR_PROXY_ERR", "servers under load, please try again later"); +error_handler!(503, "ERR_SERVER_OVERLOADED", "servers under load, please try again later"); +error_handler!(504, "ERR_PROXY_TIMEOUT", "servers under load, please try again later"); +error_handler!(505, "ERR_CLIENT_UNSUPPORTED", "your version of dnclient is out of date, please update"); + */ let _ = rocket::custom(figment) - .mount("/", routes![enroll_endpoint, dnclient_endpoint]) + .mount("/", routes![ + crate::routes::v1::auth::verify_magic_link::verify_magic_link + ]) + .register("/", catchers![ + crate::routes::handler_400, + crate::routes::handler_401, + crate::routes::handler_403, + crate::routes::handler_404, + crate::routes::handler_500, + crate::routes::handler_501, + crate::routes::handler_502, + crate::routes::handler_503, + crate::routes::handler_504, + crate::routes::handler_505, + ]) .manage(pool) .manage(config) .launch().await?; diff --git a/trifid-api/src/routes/mod.rs b/trifid-api/src/routes/mod.rs new file mode 100644 index 0000000..383c6a6 --- /dev/null +++ b/trifid-api/src/routes/mod.rs @@ -0,0 +1,43 @@ +pub mod v1; + +use rocket::catch; +use serde::{Serialize}; +use rocket::http::Status; + +pub const ERR_MSG_MALFORMED_REQUEST: &str = "unable to parse the request body - is it valid JSON, using correct types?"; +pub const ERR_MSG_MALFORMED_REQUEST_CODE: &str = "ERR_MALFORMED_REQUEST"; + + +#[derive(Serialize)] +#[serde(crate = "rocket::serde")] +pub struct APIError { + errors: Vec +} +#[derive(Serialize)] +#[serde(crate = "rocket::serde")] +pub struct APIErrorSingular { + code: String, + message: String +} + +macro_rules! error_handler { + ($code: expr, $err: expr, $msg: expr) => { + ::paste::paste! { + #[catch($code)] + pub fn []() -> (Status, String) { + (Status::from_code($code).unwrap(), format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", $err, $msg)) + } + } + }; +} +error_handler!(400, "ERR_MALFORMED_REQUEST", "unable to parse the request body, is it properly formatted?"); +error_handler!(401, "ERR_AUTHENTICATION_REQUIRED", "this endpoint requires authentication but it was not provided"); +error_handler!(403, "ERR_UNAUTHORIZED", "authorization was provided but it is expired or invalid"); +error_handler!(404, "ERR_NOT_FOUND", "resource not found"); +error_handler!(405, "ERR_METHOD_NOT_ALLOWED", "method not allowed for this endpoint"); +error_handler!(500, "ERR_QL_QUERY_FAILED", "graphql query timed out"); +error_handler!(501, "ERR_NOT_IMPLEMENTED", "query not supported by this version of graphql"); +error_handler!(502, "ERR_PROXY_ERR", "servers under load, please try again later"); +error_handler!(503, "ERR_SERVER_OVERLOADED", "servers under load, please try again later"); +error_handler!(504, "ERR_PROXY_TIMEOUT", "servers under load, please try again later"); +error_handler!(505, "ERR_CLIENT_UNSUPPORTED", "your version of dnclient is out of date, please update"); diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs new file mode 100644 index 0000000..06675be --- /dev/null +++ b/trifid-api/src/routes/v1/auth/mod.rs @@ -0,0 +1 @@ +pub mod verify_magic_link; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs new file mode 100644 index 0000000..4f67255 --- /dev/null +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -0,0 +1,38 @@ +use rocket::http::{ContentType, Status}; +use rocket::serde::json::Json; +use serde::{Serialize, Deserialize}; +use crate::routes::{APIError, APIErrorSingular, ERR_MSG_MALFORMED_REQUEST, ERR_MSG_MALFORMED_REQUEST_CODE}; +use rocket::post; + +#[derive(Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct VerifyMagicLinkRequest { + #[serde(rename = "magicLinkToken")] + pub magic_link_token: String, +} + + +#[derive(Serialize, Deserialize)] +pub struct VerifyMagicLinkResponseMetadata {} + +#[derive(Serialize, Deserialize)] +pub struct VerifyMagicLinkResponseData { + #[serde(rename = "sessionToken")] + pub session_token: String, +} + +#[derive(Serialize, Deserialize)] +pub struct VerifyMagicLinkResponse { + pub data: VerifyMagicLinkResponseData, + pub metadata: VerifyMagicLinkResponseMetadata, +} + +#[post("/v1/auth/verify-magic-link", data = "")] +pub fn verify_magic_link(req: Json) -> Result<(ContentType, Json), (Status, Json)> { + // handle request + + Ok((ContentType::JSON, Json(VerifyMagicLinkResponse { + data: VerifyMagicLinkResponseData { session_token: "sd[if0sf0dsfsdf".to_string() }, + metadata: VerifyMagicLinkResponseMetadata {}, + }))) +} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs new file mode 100644 index 0000000..5696e21 --- /dev/null +++ b/trifid-api/src/routes/v1/mod.rs @@ -0,0 +1 @@ +pub mod auth; \ No newline at end of file