diff --git a/Cargo.lock b/Cargo.lock index 915a3e4d16..2aeae5ea3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3720d0064a0ce5c0de7bd93bdb0a6caebab2a9b5668746145d7b3b0c5da02914" dependencies = [ - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix_derive", "bitflags", "bytes 1.0.1", @@ -22,7 +22,7 @@ dependencies = [ "parking_lot 0.11.1", "pin-project-lite 0.2.6", "smallvec 1.6.1", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-util 0.6.9", ] @@ -55,7 +55,7 @@ dependencies = [ "log", "memchr", "pin-project-lite 0.2.6", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-util 0.6.9", ] @@ -181,7 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9b510d35f13987537289f38bf136e7e702a5c87cc28760310cc459544f40afd" dependencies = [ "actix-codec 0.4.1", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-tls 3.0.0-beta.7", "actix-utils 3.0.0", @@ -212,7 +212,7 @@ dependencies = [ "rand 0.8.4", "sha-1", "smallvec 1.6.1", - "tokio 1.13.0", + "tokio 1.16.1", "zstd", ] @@ -299,13 +299,13 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.4.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0c218d0a17c120f10ee0c69c9f0c45d87319e8f66b1f065e8412b612fc3e24" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" dependencies = [ "actix-macros 0.2.3", "futures-core", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -334,14 +334,14 @@ version = "2.0.0-beta.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7367665785765b066ad16e1086d26a087f696bc7c42b6f93004ced6cfcf1eeca" dependencies = [ - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-utils 3.0.0", "futures-core", "log", "mio 0.7.11", "num_cpus", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -413,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4af84e13e4600829858a3e68079be710d1ada461431e1e4c5ae663479ea0a3c" dependencies = [ "actix-codec 0.4.1", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-utils 3.0.0", "derive_more", @@ -502,7 +502,7 @@ dependencies = [ "actix-http 3.0.0-beta.11", "actix-macros 0.2.3", "actix-router 0.5.0-beta.2", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-server 2.0.0-beta.6", "actix-service 2.0.1", "actix-utils 3.0.0", @@ -547,7 +547,7 @@ dependencies = [ "bytestring", "futures-core", "pin-project 1.0.7", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -710,7 +710,7 @@ dependencies = [ "smart-default", "smol_str", "thiserror", - "tokio 1.13.0", + "tokio 1.16.1", "uuid", ] @@ -877,7 +877,7 @@ dependencies = [ "serde_derive", "serde_json", "sqlx", - "tokio 1.13.0", + "tokio 1.16.1", "toml", "url_config", ] @@ -1147,7 +1147,7 @@ dependencies = [ "futures-core", "memchr", "pin-project-lite 0.2.6", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-util 0.6.9", ] @@ -1639,7 +1639,7 @@ dependencies = [ "subprocess", "subprocess_common", "tempdir", - "tokio 1.13.0", + "tokio 1.16.1", "tts_common", ] @@ -2032,7 +2032,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-util 0.6.9", "tracing", ] @@ -2189,6 +2189,7 @@ version = "0.0.1" dependencies = [ "actix-cors 0.6.0-beta.3", "actix-http 3.0.0-beta.11", + "actix-rt 2.7.0", "actix-web 4.0.0-beta.10", "anyhow", "base64 0.13.0", @@ -2199,9 +2200,11 @@ dependencies = [ "log", "once_cell", "regex", + "reusable_types", "serde", "serde_derive", "serde_json", + "speculoos", "toml", ] @@ -2274,7 +2277,7 @@ dependencies = [ "itoa 0.4.7", "pin-project-lite 0.2.6", "socket2 0.4.0", - "tokio 1.13.0", + "tokio 1.16.1", "tower-service", "tracing", "want", @@ -2289,7 +2292,7 @@ dependencies = [ "bytes 1.0.1", "hyper 0.14.14", "native-tls", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-native-tls", ] @@ -2334,7 +2337,7 @@ dependencies = [ "actix-files", "actix-http 3.0.0-beta.11", "actix-multipart", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-utils 3.0.0", "actix-web 4.0.0-beta.10", @@ -2393,7 +2396,7 @@ dependencies = [ "subprocess", "tempdir", "time 0.2.26", - "tokio 1.13.0", + "tokio 1.16.1", "toml", "tts_common", "twitch_common", @@ -2453,7 +2456,7 @@ dependencies = [ "storage_buckets_common", "subprocess", "tempdir", - "tokio 1.13.0", + "tokio 1.16.1", "tts_common", ] @@ -2996,7 +2999,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "tokio 1.13.0", + "tokio 1.16.1", "uuid", ] @@ -3664,7 +3667,7 @@ dependencies = [ "percent-encoding 2.2.0", "pin-project-lite 0.2.6", "sha1", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-util 0.6.9", "url 2.3.1", ] @@ -3758,7 +3761,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-native-tls", "url 2.3.1", "wasm-bindgen", @@ -3886,7 +3889,7 @@ dependencies = [ "sha2 0.10.5", "thiserror", "time 0.3.14", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-stream", "url 2.3.1", ] @@ -3941,7 +3944,7 @@ dependencies = [ "pin-project-lite 0.2.6", "rxml_validation", "smartstring", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -4378,10 +4381,10 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d1bd069de53442e7a320f525a6d4deb8bb0621ac7a55f7eccbc2b58b57f43d0" dependencies = [ - "actix-rt 2.4.0", + "actix-rt 2.7.0", "native-tls", "once_cell", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-native-tls", ] @@ -4457,7 +4460,7 @@ dependencies = [ "container_common", "log", "rust-s3", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -4468,7 +4471,7 @@ dependencies = [ "actix-files", "actix-http 3.0.0-beta.11", "actix-multipart", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-utils 3.0.0", "actix-web 4.0.0-beta.10", @@ -4532,7 +4535,7 @@ dependencies = [ "subprocess", "tempdir", "time 0.2.26", - "tokio 1.13.0", + "tokio 1.16.1", "toml", "tts_common", "twitch_api2", @@ -4813,11 +4816,10 @@ dependencies = [ [[package]] name = "tokio" -version = "1.13.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ - "autocfg 1.0.1", "bytes 1.0.1", "libc", "memchr", @@ -4865,9 +4867,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.5.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -4881,7 +4883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -4910,7 +4912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", - "tokio 1.13.0", + "tokio 1.16.1", "webpki", ] @@ -4922,7 +4924,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite 0.2.6", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -4959,7 +4961,7 @@ dependencies = [ "log", "pin-project 1.0.7", "rustls", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-rustls", "tungstenite", "webpki", @@ -5009,7 +5011,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.2.6", - "tokio 1.13.0", + "tokio 1.16.1", ] [[package]] @@ -5130,7 +5132,7 @@ dependencies = [ "storage_buckets_common", "subprocess", "tempdir", - "tokio 1.13.0", + "tokio 1.16.1", "tts_common", ] @@ -5161,7 +5163,7 @@ dependencies = [ "storage_buckets_common", "subprocess", "tempdir", - "tokio 1.13.0", + "tokio 1.16.1", "tts_common", ] @@ -5211,7 +5213,7 @@ dependencies = [ "actix-cors 0.5.4", "actix-files", "actix-http 3.0.0-beta.11", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-utils 3.0.0", "actix-web 4.0.0-beta.10", @@ -5259,7 +5261,7 @@ dependencies = [ "sha2 0.9.5", "sqlx", "time 0.2.26", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-tungstenite", "toml", "tts_common", @@ -5464,6 +5466,7 @@ dependencies = [ "actix-web 4.0.0-beta.10", "anyhow", "http_server_common", + "reusable_types", ] [[package]] @@ -5576,7 +5579,7 @@ dependencies = [ "storage_buckets_common", "subprocess", "tempdir", - "tokio 1.13.0", + "tokio 1.16.1", "tts_common", ] @@ -5607,7 +5610,7 @@ dependencies = [ "storage_buckets_common", "subprocess", "tempdir", - "tokio 1.13.0", + "tokio 1.16.1", "tts_common", ] @@ -5742,7 +5745,7 @@ dependencies = [ "actix-cors 0.5.4", "actix-files", "actix-http 3.0.0-beta.11", - "actix-rt 2.4.0", + "actix-rt 2.7.0", "actix-service 2.0.1", "actix-utils 3.0.0", "actix-web 4.0.0-beta.10", @@ -5791,7 +5794,7 @@ dependencies = [ "sha2 0.9.5", "sqlx", "time 0.2.26", - "tokio 1.13.0", + "tokio 1.16.1", "tokio-tungstenite", "toml", "twitch_api2", diff --git a/crates/lib/http_server_common/Cargo.toml b/crates/lib/http_server_common/Cargo.toml index fc005df1d5..ae6e251573 100644 --- a/crates/lib/http_server_common/Cargo.toml +++ b/crates/lib/http_server_common/Cargo.toml @@ -16,6 +16,7 @@ path = "src/lib.rs" # Internal container_common = { path = "../container_common" } +reusable_types = { path = "../reusable_types" } # External anyhow = "1.0.40" @@ -40,3 +41,7 @@ serde = "1.0.125" serde_derive = "1.0.125" serde_json = "1.0.64" toml = "0.5.8" + +[dev-dependencies] +actix-rt = "2.7.0" +speculoos = "0.9.0" # Fluent assertions diff --git a/crates/lib/http_server_common/src/cors.rs b/crates/lib/http_server_common/src/cors.rs index a9363d0661..a2b0208076 100644 --- a/crates/lib/http_server_common/src/cors.rs +++ b/crates/lib/http_server_common/src/cors.rs @@ -1,10 +1,174 @@ use actix_cors::Cors; use actix_http::http; +use log::info; +use reusable_types::server_environment::ServerEnvironment; -// TODO: Take an environment. /// Return cors config for FakeYou / Vocodes / OBS / local development -pub fn build_common_cors_config() -> Cors { - Cors::default() +pub fn build_cors_config(server_environment: ServerEnvironment) -> Cors { + let is_production = server_environment.is_deployed_in_production(); + + info!("Building CORS for environment: {:?}", server_environment); + + do_build_cors_config(is_production) +} + +/// Return cors config for FakeYou / Vocodes / OBS / local development +pub fn build_production_cors_config() -> Cors { + const IS_PRODUCTION : bool = true; + do_build_cors_config(IS_PRODUCTION) +} + +fn do_build_cors_config(is_production: bool) -> Cors { + let mut cors = Cors::default(); + + info!("Building CORS for production: {}", is_production); + + cors = add_fakeyou(cors, is_production); + cors = add_storyteller(cors, is_production); + cors = add_power_stream(cors, is_production); + cors = add_legacy_storyteller_stream(cors, is_production); + cors = add_legacy_vocodes(cors, is_production); + cors = add_legacy_trumped(cors, is_production); + + if !is_production { + cors = add_development_only(cors); + } + + // Remaining setup + cors.allowed_methods(vec!["GET", "POST", "OPTIONS", "DELETE"]) + .supports_credentials() + .allowed_headers(vec![ + http::header::ACCEPT, + http::header::ACCESS_CONTROL_ALLOW_ORIGIN, // Tabulator Ajax + http::header::CONTENT_TYPE, + http::header::ACCESS_CONTROL_ALLOW_CREDENTIALS, // https://stackoverflow.com/a/46412839 + http::header::HeaderName::from_static("x-requested-with") // Tabulator Ajax sends + ]) + .max_age(3600) +} + +pub fn add_fakeyou(cors: Cors, is_production: bool) -> Cors { + // TODO: Remove non-SSL "http://" from production in safe rollout + if is_production { + cors + // FakeYou (Production) + .allowed_origin("http://api.fakeyou.com") + .allowed_origin("http://fakeyou.com") + .allowed_origin("https://api.fakeyou.com") + .allowed_origin("https://fakeyou.com") + // FakeYou (Staging) + .allowed_origin("http://staging.fakeyou.com") + .allowed_origin("https://staging.fakeyou.com") + } else { + cors + // FakeYou (Development) + .allowed_origin("http://dev.fakeyou.com") + .allowed_origin("http://development.fakeyou.com") + .allowed_origin("https://dev.fakeyou.com") + .allowed_origin("https://development.fakeyou.com") + } +} + +pub fn add_power_stream(cors: Cors, is_production: bool) -> Cors { + // TODO: Remove non-SSL "http://" from production in safe rollout + if is_production { + cors + .allowed_origin("https://dash.power.stream") + .allowed_origin("https://power.stream") + } else { + cors + .allowed_origin("http://dev.dash.power.stream") + .allowed_origin("http://dev.power.stream") + .allowed_origin("https://dev.dash.power.stream") + .allowed_origin("https://dev.power.stream") + } +} + +pub fn add_storyteller(cors: Cors, is_production: bool) -> Cors { + // TODO: Remove non-SSL "http://" from production in safe rollout + if is_production { + cors + // Storyteller.io (Production) + .allowed_origin("http://api.storyteller.io") + .allowed_origin("http://storyteller.io") + .allowed_origin("https://api.storyteller.io") + .allowed_origin("https://storyteller.io") + // Storyteller.io (Staging) + .allowed_origin("http://staging.storyteller.io") + .allowed_origin("https://staging.storyteller.io") + // Storyteller.io (Development Proxy) + .allowed_origin("http://development-proxy.storyteller.io") + .allowed_origin("https://development-proxy.storyteller.io") + } else { + cors + // Storyteller.io (Development) + .allowed_origin("http://dev.storyteller.io") + .allowed_origin("https://dev.storyteller.io") + } +} + +pub fn add_legacy_storyteller_stream(cors: Cors, is_production: bool) -> Cors { + // TODO: Remove non-SSL "http://" from production in safe rollout + if is_production { + cors + // Storyteller.stream (Production) + .allowed_origin("http://api.storyteller.stream") + .allowed_origin("http://obs.storyteller.stream") + .allowed_origin("http://storyteller.stream") + .allowed_origin("http://ws.storyteller.stream") + .allowed_origin("https://api.storyteller.stream") + .allowed_origin("https://obs.storyteller.stream") + .allowed_origin("https://storyteller.stream") + .allowed_origin("https://ws.storyteller.stream") + // Storyteller.stream (Staging) + .allowed_origin("http://staging.obs.storyteller.stream") + .allowed_origin("http://staging.storyteller.stream") + .allowed_origin("https://staging.obs.storyteller.stream") + .allowed_origin("https://staging.storyteller.stream") + // Legacy "create.storyteller.io" (Production) + .allowed_origin("http://create.storyteller.io") + .allowed_origin("http://obs.storyteller.io") + .allowed_origin("http://ws.storyteller.io") + .allowed_origin("https://create.storyteller.io") + .allowed_origin("https://obs.storyteller.io") + .allowed_origin("https://ws.storyteller.io") + } else { + cors // NB: None! + } +} + +pub fn add_legacy_vocodes(cors: Cors, is_production: bool) -> Cors { + if is_production { + cors + // Vocodes (Production) + .allowed_origin("https://api.vo.codes") + .allowed_origin("https://vo.codes") + .allowed_origin("https://vocodes.com") + } else { + cors + // Vocodes (Development) + .allowed_origin("http://dev.api.vo.codes") + .allowed_origin("http://dev.vo.codes") + .allowed_origin("https://dev.api.vo.codes") + .allowed_origin("https://dev.vo.codes") + } +} + +pub fn add_legacy_trumped(cors: Cors, is_production: bool) -> Cors { + if is_production { + cors + // Trumped (Production) + .allowed_origin("https://trumped.com") + } else { + cors + // Trumped (Development) + .allowed_origin("http://dev.trumped.com") + .allowed_origin("https://dev.trumped.com") + } +} + +pub fn add_development_only(cors: Cors) -> Cors { + cors // Local Development (Localhost) .allowed_origin("http://localhost:12345") .allowed_origin("http://localhost:3000") @@ -33,79 +197,98 @@ pub fn build_common_cors_config() -> Cors { .allowed_origin("https://jungle.horse") .allowed_origin("https://obs.jungle.horse") .allowed_origin("https://ws.jungle.horse") - // FakeYou - .allowed_origin("http://api.fakeyou.com") - .allowed_origin("http://fakeyou.com") - .allowed_origin("https://api.fakeyou.com") - .allowed_origin("https://fakeyou.com") - // FakeYou (Staging) - .allowed_origin("http://staging.fakeyou.com") - .allowed_origin("https://staging.fakeyou.com") - // FakeYou (Development) - .allowed_origin("http://development.fakeyou.com") - .allowed_origin("https://development.fakeyou.com") - .allowed_origin("http://dev.fakeyou.com") - .allowed_origin("https://dev.fakeyou.com") - // FakeYou (Development Proxy) - .allowed_origin("http://development-proxy.fakeyou.com") - .allowed_origin("https://development-proxy.fakeyou.com") - // Storyteller.io - .allowed_origin("http://api.storyteller.io") - .allowed_origin("http://create.storyteller.io") - .allowed_origin("http://obs.storyteller.io") - .allowed_origin("http://storyteller.io") - .allowed_origin("http://ws.storyteller.io") - .allowed_origin("https://api.storyteller.io") - .allowed_origin("https://create.storyteller.io") - .allowed_origin("https://obs.storyteller.io") - .allowed_origin("https://storyteller.io") - .allowed_origin("https://ws.storyteller.io") - // Storyteller.io (Staging) - .allowed_origin("http://staging.storyteller.io") - .allowed_origin("https://staging.storyteller.io") - // Storyteller.io (Development) - .allowed_origin("http://development.storyteller.io") - .allowed_origin("https://development.storyteller.io") - .allowed_origin("http://dev.storyteller.io") - .allowed_origin("https://dev.storyteller.io") - // Storyteller.io (Development Proxy) - .allowed_origin("http://development-proxy.storyteller.io") - .allowed_origin("https://development-proxy.storyteller.io") - // Storyteller.stream - .allowed_origin("http://api.storyteller.stream") - .allowed_origin("http://obs.storyteller.stream") - .allowed_origin("http://storyteller.stream") - .allowed_origin("http://ws.storyteller.stream") - .allowed_origin("https://api.storyteller.stream") - .allowed_origin("https://obs.storyteller.stream") - .allowed_origin("https://storyteller.stream") - .allowed_origin("https://ws.storyteller.stream") - // Storyteller.stream (Staging) - .allowed_origin("http://staging.obs.storyteller.stream") - .allowed_origin("http://staging.storyteller.stream") - .allowed_origin("https://staging.obs.storyteller.stream") - .allowed_origin("https://staging.storyteller.stream") - // Trumped (legacy) - .allowed_origin("http://trumped.com") - .allowed_origin("https://trumped.com") - // Vocodes (legacy) - .allowed_origin("http://api.vo.codes") - .allowed_origin("http://mumble.stream") - .allowed_origin("http://vo.codes") - .allowed_origin("http://vocodes.com") - .allowed_origin("https://api.vo.codes") - .allowed_origin("https://mumble.stream") - .allowed_origin("https://vo.codes") - .allowed_origin("https://vocodes.com") - // Remaining setup - .allowed_methods(vec!["GET", "POST", "OPTIONS", "DELETE"]) - .supports_credentials() - .allowed_headers(vec![ - http::header::ACCEPT, - http::header::ACCESS_CONTROL_ALLOW_ORIGIN, // Tabulator Ajax - http::header::CONTENT_TYPE, - http::header::ACCESS_CONTROL_ALLOW_CREDENTIALS, // https://stackoverflow.com/a/46412839 - http::header::HeaderName::from_static("x-requested-with") // Tabulator Ajax sends - ]) - .max_age(3600) -} \ No newline at end of file +} + +#[cfg(test)] +mod tests { + use actix_cors::Cors; + use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; + use actix_web::http::StatusCode; + use actix_web::test::TestRequest; + use actix_web::{Error, test}; + use crate::cors::build_cors_config; + use reusable_types::server_environment::ServerEnvironment; + use speculoos::{assert_that, asserting}; + + async fn make_test_request(cors: &Cors, hostname: &str) -> ServiceResponse { + let cors= cors.new_transform(test::ok_service()) + .await + .unwrap(); + + let request = TestRequest::default() + .insert_header(("Origin", hostname)) + .to_srv_request(); + + test::call_service(&cors, request).await + } + + async fn assert_origin_ok(cors: &Cors, hostname: &str) { + let response = make_test_request(cors, hostname).await; + asserting(&format!("Hostname {} is valid", hostname)) + .that(&response.status()) + .is_equal_to(StatusCode::OK); + } + + async fn assert_origin_invalid(cors: &Cors, hostname: &str) { + let response = make_test_request(cors, hostname).await; + asserting(&format!("Hostname {} is invalid", hostname)) + .that(&response.status()) + .is_equal_to(StatusCode::BAD_REQUEST); + } + + #[actix_rt::test] + async fn test_fakeyou_production() { + let production_cors = build_cors_config(ServerEnvironment::Production); + + // Valid Origin + assert_origin_ok(&production_cors, "https://fakeyou.com").await; + assert_origin_ok(&production_cors, "https://api.fakeyou.com").await; + assert_origin_ok(&production_cors, "https://staging.fakeyou.com").await; + + // Invalid Origin + assert_origin_invalid(&production_cors, "https://fake.fakeyou.com").await; + assert_origin_invalid(&production_cors, "https://jungle.horse").await; + assert_origin_invalid(&production_cors, "http://localhost:54321").await; + } + + #[actix_rt::test] + async fn test_fakeyou_development() { + let development_cors = build_cors_config(ServerEnvironment::Development); + + // Valid Origin + assert_origin_ok(&development_cors, "https://dev.fakeyou.com").await; + assert_origin_ok(&development_cors, "http://localhost:54321").await; + + // Invalid Origin + assert_origin_invalid(&development_cors, "https://fakeyou.com").await; + assert_origin_invalid(&development_cors, "https://api.fakeyou.com").await; + assert_origin_invalid(&development_cors, "https://staging.fakeyou.com").await; + } + + #[actix_rt::test] + async fn test_storyteller_production() { + let production_cors = build_cors_config(ServerEnvironment::Production); + + // Valid Origin + assert_origin_ok(&production_cors, "https://storyteller.io").await; + assert_origin_ok(&production_cors, "https://api.storyteller.io").await; + assert_origin_ok(&production_cors, "https://staging.storyteller.io").await; + + // Invalid Origin + assert_origin_invalid(&production_cors, "https://dev.storyteller.io").await; + assert_origin_invalid(&production_cors, "http://dev.storyteller.io").await; + } + + #[actix_rt::test] + async fn test_storyteller_development() { + let development_cors = build_cors_config(ServerEnvironment::Development); + + // Valid Origin + assert_origin_ok(&development_cors, "https://dev.storyteller.io").await; + assert_origin_ok(&development_cors, "http://localhost:54321").await; + + // Invalid Origin + assert_origin_invalid(&development_cors, "https://storyteller.io").await; + assert_origin_invalid(&development_cors, "https://staging.storyteller.io").await; + } +} diff --git a/crates/lib/reusable_types/src/lib.rs b/crates/lib/reusable_types/src/lib.rs index d29d22ea65..25a7163e22 100644 --- a/crates/lib/reusable_types/src/lib.rs +++ b/crates/lib/reusable_types/src/lib.rs @@ -19,4 +19,5 @@ pub mod generic_download_type; pub mod stripe; pub mod vocoder_type; -#[cfg(test)] pub mod test_helpers; \ No newline at end of file +#[cfg(test)] pub mod test_helpers; +pub mod server_environment; diff --git a/crates/lib/url_config/src/server_environment.rs b/crates/lib/reusable_types/src/server_environment.rs similarity index 73% rename from crates/lib/url_config/src/server_environment.rs rename to crates/lib/reusable_types/src/server_environment.rs index faaf7bd798..affaede563 100644 --- a/crates/lib/url_config/src/server_environment.rs +++ b/crates/lib/reusable_types/src/server_environment.rs @@ -17,9 +17,20 @@ impl ServerEnvironment { match environment { "dev" | "DEV" | "development" | "DEVELOPMENT" => Some(ServerEnvironment::Development), "prod" | "PROD" | "production" | "PRODUCTION" => Some(ServerEnvironment::Production), + // "stage" | "STAGE" | "staging" | "STAGING" => Some(ServerEnvironment::Staging), _ => None, } } + + /// Reports whether the software is running in production. + /// This is useful once proper "staging" backends gets introduced, as currently staging frontend + /// is served by the production backend. + pub fn is_deployed_in_production(&self) -> bool { + match self { + ServerEnvironment::Development => false, + ServerEnvironment::Production => true, + } + } } #[cfg(test)] @@ -49,4 +60,10 @@ mod tests { assert_eq!(ServerEnvironment::from_str("pRoDs"), None); assert_eq!(ServerEnvironment::from_str("staging"), None); } + + #[test] + fn test_is_deployed_in_production() { + assert_eq!(true, ServerEnvironment::Production.is_deployed_in_production()); + assert_eq!(false, ServerEnvironment::Development.is_deployed_in_production()); + } } diff --git a/crates/lib/url_config/Cargo.toml b/crates/lib/url_config/Cargo.toml index 22eae0bff6..534cbb5ff3 100644 --- a/crates/lib/url_config/Cargo.toml +++ b/crates/lib/url_config/Cargo.toml @@ -16,6 +16,7 @@ path = "src/lib.rs" # Internal http_server_common = { path = "../../lib/http_server_common" } +reusable_types = { path = "../../lib/reusable_types" } # External actix-web = "4.0.0-beta.10" diff --git a/crates/lib/url_config/src/lib.rs b/crates/lib/url_config/src/lib.rs index 3bd23523b0..068a15ae3a 100644 --- a/crates/lib/url_config/src/lib.rs +++ b/crates/lib/url_config/src/lib.rs @@ -51,5 +51,4 @@ Here are the domain names we handle: #![allow(dead_code)] #![allow(non_snake_case)] -pub mod server_environment; pub mod third_party_url_redirector; diff --git a/crates/lib/url_config/src/third_party_url_redirector.rs b/crates/lib/url_config/src/third_party_url_redirector.rs index 49c53405d2..6f919c8b45 100644 --- a/crates/lib/url_config/src/third_party_url_redirector.rs +++ b/crates/lib/url_config/src/third_party_url_redirector.rs @@ -1,8 +1,8 @@ use actix_web::HttpRequest; use actix_web::http::Uri; use anyhow::anyhow; -use crate::server_environment::ServerEnvironment; use http_server_common::request::get_request_host::get_request_host; +use reusable_types::server_environment::ServerEnvironment; use std::ops::Deref; use std::str::FromStr; @@ -79,7 +79,7 @@ mod tests { use actix_web::HttpRequest; use actix_web::http::header::HOST; use actix_web::test::TestRequest; - use crate::server_environment::ServerEnvironment; + use reusable_types::server_environment::ServerEnvironment; use crate::third_party_url_redirector::ThirdPartyUrlRedirector; fn request_with_host(hostname: &str) -> HttpRequest { diff --git a/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs b/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs index cedab33f53..18dbfdb51e 100644 --- a/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs +++ b/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_json_handler.rs @@ -16,7 +16,7 @@ use sqlx::MySqlPool; use std::collections::HashMap; use std::fmt; use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems}; -use url_config::server_environment::ServerEnvironment; +use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; // =============== Request =============== diff --git a/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs b/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs index dd9a622a56..878c226f3b 100644 --- a/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs +++ b/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_redirect_handler.rs @@ -15,7 +15,7 @@ use sqlx::MySqlPool; use std::collections::HashMap; use std::fmt; use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems}; -use url_config::server_environment::ServerEnvironment; +use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; // =============== Request =============== diff --git a/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_shared.rs b/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_shared.rs index 30e2b98ae8..d1a8bd491f 100644 --- a/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_shared.rs +++ b/crates/plugins/billing/src/stripe/http_endpoints/checkout/create/stripe_create_checkout_session_shared.rs @@ -11,7 +11,7 @@ use sqlx::MySqlPool; use std::collections::HashMap; use std::str::FromStr; use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionAutomaticTax, CreateCheckoutSessionLineItems, CreateCheckoutSessionPaymentIntentData, CreateCheckoutSessionSubscriptionData, CustomerId, ParseIdError}; -use url_config::server_environment::ServerEnvironment; +use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; /// Create a checkout session and return the URL @@ -190,7 +190,7 @@ mod tests { use crate::stripe::traits::internal_user_lookup::{MockInternalUserLookup, UserMetadata}; use mockall::predicate::*; use tokio; - use url_config::server_environment::ServerEnvironment; + use reusable_types::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; #[tokio::test] diff --git a/crates/plugins/billing/src/stripe/traits/internal_product_to_stripe_lookup.rs b/crates/plugins/billing/src/stripe/traits/internal_product_to_stripe_lookup.rs index 31731532f3..dfffcfeefd 100644 --- a/crates/plugins/billing/src/stripe/traits/internal_product_to_stripe_lookup.rs +++ b/crates/plugins/billing/src/stripe/traits/internal_product_to_stripe_lookup.rs @@ -3,7 +3,7 @@ use mockall::{automock, mock, predicate::*}; use std::error::Error; use std::fmt::{Display, Formatter}; -use url_config::server_environment::ServerEnvironment; +use reusable_types::server_environment::ServerEnvironment; /// Errors for this component are not strongly typed. #[derive(Debug)] diff --git a/crates/service/image_generation_service/src/main.rs b/crates/service/image_generation_service/src/main.rs index 5461973fcc..16748931a7 100644 --- a/crates/service/image_generation_service/src/main.rs +++ b/crates/service/image_generation_service/src/main.rs @@ -27,7 +27,7 @@ use config::shared_constants::{DEFAULT_MYSQL_CONNECTION_STRING, DEFAULT_RUST_LOG use container_common::anyhow_result::AnyhowResult; use crate::routes::add_routes; use crate::server_state::{EnvConfig, InMemoryCaches, ServerState}; -use http_server_common::cors::build_common_cors_config; +use http_server_common::cors::build_production_cors_config; use limitation::Limiter; use log::info; use memory_caching::single_item_ttl_cache::SingleItemTtlCache; @@ -227,7 +227,7 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> let app = App::new() .app_data(server_state_arc.clone()) - .wrap(build_common_cors_config()) + .wrap(build_production_cors_config()) .wrap(DefaultHeaders::new() .header("X-Backend-Hostname", &hostname) .header("X-Build-Sha", build_sha.trim())) diff --git a/crates/service/storyteller_web/src/billing/internal_product_to_stripe_lookup_impl.rs b/crates/service/storyteller_web/src/billing/internal_product_to_stripe_lookup_impl.rs index 52a0cbc54d..ba95bc9678 100644 --- a/crates/service/storyteller_web/src/billing/internal_product_to_stripe_lookup_impl.rs +++ b/crates/service/storyteller_web/src/billing/internal_product_to_stripe_lookup_impl.rs @@ -1,6 +1,6 @@ use billing_component::stripe::traits::internal_product_to_stripe_lookup::{InternalProductToStripeLookup, StripeProduct, StripeProductLookupError}; use crate::configs::plans::plan_list::{ALL_PLANS_BY_SLUG, DEVELOPMENT_PREMIUM_PLANS_BY_SLUG, PRODUCTION_PREMIUM_PLANS_BY_SLUG}; -use url_config::server_environment::ServerEnvironment; +use reusable_types::server_environment::ServerEnvironment; /// A simple Actix injectable action #[derive(Clone, Copy)] diff --git a/crates/service/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs b/crates/service/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs index 44b0722950..8b4df804c3 100644 --- a/crates/service/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs +++ b/crates/service/storyteller_web/src/configs/plans/get_correct_plan_for_session.rs @@ -1,8 +1,8 @@ -use std::ops::Deref; use chrono::Utc; use crate::configs::plans::plan::Plan; use crate::configs::plans::plan_list::{DEVELOPMENT_PREMIUM_PLANS, DEVELOPMENT_PREMIUM_PLANS_BY_SLUG, FREE_LOGGED_IN_PLAN, FREE_LOGGED_OUT_PLAN, LOYALTY_PLANS_BY_SLUG, PRODUCTION_PREMIUM_PLANS, PRODUCTION_PREMIUM_PLANS_BY_SLUG}; -use url_config::server_environment::ServerEnvironment; +use reusable_types::server_environment::ServerEnvironment; +use std::ops::Deref; use users_component::utils::user_session_extended::{UserSessionExtended, UserSessionSubscriptionPlan}; /// Look up the most appropriate plan for the session. @@ -64,7 +64,7 @@ pub mod tests { use chrono::{Duration, Utc}; use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session; use crate::configs::plans::plan_list::{ALL_PLANS_BY_SLUG, FREE_LOGGED_IN_PLAN, FREE_LOGGED_OUT_PLAN}; - use url_config::server_environment::ServerEnvironment; + use reusable_types::server_environment::ServerEnvironment; use users_component::utils::user_session_extended::{UserSessionExtended, UserSessionSubscriptionPlan}; #[test] diff --git a/crates/service/storyteller_web/src/main.rs b/crates/service/storyteller_web/src/main.rs index 0efdd977e9..8398c8bd22 100644 --- a/crates/service/storyteller_web/src/main.rs +++ b/crates/service/storyteller_web/src/main.rs @@ -68,13 +68,14 @@ use crate::util::encrypted_sort_id::SortKeyCrypto; use database_queries::mediators::badge_granter::BadgeGranter; use database_queries::mediators::firehose_publisher::FirehosePublisher; use futures::Future; -use http_server_common::cors::build_common_cors_config; +use http_server_common::cors::{build_cors_config, build_production_cors_config}; use limitation::Limiter; use log::{error, info}; use memory_caching::single_item_ttl_cache::SingleItemTtlCache; use r2d2_redis::RedisConnectionManager; use r2d2_redis::r2d2; use r2d2_redis::redis::Commands; +use reusable_types::server_environment::ServerEnvironment; use sqlx::MySqlPool; use sqlx::mysql::MySqlPoolOptions; use std::sync::Arc; @@ -82,7 +83,6 @@ use std::time::Duration; use storage_buckets_common::bucket_client::BucketClient; use tokio::runtime::Runtime; use twitch_common::twitch_secrets::TwitchSecrets; -use url_config::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; use users_component::utils::session_checker::SessionChecker; use users_component::utils::session_cookie_manager::SessionCookieManager; @@ -438,7 +438,7 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> .app_data(web::Data::from(stripe_lookup)) // NB: Data::from(Arc) for dynamic dispatch .app_data(web::Data::from(user_lookup)) // NB: Data::from(Arc) for dynamic dispatch .app_data(server_state_arc.clone()) - .wrap(build_common_cors_config()) + .wrap(build_cors_config(server_state_arc.server_environment.clone())) .wrap(DefaultHeaders::new() .header("X-Backend-Hostname", &hostname) .header("X-Build-Sha", build_sha.trim())) diff --git a/crates/service/storyteller_web/src/server_state.rs b/crates/service/storyteller_web/src/server_state.rs index cf04bc7e43..11ff27bf89 100644 --- a/crates/service/storyteller_web/src/server_state.rs +++ b/crates/service/storyteller_web/src/server_state.rs @@ -12,9 +12,9 @@ use database_queries::queries::tts::tts_inference_jobs::get_pending_tts_inferenc use database_queries::queries::w2l::w2l_templates::list_w2l_templates::W2lTemplateRecordForList; use memory_caching::single_item_ttl_cache::SingleItemTtlCache; use r2d2_redis::{r2d2, RedisConnectionManager}; +use reusable_types::server_environment::ServerEnvironment; use sqlx::MySqlPool; use storage_buckets_common::bucket_client::BucketClient; -use url_config::server_environment::ServerEnvironment; use url_config::third_party_url_redirector::ThirdPartyUrlRedirector; use users_component::utils::session_checker::SessionChecker; use users_component::utils::session_cookie_manager::SessionCookieManager; diff --git a/crates/service/websocket_gateway/src/main.rs b/crates/service/websocket_gateway/src/main.rs index f7e17007fa..42e16ecf8f 100644 --- a/crates/service/websocket_gateway/src/main.rs +++ b/crates/service/websocket_gateway/src/main.rs @@ -28,7 +28,7 @@ use crate::endpoints_ws::obs_gateway_websocket_handler::obs_gateway_websocket_ha use crate::server_state::{ObsGatewayServerState, EnvConfig, TwitchOauthSecrets, BackendsConfig, MultithreadingConfig}; use futures::Future; use futures::executor::ThreadPool; -use http_server_common::cors::build_common_cors_config; +use http_server_common::cors::build_production_cors_config; use http_server_common::endpoints::default_route_404::default_route_404; use http_server_common::endpoints::root_index::get_root_index; use limitation::Limiter; @@ -165,7 +165,7 @@ pub async fn serve(server_state: ObsGatewayServerState) -> AnyhowResult<()> HttpServer::new(move || { App::new() .app_data(server_state_arc.clone()) - .wrap(build_common_cors_config()) + .wrap(build_production_cors_config()) .wrap(Logger::new(&log_format) .exclude("/liveness") .exclude("/readiness"))