From 851ffe1e2b9a57ab9f5c61b7f4b29eb501be94ba Mon Sep 17 00:00:00 2001 From: Max Kalashnikoff Date: Mon, 9 Oct 2023 22:32:59 +0300 Subject: [PATCH 1/5] adding `relay_public_key` to environment, config and terraform --- .env.example | 1 + .env.multi-tenant-example | 3 +++ .env.single-tenant-example | 3 +++ .github/workflows/cd.yml | 2 ++ .github/workflows/ci_terraform.yml | 1 + src/config.rs | 10 ++++++++-- terraform/ecs/main.tf | 3 ++- terraform/ecs/variables.tf | 5 +++++ terraform/main.tf | 1 + terraform/variables.tf | 5 +++++ 10 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 5ab6e3b3..f8d05c01 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,7 @@ DISABLE_HEADER=false # Should Echo Server validate messages it recieves are from the Relay when attempting to send a push notification VALIDATE_SIGNATURES=true +RELAY_PUBLIC_KEY= # Filter irrelevant logs from other crates, but enable traces for the relay. # We're using separate log levels for stderr and telemetry. Note: telemetry diff --git a/.env.multi-tenant-example b/.env.multi-tenant-example index 1f61037d..cc772ae3 100644 --- a/.env.multi-tenant-example +++ b/.env.multi-tenant-example @@ -3,6 +3,9 @@ PUBLIC_URL=http://localhost:3000 DATABASE_URL=postgres://user:pass@host:port/database LOG_LEVEL=debug,echo-server=debug +# Public key can be obtained from the https://relay.walletconnect.com/public-key +RELAY_PUBLIC_KEY= + # Don't validate signatures - allows for users to send push notifications from # HTTP clients e.g. curl, insomnia, postman, etc VALIDATE_SIGNATURES=false diff --git a/.env.single-tenant-example b/.env.single-tenant-example index 0ba59582..785b5378 100644 --- a/.env.single-tenant-example +++ b/.env.single-tenant-example @@ -3,6 +3,9 @@ PUBLIC_URL=http://localhost:3000 DATABASE_URL=postgres://user:pass@host:port/database LOG_LEVEL=debug,echo-server=debug +# Public key can be obtained from the https://relay.walletconnect.com/public-key +RELAY_PUBLIC_KEY= + # Don't validate signatures - allows for users to send push notifications from # HTTP clients e.g. curl, insomnia, postman, etc VALIDATE_SIGNATURES=false diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 21abb0fb..5d3df508 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -86,6 +86,7 @@ jobs: TF_VAR_cloud_api_key: ${{ secrets.CLOUD_API_KEY }} TF_VAR_jwt_secret: ${{ secrets.JWT_SECRET }} TF_VAR_image_version: ${{ inputs.image_tag }} + TF_VAR_relay_public_key: ${{ secrets.RELAY_PUBLIC_KEY }} with: environment: "staging" @@ -156,6 +157,7 @@ jobs: TF_VAR_cloud_api_key: ${{ secrets.CLOUD_API_KEY }} TF_VAR_jwt_secret: ${{ secrets.JWT_SECRET }} TF_VAR_image_version: ${{ inputs.image_tag }} + TF_VAR_relay_public_key: ${{ secrets.RELAY_PUBLIC_KEY }} with: environment: "prod" diff --git a/.github/workflows/ci_terraform.yml b/.github/workflows/ci_terraform.yml index bcf5b59f..32259b95 100644 --- a/.github/workflows/ci_terraform.yml +++ b/.github/workflows/ci_terraform.yml @@ -126,6 +126,7 @@ jobs: TF_VAR_grafana_endpoint: ${{ steps.grafana-get-details.outputs.endpoint }} TF_VAR_cloud_api_key: ${{ secrets.CLOUD_API_KEY }} TF_VAR_jwt_secret: ${{ secrets.JWT_SECRET }} + TF_VAR_relay_public_key: ${{ secrets.RELAY_PUBLIC_KEY }} with: github-token: ${{ secrets.GITHUB_TOKEN }} environment: staging diff --git a/src/config.rs b/src/config.rs index e5f11fd4..290b6231 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,8 +24,7 @@ pub struct Config { pub log_level_otel: String, #[serde(default = "default_disable_header")] pub disable_header: bool, - #[serde(default = "default_relay_url")] - pub relay_url: String, + pub relay_public_key: String, #[serde(default = "default_validate_signatures")] pub validate_signatures: bool, pub database_url: String, @@ -111,6 +110,13 @@ impl Config { Err(e) => Err(e), }?; + // Empty Relay public key is not allowed + if self.relay_public_key.is_empty() { + return Err(InvalidConfiguration( + "`RELAY_PUBLIC_KEY` cannot be empty".to_string(), + )); + } + Ok(()) } diff --git a/terraform/ecs/main.tf b/terraform/ecs/main.tf index b8815b52..515e054e 100644 --- a/terraform/ecs/main.tf +++ b/terraform/ecs/main.tf @@ -95,7 +95,8 @@ resource "aws_ecs_task_definition" "app_task_definition" { { name = "CLOUD_API_KEY", value = var.cloud_api_key }, { name = "CLOUD_API_URL", value = var.cloud_api_url }, - { name = "JWT_SECRET", value = var.jwt_secret } + { name = "JWT_SECRET", value = var.jwt_secret }, + { name = "RELAY_PUBLIC_KEY", value = var.relay_public_key } ], dependsOn = [ { containerName = "aws-otel-collector", condition = "START" } diff --git a/terraform/ecs/variables.tf b/terraform/ecs/variables.tf index f4f66e52..10e8e888 100644 --- a/terraform/ecs/variables.tf +++ b/terraform/ecs/variables.tf @@ -137,3 +137,8 @@ variable "jwt_secret" { type = string sensitive = true } + +variable "relay_public_key" { + type = string + sensitive = true +} diff --git a/terraform/main.tf b/terraform/main.tf index bbf424f6..48f2d66d 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -179,6 +179,7 @@ module "ecs" { cloud_api_url = "https://registry.walletconnect.com/" jwt_secret = var.jwt_secret + relay_public_key = var.relay_public_key autoscaling_max_capacity = local.environment == "prod" ? 4 : 1 autoscaling_min_capacity = local.environment == "prod" ? 2 : 1 diff --git a/terraform/variables.tf b/terraform/variables.tf index d32cd426..778c0ca9 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -37,3 +37,8 @@ variable "jwt_secret" { type = string sensitive = true } + +variable "relay_public_key" { + type = string + sensitive = true +} From 411fef5a7e3954cc634b57f8a4469972509b09dd Mon Sep 17 00:00:00 2001 From: Max Kalashnikoff Date: Mon, 9 Oct 2023 23:21:12 +0300 Subject: [PATCH 2/5] scraping key requesting logic to use the key from config --- src/config.rs | 6 --- src/lib.rs | 8 +--- src/middleware/validate_signature.rs | 12 +++--- src/relay/mod.rs | 64 ++++++---------------------- src/state.rs | 6 +-- tests/context/server.rs | 1 - tests/unit/relay.rs | 14 ------ 7 files changed, 22 insertions(+), 89 deletions(-) delete mode 100644 tests/unit/relay.rs diff --git a/src/config.rs b/src/config.rs index 290b6231..556b419e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -197,12 +197,6 @@ fn default_validate_signatures() -> bool { true } -pub const RELAY_URL: &str = "https://relay.walletconnect.com"; - -fn default_relay_url() -> String { - RELAY_URL.to_string() -} - fn default_is_test() -> bool { false } diff --git a/src/lib.rs b/src/lib.rs index cbd27794..b2ebbe43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ use { request_id::{PropagateRequestIdLayer, SetRequestIdLayer}, trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer}, }, - tracing::{info, log::LevelFilter, warn, Level}, + tracing::{info, log::LevelFilter, Level}, }; #[cfg(not(feature = "multitenant"))] @@ -155,12 +155,6 @@ pub async fn bootstap(mut shutdown: broadcast::Receiver<()>, config: Config) -> .collect::>() .join(", "); - // Fetch public key so it's cached for the first 6hrs - let public_key = state.relay_client.public_key().await; - if public_key.is_err() { - warn!("Failed initial fetch of Relay's Public Key, this may prevent webhook validation.") - } - if state.config.telemetry_prometheus_port.is_some() { state.set_metrics(metrics::Metrics::new(Resource::new(vec![ KeyValue::new("service_name", "echo-server"), diff --git a/src/middleware/validate_signature.rs b/src/middleware/validate_signature.rs index a386a9ce..be94fef2 100644 --- a/src/middleware/validate_signature.rs +++ b/src/middleware/validate_signature.rs @@ -1,11 +1,8 @@ use { crate::{ error::Error::{ - FromRequestError, - MissingAllSignatureHeader, - MissingSignatureHeader, - MissingTimestampHeader, - ToBytesError, + FromRequestError, MissingAllSignatureHeader, MissingSignatureHeader, + MissingTimestampHeader, ToBytesError, }, state::State, }, @@ -44,7 +41,8 @@ where let s = span!(tracing::Level::DEBUG, "validate_signature"); let _ = s.enter(); - let public_key = state.relay_client().public_key().await?; + let state_binding = state.relay_client(); + let public_key = state_binding.get_verifying_key(); let (parts, body_raw) = req.into_parts(); let bytes = hyper::body::to_bytes(body_raw) @@ -64,7 +62,7 @@ where match (signature_header, timestamp_header) { (Some(signature), Some(timestamp)) - if signature_is_valid(signature, timestamp, &body, &public_key).await? => + if signature_is_valid(signature, timestamp, &body, public_key).await? => { let req = Request::::from_parts(parts, bytes.into()); Ok(T::from_request(req, state) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 3b03253f..9da31482 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,62 +1,26 @@ -use { - chrono::{DateTime, Duration, Utc}, - ed25519_dalek::VerifyingKey, - std::ops::Add, -}; - -const PUBLIC_KEY_TTL_HOURS: i64 = 6; +use ed25519_dalek::VerifyingKey; #[derive(Clone)] pub struct RelayClient { - http_client: reqwest::Client, - base_url: String, - public_key: Option, - public_key_last_fetched: DateTime, + public_key: VerifyingKey, } impl RelayClient { - pub fn new(base_url: String) -> RelayClient { - RelayClient { - http_client: reqwest::Client::new(), - base_url, - public_key: None, - public_key_last_fetched: DateTime::::MIN_UTC, - } - } - - /// Fetches the public key with a TTL - pub async fn public_key(&mut self) -> crate::error::Result { - if let Some(public_key) = self.public_key { - // TTL Not exceeded - if self - .public_key_last_fetched - .add(Duration::hours(PUBLIC_KEY_TTL_HOURS)) - < Utc::now() - { - return Ok(public_key); - } - } - - let public_key = self.fetch_public_key().await?; - self.public_key = Some(public_key); - self.public_key_last_fetched = Utc::now(); - Ok(public_key) + pub fn new(string_public_key: String) -> crate::error::Result { + let verifying_key = Self::string_to_verifying_key(&string_public_key)?; + Ok(RelayClient { + public_key: verifying_key, + }) } - async fn fetch_public_key(&self) -> crate::error::Result { - let response = self - .http_client - .get(self.get_url("public-key")) - .send() - .await?; - let body = response.text().await?; - let key_bytes = hex::decode(body)?; - let public_key = - VerifyingKey::from_bytes(<&[u8; 32]>::try_from(key_bytes.as_slice()).unwrap())?; - Ok(public_key) + pub fn get_verifying_key(&self) -> &VerifyingKey { + &self.public_key } - fn get_url(&self, path: &str) -> String { - format!("{}/{}", self.base_url, path) + fn string_to_verifying_key(string_key: &str) -> crate::error::Result { + let key_bytes = hex::decode(string_key)?; + Ok(VerifyingKey::from_bytes( + <&[u8; 32]>::try_from(key_bytes.as_slice()).unwrap(), + )?) } } diff --git a/src/state.rs b/src/state.rs index 101fef16..c50515e5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -71,8 +71,6 @@ pub fn new_state( #[cfg(not(feature = "multitenant"))] let is_multitenant = false; - let relay_url = config.relay_url.to_string(); - #[cfg(feature = "cloud")] let (cloud_url, cloud_api_key) = (config.cloud_api_url.clone(), config.cloud_api_key.clone()); @@ -86,7 +84,7 @@ pub fn new_state( }; Ok(AppState { - config, + config: config.clone(), build_info: build_info.clone(), metrics: None, #[cfg(feature = "analytics")] @@ -94,7 +92,7 @@ pub fn new_state( client_store, notification_store, tenant_store, - relay_client: RelayClient::new(relay_url), + relay_client: RelayClient::new(config.relay_public_key)?, #[cfg(feature = "cloud")] registry_client: RegistryHttpClient::new(cloud_url, cloud_api_key.as_str())?, #[cfg(feature = "multitenant")] diff --git a/tests/context/server.rs b/tests/context/server.rs index f6e8fff3..86f974e3 100644 --- a/tests/context/server.rs +++ b/tests/context/server.rs @@ -30,7 +30,6 @@ impl EchoServer { log_level: "info,echo-server=info".into(), log_level_otel: "info,echo-server=trace".into(), disable_header: true, - relay_url: "https://relay.walletconnect.com".into(), validate_signatures: false, database_url: DATABASE_URL.into(), #[cfg(feature = "multitenant")] diff --git a/tests/unit/relay.rs b/tests/unit/relay.rs deleted file mode 100644 index f8bc38ab..00000000 --- a/tests/unit/relay.rs +++ /dev/null @@ -1,14 +0,0 @@ -use echo_server::{config, relay::RelayClient}; - -pub fn get_client() -> RelayClient { - RelayClient::new(config::RELAY_URL.to_string()) -} - -#[tokio::test] -pub async fn fetch_public_key() { - let mut client = get_client(); - - let res = client.public_key().await; - - assert!(res.is_ok()); -} From 3925136a05ce27e8e99ab841ff76a3f4b7fbeb81 Mon Sep 17 00:00:00 2001 From: Max Kalashnikoff Date: Tue, 10 Oct 2023 10:37:38 +0300 Subject: [PATCH 3/5] fix unit tests --- .env.example | 4 +++- .github/workflows/ci.yml | 4 ++++ src/config.rs | 21 +++++++++++++++++- src/middleware/validate_signature.rs | 7 ++++-- tests/context/mod.rs | 33 +++++++++++++++++++--------- tests/context/server.rs | 14 +++++------- tests/context/stores.rs | 10 +++++---- tests/unit/mod.rs | 1 - 8 files changed, 66 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index f8d05c01..5d905397 100644 --- a/.env.example +++ b/.env.example @@ -3,9 +3,11 @@ PUBLIC_URL=https://echo.walletconnect.com DATABASE_URL=postgres://user:pass@host:port/database DISABLE_HEADER=false +# Public key can be obtained from the https://relay.walletconnect.com/public-key +RELAY_PUBLIC_KEY= + # Should Echo Server validate messages it recieves are from the Relay when attempting to send a push notification VALIDATE_SIGNATURES=true -RELAY_PUBLIC_KEY= # Filter irrelevant logs from other crates, but enable traces for the relay. # We're using separate log levels for stderr and telemetry. Note: telemetry diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 933b033a..c80c23d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,10 @@ jobs: RUSTC_WRAPPER: sccache SCCACHE_CACHE_SIZE: 1G SCCACHE_DIR: ${{ matrix.sccache-path }} + # Unit test environment variables dependencies + DATABASE_URL: postgres://postgres:root@localhost:5432/postgres + TENANT_DATABASE_URL: postgres://postgres:root@localhost:5433/postgres + RELAY_PUBLIC_KEY: ${{ secrets.RELAY_PUBLIC_KEY }} steps: # Checkout code - name: "Git checkout" diff --git a/src/config.rs b/src/config.rs index 556b419e..e3ee9c92 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ use crate::providers::ProviderKind; pub struct Config { #[serde(default = "default_port")] pub port: u16, + #[serde(default = "default_public_url")] pub public_url: String, #[serde(default = "default_log_level")] pub log_level: String, @@ -63,9 +64,9 @@ pub struct Config { pub fcm_api_key: Option, // Multi-tenancy - #[cfg(feature = "multitenant")] pub tenant_database_url: String, #[cfg(feature = "multitenant")] + #[serde(default = "default_jwt_secret")] pub jwt_secret: String, // Analytics @@ -78,9 +79,11 @@ pub struct Config { pub geoip_db_key: Option, #[cfg(feature = "analytics")] + #[serde(default = "default_analytics_export_bucket")] pub analytics_export_bucket: String, #[cfg(feature = "geoblock")] + #[serde(default = "default_blocked_countries")] pub blocked_countries: Vec, // Cloud @@ -181,6 +184,10 @@ fn default_port() -> u16 { 3000 } +fn default_public_url() -> String { + format!("http://127.0.0.1:{}", default_port()) +} + fn default_log_level() -> String { "info,echo-server=info".to_string() } @@ -205,6 +212,18 @@ fn default_cors_allowed_origins() -> Vec { vec!["*".to_string()] } +fn default_jwt_secret() -> String { + "n/a".to_string() +} + +fn default_analytics_export_bucket() -> String { + "example-bucket".to_string() +} + +fn default_blocked_countries() -> Vec { + vec![] +} + pub fn get_config() -> error::Result { let config = envy::from_env::()?; Ok(config) diff --git a/src/middleware/validate_signature.rs b/src/middleware/validate_signature.rs index be94fef2..bf8c68fc 100644 --- a/src/middleware/validate_signature.rs +++ b/src/middleware/validate_signature.rs @@ -1,8 +1,11 @@ use { crate::{ error::Error::{ - FromRequestError, MissingAllSignatureHeader, MissingSignatureHeader, - MissingTimestampHeader, ToBytesError, + FromRequestError, + MissingAllSignatureHeader, + MissingSignatureHeader, + MissingTimestampHeader, + ToBytesError, }, state::State, }, diff --git a/tests/context/mod.rs b/tests/context/mod.rs index d3d43700..651b1b51 100644 --- a/tests/context/mod.rs +++ b/tests/context/mod.rs @@ -1,17 +1,21 @@ use { self::server::EchoServer, async_trait::async_trait, - echo_server::state::{ClientStoreArc, NotificationStoreArc, TenantStoreArc}, + echo_server::{ + config::Config, + state::{ClientStoreArc, NotificationStoreArc, TenantStoreArc}, + }, sqlx::{Pool, Postgres}, std::sync::Arc, - test_context::AsyncTestContext, + test_context::{AsyncTestContext, TestContext}, }; mod server; mod stores; -pub const DATABASE_URL: &str = "postgres://postgres:root@localhost:5432/postgres"; -pub const TENANT_DATABASE_URL: &str = "postgres://postgres:root@localhost:5433/postgres"; +pub struct ConfigContext { + pub config_from_env: Config, +} pub struct EchoServerContext { pub server: EchoServer, @@ -26,22 +30,31 @@ pub struct StoreContext { pub tenants: TenantStoreArc, } +impl TestContext for ConfigContext { + fn setup() -> Self { + let config_from_env = envy::from_env::().unwrap(); + Self { config_from_env } + } +} + #[async_trait] impl AsyncTestContext for EchoServerContext { async fn setup() -> Self { - let server = EchoServer::start().await; + let config_from_env = ConfigContext::setup().config_from_env; + let server = EchoServer::start(&config_from_env).await; Self { server } } - - async fn teardown(mut self) { - self.server.shutdown().await; - } } #[async_trait] impl AsyncTestContext for StoreContext { async fn setup() -> Self { - let (db, tenant_db) = stores::open_pg_connections().await; + let config_from_env = ConfigContext::setup().config_from_env; + let (db, tenant_db) = stores::open_pg_connections( + &config_from_env.database_url, + &config_from_env.tenant_database_url, + ) + .await; let db_arc = Arc::new(db); let tenant_db_arc = Arc::new(tenant_db); diff --git a/tests/context/server.rs b/tests/context/server.rs index 86f974e3..584a7acf 100644 --- a/tests/context/server.rs +++ b/tests/context/server.rs @@ -1,5 +1,4 @@ use { - crate::context::DATABASE_URL, echo_server::config::Config, std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener}, tokio::{ @@ -9,9 +8,6 @@ use { }, }; -#[cfg(feature = "multitenant")] -use crate::context::TENANT_DATABASE_URL; - pub struct EchoServer { pub public_addr: SocketAddr, shutdown_signal: tokio::sync::broadcast::Sender<()>, @@ -22,18 +18,18 @@ pub struct EchoServer { pub enum Error {} impl EchoServer { - pub async fn start() -> Self { + pub async fn start(config_from_env: &Config) -> Self { let public_port = get_random_port(); - let config: Config = Config { + let config = Config { port: public_port, public_url: format!("http://127.0.0.1:{public_port}"), log_level: "info,echo-server=info".into(), log_level_otel: "info,echo-server=trace".into(), disable_header: true, + relay_public_key: config_from_env.relay_public_key.clone(), validate_signatures: false, - database_url: DATABASE_URL.into(), - #[cfg(feature = "multitenant")] - tenant_database_url: TENANT_DATABASE_URL.into(), + database_url: config_from_env.database_url.clone(), + tenant_database_url: config_from_env.tenant_database_url.clone(), #[cfg(feature = "multitenant")] jwt_secret: "n/a".to_string(), otel_exporter_otlp_endpoint: None, diff --git a/tests/context/stores.rs b/tests/context/stores.rs index 16778a84..f8891e9b 100644 --- a/tests/context/stores.rs +++ b/tests/context/stores.rs @@ -1,5 +1,4 @@ use { - crate::context::{DATABASE_URL, TENANT_DATABASE_URL}, sqlx::{ postgres::{PgConnectOptions, PgPoolOptions}, ConnectOptions, @@ -10,8 +9,11 @@ use { tracing::log::LevelFilter, }; -pub async fn open_pg_connections() -> (Pool, Pool) { - let pg_options = PgConnectOptions::from_str(DATABASE_URL) +pub async fn open_pg_connections( + database_uri: &str, + tenant_database_uri: &str, +) -> (Pool, Pool) { + let pg_options = PgConnectOptions::from_str(database_uri) .expect("failed to parse postgres url") .log_statements(LevelFilter::Debug) .log_slow_statements(LevelFilter::Info, Duration::from_millis(250)) @@ -28,7 +30,7 @@ pub async fn open_pg_connections() -> (Pool, Pool) { .await .expect("failed to run migrations"); - let tenant_pg_options = PgConnectOptions::from_str(TENANT_DATABASE_URL) + let tenant_pg_options = PgConnectOptions::from_str(tenant_database_uri) .expect("failed to parse postgres url") .log_statements(LevelFilter::Debug) .log_slow_statements(LevelFilter::Info, Duration::from_millis(250)) diff --git a/tests/unit/mod.rs b/tests/unit/mod.rs index 20d6e784..1a4d2be4 100644 --- a/tests/unit/mod.rs +++ b/tests/unit/mod.rs @@ -1,3 +1,2 @@ mod messages; mod middleware; -mod relay; From fa2dba6194620076ab0a5e8f74151923da41868c Mon Sep 17 00:00:00 2001 From: Max Kalashnikoff Date: Tue, 10 Oct 2023 23:36:15 +0300 Subject: [PATCH 4/5] fixing nits in the env comments --- .env.example | 2 +- .env.multi-tenant-example | 2 +- .env.single-tenant-example | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 5d905397..d856a884 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ PUBLIC_URL=https://echo.walletconnect.com DATABASE_URL=postgres://user:pass@host:port/database DISABLE_HEADER=false -# Public key can be obtained from the https://relay.walletconnect.com/public-key +# Public key can be obtained from https://relay.walletconnect.com/public-key RELAY_PUBLIC_KEY= # Should Echo Server validate messages it recieves are from the Relay when attempting to send a push notification diff --git a/.env.multi-tenant-example b/.env.multi-tenant-example index cc772ae3..77ce5f01 100644 --- a/.env.multi-tenant-example +++ b/.env.multi-tenant-example @@ -3,7 +3,7 @@ PUBLIC_URL=http://localhost:3000 DATABASE_URL=postgres://user:pass@host:port/database LOG_LEVEL=debug,echo-server=debug -# Public key can be obtained from the https://relay.walletconnect.com/public-key +# Public key can be obtained from https://relay.walletconnect.com/public-key RELAY_PUBLIC_KEY= # Don't validate signatures - allows for users to send push notifications from diff --git a/.env.single-tenant-example b/.env.single-tenant-example index 785b5378..3f8fb3a1 100644 --- a/.env.single-tenant-example +++ b/.env.single-tenant-example @@ -3,7 +3,7 @@ PUBLIC_URL=http://localhost:3000 DATABASE_URL=postgres://user:pass@host:port/database LOG_LEVEL=debug,echo-server=debug -# Public key can be obtained from the https://relay.walletconnect.com/public-key +# Public key can be obtained from https://relay.walletconnect.com/public-key RELAY_PUBLIC_KEY= # Don't validate signatures - allows for users to send push notifications from From b4f8e427fac33e1342516347b433d76be67ca4af Mon Sep 17 00:00:00 2001 From: Max Kalashnikoff Date: Wed, 11 Oct 2023 00:19:34 +0300 Subject: [PATCH 5/5] Get database uri and relay public key from env instead of deserialization --- src/config.rs | 20 ------------ tests/context/mod.rs | 67 +++++++++++++++++++++++++++++++++-------- tests/context/server.rs | 53 ++------------------------------ 3 files changed, 57 insertions(+), 83 deletions(-) diff --git a/src/config.rs b/src/config.rs index e3ee9c92..dc5cddf9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,6 @@ use crate::providers::ProviderKind; pub struct Config { #[serde(default = "default_port")] pub port: u16, - #[serde(default = "default_public_url")] pub public_url: String, #[serde(default = "default_log_level")] pub log_level: String, @@ -66,7 +65,6 @@ pub struct Config { // Multi-tenancy pub tenant_database_url: String, #[cfg(feature = "multitenant")] - #[serde(default = "default_jwt_secret")] pub jwt_secret: String, // Analytics @@ -79,11 +77,9 @@ pub struct Config { pub geoip_db_key: Option, #[cfg(feature = "analytics")] - #[serde(default = "default_analytics_export_bucket")] pub analytics_export_bucket: String, #[cfg(feature = "geoblock")] - #[serde(default = "default_blocked_countries")] pub blocked_countries: Vec, // Cloud @@ -184,10 +180,6 @@ fn default_port() -> u16 { 3000 } -fn default_public_url() -> String { - format!("http://127.0.0.1:{}", default_port()) -} - fn default_log_level() -> String { "info,echo-server=info".to_string() } @@ -212,18 +204,6 @@ fn default_cors_allowed_origins() -> Vec { vec!["*".to_string()] } -fn default_jwt_secret() -> String { - "n/a".to_string() -} - -fn default_analytics_export_bucket() -> String { - "example-bucket".to_string() -} - -fn default_blocked_countries() -> Vec { - vec![] -} - pub fn get_config() -> error::Result { let config = envy::from_env::()?; Ok(config) diff --git a/tests/context/mod.rs b/tests/context/mod.rs index 651b1b51..3db01df8 100644 --- a/tests/context/mod.rs +++ b/tests/context/mod.rs @@ -6,7 +6,7 @@ use { state::{ClientStoreArc, NotificationStoreArc, TenantStoreArc}, }, sqlx::{Pool, Postgres}, - std::sync::Arc, + std::{env, sync::Arc}, test_context::{AsyncTestContext, TestContext}, }; @@ -14,7 +14,7 @@ mod server; mod stores; pub struct ConfigContext { - pub config_from_env: Config, + pub config: Config, } pub struct EchoServerContext { @@ -32,16 +32,62 @@ pub struct StoreContext { impl TestContext for ConfigContext { fn setup() -> Self { - let config_from_env = envy::from_env::().unwrap(); - Self { config_from_env } + let public_port = self::server::get_random_port(); + let config = Config { + port: public_port, + public_url: format!("http://127.0.0.1:{public_port}"), + log_level: "info,echo-server=info".into(), + log_level_otel: "info,echo-server=trace".into(), + disable_header: true, + validate_signatures: false, + relay_public_key: env::var("RELAY_PUBLIC_KEY").unwrap_or("none".to_string()), + database_url: env::var("DATABASE_URL").unwrap(), + tenant_database_url: env::var("TENANT_DATABASE_URL").unwrap(), + #[cfg(feature = "multitenant")] + jwt_secret: "n/a".to_string(), + otel_exporter_otlp_endpoint: None, + telemetry_prometheus_port: Some(self::server::get_random_port()), + #[cfg(not(feature = "multitenant"))] + apns_type: None, + #[cfg(not(feature = "multitenant"))] + apns_certificate: None, + #[cfg(not(feature = "multitenant"))] + apns_certificate_password: None, + #[cfg(not(feature = "multitenant"))] + apns_pkcs8_pem: None, + #[cfg(not(feature = "multitenant"))] + apns_team_id: None, + #[cfg(not(feature = "multitenant"))] + apns_key_id: None, + #[cfg(not(feature = "multitenant"))] + apns_topic: None, + #[cfg(not(feature = "multitenant"))] + fcm_api_key: None, + #[cfg(any(feature = "analytics", feature = "geoblock"))] + s3_endpoint: None, + #[cfg(any(feature = "analytics", feature = "geoblock"))] + geoip_db_bucket: None, + #[cfg(any(feature = "analytics", feature = "geoblock"))] + geoip_db_key: None, + #[cfg(feature = "analytics")] + analytics_export_bucket: "example-bucket".to_string(), + is_test: true, + cors_allowed_origins: vec!["*".to_string()], + #[cfg(feature = "cloud")] + cloud_api_url: "https://example.com".to_string(), + #[cfg(feature = "cloud")] + cloud_api_key: "n/a".to_string(), + #[cfg(feature = "geoblock")] + blocked_countries: vec![], + }; + Self { config } } } #[async_trait] impl AsyncTestContext for EchoServerContext { async fn setup() -> Self { - let config_from_env = ConfigContext::setup().config_from_env; - let server = EchoServer::start(&config_from_env).await; + let server = EchoServer::start(ConfigContext::setup().config).await; Self { server } } } @@ -49,12 +95,9 @@ impl AsyncTestContext for EchoServerContext { #[async_trait] impl AsyncTestContext for StoreContext { async fn setup() -> Self { - let config_from_env = ConfigContext::setup().config_from_env; - let (db, tenant_db) = stores::open_pg_connections( - &config_from_env.database_url, - &config_from_env.tenant_database_url, - ) - .await; + let config = ConfigContext::setup().config; + let (db, tenant_db) = + stores::open_pg_connections(&config.database_url, &config.tenant_database_url).await; let db_arc = Arc::new(db); let tenant_db_arc = Arc::new(tenant_db); diff --git a/tests/context/server.rs b/tests/context/server.rs index 584a7acf..8811d454 100644 --- a/tests/context/server.rs +++ b/tests/context/server.rs @@ -18,57 +18,8 @@ pub struct EchoServer { pub enum Error {} impl EchoServer { - pub async fn start(config_from_env: &Config) -> Self { - let public_port = get_random_port(); - let config = Config { - port: public_port, - public_url: format!("http://127.0.0.1:{public_port}"), - log_level: "info,echo-server=info".into(), - log_level_otel: "info,echo-server=trace".into(), - disable_header: true, - relay_public_key: config_from_env.relay_public_key.clone(), - validate_signatures: false, - database_url: config_from_env.database_url.clone(), - tenant_database_url: config_from_env.tenant_database_url.clone(), - #[cfg(feature = "multitenant")] - jwt_secret: "n/a".to_string(), - otel_exporter_otlp_endpoint: None, - telemetry_prometheus_port: Some(get_random_port()), - #[cfg(not(feature = "multitenant"))] - apns_type: None, - #[cfg(not(feature = "multitenant"))] - apns_certificate: None, - #[cfg(not(feature = "multitenant"))] - apns_certificate_password: None, - #[cfg(not(feature = "multitenant"))] - apns_pkcs8_pem: None, - #[cfg(not(feature = "multitenant"))] - apns_team_id: None, - #[cfg(not(feature = "multitenant"))] - apns_key_id: None, - #[cfg(not(feature = "multitenant"))] - apns_topic: None, - #[cfg(not(feature = "multitenant"))] - fcm_api_key: None, - #[cfg(any(feature = "analytics", feature = "geoblock"))] - s3_endpoint: None, - #[cfg(any(feature = "analytics", feature = "geoblock"))] - geoip_db_bucket: None, - #[cfg(any(feature = "analytics", feature = "geoblock"))] - geoip_db_key: None, - #[cfg(feature = "analytics")] - analytics_export_bucket: "example-bucket".to_string(), - is_test: true, - cors_allowed_origins: vec!["*".to_string()], - #[cfg(feature = "cloud")] - cloud_api_url: "https://example.com".to_string(), - #[cfg(feature = "cloud")] - cloud_api_key: "n/a".to_string(), - #[cfg(feature = "geoblock")] - blocked_countries: vec![], - }; + pub async fn start(config: Config) -> Self { let (public_addr, signal, is_shutdown) = start_server(config).await; - Self { public_addr, shutdown_signal: signal, @@ -114,7 +65,7 @@ async fn start_server( } // Finds a free port. -fn get_random_port() -> u16 { +pub fn get_random_port() -> u16 { use std::sync::atomic::{AtomicU16, Ordering}; static NEXT_PORT: AtomicU16 = AtomicU16::new(9000);