diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index f04b3ae..32b683f 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -72,6 +72,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::auth::totp::totp_request) .service(routes::v1::networks::get_networks) .service(routes::v1::organization::create_org_request) + .service(routes::v1::networks::get_network_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 7066710..19f868e 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -1,6 +1,6 @@ use serde::{Serialize, Deserialize}; use actix_web::{get, HttpRequest, HttpResponse}; -use actix_web::web::{Data, Query}; +use actix_web::web::{Data, Path, Query}; use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; @@ -26,7 +26,7 @@ pub struct GetNetworksResponseData { #[serde(rename = "signingCAID")] pub signing_ca_id: String, #[serde(rename = "createdAt")] - pub created_at: String, // 2023-03-22T18:55:47.009Z, %Y-%m-%dT%H-%M-%S.%.3fZ + pub created_at: String, // 2023-03-22T18:55:47.009Z, %Y-%m-%dT%H-%M-%S%.3fZ pub name: String, #[serde(rename = "lighthousesAsRelays")] pub lighthouses_as_relays: bool @@ -247,4 +247,87 @@ pub async fn get_networks(opts: Query, req_info: HttpReq } else { None }, }, }) -} \ No newline at end of file +} + +#[get("/v1/networks/{network_id}")] +pub async fn get_network_request(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with networks:list + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["networks:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the networks:read scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let network: Option = match network::Entity::find().filter(network::Column::Id.eq(net.into_inner())).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(network) = network { + HttpResponse::Ok().json(GetNetworkResponse { + data: GetNetworksResponseData { + id: network.id, + cidr: network.cidr, + organization_id: network.organization, + signing_ca_id: network.signing_ca, + created_at: Utc.timestamp_opt(network.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + name: network.name, + lighthouses_as_relays: network.lighthouses_as_relays, + }, + metadata: GetNetworkResponseMetadata {}, + }) + } else { + HttpResponse::NotFound().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_MISSING_NETWORK".to_string(), + message: "Network does not exist".to_string(), + path: None, + } + ], + }) + + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworkResponse { + pub data: GetNetworksResponseData, + pub metadata: GetNetworkResponseMetadata +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworkResponseMetadata {} \ No newline at end of file