diff --git a/.github/workflows/review-api.yaml b/.github/workflows/review-api.yaml index c459cc6..ff4f0fe 100644 --- a/.github/workflows/review-api.yaml +++ b/.github/workflows/review-api.yaml @@ -77,7 +77,8 @@ jobs: --image ghcr.io/christianfosli/stellerom/review-api:${{ github.sha }} \ --min-replicas 0 --max-replicas 2 \ --set-env-vars "REVIEW_API_DB_CONNSTR=secretref:db-connstr" "REVIEW_API_DB_NAME=review-api-dev" \ - "ROOM_API_URL=https://capp-stellerom-room-api-dev.wonderfulsand-142627d1.westeurope.azurecontainerapps.io" + "ROOM_API_URL=https://capp-stellerom-room-api-dev.wonderfulsand-142627d1.westeurope.azurecontainerapps.io" \ + "ALLOWED_IMAGE_BASE_URLS='["https://ststelleromdev.blob.core.windows.net"]'" # TODO: Switch above URL to https://room-api-dev.stellebord.no when azure container apps get support for custom domains with managed TLS certs env: DB_CONNSTR: "mongodb+srv://${{ secrets.REVIEW_API_DB_USERNAME }}:${{ secrets.REVIEW_API_DB_PASSWORD}}@azure-stellerom.au87e49.mongodb.net" @@ -113,7 +114,8 @@ jobs: --image ghcr.io/christianfosli/stellerom/review-api:${{ github.sha }} \ --min-replicas 1 --max-replicas 5 \ --set-env-vars "REVIEW_API_DB_CONNSTR=secretref:db-connstr" "REVIEW_API_DB_NAME=review-api-prod" \ - "ROOM_API_URL=https://capp-stellerom-room-api-prod.proudfield-3e3747dd.westeurope.azurecontainerapps.io" + "ROOM_API_URL=https://capp-stellerom-room-api-prod.proudfield-3e3747dd.westeurope.azurecontainerapps.io" \ + "ALLOWED_IMAGE_BASE_URLS='["https://ststelleromprod.blob.core.windows.net"]'" # TODO: Switch above URL to https://room-api-prod.stellebord.no when azure container apps get support for custom domains with managed TLS certs env: DB_CONNSTR: "mongodb+srv://${{ secrets.REVIEW_API_DB_USERNAME }}:${{ secrets.REVIEW_API_DB_PASSWORD}}@azure-stellerom.au87e49.mongodb.net" diff --git a/review-api/Cargo.lock b/review-api/Cargo.lock index 107718e..51a39da 100644 --- a/review-api/Cargo.lock +++ b/review-api/Cargo.lock @@ -1156,6 +1156,7 @@ dependencies = [ "bounded-integer", "chrono", "futures", + "lazy_static", "mongodb", "reqwest", "serde", diff --git a/review-api/Cargo.toml b/review-api/Cargo.toml index 57ea20f..606de2e 100644 --- a/review-api/Cargo.toml +++ b/review-api/Cargo.toml @@ -10,6 +10,7 @@ axum = "0.5" bounded-integer = { version = "0.5", features = ["std", "types", "serde1"] } chrono = { version = "0.4", features = ["serde"] } futures = "0.3" +lazy_static = "1.4.0" mongodb = "2" reqwest = { version = "0.11.11", features = ["json"] } serde = { version = "1", features = ["derive"] } diff --git a/review-api/README.md b/review-api/README.md index f04eceb..0bb5640 100644 --- a/review-api/README.md +++ b/review-api/README.md @@ -11,5 +11,8 @@ Run a local mongodb instance (see ../docker-compose.yaml) Start the service: ``` +ALLOWED_IMAGE_BASE_URLS='["https://ststelleromdev.blob.core.windows.net/"]' \ +ROOM_API_URL=http://localhost:3000 \ +RUST_LOG=info \ cargo run ``` diff --git a/review-api/src/create_review.rs b/review-api/src/create_review.rs index 34e48f0..ee14249 100644 --- a/review-api/src/create_review.rs +++ b/review-api/src/create_review.rs @@ -12,6 +12,12 @@ use std::env; use crate::models::{Review, StarRating}; +lazy_static! { + static ref ALLOWED_IMAGE_BASE_URLS: Vec = + serde_json::from_str(&env::var("ALLOWED_IMAGE_BASE_URLS").unwrap_or("[]".to_owned())) + .unwrap(); +} + #[derive(Clone, Debug, Deserialize)] pub struct CreateReview { #[serde(rename = "roomId")] @@ -23,6 +29,8 @@ pub struct CreateReview { #[serde(rename = "cleanlinessRating")] pub cleanliness_rating: StarRating, pub review: Option, + #[serde(rename = "imageUrl")] + pub image_url: Option, #[serde(rename = "reviewedBy")] pub reviewed_by: Option, } @@ -31,6 +39,8 @@ pub async fn create_review( Json(payload): Json, Extension(db): Extension, ) -> Result<(StatusCode, Json), (StatusCode, String)> { + validate_payload(&payload)?; + let collection = db.collection::("reviews"); let review = Review { @@ -39,6 +49,7 @@ pub async fn create_review( safety_rating: payload.safety_rating, cleanliness_rating: payload.cleanliness_rating, review: payload.review, + image_url: payload.image_url, reviewed_by: payload.reviewed_by, reviewed_at: Utc::now(), }; @@ -67,6 +78,28 @@ pub async fn create_review( Ok((StatusCode::CREATED, Json(review))) } +fn validate_payload(payload: &CreateReview) -> Result<(), (StatusCode, String)> { + if payload.image_url.is_some() + && ALLOWED_IMAGE_BASE_URLS + .iter() + .any(|allowed| !payload.image_url.clone().unwrap().starts_with(allowed)) + { + tracing::error!( + url = payload.image_url, + "Validation error: Illegal image URL" + ); + return Err(( + StatusCode::UNPROCESSABLE_ENTITY, + format!( + "Invalid image url. URL's must start with {:?}", + ALLOWED_IMAGE_BASE_URLS.join(",") + ), + )); + } else { + Ok(()) + } +} + async fn update_room_ratings( collection: &Collection, room_id: &Uuid, diff --git a/review-api/src/main.rs b/review-api/src/main.rs index 628a402..39a5e49 100644 --- a/review-api/src/main.rs +++ b/review-api/src/main.rs @@ -8,6 +8,9 @@ use mongodb::options::ClientOptions; use mongodb::{Client, Database}; use tower_http::cors::CorsLayer; +#[macro_use] +extern crate lazy_static; + use crate::create_review::create_review; use crate::get_reviews::get_reviews; use crate::healthcheck::{live, ready}; diff --git a/review-api/src/models.rs b/review-api/src/models.rs index 8388145..56a28bf 100644 --- a/review-api/src/models.rs +++ b/review-api/src/models.rs @@ -16,6 +16,8 @@ pub struct Review { #[serde(rename = "cleanlinessRating")] pub cleanliness_rating: StarRating, pub review: Option, + #[serde(rename = "imageUrl")] + pub image_url: Option, #[serde(rename = "reviewedAt")] pub reviewed_at: DateTime, #[serde(rename = "reviewedBy")]