From 3bf75d328971f3690bee6eb446a703b3dbfe3157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 18 Aug 2025 11:33:17 +0200 Subject: [PATCH 01/16] move existing integration tests into a dedicated module --- .../tests/integration/{ => api}/acl.rs | 2 +- .../tests/integration/{ => api}/api_tokens.rs | 2 +- .../tests/integration/{ => api}/auth.rs | 2 +- .../integration/{ => api}/common/client.rs | 0 .../tests/integration/{ => api}/common/mod.rs | 0 .../tests/integration/{ => api}/enrollment.rs | 2 +- .../{ => api}/enterprise_settings.rs | 2 +- .../integration/{ => api}/forward_auth.rs | 2 +- .../tests/integration/{ => api}/group.rs | 2 +- .../tests/integration/api/mod.rs | 21 ++++++++++++++++++ .../tests/integration/{ => api}/oauth.rs | 2 +- .../tests/integration/{ => api}/openid.rs | 2 +- .../integration/{ => api}/openid_login.rs | 2 +- .../tests/integration/{ => api}/settings.rs | 2 +- .../tests/integration/{ => api}/snat.rs | 2 +- .../tests/integration/{ => api}/user.rs | 2 +- .../tests/integration/{ => api}/webhook.rs | 2 +- .../tests/integration/{ => api}/wireguard.rs | 2 +- .../wireguard_network_allowed_groups.rs | 2 +- .../{ => api}/wireguard_network_devices.rs | 2 +- .../{ => api}/wireguard_network_import.rs | 2 +- .../{ => api}/wireguard_network_stats.rs | 2 +- .../tests/integration/{ => api}/worker.rs | 2 +- .../defguard_core/tests/integration/main.rs | 22 +------------------ 24 files changed, 42 insertions(+), 41 deletions(-) rename crates/defguard_core/tests/integration/{ => api}/acl.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/api_tokens.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/auth.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/common/client.rs (100%) rename crates/defguard_core/tests/integration/{ => api}/common/mod.rs (100%) rename crates/defguard_core/tests/integration/{ => api}/enrollment.rs (98%) rename crates/defguard_core/tests/integration/{ => api}/enterprise_settings.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/forward_auth.rs (96%) rename crates/defguard_core/tests/integration/{ => api}/group.rs (99%) create mode 100644 crates/defguard_core/tests/integration/api/mod.rs rename crates/defguard_core/tests/integration/{ => api}/oauth.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/openid.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/openid_login.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/settings.rs (96%) rename crates/defguard_core/tests/integration/{ => api}/snat.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/user.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/webhook.rs (98%) rename crates/defguard_core/tests/integration/{ => api}/wireguard.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/wireguard_network_allowed_groups.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/wireguard_network_devices.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/wireguard_network_import.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/wireguard_network_stats.rs (99%) rename crates/defguard_core/tests/integration/{ => api}/worker.rs (99%) diff --git a/crates/defguard_core/tests/integration/acl.rs b/crates/defguard_core/tests/integration/api/acl.rs similarity index 99% rename from crates/defguard_core/tests/integration/acl.rs rename to crates/defguard_core/tests/integration/api/acl.rs index 103c69c845..9762dab2ad 100644 --- a/crates/defguard_core/tests/integration/acl.rs +++ b/crates/defguard_core/tests/integration/api/acl.rs @@ -21,7 +21,7 @@ use sqlx::{ }; use tokio::net::TcpListener; -use crate::common::{ +use super::common::{ authenticate_admin, client::TestClient, exceed_enterprise_limits, init_config, initialize_users, make_base_client, make_test_client, omit_id, setup_pool, }; diff --git a/crates/defguard_core/tests/integration/api_tokens.rs b/crates/defguard_core/tests/integration/api/api_tokens.rs similarity index 99% rename from crates/defguard_core/tests/integration/api_tokens.rs rename to crates/defguard_core/tests/integration/api/api_tokens.rs index f7805efa50..0c5ab07893 100644 --- a/crates/defguard_core/tests/integration/api_tokens.rs +++ b/crates/defguard_core/tests/integration/api/api_tokens.rs @@ -11,7 +11,7 @@ use reqwest::{StatusCode, header::HeaderName}; use serde::Deserialize; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_client, make_client_with_state, setup_pool}; +use super::common::{make_client, make_client_with_state, setup_pool}; #[sqlx::test] async fn test_normal_user_cannot_access_token_endpoints( diff --git a/crates/defguard_core/tests/integration/auth.rs b/crates/defguard_core/tests/integration/api/auth.rs similarity index 99% rename from crates/defguard_core/tests/integration/auth.rs rename to crates/defguard_core/tests/integration/api/auth.rs index 4ac9279557..c6f29abd6c 100644 --- a/crates/defguard_core/tests/integration/auth.rs +++ b/crates/defguard_core/tests/integration/api/auth.rs @@ -20,7 +20,7 @@ use totp_lite::{Sha1, totp_custom}; use webauthn_authenticator_rs::{WebauthnAuthenticator, prelude::Url, softpasskey::SoftPasskey}; use webauthn_rs::prelude::{CreationChallengeResponse, RequestChallengeResponse}; -use crate::common::{ +use super::common::{ X_FORWARDED_FOR, fetch_user_details, make_client, make_client_with_db, make_client_with_state, make_test_client, setup_pool, }; diff --git a/crates/defguard_core/tests/integration/common/client.rs b/crates/defguard_core/tests/integration/api/common/client.rs similarity index 100% rename from crates/defguard_core/tests/integration/common/client.rs rename to crates/defguard_core/tests/integration/api/common/client.rs diff --git a/crates/defguard_core/tests/integration/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs similarity index 100% rename from crates/defguard_core/tests/integration/common/mod.rs rename to crates/defguard_core/tests/integration/api/common/mod.rs diff --git a/crates/defguard_core/tests/integration/enrollment.rs b/crates/defguard_core/tests/integration/api/enrollment.rs similarity index 98% rename from crates/defguard_core/tests/integration/enrollment.rs rename to crates/defguard_core/tests/integration/api/enrollment.rs index 71eeb1eceb..7068650a3d 100644 --- a/crates/defguard_core/tests/integration/enrollment.rs +++ b/crates/defguard_core/tests/integration/api/enrollment.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{fetch_user_details, make_client_with_db, setup_pool}; +use super::common::{fetch_user_details, make_client_with_db, setup_pool}; #[sqlx::test] async fn test_initialize_enrollment(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/enterprise_settings.rs b/crates/defguard_core/tests/integration/api/enterprise_settings.rs similarity index 99% rename from crates/defguard_core/tests/integration/enterprise_settings.rs rename to crates/defguard_core/tests/integration/api/enterprise_settings.rs index 821899c6b0..7634964196 100644 --- a/crates/defguard_core/tests/integration/enterprise_settings.rs +++ b/crates/defguard_core/tests/integration/api/enterprise_settings.rs @@ -9,7 +9,7 @@ use reqwest::StatusCode; use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{exceed_enterprise_limits, make_network, make_test_client, setup_pool}; +use super::common::{exceed_enterprise_limits, make_network, make_test_client, setup_pool}; #[sqlx::test] async fn test_only_enterprise_can_modify(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/forward_auth.rs b/crates/defguard_core/tests/integration/api/forward_auth.rs similarity index 96% rename from crates/defguard_core/tests/integration/forward_auth.rs rename to crates/defguard_core/tests/integration/api/forward_auth.rs index fce53bc90d..83b97c1ba9 100644 --- a/crates/defguard_core/tests/integration/forward_auth.rs +++ b/crates/defguard_core/tests/integration/api/forward_auth.rs @@ -2,7 +2,7 @@ use defguard_core::{SERVER_CONFIG, handlers::Auth}; use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{X_FORWARDED_HOST, X_FORWARDED_URI, make_client, setup_pool}; +use super::common::{X_FORWARDED_HOST, X_FORWARDED_URI, make_client, setup_pool}; #[sqlx::test] async fn test_forward_auth(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/group.rs b/crates/defguard_core/tests/integration/api/group.rs similarity index 99% rename from crates/defguard_core/tests/integration/group.rs rename to crates/defguard_core/tests/integration/api/group.rs index 42ea560e02..f96d78904b 100644 --- a/crates/defguard_core/tests/integration/group.rs +++ b/crates/defguard_core/tests/integration/api/group.rs @@ -3,7 +3,7 @@ use reqwest::StatusCode; use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_test_client, setup_pool}; +use super::common::{make_test_client, setup_pool}; #[sqlx::test] async fn test_create_group(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/api/mod.rs b/crates/defguard_core/tests/integration/api/mod.rs new file mode 100644 index 0000000000..4891501678 --- /dev/null +++ b/crates/defguard_core/tests/integration/api/mod.rs @@ -0,0 +1,21 @@ +mod acl; +mod api_tokens; +mod auth; +mod common; +mod enrollment; +mod enterprise_settings; +mod forward_auth; +mod group; +mod oauth; +mod openid; +mod openid_login; +mod settings; +mod snat; +mod user; +mod webhook; +mod wireguard; +mod wireguard_network_allowed_groups; +mod wireguard_network_devices; +mod wireguard_network_import; +mod wireguard_network_stats; +mod worker; diff --git a/crates/defguard_core/tests/integration/oauth.rs b/crates/defguard_core/tests/integration/api/oauth.rs similarity index 99% rename from crates/defguard_core/tests/integration/oauth.rs rename to crates/defguard_core/tests/integration/api/oauth.rs index ec5e4363c2..06d86dbe3b 100644 --- a/crates/defguard_core/tests/integration/oauth.rs +++ b/crates/defguard_core/tests/integration/api/oauth.rs @@ -14,7 +14,7 @@ use reqwest::{StatusCode, Url, header::CONTENT_TYPE}; use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_client_with_db, setup_pool}; +use super::common::{make_client_with_db, setup_pool}; #[sqlx::test] async fn test_authorize(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/openid.rs b/crates/defguard_core/tests/integration/api/openid.rs similarity index 99% rename from crates/defguard_core/tests/integration/openid.rs rename to crates/defguard_core/tests/integration/api/openid.rs index 30f34ee78c..3a2b141d5a 100644 --- a/crates/defguard_core/tests/integration/openid.rs +++ b/crates/defguard_core/tests/integration/api/openid.rs @@ -26,7 +26,7 @@ use rsa::RsaPrivateKey; use serde::Deserialize; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{ +use super::common::{ client::TestClient, make_client, make_client_with_state, make_test_client, setup_pool, }; diff --git a/crates/defguard_core/tests/integration/openid_login.rs b/crates/defguard_core/tests/integration/api/openid_login.rs similarity index 99% rename from crates/defguard_core/tests/integration/openid_login.rs rename to crates/defguard_core/tests/integration/api/openid_login.rs index d088318d24..af21f04c96 100644 --- a/crates/defguard_core/tests/integration/openid_login.rs +++ b/crates/defguard_core/tests/integration/api/openid_login.rs @@ -12,7 +12,7 @@ use reqwest::{StatusCode, Url}; use serde::Deserialize; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{exceed_enterprise_limits, make_client, setup_pool}; +use super::common::{exceed_enterprise_limits, make_client, setup_pool}; // Temporarily disabled because of the issue with test_openid_login // async fn make_client_with_real_url() -> TestClient { diff --git a/crates/defguard_core/tests/integration/settings.rs b/crates/defguard_core/tests/integration/api/settings.rs similarity index 96% rename from crates/defguard_core/tests/integration/settings.rs rename to crates/defguard_core/tests/integration/api/settings.rs index 1952708765..a832b60eb8 100644 --- a/crates/defguard_core/tests/integration/settings.rs +++ b/crates/defguard_core/tests/integration/api/settings.rs @@ -5,7 +5,7 @@ use defguard_core::{ use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_client_with_state, setup_pool}; +use super::common::{make_client_with_state, setup_pool}; #[sqlx::test] async fn test_settings(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/snat.rs b/crates/defguard_core/tests/integration/api/snat.rs similarity index 99% rename from crates/defguard_core/tests/integration/snat.rs rename to crates/defguard_core/tests/integration/api/snat.rs index 9fb4f2abb7..a396041d4c 100644 --- a/crates/defguard_core/tests/integration/snat.rs +++ b/crates/defguard_core/tests/integration/api/snat.rs @@ -12,7 +12,7 @@ use defguard_core::{ use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{ +use super::common::{ authenticate_admin, exceed_enterprise_limits, make_network, make_test_client, setup_pool, }; diff --git a/crates/defguard_core/tests/integration/user.rs b/crates/defguard_core/tests/integration/api/user.rs similarity index 99% rename from crates/defguard_core/tests/integration/user.rs rename to crates/defguard_core/tests/integration/api/user.rs index f1f8f85c38..a7433cacde 100644 --- a/crates/defguard_core/tests/integration/user.rs +++ b/crates/defguard_core/tests/integration/api/user.rs @@ -9,7 +9,7 @@ use reqwest::{StatusCode, header::USER_AGENT}; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use tokio_stream::{self as stream, StreamExt}; -use crate::common::{fetch_user_details, make_client, make_network, make_test_client, setup_pool}; +use super::common::{fetch_user_details, make_client, make_network, make_test_client, setup_pool}; #[sqlx::test] async fn test_authenticate(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/webhook.rs b/crates/defguard_core/tests/integration/api/webhook.rs similarity index 98% rename from crates/defguard_core/tests/integration/webhook.rs rename to crates/defguard_core/tests/integration/api/webhook.rs index 67dab616ac..326592f3e0 100644 --- a/crates/defguard_core/tests/integration/webhook.rs +++ b/crates/defguard_core/tests/integration/api/webhook.rs @@ -5,7 +5,7 @@ use defguard_core::{ use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_client, setup_pool}; +use super::common::{make_client, setup_pool}; #[sqlx::test] async fn test_webhooks(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs similarity index 99% rename from crates/defguard_core/tests/integration/wireguard.rs rename to crates/defguard_core/tests/integration/api/wireguard.rs index f3d1385e64..fa8c9a9589 100644 --- a/crates/defguard_core/tests/integration/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -24,7 +24,7 @@ use reqwest::StatusCode; use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{ +use super::common::{ authenticate_admin, exceed_enterprise_limits, make_network, make_test_client, setup_pool, }; diff --git a/crates/defguard_core/tests/integration/wireguard_network_allowed_groups.rs b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs similarity index 99% rename from crates/defguard_core/tests/integration/wireguard_network_allowed_groups.rs rename to crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs index 16288fa64f..51ecb04207 100644 --- a/crates/defguard_core/tests/integration/wireguard_network_allowed_groups.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_allowed_groups.rs @@ -14,7 +14,7 @@ use sqlx::{ postgres::{PgConnectOptions, PgPoolOptions}, }; -use crate::common::{fetch_user_details, make_test_client, setup_pool}; +use super::common::{fetch_user_details, make_test_client, setup_pool}; // setup user groups, test users and devices async fn setup_test_users(pool: &PgPool) -> (Vec>, Vec>) { diff --git a/crates/defguard_core/tests/integration/wireguard_network_devices.rs b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs similarity index 99% rename from crates/defguard_core/tests/integration/wireguard_network_devices.rs rename to crates/defguard_core/tests/integration/api/wireguard_network_devices.rs index 1867226b06..7ae1d506da 100644 --- a/crates/defguard_core/tests/integration/wireguard_network_devices.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs @@ -11,7 +11,7 @@ use serde::Deserialize; use serde_json::{Value, json}; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_test_client, setup_pool}; +use super::common::{make_test_client, setup_pool}; fn make_network() -> Value { json!({ diff --git a/crates/defguard_core/tests/integration/wireguard_network_import.rs b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs similarity index 99% rename from crates/defguard_core/tests/integration/wireguard_network_import.rs rename to crates/defguard_core/tests/integration/api/wireguard_network_import.rs index 59217fb90f..aad4c8d62c 100644 --- a/crates/defguard_core/tests/integration/wireguard_network_import.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_import.rs @@ -18,7 +18,7 @@ use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use tokio::sync::broadcast::error::TryRecvError; -use crate::common::{fetch_user_details, make_test_client, setup_pool}; +use super::common::{fetch_user_details, make_test_client, setup_pool}; #[sqlx::test] async fn test_config_import(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/wireguard_network_stats.rs b/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs similarity index 99% rename from crates/defguard_core/tests/integration/wireguard_network_stats.rs rename to crates/defguard_core/tests/integration/api/wireguard_network_stats.rs index 5efb4b5feb..8a02b5c994 100644 --- a/crates/defguard_core/tests/integration/wireguard_network_stats.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs @@ -18,7 +18,7 @@ use serde::Deserialize; use serde_json::json; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_network, make_test_client, setup_pool}; +use super::common::{make_network, make_test_client, setup_pool}; static DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:00Z"; diff --git a/crates/defguard_core/tests/integration/worker.rs b/crates/defguard_core/tests/integration/api/worker.rs similarity index 99% rename from crates/defguard_core/tests/integration/worker.rs rename to crates/defguard_core/tests/integration/api/worker.rs index 7243311ff6..88833a892d 100644 --- a/crates/defguard_core/tests/integration/worker.rs +++ b/crates/defguard_core/tests/integration/api/worker.rs @@ -8,7 +8,7 @@ use defguard_core::{ use reqwest::StatusCode; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use crate::common::{make_client_with_state, setup_pool}; +use super::common::{make_client_with_state, setup_pool}; #[sqlx::test] async fn test_scheduling_worker_jobs(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/main.rs b/crates/defguard_core/tests/integration/main.rs index 4891501678..b32f9e29f1 100644 --- a/crates/defguard_core/tests/integration/main.rs +++ b/crates/defguard_core/tests/integration/main.rs @@ -1,21 +1 @@ -mod acl; -mod api_tokens; -mod auth; -mod common; -mod enrollment; -mod enterprise_settings; -mod forward_auth; -mod group; -mod oauth; -mod openid; -mod openid_login; -mod settings; -mod snat; -mod user; -mod webhook; -mod wireguard; -mod wireguard_network_allowed_groups; -mod wireguard_network_devices; -mod wireguard_network_import; -mod wireguard_network_stats; -mod worker; +mod api; From beee64a707d76dbbe2ba051852bf90aeaad574c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 18 Aug 2025 11:35:14 +0200 Subject: [PATCH 02/16] update dependencies --- flake.lock | 12 +- web/package.json | 12 +- web/pnpm-lock.yaml | 351 +++++++++++++++++++++++---------------------- 3 files changed, 188 insertions(+), 187 deletions(-) diff --git a/flake.lock b/flake.lock index 5b1565932a..de079cca89 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755027561, - "narHash": "sha256-IVft239Bc8p8Dtvf7UAACMG5P3ZV+3/aO28gXpGtMXI=", + "lastModified": 1755186698, + "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "005433b926e16227259a1843015b5b2b7f7d1fc3", + "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1755139244, - "narHash": "sha256-SN1BFA00m+siVAQiGLtTwjv9LV9TH5n8tQcSziV6Nv4=", + "lastModified": 1755485198, + "narHash": "sha256-C3042ST2lUg0nh734gmuP4lRRIBitA6Maegg2/jYRM4=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "aeae248beb2a419e39d483dd9b7fec924aba8d4d", + "rev": "aa45e63d431b28802ca4490cfc796b9e31731df7", "type": "github" }, "original": { diff --git a/web/package.json b/web/package.json index ca7f2ebd3b..dc4ed24f84 100644 --- a/web/package.json +++ b/web/package.json @@ -50,8 +50,8 @@ "@react-rxjs/core": "^0.10.8", "@stablelib/base64": "^2.0.1", "@stablelib/x25519": "^2.0.1", - "@tanstack/query-core": "^5.85.2", - "@tanstack/react-query": "^5.85.2", + "@tanstack/query-core": "^5.85.3", + "@tanstack/react-query": "^5.85.3", "@tanstack/react-virtual": "3.13.12", "@tanstack/virtual-core": "3.13.12", "@use-gesture/react": "^10.3.1", @@ -85,7 +85,7 @@ "radash": "^12.1.1", "react": "^19.1.1", "react-click-away-listener": "^2.4.0", - "react-datepicker": "^8.4.0", + "react-datepicker": "^8.5.0", "react-dom": "^19.1.1", "react-hook-form": "^7.62.0", "react-idle-timer": "^5.7.2", @@ -113,17 +113,17 @@ "zustand": "^5.0.7" }, "devDependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.28.3", "@biomejs/biome": "2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@hookform/devtools": "^4.4.0", - "@tanstack/react-query-devtools": "^5.85.2", + "@tanstack/react-query-devtools": "^5.85.3", "@types/byte-size": "^8.1.2", "@types/file-saver": "^2.0.7", "@types/humanize-duration": "^3.27.4", "@types/lodash-es": "^4.17.12", - "@types/node": "^24.2.1", + "@types/node": "^24.3.0", "@types/qs": "^6.14.0", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index ef34dff985..2db9529128 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -30,11 +30,11 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tanstack/query-core': - specifier: ^5.85.2 - version: 5.85.2 + specifier: ^5.85.3 + version: 5.85.3 '@tanstack/react-query': - specifier: ^5.85.2 - version: 5.85.2(react@19.1.1) + specifier: ^5.85.3 + version: 5.85.3(react@19.1.1) '@tanstack/react-virtual': specifier: 3.13.12 version: 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -135,8 +135,8 @@ importers: specifier: ^2.4.0 version: 2.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-datepicker: - specifier: ^8.4.0 - version: 8.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: ^8.5.0 + version: 8.5.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-dom: specifier: ^19.1.1 version: 19.1.1(react@19.1.1) @@ -214,8 +214,8 @@ importers: version: 5.0.7(@types/react@19.1.10)(immer@10.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) devDependencies: '@babel/core': - specifier: ^7.28.0 - version: 7.28.0 + specifier: ^7.28.3 + version: 7.28.3 '@biomejs/biome': specifier: 2.1.4 version: 2.1.4 @@ -229,8 +229,8 @@ importers: specifier: ^4.4.0 version: 4.4.0(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-query-devtools': - specifier: ^5.85.2 - version: 5.85.2(@tanstack/react-query@5.85.2(react@19.1.1))(react@19.1.1) + specifier: ^5.85.3 + version: 5.85.3(@tanstack/react-query@5.85.3(react@19.1.1))(react@19.1.1) '@types/byte-size': specifier: ^8.1.2 version: 8.1.2 @@ -244,8 +244,8 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^24.2.1 - version: 24.2.1 + specifier: ^24.3.0 + version: 24.3.0 '@types/qs': specifier: ^6.14.0 version: 6.14.0 @@ -263,7 +263,7 @@ importers: version: 1.8.8 '@vitejs/plugin-react-swc': specifier: ^4.0.0 - version: 4.0.0(vite@7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) + version: 4.0.0(vite@7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -299,10 +299,10 @@ importers: version: 5.9.2 vite: specifier: ^7.1.2 - version: 7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + version: 7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) vite-plugin-package-version: specifier: ^1.1.0 - version: 1.1.0(vite@7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) + version: 1.1.0(vite@7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) packages: @@ -318,12 +318,12 @@ packages: resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -338,8 +338,8 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -356,25 +356,25 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.28.2': - resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} engines: {node: '>=6.9.0'} '@babel/types@7.28.2': @@ -749,103 +749,103 @@ packages: '@rolldown/pluginutils@1.0.0-beta.30': resolution: {integrity: sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==} - '@rollup/rollup-android-arm-eabi@4.46.2': - resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + '@rollup/rollup-android-arm-eabi@4.46.3': + resolution: {integrity: sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.46.2': - resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + '@rollup/rollup-android-arm64@4.46.3': + resolution: {integrity: sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.46.2': - resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + '@rollup/rollup-darwin-arm64@4.46.3': + resolution: {integrity: sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.46.2': - resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + '@rollup/rollup-darwin-x64@4.46.3': + resolution: {integrity: sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.46.2': - resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + '@rollup/rollup-freebsd-arm64@4.46.3': + resolution: {integrity: sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.2': - resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + '@rollup/rollup-freebsd-x64@4.46.3': + resolution: {integrity: sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + '@rollup/rollup-linux-arm-gnueabihf@4.46.3': + resolution: {integrity: sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + '@rollup/rollup-linux-arm-musleabihf@4.46.3': + resolution: {integrity: sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.46.2': - resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + '@rollup/rollup-linux-arm64-gnu@4.46.3': + resolution: {integrity: sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.46.2': - resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + '@rollup/rollup-linux-arm64-musl@4.46.3': + resolution: {integrity: sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + '@rollup/rollup-linux-loongarch64-gnu@4.46.3': + resolution: {integrity: sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + '@rollup/rollup-linux-ppc64-gnu@4.46.3': + resolution: {integrity: sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + '@rollup/rollup-linux-riscv64-gnu@4.46.3': + resolution: {integrity: sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.46.2': - resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + '@rollup/rollup-linux-riscv64-musl@4.46.3': + resolution: {integrity: sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.46.2': - resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + '@rollup/rollup-linux-s390x-gnu@4.46.3': + resolution: {integrity: sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.46.2': - resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + '@rollup/rollup-linux-x64-gnu@4.46.3': + resolution: {integrity: sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.46.2': - resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + '@rollup/rollup-linux-x64-musl@4.46.3': + resolution: {integrity: sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.46.2': - resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + '@rollup/rollup-win32-arm64-msvc@4.46.3': + resolution: {integrity: sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.46.2': - resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + '@rollup/rollup-win32-ia32-msvc@4.46.3': + resolution: {integrity: sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.2': - resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + '@rollup/rollup-win32-x64-msvc@4.46.3': + resolution: {integrity: sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==} cpu: [x64] os: [win32] @@ -959,20 +959,20 @@ packages: '@swc/types@0.1.24': resolution: {integrity: sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==} - '@tanstack/query-core@5.85.2': - resolution: {integrity: sha512-h9vFUVYKoNNgXzb5mK4dP686yawJp9CLLTMjB2PF4KA1D5AbUI2KfxnxBdEQKnLwzzMjmBpucmmsGChZK7RqcQ==} + '@tanstack/query-core@5.85.3': + resolution: {integrity: sha512-9Ne4USX83nHmRuEYs78LW+3lFEEO2hBDHu7mrdIgAFx5Zcrs7ker3n/i8p4kf6OgKExmaDN5oR0efRD7i2J0DQ==} '@tanstack/query-devtools@5.84.0': resolution: {integrity: sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ==} - '@tanstack/react-query-devtools@5.85.2': - resolution: {integrity: sha512-vUcfAcOW5vqWfeb4ZlyiWgtKSLDtHg6hBEuRi7LH0HpsNLgR6HncP2HwRxPcVZo6OxpSEv6gJ4Z5H8qmFJMs9A==} + '@tanstack/react-query-devtools@5.85.3': + resolution: {integrity: sha512-WSVweCE1Kh1BVvPDHAmLgGT+GGTJQ9+a7bVqzD+zUiUTht+salJjYm5nikpMNaHFPJV102TCYdvgHgBXtURRNg==} peerDependencies: - '@tanstack/react-query': ^5.85.2 + '@tanstack/react-query': ^5.85.3 react: ^18 || ^19 - '@tanstack/react-query@5.85.2': - resolution: {integrity: sha512-B/sLNHkhGi3hVZg3xsH82sCSkEqdyfZXBrkumo7FeIKuXgsCRp4tmLSCC/2YU/oPyQuoVGzQhyzM8p3UjO19kw==} + '@tanstack/react-query@5.85.3': + resolution: {integrity: sha512-AqU8TvNh5GVIE8I+TUU0noryBRy7gOY0XhSayVXmOPll4UkZeLWKDwi0rtWOZbwLRCbyxorfJ5DIjDqE7GXpcQ==} peerDependencies: react: ^18 || ^19 @@ -1051,8 +1051,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.2.1': - resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==} + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1528,8 +1528,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.200: - resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} + electron-to-chromium@1.5.203: + resolution: {integrity: sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1561,8 +1561,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.39.9: - resolution: {integrity: sha512-9OtbkZmTA2Qc9groyA1PUNeb6knVTkvB2RSdr/LcJXDL8IdEakaxwXLHXa7VX/Wj0GmdMJPR3WhnPGhiP3E+qg==} + es-toolkit@1.39.10: + resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} @@ -1597,8 +1597,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2378,8 +2379,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-datepicker@8.4.0: - resolution: {integrity: sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==} + react-datepicker@8.5.0: + resolution: {integrity: sha512-GdKElMgEBgyAv2L5QjwvlStdZU8Nz76roBZ2rkpxFx8eIJ0SAHaAGckG8+FSi00EQnzKqA2N9qA61oxll8CyhA==} peerDependencies: react: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc @@ -2574,8 +2575,8 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rollup@4.46.2: - resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + rollup@4.46.3: + resolution: {integrity: sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3098,17 +3099,17 @@ snapshots: '@babel/compat-data@7.28.0': {} - '@babel/core@7.28.0': + '@babel/core@7.28.3': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 @@ -3118,9 +3119,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.0': + '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/types': 7.28.2 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.30 @@ -3138,17 +3139,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color @@ -3158,29 +3159,29 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.2': + '@babel/helpers@7.28.3': dependencies: '@babel/template': 7.27.2 '@babel/types': 7.28.2 - '@babel/parser@7.28.0': + '@babel/parser@7.28.3': dependencies: '@babel/types': 7.28.2 - '@babel/runtime@7.28.2': {} + '@babel/runtime@7.28.3': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/types': 7.28.2 - '@babel/traverse@7.28.0': + '@babel/traverse@7.28.3': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.3 '@babel/template': 7.27.2 '@babel/types': 7.28.2 debug: 4.4.1 @@ -3236,7 +3237,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.27.1 - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.3 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -3267,7 +3268,7 @@ snapshots: '@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.3 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 @@ -3293,7 +3294,7 @@ snapshots: '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.10)(react@19.1.1))(@types/react@19.1.10)(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.3 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 '@emotion/react': 11.14.0(@types/react@19.1.10)(react@19.1.1) @@ -3501,64 +3502,64 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.30': {} - '@rollup/rollup-android-arm-eabi@4.46.2': + '@rollup/rollup-android-arm-eabi@4.46.3': optional: true - '@rollup/rollup-android-arm64@4.46.2': + '@rollup/rollup-android-arm64@4.46.3': optional: true - '@rollup/rollup-darwin-arm64@4.46.2': + '@rollup/rollup-darwin-arm64@4.46.3': optional: true - '@rollup/rollup-darwin-x64@4.46.2': + '@rollup/rollup-darwin-x64@4.46.3': optional: true - '@rollup/rollup-freebsd-arm64@4.46.2': + '@rollup/rollup-freebsd-arm64@4.46.3': optional: true - '@rollup/rollup-freebsd-x64@4.46.2': + '@rollup/rollup-freebsd-x64@4.46.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + '@rollup/rollup-linux-arm-gnueabihf@4.46.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.46.2': + '@rollup/rollup-linux-arm-musleabihf@4.46.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.46.2': + '@rollup/rollup-linux-arm64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.46.2': + '@rollup/rollup-linux-arm64-musl@4.46.3': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + '@rollup/rollup-linux-loongarch64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.46.2': + '@rollup/rollup-linux-ppc64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.2': + '@rollup/rollup-linux-riscv64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.46.2': + '@rollup/rollup-linux-riscv64-musl@4.46.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.2': + '@rollup/rollup-linux-s390x-gnu@4.46.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.46.2': + '@rollup/rollup-linux-x64-gnu@4.46.3': optional: true - '@rollup/rollup-linux-x64-musl@4.46.2': + '@rollup/rollup-linux-x64-musl@4.46.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.2': + '@rollup/rollup-win32-arm64-msvc@4.46.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.2': + '@rollup/rollup-win32-ia32-msvc@4.46.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.46.2': + '@rollup/rollup-win32-x64-msvc@4.46.3': optional: true '@rx-state/core@0.1.4(rxjs@7.8.2)': @@ -3648,19 +3649,19 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.85.2': {} + '@tanstack/query-core@5.85.3': {} '@tanstack/query-devtools@5.84.0': {} - '@tanstack/react-query-devtools@5.85.2(@tanstack/react-query@5.85.2(react@19.1.1))(react@19.1.1)': + '@tanstack/react-query-devtools@5.85.3(@tanstack/react-query@5.85.3(react@19.1.1))(react@19.1.1)': dependencies: '@tanstack/query-devtools': 5.84.0 - '@tanstack/react-query': 5.85.2(react@19.1.1) + '@tanstack/react-query': 5.85.3(react@19.1.1) react: 19.1.1 - '@tanstack/react-query@5.85.2(react@19.1.1)': + '@tanstack/react-query@5.85.3(react@19.1.1)': dependencies: - '@tanstack/query-core': 5.85.2 + '@tanstack/query-core': 5.85.3 react: 19.1.1 '@tanstack/react-virtual@3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': @@ -3731,7 +3732,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@24.2.1': + '@types/node@24.3.0': dependencies: undici-types: 7.10.0 @@ -3779,11 +3780,11 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.1.1 - '@vitejs/plugin-react-swc@4.0.0(vite@7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1))': + '@vitejs/plugin-react-swc@4.0.0(vite@7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.30 '@swc/core': 1.13.3 - vite: 7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) transitivePeerDependencies: - '@swc/helpers' @@ -3838,7 +3839,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.3 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -3862,7 +3863,7 @@ snapshots: browserslist@4.25.2: dependencies: caniuse-lite: 1.0.30001735 - electron-to-chromium: 1.5.200 + electron-to-chromium: 1.5.203 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) @@ -4240,7 +4241,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.200: {} + electron-to-chromium@1.5.203: {} emoji-regex@8.0.0: {} @@ -4267,7 +4268,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.39.9: {} + es-toolkit@1.39.10: {} esbuild@0.25.9: optionalDependencies: @@ -4314,7 +4315,7 @@ snapshots: fast-deep-equal@3.1.3: {} - fdir@6.4.6(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -5188,7 +5189,7 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-datepicker@8.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-datepicker@8.5.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@floating-ui/react': 0.27.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) clsx: 2.1.1 @@ -5294,7 +5295,7 @@ snapshots: react-window@1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.3 memoize-one: 5.2.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -5350,7 +5351,7 @@ snapshots: '@reduxjs/toolkit': 2.8.2(react-redux@9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1))(react@19.1.1) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.39.9 + es-toolkit: 1.39.10 eventemitter3: 5.0.1 immer: 10.1.1 react: 19.1.1 @@ -5427,30 +5428,30 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup@4.46.2: + rollup@4.46.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.2 - '@rollup/rollup-android-arm64': 4.46.2 - '@rollup/rollup-darwin-arm64': 4.46.2 - '@rollup/rollup-darwin-x64': 4.46.2 - '@rollup/rollup-freebsd-arm64': 4.46.2 - '@rollup/rollup-freebsd-x64': 4.46.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 - '@rollup/rollup-linux-arm-musleabihf': 4.46.2 - '@rollup/rollup-linux-arm64-gnu': 4.46.2 - '@rollup/rollup-linux-arm64-musl': 4.46.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 - '@rollup/rollup-linux-ppc64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-musl': 4.46.2 - '@rollup/rollup-linux-s390x-gnu': 4.46.2 - '@rollup/rollup-linux-x64-gnu': 4.46.2 - '@rollup/rollup-linux-x64-musl': 4.46.2 - '@rollup/rollup-win32-arm64-msvc': 4.46.2 - '@rollup/rollup-win32-ia32-msvc': 4.46.2 - '@rollup/rollup-win32-x64-msvc': 4.46.2 + '@rollup/rollup-android-arm-eabi': 4.46.3 + '@rollup/rollup-android-arm64': 4.46.3 + '@rollup/rollup-darwin-arm64': 4.46.3 + '@rollup/rollup-darwin-x64': 4.46.3 + '@rollup/rollup-freebsd-arm64': 4.46.3 + '@rollup/rollup-freebsd-x64': 4.46.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.3 + '@rollup/rollup-linux-arm-musleabihf': 4.46.3 + '@rollup/rollup-linux-arm64-gnu': 4.46.3 + '@rollup/rollup-linux-arm64-musl': 4.46.3 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.3 + '@rollup/rollup-linux-ppc64-gnu': 4.46.3 + '@rollup/rollup-linux-riscv64-gnu': 4.46.3 + '@rollup/rollup-linux-riscv64-musl': 4.46.3 + '@rollup/rollup-linux-s390x-gnu': 4.46.3 + '@rollup/rollup-linux-x64-gnu': 4.46.3 + '@rollup/rollup-linux-x64-musl': 4.46.3 + '@rollup/rollup-win32-arm64-msvc': 4.46.3 + '@rollup/rollup-win32-ia32-msvc': 4.46.3 + '@rollup/rollup-win32-x64-msvc': 4.46.3 fsevents: 2.3.3 rxjs@7.8.2: @@ -5730,7 +5731,7 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 to-regex-range@5.0.1: @@ -5819,7 +5820,7 @@ snapshots: use-deep-compare-effect@1.8.1(react@19.1.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.3 dequal: 2.0.3 react: 19.1.1 @@ -5868,20 +5869,20 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-package-version@1.1.0(vite@7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)): + vite-plugin-package-version@1.1.0(vite@7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)): dependencies: - vite: 7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + vite: 7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) - vite@7.1.2(@types/node@24.2.1)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1): + vite@7.1.2(@types/node@24.3.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1): dependencies: esbuild: 0.25.9 - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.46.2 + rollup: 4.46.3 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 24.2.1 + '@types/node': 24.3.0 fsevents: 2.3.3 jiti: 2.4.2 sass: 1.70.0 From dfd0e2bb329d1784d492e30f30c9d8d2d003f918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 18 Aug 2025 12:37:58 +0200 Subject: [PATCH 03/16] explicitly stop test API server task --- .../tests/integration/api/common/client.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/tests/integration/api/common/client.rs b/crates/defguard_core/tests/integration/api/common/client.rs index bcf680cca3..61a45135dc 100644 --- a/crates/defguard_core/tests/integration/api/common/client.rs +++ b/crates/defguard_core/tests/integration/api/common/client.rs @@ -9,7 +9,7 @@ use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT}, redirect::Policy, }; -use tokio::{net::TcpListener, sync::mpsc::UnboundedReceiver}; +use tokio::{net::TcpListener, sync::mpsc::UnboundedReceiver, task::JoinHandle}; pub struct TestClient { client: Client, @@ -18,6 +18,7 @@ pub struct TestClient { // Has to live during whole test #[allow(dead_code)] api_event_rx: UnboundedReceiver, + api_task_handle: JoinHandle<()>, } impl TestClient { @@ -29,7 +30,7 @@ impl TestClient { ) -> Self { let port = listener.local_addr().unwrap().port(); - tokio::spawn(async move { + let api_task_handle = tokio::spawn(async move { let server = serve( listener, app.into_make_service_with_connect_info::(), @@ -54,6 +55,7 @@ impl TestClient { jar, port, api_event_rx, + api_task_handle, } } @@ -122,6 +124,13 @@ impl TestClient { } } +impl Drop for TestClient { + fn drop(&mut self) { + // explicitly stop spawned API server task + self.api_task_handle.abort(); + } +} + pub struct RequestBuilder { builder: reqwest::RequestBuilder, } From 2ee62782cb59e48334a3b937199fe36a5ce9beac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 18 Aug 2025 18:13:06 +0200 Subject: [PATCH 04/16] setup basic gRPC test server & mock gateway client --- Cargo.lock | 2 + crates/defguard_core/Cargo.toml | 2 + crates/defguard_core/src/grpc/mod.rs | 68 ++++++--- .../integration/grpc/common/mock_gateway.rs | 39 ++++++ .../tests/integration/grpc/common/mod.rs | 130 ++++++++++++++++++ .../tests/integration/grpc/gateway_status.rs | 11 ++ .../tests/integration/grpc/mod.rs | 2 + .../defguard_core/tests/integration/main.rs | 1 + 8 files changed, 236 insertions(+), 19 deletions(-) create mode 100644 crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs create mode 100644 crates/defguard_core/tests/integration/grpc/common/mod.rs create mode 100644 crates/defguard_core/tests/integration/grpc/gateway_status.rs create mode 100644 crates/defguard_core/tests/integration/grpc/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 16cb73358a..52dd019ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,6 +1045,7 @@ dependencies = [ "dotenvy", "ed25519-dalek", "humantime", + "hyper-util", "ipnetwork", "jsonwebkey", "jsonwebtoken", @@ -1090,6 +1091,7 @@ dependencies = [ "tonic-prost", "tonic-prost-build", "totp-lite", + "tower", "tower-http", "tracing", "tracing-subscriber", diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index 598dec8d44..6179f47f0c 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -93,6 +93,7 @@ version = "=2.4.2" [dev-dependencies] bytes = "1.6" claims = "0.8" +hyper-util = "0.1" matches = "0.1" regex = "1.10" reqwest = { version = "0.12", features = [ @@ -103,6 +104,7 @@ reqwest = { version = "0.12", features = [ "stream", ], default-features = false } serde_qs = "0.13" +tower = "0.5" webauthn-authenticator-rs = { version = "0.5", features = ["softpasskey"] } [build-dependencies] diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 4dd3b857b0..d49b542725 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -27,7 +27,9 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::sync::CancellationToken; use tonic::{ Code, Status, - transport::{Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig}, + transport::{ + Certificate, ClientTlsConfig, Endpoint, Identity, Server, ServerTlsConfig, server::Router, + }, }; use utoipa::ToSchema; use uuid::Uuid; @@ -863,6 +865,49 @@ pub async fn run_grpc_server( grpc_event_tx: UnboundedSender, ) -> Result<(), anyhow::Error> { // Build gRPC services + let server = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { + let identity = Identity::from_pem(cert, key); + Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? + } else { + Server::builder() + }; + + let router = build_grpc_service_router( + server, + pool, + worker_state, + gateway_state, + wireguard_tx, + mail_tx, + failed_logins, + grpc_event_tx, + ) + .await; + + // Run gRPC server + let addr = SocketAddr::new( + server_config() + .grpc_bind_address + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + server_config().grpc_port, + ); + debug!("Starting gRPC services"); + router.serve(addr).await?; + info!("gRPC server started on {addr}"); + Ok(()) +} + +#[must_use] +pub async fn build_grpc_service_router( + server: Server, + pool: PgPool, + worker_state: Arc>, + gateway_state: Arc>, + wireguard_tx: Sender, + mail_tx: UnboundedSender, + failed_logins: Arc>, + grpc_event_tx: UnboundedSender, +) -> Router { let auth_service = AuthServiceServer::new(AuthServer::new(pool.clone(), failed_logins)); #[cfg(feature = "worker")] let worker_service = WorkerServiceServer::with_interceptor( @@ -880,21 +925,7 @@ pub async fn run_grpc_server( .set_serving::>() .await; - // Run gRPC server - let addr = SocketAddr::new( - server_config() - .grpc_bind_address - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - server_config().grpc_port, - ); - debug!("Starting gRPC services"); - let builder = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) { - let identity = Identity::from_pem(cert, key); - Server::builder().tls_config(ServerTlsConfig::new().identity(identity))? - } else { - Server::builder() - }; - let router = builder + let router = server .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) .add_service(health_service) @@ -903,9 +934,8 @@ pub async fn run_grpc_server( let router = router.add_service(gateway_service); #[cfg(feature = "worker")] let router = router.add_service(worker_service); - router.serve(addr).await?; - info!("gRPC server started on {addr}"); - Ok(()) + + router } #[cfg(feature = "worker")] diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs new file mode 100644 index 0000000000..410f3797ab --- /dev/null +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -0,0 +1,39 @@ +use axum::http::Uri; +use defguard_core::grpc::proto::gateway::gateway_service_client::GatewayServiceClient; +use hyper_util::rt::TokioIo; +use tokio::io::DuplexStream; +use tonic::transport::{Channel, Endpoint}; +use tower::service_fn; + +pub(crate) struct MockGateway { + client: GatewayServiceClient, +} + +impl MockGateway { + #[must_use] + pub(crate) async fn new(client_stream: DuplexStream) -> Self { + // Move client to an option so we can _move_ the inner value + // on the first attempt to connect. All other attempts will fail. + // reference: https://github.com/hyperium/tonic/blob/master/examples/src/mock/mock.rs#L31 + let mut client = Some(client_stream); + let channel = Endpoint::try_from("http://[::]:50051") + .expect("Failed to create channel") + .connect_with_connector(service_fn(move |_: Uri| { + let client = client.take(); + + async move { + if let Some(client) = client { + Ok(TokioIo::new(client)) + } else { + Err(std::io::Error::other("Client already taken")) + } + } + })) + .await + .expect("Failed to create client channel"); + + let client = GatewayServiceClient::new(channel); + + Self { client } + } +} diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs new file mode 100644 index 0000000000..cf677f11ed --- /dev/null +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -0,0 +1,130 @@ +use std::sync::{Arc, Mutex}; + +use defguard_core::{ + auth::failed_login::FailedLoginMap, + db::{AppEvent, GatewayEvent}, + enterprise::license::{License, set_cached_license}, + events::GrpcEvent, + grpc::{GatewayMap, WorkerState, build_grpc_service_router}, + mail::Mail, +}; +use sqlx::PgPool; +use tokio::{ + sync::{ + broadcast::{self, Receiver}, + mpsc::{UnboundedReceiver, unbounded_channel}, + }, + task::JoinHandle, +}; +use tonic::transport::{Server, server::Router}; + +use crate::grpc::common::mock_gateway::MockGateway; + +mod mock_gateway; + +pub struct TestGrpcClient { + gateway: MockGateway, + grpc_server_task_handle: JoinHandle<()>, + grpc_event_rx: UnboundedReceiver, + app_event_rx: UnboundedReceiver, + wireguard_rx: Receiver, + mail_rx: UnboundedReceiver, + worker_state: Arc>, + gateway_state: Arc>, + failed_logins: Arc>, +} + +impl TestGrpcClient { + pub async fn new( + grpc_router: Router, + grpc_event_rx: UnboundedReceiver, + app_event_rx: UnboundedReceiver, + wireguard_rx: Receiver, + mail_rx: UnboundedReceiver, + worker_state: Arc>, + gateway_state: Arc>, + failed_logins: Arc>, + ) -> Self { + // create communication channel + let (client_stream, server_stream) = tokio::io::duplex(1024); + + // spawn test gRPC server + let grpc_server_task_handle = tokio::spawn(async move { + grpc_router + .serve_with_incoming(tokio_stream::once(Ok::<_, std::io::Error>(server_stream))) + .await + .map_err(|err| eprintln!("Unexpected test gRPC server error: {err}")) + .unwrap() + }); + + // setup mock gateway + let gateway = MockGateway::new(client_stream).await; + + Self { + gateway, + grpc_server_task_handle, + grpc_event_rx, + app_event_rx, + wireguard_rx, + mail_rx, + worker_state, + gateway_state, + failed_logins, + } + } +} + +impl Drop for TestGrpcClient { + fn drop(&mut self) { + // explicitly stop spawned gRPC server task + self.grpc_server_task_handle.abort(); + } +} + +pub(crate) async fn make_grpc_test_client(pool: PgPool) -> TestGrpcClient { + // setup helper structs + let (grpc_event_tx, grpc_event_rx) = unbounded_channel::(); + let (app_event_tx, app_event_rx) = unbounded_channel::(); + let worker_state = Arc::new(Mutex::new(WorkerState::new(app_event_tx.clone()))); + let (wg_tx, wg_rx) = broadcast::channel::(16); + let (mail_tx, mail_rx) = unbounded_channel::(); + let gateway_state = Arc::new(Mutex::new(GatewayMap::new())); + + let failed_logins = FailedLoginMap::new(); + let failed_logins = Arc::new(Mutex::new(failed_logins)); + + let license = License::new( + "test_customer".to_string(), + false, + // Permanent license + None, + None, + ); + + set_cached_license(Some(license)); + let server = Server::builder(); + + let grpc_router = build_grpc_service_router( + server, + pool, + worker_state.clone(), + gateway_state.clone(), + wg_tx, + mail_tx, + failed_logins.clone(), + grpc_event_tx, + ) + .await; + + TestGrpcClient::new( + grpc_router, + grpc_event_rx, + app_event_rx, + wg_rx, + mail_rx, + worker_state, + gateway_state, + failed_logins, + ) + .await +} diff --git a/crates/defguard_core/tests/integration/grpc/gateway_status.rs b/crates/defguard_core/tests/integration/grpc/gateway_status.rs new file mode 100644 index 0000000000..a5ac100b7c --- /dev/null +++ b/crates/defguard_core/tests/integration/grpc/gateway_status.rs @@ -0,0 +1,11 @@ +use defguard_core::db::setup_pool; +use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; + +use crate::grpc::common::make_grpc_test_client; + +#[sqlx::test] +async fn test_gateway_connect(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let test_client = make_grpc_test_client(pool).await; + todo!() +} diff --git a/crates/defguard_core/tests/integration/grpc/mod.rs b/crates/defguard_core/tests/integration/grpc/mod.rs new file mode 100644 index 0000000000..828d87d096 --- /dev/null +++ b/crates/defguard_core/tests/integration/grpc/mod.rs @@ -0,0 +1,2 @@ +mod common; +mod gateway_status; diff --git a/crates/defguard_core/tests/integration/main.rs b/crates/defguard_core/tests/integration/main.rs index b32f9e29f1..576fb0daeb 100644 --- a/crates/defguard_core/tests/integration/main.rs +++ b/crates/defguard_core/tests/integration/main.rs @@ -1 +1,2 @@ mod api; +mod grpc; From 34f8cde8ee5d1a5a9b658b5fed7cf7149eacb565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 20 Aug 2025 14:02:54 +0200 Subject: [PATCH 05/16] split test server and mock gateway setup --- .../integration/grpc/common/mock_gateway.rs | 33 +++++++++++-- .../tests/integration/grpc/common/mod.rs | 49 +++++++++---------- .../tests/integration/grpc/gateway.rs | 30 ++++++++++++ .../tests/integration/grpc/gateway_status.rs | 11 ----- .../tests/integration/grpc/mod.rs | 2 +- 5 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 crates/defguard_core/tests/integration/grpc/gateway.rs delete mode 100644 crates/defguard_core/tests/integration/grpc/gateway_status.rs diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index 410f3797ab..622612778c 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -1,12 +1,20 @@ use axum::http::Uri; -use defguard_core::grpc::proto::gateway::gateway_service_client::GatewayServiceClient; +use defguard_core::grpc::proto::gateway::{ + ConfigurationRequest, gateway_service_client::GatewayServiceClient, +}; use hyper_util::rt::TokioIo; use tokio::io::DuplexStream; -use tonic::transport::{Channel, Endpoint}; +use tonic::{ + Request, Status, + metadata::MetadataValue, + transport::{Channel, Endpoint}, +}; use tower::service_fn; pub(crate) struct MockGateway { client: GatewayServiceClient, + auth_token: Option, + hostname: Option, } impl MockGateway { @@ -34,6 +42,25 @@ impl MockGateway { let client = GatewayServiceClient::new(channel); - Self { client } + Self { + client, + auth_token: None, + hostname: None, + } } + + // pub(crate) async fn get_gateway_config( + // &self, + // ) -> Result { + // let request = Request::new(ConfigurationRequest { + // name: self.hostname, + // }); + // if let Some(token) = self.auth_token { + // request + // .metadata_mut() + // .insert("authorization", MetadataValue::try_from(token)); + // }; + + // self.client.config(request).await + // } } diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index cf677f11ed..2c682367e9 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -10,6 +10,7 @@ use defguard_core::{ }; use sqlx::PgPool; use tokio::{ + io::DuplexStream, sync::{ broadcast::{self, Receiver}, mpsc::{UnboundedReceiver, unbounded_channel}, @@ -18,12 +19,9 @@ use tokio::{ }; use tonic::transport::{Server, server::Router}; -use crate::grpc::common::mock_gateway::MockGateway; +pub mod mock_gateway; -mod mock_gateway; - -pub struct TestGrpcClient { - gateway: MockGateway, +pub struct TestGrpcServer { grpc_server_task_handle: JoinHandle<()>, grpc_event_rx: UnboundedReceiver, app_event_rx: UnboundedReceiver, @@ -34,8 +32,9 @@ pub struct TestGrpcClient { failed_logins: Arc>, } -impl TestGrpcClient { +impl TestGrpcServer { pub async fn new( + server_stream: DuplexStream, grpc_router: Router, grpc_event_rx: UnboundedReceiver, app_event_rx: UnboundedReceiver, @@ -45,9 +44,6 @@ impl TestGrpcClient { gateway_state: Arc>, failed_logins: Arc>, ) -> Self { - // create communication channel - let (client_stream, server_stream) = tokio::io::duplex(1024); - // spawn test gRPC server let grpc_server_task_handle = tokio::spawn(async move { grpc_router @@ -57,11 +53,7 @@ impl TestGrpcClient { .unwrap() }); - // setup mock gateway - let gateway = MockGateway::new(client_stream).await; - Self { - gateway, grpc_server_task_handle, grpc_event_rx, app_event_rx, @@ -74,14 +66,17 @@ impl TestGrpcClient { } } -impl Drop for TestGrpcClient { +impl Drop for TestGrpcServer { fn drop(&mut self) { // explicitly stop spawned gRPC server task self.grpc_server_task_handle.abort(); } } -pub(crate) async fn make_grpc_test_client(pool: PgPool) -> TestGrpcClient { +pub(crate) async fn make_grpc_test_server(pool: PgPool) -> (TestGrpcServer, DuplexStream) { + // create communication channel for clients + let (client_stream, server_stream) = tokio::io::duplex(1024); + // setup helper structs let (grpc_event_tx, grpc_event_rx) = unbounded_channel::(); let (app_event_tx, app_event_rx) = unbounded_channel::(); @@ -116,15 +111,19 @@ pub(crate) async fn make_grpc_test_client(pool: PgPool) -> TestGrpcClient { ) .await; - TestGrpcClient::new( - grpc_router, - grpc_event_rx, - app_event_rx, - wg_rx, - mail_rx, - worker_state, - gateway_state, - failed_logins, + ( + TestGrpcServer::new( + server_stream, + grpc_router, + grpc_event_rx, + app_event_rx, + wg_rx, + mail_rx, + worker_state, + gateway_state, + failed_logins, + ) + .await, + client_stream, ) - .await } diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs new file mode 100644 index 0000000000..fe9ae91912 --- /dev/null +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -0,0 +1,30 @@ +use defguard_core::{db::setup_pool, grpc::proto::gateway::ConfigurationRequest}; +use sqlx::{ + PgPool, + postgres::{PgConnectOptions, PgPoolOptions}, +}; +use tonic::Request; + +use crate::grpc::common::{TestGrpcServer, make_grpc_test_server, mock_gateway::MockGateway}; + +async fn setup_test_server(pool: PgPool) -> (TestGrpcServer, MockGateway) { + let (test_server, client_stream) = make_grpc_test_server(pool).await; + + // setup mock gateway + let gateway = MockGateway::new(client_stream).await; + (test_server, gateway) +} + +#[sqlx::test] +async fn test_gateway_authorization(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (test_server, gateway) = setup_test_server(pool).await; + + // make a request without auth token + let request = Request::new(ConfigurationRequest { + name: Some("test_gw".into()), + }); + + // check that response is Status::Unauthorized + let response = todo!(); +} diff --git a/crates/defguard_core/tests/integration/grpc/gateway_status.rs b/crates/defguard_core/tests/integration/grpc/gateway_status.rs deleted file mode 100644 index a5ac100b7c..0000000000 --- a/crates/defguard_core/tests/integration/grpc/gateway_status.rs +++ /dev/null @@ -1,11 +0,0 @@ -use defguard_core::db::setup_pool; -use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; - -use crate::grpc::common::make_grpc_test_client; - -#[sqlx::test] -async fn test_gateway_connect(_: PgPoolOptions, options: PgConnectOptions) { - let pool = setup_pool(options).await; - let test_client = make_grpc_test_client(pool).await; - todo!() -} diff --git a/crates/defguard_core/tests/integration/grpc/mod.rs b/crates/defguard_core/tests/integration/grpc/mod.rs index 828d87d096..5b53a1b0d6 100644 --- a/crates/defguard_core/tests/integration/grpc/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/mod.rs @@ -1,2 +1,2 @@ mod common; -mod gateway_status; +mod gateway; From acf622d55bf915c7e9e80fdf4f57e1ef4b46c474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 20 Aug 2025 18:06:26 +0200 Subject: [PATCH 06/16] test gateway authorization and hostname --- .../defguard_core/src/db/models/wireguard.rs | 19 ++++ crates/defguard_core/src/error.rs | 5 +- .../defguard_core/src/handlers/wireguard.rs | 9 +- .../integration/grpc/common/mock_gateway.rs | 60 +++++++++---- .../tests/integration/grpc/common/mod.rs | 4 +- .../tests/integration/grpc/gateway.rs | 90 ++++++++++++++++--- 6 files changed, 147 insertions(+), 40 deletions(-) diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index 67d996dd10..bf7796c583 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -30,6 +30,7 @@ use super::{ }; use crate::{ AsCsv, + auth::{Claims, ClaimsType}, db::{Id, NoId}, enterprise::firewall::FirewallError, grpc::{ @@ -213,6 +214,8 @@ pub enum WireguardNetworkError { DeviceError(#[from] DeviceError), #[error("Firewall config error: {0}")] FirewallError(#[from] FirewallError), + #[error(transparent)] + TokenError(#[from] jsonwebtoken::errors::Error), } #[derive(Debug, Error)] @@ -1303,6 +1306,22 @@ impl WireguardNetwork { Ok(locations) } + + /// Generates auth token for a VPN gateway + #[must_use] + pub fn generate_gateway_token(&self) -> Result { + let location_id = self.id; + + let token = Claims::new( + ClaimsType::Gateway, + format!("DEFGUARD-NETWORK-{location_id}"), + location_id.to_string(), + u32::MAX.into(), + ) + .to_jwt()?; + + Ok(token) + } } // [`IpNetwork`] does not implement [`Default`] diff --git a/crates/defguard_core/src/error.rs b/crates/defguard_core/src/error.rs index 162c04ab1a..c7888906a0 100644 --- a/crates/defguard_core/src/error.rs +++ b/crates/defguard_core/src/error.rs @@ -140,9 +140,8 @@ impl From for WebError { | WireguardNetworkError::Unexpected(_) | WireguardNetworkError::DeviceError(_) | WireguardNetworkError::DeviceNotAllowed(_) - | WireguardNetworkError::FirewallError(_) => { - Self::Http(StatusCode::INTERNAL_SERVER_ERROR) - } + | WireguardNetworkError::FirewallError(_) + | WireguardNetworkError::TokenError(_) => Self::Http(StatusCode::INTERNAL_SERVER_ERROR), } } } diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index 54e4d27bd8..a035d5aeb5 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -1244,14 +1244,7 @@ pub(crate) async fn create_network_token( ) -> ApiResult { debug!("Generating a new token for network ID {network_id}"); let network = find_network(network_id, &appstate.pool).await?; - let token = Claims::new( - ClaimsType::Gateway, - format!("DEFGUARD-NETWORK-{network_id}"), - network_id.to_string(), - u32::MAX.into(), - ) - .to_jwt() - .map_err(|_| { + let token = network.generate_gateway_token().map_err(|_| { error!("Failed to create token for gateway {}", network.name); WebError::Authorization(format!( "Failed to create token for gateway {}", diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index 622612778c..c24db0d690 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -1,11 +1,11 @@ use axum::http::Uri; use defguard_core::grpc::proto::gateway::{ - ConfigurationRequest, gateway_service_client::GatewayServiceClient, + Configuration, ConfigurationRequest, gateway_service_client::GatewayServiceClient, }; use hyper_util::rt::TokioIo; use tokio::io::DuplexStream; use tonic::{ - Request, Status, + Request, Response, Status, metadata::MetadataValue, transport::{Channel, Endpoint}, }; @@ -49,18 +49,48 @@ impl MockGateway { } } - // pub(crate) async fn get_gateway_config( - // &self, - // ) -> Result { - // let request = Request::new(ConfigurationRequest { - // name: self.hostname, - // }); - // if let Some(token) = self.auth_token { - // request - // .metadata_mut() - // .insert("authorization", MetadataValue::try_from(token)); - // }; + fn add_request_metadata(&self, request: &mut Request) { + // add authorization token + if let Some(token) = &self.auth_token { + request.metadata_mut().insert( + "authorization", + MetadataValue::try_from(token).expect("failed to convert token into metadata"), + ); + }; - // self.client.config(request).await - // } + // add gateway hostname + if let Some(hostname) = &self.hostname { + request.metadata_mut().insert( + "hostname", + MetadataValue::try_from(hostname) + .expect("failed to convert hostname into metadata"), + ); + }; + } + + pub(crate) async fn get_gateway_config(&mut self) -> Result, Status> { + let mut request = Request::new(ConfigurationRequest { + name: self.hostname.clone(), + }); + + self.add_request_metadata(&mut request); + + self.client.config(request).await + } + + pub(crate) fn set_token(&mut self, token: &str) { + self.auth_token = Some(token.into()) + } + + pub(crate) fn clear_token(&mut self) { + self.auth_token = None; + } + + pub(crate) fn set_hostname(&mut self, hostname: &str) { + self.hostname = Some(hostname.into()) + } + + pub(crate) fn clear_hostname(&mut self) { + self.hostname = None; + } } diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index 2c682367e9..e94cc9dd13 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -73,7 +73,7 @@ impl Drop for TestGrpcServer { } } -pub(crate) async fn make_grpc_test_server(pool: PgPool) -> (TestGrpcServer, DuplexStream) { +pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, DuplexStream) { // create communication channel for clients let (client_stream, server_stream) = tokio::io::duplex(1024); @@ -101,7 +101,7 @@ pub(crate) async fn make_grpc_test_server(pool: PgPool) -> (TestGrpcServer, Dupl let grpc_router = build_grpc_service_router( server, - pool, + pool.clone(), worker_state.clone(), gateway_state.clone(), wg_tx, diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index fe9ae91912..a41a84b8ff 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -1,30 +1,96 @@ -use defguard_core::{db::setup_pool, grpc::proto::gateway::ConfigurationRequest}; +use defguard_core::db::{Id, WireguardNetwork, models::wireguard::LocationMfaMode, setup_pool}; use sqlx::{ PgPool, postgres::{PgConnectOptions, PgPoolOptions}, }; -use tonic::Request; +use tonic::Code; use crate::grpc::common::{TestGrpcServer, make_grpc_test_server, mock_gateway::MockGateway}; -async fn setup_test_server(pool: PgPool) -> (TestGrpcServer, MockGateway) { - let (test_server, client_stream) = make_grpc_test_server(pool).await; +async fn setup_test_server(pool: PgPool) -> (TestGrpcServer, MockGateway, WireguardNetwork) { + let (test_server, client_stream) = make_grpc_test_server(&pool).await; // setup mock gateway - let gateway = MockGateway::new(client_stream).await; - (test_server, gateway) + let mut gateway = MockGateway::new(client_stream).await; + + // create a test location + let location = WireguardNetwork::new( + "test location".to_string(), + Vec::new(), + 1000, + "endpoint1".to_string(), + None, + Vec::new(), + 100, + 100, + false, + false, + LocationMfaMode::Disabled, + ) + .save(&pool) + .await + .unwrap(); + + // set auth token for gateway + let token = location + .generate_gateway_token() + .expect("failed to generate gateway token"); + gateway.set_token(&token); + + // set hostname for gateway + gateway.set_hostname("test_gateway"); + + (test_server, gateway, location) } #[sqlx::test] async fn test_gateway_authorization(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; - let (test_server, gateway) = setup_test_server(pool).await; + let (_test_server, mut gateway, test_location) = setup_test_server(pool).await; + + // remove auth token + gateway.clear_token(); // make a request without auth token - let request = Request::new(ConfigurationRequest { - name: Some("test_gw".into()), - }); + let response = gateway.get_gateway_config().await; + + // check that response code is Status::Unauthenticated + assert!(response.is_err()); + let status = response.err().unwrap(); + assert_eq!(status.code(), Code::Unauthenticated); + + // set invalid token and check again + gateway.set_token("invalid_token"); + let response = gateway.get_gateway_config().await; + assert!(response.is_err()); + let status = response.err().unwrap(); + assert_eq!(status.code(), Code::Unauthenticated); + + // set valid token and retry + let token = test_location.generate_gateway_token().unwrap(); + gateway.set_token(&token); + let response = gateway.get_gateway_config().await; + assert!(response.is_ok()); +} + +#[sqlx::test] +async fn test_gateway_hostname_is_required(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (_test_server, mut gateway, _test_location) = setup_test_server(pool).await; + + // remove hostname + gateway.clear_hostname(); + + // make a request without hostname + let response = gateway.get_gateway_config().await; + + // check that response code is Status::Unauthenticated + assert!(response.is_err()); + let status = response.err().unwrap(); + assert_eq!(status.code(), Code::Internal); - // check that response is Status::Unauthorized - let response = todo!(); + // set hostname and retry + gateway.set_hostname("hostname"); + let response = gateway.get_gateway_config().await; + assert!(response.is_ok()); } From c1e9b63daf2a1938adafc2c358103b4146aa51d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 21 Aug 2025 14:16:52 +0200 Subject: [PATCH 07/16] test gateway status updates --- crates/defguard_core/src/grpc/mod.rs | 4 ++ .../tests/integration/api/acl.rs | 6 +- .../tests/integration/api/common/mod.rs | 52 +++++++------- .../defguard_core/tests/integration/common.rs | 34 +++++++++ .../integration/grpc/common/mock_gateway.rs | 19 ++++- .../tests/integration/grpc/common/mod.rs | 38 ++++++---- .../tests/integration/grpc/gateway.rs | 72 +++++++++++++++++++ .../defguard_core/tests/integration/main.rs | 1 + 8 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 crates/defguard_core/tests/integration/common.rs diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index d49b542725..0e3e9b1903 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -138,6 +138,10 @@ impl GatewayMap { Self(HashMap::new()) } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + // add a new gateway to map // this method is meant to be called when a gateway requests a config // as a sort of "registration" diff --git a/crates/defguard_core/tests/integration/api/acl.rs b/crates/defguard_core/tests/integration/api/acl.rs index 9762dab2ad..636695c91c 100644 --- a/crates/defguard_core/tests/integration/api/acl.rs +++ b/crates/defguard_core/tests/integration/api/acl.rs @@ -21,9 +21,11 @@ use sqlx::{ }; use tokio::net::TcpListener; +use crate::common::{init_config, initialize_users}; + use super::common::{ - authenticate_admin, client::TestClient, exceed_enterprise_limits, init_config, - initialize_users, make_base_client, make_test_client, omit_id, setup_pool, + authenticate_admin, client::TestClient, exceed_enterprise_limits, make_base_client, + make_test_client, omit_id, setup_pool, }; async fn make_client_v2(pool: PgPool, config: DefGuardConfig) -> TestClient { diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index e167b3146d..9f8dafdfec 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -35,6 +35,8 @@ use tokio::{ pub use defguard_core::db::setup_pool; +use crate::common::{init_config, initialize_users}; + use self::client::TestClient; #[allow(clippy::declare_interior_mutable_const)] @@ -44,33 +46,33 @@ pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for #[allow(clippy::declare_interior_mutable_const)] pub const X_FORWARDED_URI: HeaderName = HeaderName::from_static("x-forwarded-uri"); -/// Allows overriding the default DefGuard URL for tests, as during the tests, the server has a random port, making the URL unpredictable beforehand. -// TODO: Allow customizing the whole config, not just the URL -pub(crate) fn init_config(custom_defguard_url: Option<&str>) -> DefGuardConfig { - let url = custom_defguard_url.unwrap_or("http://localhost:8000"); - let mut config = DefGuardConfig::new_test_config(); - config.url = Url::from_str(url).unwrap(); - let _ = SERVER_CONFIG.set(config.clone()); - config -} +// /// Allows overriding the default DefGuard URL for tests, as during the tests, the server has a random port, making the URL unpredictable beforehand. +// // TODO: Allow customizing the whole config, not just the URL +// pub(crate) fn init_config(custom_defguard_url: Option<&str>) -> DefGuardConfig { +// let url = custom_defguard_url.unwrap_or("http://localhost:8000"); +// let mut config = DefGuardConfig::new_test_config(); +// config.url = Url::from_str(url).unwrap(); +// let _ = SERVER_CONFIG.set(config.clone()); +// config +// } -pub(crate) async fn initialize_users(pool: &PgPool, config: &DefGuardConfig) { - User::init_admin_user(pool, config.default_admin_password.expose_secret()) - .await - .unwrap(); +// pub(crate) async fn initialize_users(pool: &PgPool, config: &DefGuardConfig) { +// User::init_admin_user(pool, config.default_admin_password.expose_secret()) +// .await +// .unwrap(); - User::new( - "hpotter", - Some("pass123"), - "Potter", - "Harry", - "h.potter@hogwart.edu.uk", - None, - ) - .save(pool) - .await - .unwrap(); -} +// User::new( +// "hpotter", +// Some("pass123"), +// "Potter", +// "Harry", +// "h.potter@hogwart.edu.uk", +// None, +// ) +// .save(pool) +// .await +// .unwrap(); +// } pub(crate) struct ClientState { pub pool: PgPool, diff --git a/crates/defguard_core/tests/integration/common.rs b/crates/defguard_core/tests/integration/common.rs new file mode 100644 index 0000000000..d89859fc9c --- /dev/null +++ b/crates/defguard_core/tests/integration/common.rs @@ -0,0 +1,34 @@ +use std::str::FromStr; + +use defguard_core::{SERVER_CONFIG, config::DefGuardConfig, db::User}; +use reqwest::Url; +use secrecy::ExposeSecret; +use sqlx::PgPool; + +/// Allows overriding the default DefGuard URL for tests, as during the tests, the server has a random port, making the URL unpredictable beforehand. +// TODO: Allow customizing the whole config, not just the URL +pub(crate) fn init_config(custom_defguard_url: Option<&str>) -> DefGuardConfig { + let url = custom_defguard_url.unwrap_or("http://localhost:8000"); + let mut config = DefGuardConfig::new_test_config(); + config.url = Url::from_str(url).unwrap(); + let _ = SERVER_CONFIG.set(config.clone()); + config +} + +pub(crate) async fn initialize_users(pool: &PgPool, config: &DefGuardConfig) { + User::init_admin_user(pool, config.default_admin_password.expose_secret()) + .await + .unwrap(); + + User::new( + "hpotter", + Some("pass123"), + "Potter", + "Harry", + "h.potter@hogwart.edu.uk", + None, + ) + .save(pool) + .await + .unwrap(); +} diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index c24db0d690..a65e6a86a5 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -1,11 +1,13 @@ +use std::collections::VecDeque; + use axum::http::Uri; use defguard_core::grpc::proto::gateway::{ - Configuration, ConfigurationRequest, gateway_service_client::GatewayServiceClient, + Configuration, ConfigurationRequest, Update, gateway_service_client::GatewayServiceClient, }; use hyper_util::rt::TokioIo; use tokio::io::DuplexStream; use tonic::{ - Request, Response, Status, + Request, Response, Status, Streaming, metadata::MetadataValue, transport::{Channel, Endpoint}, }; @@ -78,6 +80,15 @@ impl MockGateway { self.client.config(request).await } + #[must_use] + pub(crate) async fn connect_to_updates_stream(&mut self) -> Streaming { + let mut request = Request::new(()); + + self.add_request_metadata(&mut request); + + self.client.updates(request).await.unwrap().into_inner() + } + pub(crate) fn set_token(&mut self, token: &str) { self.auth_token = Some(token.into()) } @@ -93,4 +104,8 @@ impl MockGateway { pub(crate) fn clear_hostname(&mut self) { self.hostname = None; } + + pub(crate) fn hostname(&self) -> String { + self.hostname.clone().unwrap_or_default() + } } diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index e94cc9dd13..ca248737ee 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex}; use defguard_core::{ auth::failed_login::FailedLoginMap, - db::{AppEvent, GatewayEvent}, + db::{AppEvent, GatewayEvent, models::settings::initialize_current_settings}, enterprise::license::{License, set_cached_license}, events::GrpcEvent, grpc::{GatewayMap, WorkerState, build_grpc_service_router}, @@ -12,21 +12,23 @@ use sqlx::PgPool; use tokio::{ io::DuplexStream, sync::{ - broadcast::{self, Receiver}, + broadcast::{self, Sender}, mpsc::{UnboundedReceiver, unbounded_channel}, }, task::JoinHandle, }; use tonic::transport::{Server, server::Router}; +use crate::common::{init_config, initialize_users}; + pub mod mock_gateway; pub struct TestGrpcServer { grpc_server_task_handle: JoinHandle<()>, grpc_event_rx: UnboundedReceiver, - app_event_rx: UnboundedReceiver, - wireguard_rx: Receiver, - mail_rx: UnboundedReceiver, + // app_event_rx: UnboundedReceiver, + wireguard_tx: Sender, + // mail_rx: UnboundedReceiver, worker_state: Arc>, gateway_state: Arc>, failed_logins: Arc>, @@ -37,9 +39,7 @@ impl TestGrpcServer { server_stream: DuplexStream, grpc_router: Router, grpc_event_rx: UnboundedReceiver, - app_event_rx: UnboundedReceiver, - wireguard_rx: Receiver, - mail_rx: UnboundedReceiver, + wireguard_tx: Sender, worker_state: Arc>, gateway_state: Arc>, failed_logins: Arc>, @@ -56,14 +56,18 @@ impl TestGrpcServer { Self { grpc_server_task_handle, grpc_event_rx, - app_event_rx, - wireguard_rx, - mail_rx, + wireguard_tx, worker_state, gateway_state, failed_logins, } } + + pub fn get_gateway_map(&self) -> std::sync::MutexGuard<'_, GatewayMap> { + self.gateway_state + .lock() + .expect("failed to acquire lock on gateway state") + } } impl Drop for TestGrpcServer { @@ -88,6 +92,12 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup let failed_logins = FailedLoginMap::new(); let failed_logins = Arc::new(Mutex::new(failed_logins)); + let config = init_config(None); + initialize_users(&pool, &config).await; + initialize_current_settings(&pool) + .await + .expect("Could not initialize settings"); + let license = License::new( "test_customer".to_string(), false, @@ -104,7 +114,7 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup pool.clone(), worker_state.clone(), gateway_state.clone(), - wg_tx, + wg_tx.clone(), mail_tx, failed_logins.clone(), grpc_event_tx, @@ -116,9 +126,7 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup server_stream, grpc_router, grpc_event_rx, - app_event_rx, - wg_rx, - mail_rx, + wg_tx, worker_state, gateway_state, failed_logins, diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index a41a84b8ff..afe6b6607c 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -1,8 +1,11 @@ +use std::time::Duration; + use defguard_core::db::{Id, WireguardNetwork, models::wireguard::LocationMfaMode, setup_pool}; use sqlx::{ PgPool, postgres::{PgConnectOptions, PgPoolOptions}, }; +use tokio::time::sleep; use tonic::Code; use crate::grpc::common::{TestGrpcServer, make_grpc_test_server, mock_gateway::MockGateway}; @@ -94,3 +97,72 @@ async fn test_gateway_hostname_is_required(_: PgPoolOptions, options: PgConnectO let response = gateway.get_gateway_config().await; assert!(response.is_ok()); } + +#[sqlx::test] +async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (test_server, mut gateway, test_location) = setup_test_server(pool).await; + + // initial gateway map is empty + { + let gateway_map = test_server.get_gateway_map(); + assert!(gateway_map.is_empty()) + } + + // gateway request initial config + // it should be added to status map as disconnected + let response = gateway.get_gateway_config().await; + assert!(response.is_ok()); + { + let gateway_map = test_server.get_gateway_map(); + let location_gateways = gateway_map.get_network_gateway_status(test_location.id); + assert_eq!(location_gateways.len(), 1); + let gateway_state = location_gateways.first().unwrap(); + assert!(!gateway_state.connected); + assert!(gateway_state.connected_at.is_none()); + assert!(gateway_state.disconnected_at.is_none()); + assert_eq!(gateway_state.hostname, gateway.hostname()); + } + + // gateway connects to updates stream + // it should be marked as connected + let updates_stream = gateway.connect_to_updates_stream().await; + { + let gateway_map = test_server.get_gateway_map(); + let location_gateways = gateway_map.get_network_gateway_status(test_location.id); + assert_eq!(location_gateways.len(), 1); + let gateway_state = location_gateways.first().unwrap(); + assert!(gateway_state.connected); + assert!(gateway_state.connected_at.is_some()); + assert!(gateway_state.disconnected_at.is_none()); + assert_eq!(gateway_state.hostname, gateway.hostname()); + } + + // gateway disconnect from updates stream + // it should be marked as disconnected + drop(updates_stream); + // wait for the background thread to handle the disconnect + sleep(Duration::from_secs(1)).await; + + { + let gateway_map = test_server.get_gateway_map(); + let location_gateways = gateway_map.get_network_gateway_status(test_location.id); + assert_eq!(location_gateways.len(), 1); + let gateway_state = location_gateways.first().unwrap(); + assert!(!gateway_state.connected); + assert!(gateway_state.connected_at.is_some()); + assert!(gateway_state.disconnected_at.is_some()); + assert_eq!(gateway_state.hostname, gateway.hostname()); + } +} + +// test correct config is sent to gw +// firewall rules are included + +// test updates stream +// filtering by id +// sent to multiple gateways + +// test client status +// connected +// disconnected diff --git a/crates/defguard_core/tests/integration/main.rs b/crates/defguard_core/tests/integration/main.rs index 576fb0daeb..f85d8d0fa3 100644 --- a/crates/defguard_core/tests/integration/main.rs +++ b/crates/defguard_core/tests/integration/main.rs @@ -1,2 +1,3 @@ mod api; +mod common; mod grpc; From dbffb8e948ff3fa5a7f90bd4aa5ccb8f077f7f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 22 Aug 2025 10:42:33 +0200 Subject: [PATCH 08/16] test client connection status --- .../integration/grpc/common/mock_gateway.rs | 21 +++++++++++++++++-- .../tests/integration/grpc/gateway.rs | 17 +++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index a65e6a86a5..751c3a4293 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -2,10 +2,15 @@ use std::collections::VecDeque; use axum::http::Uri; use defguard_core::grpc::proto::gateway::{ - Configuration, ConfigurationRequest, Update, gateway_service_client::GatewayServiceClient, + Configuration, ConfigurationRequest, StatsUpdate, Update, + gateway_service_client::GatewayServiceClient, }; use hyper_util::rt::TokioIo; -use tokio::io::DuplexStream; +use tokio::{ + io::DuplexStream, + sync::mpsc::{UnboundedSender, unbounded_channel}, +}; +use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::{ Request, Response, Status, Streaming, metadata::MetadataValue, @@ -89,6 +94,18 @@ impl MockGateway { self.client.updates(request).await.unwrap().into_inner() } + #[must_use] + pub(crate) async fn setup_stats_update_stream(&mut self) -> UnboundedSender { + let (tx, rx) = unbounded_channel(); + + self.client + .stats(UnboundedReceiverStream::new(rx)) + .await + .unwrap(); + + tx + } + pub(crate) fn set_token(&mut self, token: &str) { self.auth_token = Some(token.into()) } diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index afe6b6607c..078050020d 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -166,3 +166,20 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { // test client status // connected // disconnected + +#[sqlx::test] +async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (test_server, mut gateway, test_location) = setup_test_server(pool).await; + + // connect stats stream + let stats_tx = gateway.setup_stats_update_stream().await; + + // send stats update for unknown device + + // verify no gRPC event is emitted + + // add user device + + // send stats update for existing device and verify gRPC event is emitted +} From 499fa63b8896fe7db07094849e4fe98ea95e47d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 22 Aug 2025 16:15:28 +0200 Subject: [PATCH 09/16] test vpn client connect --- crates/defguard_core/src/grpc/gateway/mod.rs | 8 +- .../integration/grpc/common/mock_gateway.rs | 29 ++++- .../tests/integration/grpc/common/mod.rs | 2 +- .../tests/integration/grpc/gateway.rs | 105 +++++++++++++++--- 4 files changed, 122 insertions(+), 22 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 99240f5a74..d955cef191 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -132,14 +132,14 @@ impl GatewayServer { #[must_use] pub fn new( pool: PgPool, - state: Arc>, + gateway_state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, grpc_event_tx: UnboundedSender, ) -> Self { Self { pool, - gateway_state: state, + gateway_state, client_state: Arc::new(Mutex::new(ClientMap::new())), wireguard_tx, mail_tx, @@ -762,7 +762,7 @@ impl gateway_service_server::GatewayService for GatewayServer { } }; - debug!("Received stats message: {stats_update:?}"); + println!("Received stats message: {stats_update:?}"); let Some(stats_update::Payload::PeerStats(peer_stats)) = stats_update.payload else { debug!("Received stats message is empty, skipping."); continue; @@ -782,6 +782,7 @@ impl gateway_service_server::GatewayService for GatewayServer { // convert stats to DB storage format let stats = WireguardPeerStats::from_peer_stats(peer_stats, network_id, device_id); + println!("received stats: {stats:?}"); // only perform client state update if stats include an endpoint IP // otherwise a peer was added to the gateway interface @@ -800,6 +801,7 @@ impl gateway_service_server::GatewayService for GatewayServer { let disconnected_clients = { // acquire lock on client state map let mut client_map = self.get_client_state_guard()?; + println!("client map: {client_map:?}"); // update connected clients map match client_map.get_vpn_client(network_id, &public_key) { diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index 751c3a4293..c157dfeab9 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -9,6 +9,7 @@ use hyper_util::rt::TokioIo; use tokio::{ io::DuplexStream, sync::mpsc::{UnboundedSender, unbounded_channel}, + task::JoinHandle, }; use tokio_stream::wrappers::UnboundedReceiverStream; use tonic::{ @@ -22,6 +23,15 @@ pub(crate) struct MockGateway { client: GatewayServiceClient, auth_token: Option, hostname: Option, + stats_update_thread_handle: Option>, +} + +impl Drop for MockGateway { + fn drop(&mut self) { + if let Some(handle) = &self.stats_update_thread_handle { + handle.abort(); + } + } } impl MockGateway { @@ -53,9 +63,11 @@ impl MockGateway { client, auth_token: None, hostname: None, + stats_update_thread_handle: None, } } + // Add required authorization and hostname headers to gRPC requests fn add_request_metadata(&self, request: &mut Request) { // add authorization token if let Some(token) = &self.auth_token { @@ -75,6 +87,7 @@ impl MockGateway { }; } + // Fetch gateway config from core pub(crate) async fn get_gateway_config(&mut self) -> Result, Status> { let mut request = Request::new(ConfigurationRequest { name: self.hostname.clone(), @@ -94,14 +107,22 @@ impl MockGateway { self.client.updates(request).await.unwrap().into_inner() } + // Connect to interface stats update endpoint + // and return a tx which can be used to send stats updates to test gRPC server #[must_use] pub(crate) async fn setup_stats_update_stream(&mut self) -> UnboundedSender { let (tx, rx) = unbounded_channel(); - self.client - .stats(UnboundedReceiverStream::new(rx)) - .await - .unwrap(); + let mut request = Request::new(UnboundedReceiverStream::new(rx)); + + self.add_request_metadata(&mut request); + + let mut client = self.client.clone(); + let task_handle = tokio::spawn(async move { + client.stats(request).await.expect("stats stream closed"); + }); + + self.stats_update_thread_handle = Some(task_handle); tx } diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index ca248737ee..fbd4dab232 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -25,7 +25,7 @@ pub mod mock_gateway; pub struct TestGrpcServer { grpc_server_task_handle: JoinHandle<()>, - grpc_event_rx: UnboundedReceiver, + pub grpc_event_rx: UnboundedReceiver, // app_event_rx: UnboundedReceiver, wireguard_tx: Sender, // mail_rx: UnboundedReceiver, diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index 078050020d..5ccb0c623c 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -1,16 +1,31 @@ use std::time::Duration; -use defguard_core::db::{Id, WireguardNetwork, models::wireguard::LocationMfaMode, setup_pool}; +use chrono::Utc; +use claims::{assert_err_eq, assert_matches}; +use defguard_core::{ + db::{ + Device, Id, User, WireguardNetwork, + models::{device::DeviceType, wireguard::LocationMfaMode}, + setup_pool, + }, + events::GrpcEvent, + grpc::proto::gateway::{PeerStats, StatsUpdate, stats_update::Payload}, +}; use sqlx::{ PgPool, postgres::{PgConnectOptions, PgPoolOptions}, }; -use tokio::time::sleep; +use tokio::{ + sync::mpsc::error::TryRecvError, + time::{advance, pause, sleep}, +}; use tonic::Code; use crate::grpc::common::{TestGrpcServer, make_grpc_test_server, mock_gateway::MockGateway}; -async fn setup_test_server(pool: PgPool) -> (TestGrpcServer, MockGateway, WireguardNetwork) { +async fn setup_test_server( + pool: PgPool, +) -> (TestGrpcServer, MockGateway, WireguardNetwork, User) { let (test_server, client_stream) = make_grpc_test_server(&pool).await; // setup mock gateway @@ -43,13 +58,19 @@ async fn setup_test_server(pool: PgPool) -> (TestGrpcServer, MockGateway, Wiregu // set hostname for gateway gateway.set_hostname("test_gateway"); - (test_server, gateway, location) + // get test user + let test_user = User::find_by_username(&pool, "hpotter") + .await + .unwrap() + .unwrap(); + + (test_server, gateway, location, test_user) } #[sqlx::test] async fn test_gateway_authorization(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; - let (_test_server, mut gateway, test_location) = setup_test_server(pool).await; + let (_test_server, mut gateway, test_location, _test_user) = setup_test_server(pool).await; // remove auth token gateway.clear_token(); @@ -79,7 +100,7 @@ async fn test_gateway_authorization(_: PgPoolOptions, options: PgConnectOptions) #[sqlx::test] async fn test_gateway_hostname_is_required(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; - let (_test_server, mut gateway, _test_location) = setup_test_server(pool).await; + let (_test_server, mut gateway, _test_location, _test_user) = setup_test_server(pool).await; // remove hostname gateway.clear_hostname(); @@ -101,7 +122,7 @@ async fn test_gateway_hostname_is_required(_: PgPoolOptions, options: PgConnectO #[sqlx::test] async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; - let (test_server, mut gateway, test_location) = setup_test_server(pool).await; + let (test_server, mut gateway, test_location, _test_user) = setup_test_server(pool).await; // initial gateway map is empty { @@ -167,19 +188,75 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { // connected // disconnected -#[sqlx::test] +#[sqlx::test()] async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; - let (test_server, mut gateway, test_location) = setup_test_server(pool).await; + let (mut test_server, mut gateway, test_location, test_user) = + setup_test_server(pool.clone()).await; // connect stats stream let stats_tx = gateway.setup_stats_update_stream().await; - - // send stats update for unknown device - - // verify no gRPC event is emitted + let mut update_id = 1; // add user device + let device_pubkey = "wYOt6ImBaQ3BEMQ3Xf5P5fTnbqwOvjcqYkkSBt+1xOg="; + let test_device = Device::new( + "test device".into(), + device_pubkey.into(), + test_user.id, + DeviceType::User, + None, + true, + ) + .save(&pool) + .await + .unwrap(); - // send stats update for existing device and verify gRPC event is emitted + // send stats update for existing device with old handshake + // and verify no gRPC event is emitted + stats_tx + .send(StatsUpdate { + id: update_id, + payload: Some(Payload::PeerStats(PeerStats { + public_key: device_pubkey.into(), + endpoint: "1.2.3.4:1234".into(), + latest_handshake: 0, + ..Default::default() + })), + }) + .expect("failed to send stats update"); + + assert_err_eq!(test_server.grpc_event_rx.try_recv(), TryRecvError::Empty); + + // send stats update with current handshake + update_id += 1; + stats_tx + .send(StatsUpdate { + id: update_id, + payload: Some(Payload::PeerStats(PeerStats { + public_key: device_pubkey.into(), + endpoint: "1.2.3.4:1234".into(), + latest_handshake: Utc::now().timestamp() as u64, + ..Default::default() + })), + }) + .expect("failed to send stats update"); + + // wait for event to be emitted + sleep(Duration::from_secs(1)).await; + let grpc_event = test_server + .grpc_event_rx + .try_recv() + .expect("failed to receive gRPC event"); + + assert_matches!( + grpc_event, + GrpcEvent::ClientConnected { + context: _, + location, + device + } if ((location.id == test_location.id) & (device.id == test_device.id)) + ); } + +// TODO: figure out how to mock time to test disconnect events From d387e96301f678cad6d526de03cca67652e42426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 22 Aug 2025 17:14:19 +0200 Subject: [PATCH 10/16] test vpn disconnect --- crates/defguard/src/main.rs | 8 +- .../src/grpc/gateway/client_state.rs | 4 + crates/defguard_core/src/grpc/gateway/mod.rs | 5 +- crates/defguard_core/src/grpc/mod.rs | 15 ++- .../tests/integration/grpc/common/mod.rs | 17 ++- .../tests/integration/grpc/gateway.rs | 105 +++++++++++++++++- 6 files changed, 141 insertions(+), 13 deletions(-) diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 26c8904468..24ce53cecc 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -18,7 +18,10 @@ use defguard_core::{ limits::update_counts, }, events::{ApiEvent, BidiStreamEvent, GrpcEvent, InternalEvent}, - grpc::{GatewayMap, WorkerState, run_grpc_bidi_stream, run_grpc_server}, + grpc::{ + GatewayMap, WorkerState, gateway::client_state::ClientMap, run_grpc_bidi_stream, + run_grpc_server, + }, init_dev_env, init_vpn_location, mail::{Mail, run_mail_handler}, run_web_server, @@ -103,6 +106,7 @@ async fn main() -> Result<(), anyhow::Error> { let worker_state = Arc::new(Mutex::new(WorkerState::new(webhook_tx.clone()))); let gateway_state = Arc::new(Mutex::new(GatewayMap::new())); + let client_state = Arc::new(Mutex::new(ClientMap::new())); // initialize admin user User::init_admin_user(&pool, config.default_admin_password.expose_secret()).await?; @@ -145,7 +149,7 @@ async fn main() -> Result<(), anyhow::Error> { // run services tokio::select! { res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), bidi_event_tx), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:?}"), - res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx) => error!("gRPC server returned early: {res:?}"), + res = run_grpc_server(Arc::clone(&worker_state), pool.clone(), Arc::clone(&gateway_state), client_state, wireguard_tx.clone(), mail_tx.clone(), grpc_cert, grpc_key, failed_logins.clone(), grpc_event_tx) => error!("gRPC server returned early: {res:?}"), res = run_web_server(worker_state, gateway_state, webhook_tx, webhook_rx, wireguard_tx.clone(), mail_tx.clone(), pool.clone(), failed_logins, api_event_tx) => error!("Web server returned early: {res:?}"), res = run_mail_handler(mail_rx) => error!("Mail handler returned early: {res:?}"), res = run_periodic_peer_disconnect(pool.clone(), wireguard_tx.clone(), internal_event_tx.clone()) => error!("Periodic peer disconnect task returned early: {res:?}"), diff --git a/crates/defguard_core/src/grpc/gateway/client_state.rs b/crates/defguard_core/src/grpc/gateway/client_state.rs index a57ab38eab..c45f83bb27 100644 --- a/crates/defguard_core/src/grpc/gateway/client_state.rs +++ b/crates/defguard_core/src/grpc/gateway/client_state.rs @@ -195,4 +195,8 @@ impl ClientMap { Ok(disconnected_clients) } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index d955cef191..3c9ed7eab8 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -1,4 +1,4 @@ -mod client_state; +pub mod client_state; use std::{ net::{IpAddr, SocketAddr}, pin::Pin, @@ -133,6 +133,7 @@ impl GatewayServer { pub fn new( pool: PgPool, gateway_state: Arc>, + client_state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, grpc_event_tx: UnboundedSender, @@ -140,7 +141,7 @@ impl GatewayServer { Self { pool, gateway_state, - client_state: Arc::new(Mutex::new(ClientMap::new())), + client_state, wireguard_tx, mail_tx, grpc_event_tx, diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 0e3e9b1903..0bd864b3ae 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -65,6 +65,7 @@ use crate::{ ldap::utils::ldap_update_user_state, }, events::{BidiStreamEvent, GrpcEvent}, + grpc::gateway::client_state::ClientMap, handlers::mail::{send_gateway_disconnected_email, send_gateway_reconnected_email}, mail::Mail, server_config, @@ -74,7 +75,7 @@ mod auth; pub(crate) mod client_mfa; pub mod enrollment; #[cfg(feature = "wireguard")] -pub(crate) mod gateway; +pub mod gateway; #[cfg(any(feature = "wireguard", feature = "worker"))] mod interceptor; pub mod password_reset; @@ -861,6 +862,7 @@ pub async fn run_grpc_server( worker_state: Arc>, pool: PgPool, gateway_state: Arc>, + client_state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, grpc_cert: Option, @@ -881,6 +883,7 @@ pub async fn run_grpc_server( pool, worker_state, gateway_state, + client_state, wireguard_tx, mail_tx, failed_logins, @@ -907,6 +910,7 @@ pub async fn build_grpc_service_router( pool: PgPool, worker_state: Arc>, gateway_state: Arc>, + client_state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, failed_logins: Arc>, @@ -920,7 +924,14 @@ pub async fn build_grpc_service_router( ); #[cfg(feature = "wireguard")] let gateway_service = GatewayServiceServer::with_interceptor( - GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx, grpc_event_tx), + GatewayServer::new( + pool, + gateway_state, + client_state, + wireguard_tx, + mail_tx, + grpc_event_tx, + ), JwtInterceptor::new(ClaimsType::Gateway), ); diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index fbd4dab232..91bee7abea 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -5,7 +5,7 @@ use defguard_core::{ db::{AppEvent, GatewayEvent, models::settings::initialize_current_settings}, enterprise::license::{License, set_cached_license}, events::GrpcEvent, - grpc::{GatewayMap, WorkerState, build_grpc_service_router}, + grpc::{GatewayMap, WorkerState, build_grpc_service_router, gateway::client_state::ClientMap}, mail::Mail, }; use sqlx::PgPool; @@ -31,10 +31,12 @@ pub struct TestGrpcServer { // mail_rx: UnboundedReceiver, worker_state: Arc>, gateway_state: Arc>, + client_state: Arc>, failed_logins: Arc>, } impl TestGrpcServer { + #[must_use] pub async fn new( server_stream: DuplexStream, grpc_router: Router, @@ -42,6 +44,7 @@ impl TestGrpcServer { wireguard_tx: Sender, worker_state: Arc>, gateway_state: Arc>, + client_state: Arc>, failed_logins: Arc>, ) -> Self { // spawn test gRPC server @@ -59,15 +62,24 @@ impl TestGrpcServer { wireguard_tx, worker_state, gateway_state, + client_state, failed_logins, } } + #[must_use] pub fn get_gateway_map(&self) -> std::sync::MutexGuard<'_, GatewayMap> { self.gateway_state .lock() .expect("failed to acquire lock on gateway state") } + + #[must_use] + pub fn get_client_map(&self) -> std::sync::MutexGuard<'_, ClientMap> { + self.client_state + .lock() + .expect("failed to acquire lock on client state") + } } impl Drop for TestGrpcServer { @@ -88,6 +100,7 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup let (wg_tx, wg_rx) = broadcast::channel::(16); let (mail_tx, mail_rx) = unbounded_channel::(); let gateway_state = Arc::new(Mutex::new(GatewayMap::new())); + let client_state = Arc::new(Mutex::new(ClientMap::new())); let failed_logins = FailedLoginMap::new(); let failed_logins = Arc::new(Mutex::new(failed_logins)); @@ -114,6 +127,7 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup pool.clone(), worker_state.clone(), gateway_state.clone(), + client_state.clone(), wg_tx.clone(), mail_tx, failed_logins.clone(), @@ -129,6 +143,7 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup wg_tx, worker_state, gateway_state, + client_state, failed_logins, ) .await, diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index 5ccb0c623c..7ea679d817 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -1,11 +1,17 @@ -use std::time::Duration; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + time::Duration, +}; -use chrono::Utc; +use chrono::{Days, Utc}; use claims::{assert_err_eq, assert_matches}; use defguard_core::{ db::{ - Device, Id, User, WireguardNetwork, - models::{device::DeviceType, wireguard::LocationMfaMode}, + Device, Id, NoId, User, WireguardNetwork, + models::{ + device::DeviceType, wireguard::LocationMfaMode, + wireguard_peer_stats::WireguardPeerStats, + }, setup_pool, }, events::GrpcEvent, @@ -188,12 +194,18 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { // connected // disconnected -#[sqlx::test()] +#[sqlx::test] async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; let (mut test_server, mut gateway, test_location, test_user) = setup_test_server(pool.clone()).await; + // initial client map is empty + { + let client_map = test_server.get_client_map(); + assert!(client_map.is_empty()) + } + // connect stats stream let stats_tx = gateway.setup_stats_update_stream().await; let mut update_id = 1; @@ -259,4 +271,85 @@ async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) ); } -// TODO: figure out how to mock time to test disconnect events +#[sqlx::test] +async fn test_vpn_client_disconnected(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (mut test_server, mut gateway, test_location, test_user) = + setup_test_server(pool.clone()).await; + + // add user device + let device_pubkey = "wYOt6ImBaQ3BEMQ3Xf5P5fTnbqwOvjcqYkkSBt+1xOg="; + let test_device = Device::new( + "test device".into(), + device_pubkey.into(), + test_user.id, + DeviceType::User, + None, + true, + ) + .save(&pool) + .await + .unwrap(); + + // insert device into client map with an old handshake + { + let mut client_map = test_server.get_client_map(); + let now = Utc::now().naive_utc(); + let stats = WireguardPeerStats { + id: NoId, + device_id: test_device.id, + collected_at: now, + network: test_location.id, + endpoint: None, + upload: 0, + download: 0, + latest_handshake: now.checked_sub_days(Days::new(1)).unwrap(), + allowed_ips: None, + }; + client_map + .connect_vpn_client( + test_location.id, + &gateway.hostname(), + device_pubkey, + &test_device, + &test_user, + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), + &stats, + ) + .expect("failed to insert connected client"); + } + + // connect stats stream + let stats_tx = gateway.setup_stats_update_stream().await; + let mut update_id = 1; + + // send stats update with old handshake + update_id += 1; + stats_tx + .send(StatsUpdate { + id: update_id, + payload: Some(Payload::PeerStats(PeerStats { + public_key: device_pubkey.into(), + endpoint: "1.2.3.4:1234".into(), + latest_handshake: 0, + ..Default::default() + })), + }) + .expect("failed to send stats update"); + + // wait for event to be emitted + sleep(Duration::from_secs(1)).await; + let grpc_event = test_server + .grpc_event_rx + .try_recv() + .expect("failed to receive gRPC event"); + + assert_matches!( + grpc_event, + GrpcEvent::ClientDisconnected { + context: _, + location, + device + } if ((location.id == test_location.id) & (device.id == test_device.id)) + ); +} From 3798a63fbf1337eb45d15de969fee95727fd6ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 22 Aug 2025 17:15:18 +0200 Subject: [PATCH 11/16] remove debug lines --- crates/defguard_core/src/grpc/gateway/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 3c9ed7eab8..248d8eec28 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -763,7 +763,7 @@ impl gateway_service_server::GatewayService for GatewayServer { } }; - println!("Received stats message: {stats_update:?}"); + debug!("Received stats message: {stats_update:?}"); let Some(stats_update::Payload::PeerStats(peer_stats)) = stats_update.payload else { debug!("Received stats message is empty, skipping."); continue; @@ -783,7 +783,6 @@ impl gateway_service_server::GatewayService for GatewayServer { // convert stats to DB storage format let stats = WireguardPeerStats::from_peer_stats(peer_stats, network_id, device_id); - println!("received stats: {stats:?}"); // only perform client state update if stats include an endpoint IP // otherwise a peer was added to the gateway interface @@ -802,7 +801,6 @@ impl gateway_service_server::GatewayService for GatewayServer { let disconnected_clients = { // acquire lock on client state map let mut client_map = self.get_client_state_guard()?; - println!("client map: {client_map:?}"); // update connected clients map match client_map.get_vpn_client(network_id, &public_key) { From 2bb0ea90e724fb714f71ac4fb11f32a02f00aa3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 25 Aug 2025 10:53:34 +0200 Subject: [PATCH 12/16] test gateway updates routing --- crates/defguard_core/src/grpc/gateway/mod.rs | 2 +- .../integration/grpc/common/mock_gateway.rs | 61 ++++----- .../tests/integration/grpc/common/mod.rs | 63 ++++++--- .../tests/integration/grpc/gateway.rs | 125 ++++++++++++++++-- 4 files changed, 187 insertions(+), 64 deletions(-) diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index 248d8eec28..980f41d44a 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -987,7 +987,7 @@ impl gateway_service_server::GatewayService for GatewayServer { error!("Failed to connect gateway on network {gateway_network_id}: {err}"); Status::new( Code::Internal, - "Failed to connect gateway on network {gateway_network_id}", + format!("Failed to connect gateway on network {gateway_network_id}"), ) })?; diff --git a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs index c157dfeab9..3414571036 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mock_gateway.rs @@ -1,29 +1,23 @@ -use std::collections::VecDeque; +use std::time::Duration; -use axum::http::Uri; use defguard_core::grpc::proto::gateway::{ Configuration, ConfigurationRequest, StatsUpdate, Update, gateway_service_client::GatewayServiceClient, }; -use hyper_util::rt::TokioIo; use tokio::{ - io::DuplexStream, sync::mpsc::{UnboundedSender, unbounded_channel}, task::JoinHandle, + time::timeout, }; use tokio_stream::wrappers::UnboundedReceiverStream; -use tonic::{ - Request, Response, Status, Streaming, - metadata::MetadataValue, - transport::{Channel, Endpoint}, -}; -use tower::service_fn; +use tonic::{Request, Response, Status, Streaming, metadata::MetadataValue, transport::Channel}; pub(crate) struct MockGateway { client: GatewayServiceClient, auth_token: Option, hostname: Option, stats_update_thread_handle: Option>, + updates_stream: Option>, } impl Drop for MockGateway { @@ -36,34 +30,15 @@ impl Drop for MockGateway { impl MockGateway { #[must_use] - pub(crate) async fn new(client_stream: DuplexStream) -> Self { - // Move client to an option so we can _move_ the inner value - // on the first attempt to connect. All other attempts will fail. - // reference: https://github.com/hyperium/tonic/blob/master/examples/src/mock/mock.rs#L31 - let mut client = Some(client_stream); - let channel = Endpoint::try_from("http://[::]:50051") - .expect("Failed to create channel") - .connect_with_connector(service_fn(move |_: Uri| { - let client = client.take(); - - async move { - if let Some(client) = client { - Ok(TokioIo::new(client)) - } else { - Err(std::io::Error::other("Client already taken")) - } - } - })) - .await - .expect("Failed to create client channel"); - - let client = GatewayServiceClient::new(channel); + pub(crate) async fn new(client_channel: Channel) -> Self { + let client = GatewayServiceClient::new(client_channel); Self { client, auth_token: None, hostname: None, stats_update_thread_handle: None, + updates_stream: None, } } @@ -98,13 +73,29 @@ impl MockGateway { self.client.config(request).await } - #[must_use] - pub(crate) async fn connect_to_updates_stream(&mut self) -> Streaming { + pub(crate) async fn connect_to_updates_stream(&mut self) { let mut request = Request::new(()); self.add_request_metadata(&mut request); - self.client.updates(request).await.unwrap().into_inner() + let updates_stream = self.client.updates(request).await.unwrap().into_inner(); + + self.updates_stream = Some(updates_stream); + } + + pub(crate) fn disconnect_from_updates_stream(&mut self) { + self.updates_stream = None; + } + + #[must_use] + pub(crate) async fn receive_next_update(&mut self) -> Option { + match &mut self.updates_stream { + Some(stream) => match timeout(Duration::from_millis(100), stream.message()).await { + Ok(result) => result.expect("failed to reveive update message"), + Err(_) => None, + }, + None => None, + } } // Connect to interface stats update endpoint diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index 91bee7abea..1b2d54d5f1 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, Mutex}; +use axum::http::Uri; use defguard_core::{ auth::failed_login::FailedLoginMap, db::{AppEvent, GatewayEvent, models::settings::initialize_current_settings}, @@ -8,6 +9,7 @@ use defguard_core::{ grpc::{GatewayMap, WorkerState, build_grpc_service_router, gateway::client_state::ClientMap}, mail::Mail, }; +use hyper_util::rt::TokioIo; use sqlx::PgPool; use tokio::{ io::DuplexStream, @@ -17,7 +19,8 @@ use tokio::{ }, task::JoinHandle, }; -use tonic::transport::{Server, server::Router}; +use tonic::transport::{Channel, Endpoint, Server, server::Router}; +use tower::service_fn; use crate::common::{init_config, initialize_users}; @@ -33,6 +36,7 @@ pub struct TestGrpcServer { gateway_state: Arc>, client_state: Arc>, failed_logins: Arc>, + pub client_channel: Channel, } impl TestGrpcServer { @@ -46,6 +50,7 @@ impl TestGrpcServer { gateway_state: Arc>, client_state: Arc>, failed_logins: Arc>, + client_channel: Channel, ) -> Self { // spawn test gRPC server let grpc_server_task_handle = tokio::spawn(async move { @@ -64,6 +69,7 @@ impl TestGrpcServer { gateway_state, client_state, failed_logins, + client_channel, } } @@ -80,6 +86,12 @@ impl TestGrpcServer { .lock() .expect("failed to acquire lock on client state") } + + pub fn send_wireguard_event(&self, event: GatewayEvent) { + self.wireguard_tx + .send(event) + .expect("failed to send gateway event"); + } } impl Drop for TestGrpcServer { @@ -89,9 +101,32 @@ impl Drop for TestGrpcServer { } } -pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, DuplexStream) { +pub(crate) async fn create_client_channel(client_stream: DuplexStream) -> Channel { + // Move client to an option so we can _move_ the inner value + // on the first attempt to connect. All other attempts will fail. + // reference: https://github.com/hyperium/tonic/blob/master/examples/src/mock/mock.rs#L31 + let mut client = Some(client_stream); + Endpoint::try_from("http://[::]:50051") + .expect("Failed to create channel") + .connect_with_connector(service_fn(move |_: Uri| { + let client = client.take(); + + async move { + if let Some(client) = client { + Ok(TokioIo::new(client)) + } else { + Err(std::io::Error::other("Client already taken")) + } + } + })) + .await + .expect("Failed to create client channel") +} + +pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> TestGrpcServer { // create communication channel for clients let (client_stream, server_stream) = tokio::io::duplex(1024); + let client_channel = create_client_channel(client_stream).await; // setup helper structs let (grpc_event_tx, grpc_event_rx) = unbounded_channel::(); @@ -135,18 +170,16 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> (TestGrpcServer, Dup ) .await; - ( - TestGrpcServer::new( - server_stream, - grpc_router, - grpc_event_rx, - wg_tx, - worker_state, - gateway_state, - client_state, - failed_logins, - ) - .await, - client_stream, + TestGrpcServer::new( + server_stream, + grpc_router, + grpc_event_rx, + wg_tx, + worker_state, + gateway_state, + client_state, + failed_logins, + client_channel, ) + .await } diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index 7ea679d817..49e7a1ea01 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -15,7 +15,10 @@ use defguard_core::{ setup_pool, }, events::GrpcEvent, - grpc::proto::gateway::{PeerStats, StatsUpdate, stats_update::Payload}, + grpc::{ + gateway::{Configuration, Update, update}, + proto::gateway::{PeerStats, StatsUpdate, stats_update::Payload}, + }, }; use sqlx::{ PgPool, @@ -25,17 +28,20 @@ use tokio::{ sync::mpsc::error::TryRecvError, time::{advance, pause, sleep}, }; +use tokio_stream::StreamExt; use tonic::Code; -use crate::grpc::common::{TestGrpcServer, make_grpc_test_server, mock_gateway::MockGateway}; +use crate::grpc::common::{ + TestGrpcServer, create_client_channel, make_grpc_test_server, mock_gateway::MockGateway, +}; async fn setup_test_server( pool: PgPool, ) -> (TestGrpcServer, MockGateway, WireguardNetwork, User) { - let (test_server, client_stream) = make_grpc_test_server(&pool).await; + let test_server = make_grpc_test_server(&pool).await; // setup mock gateway - let mut gateway = MockGateway::new(client_stream).await; + let mut gateway = MockGateway::new(test_server.client_channel.clone()).await; // create a test location let location = WireguardNetwork::new( @@ -153,7 +159,7 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { // gateway connects to updates stream // it should be marked as connected - let updates_stream = gateway.connect_to_updates_stream().await; + gateway.connect_to_updates_stream().await; { let gateway_map = test_server.get_gateway_map(); let location_gateways = gateway_map.get_network_gateway_status(test_location.id); @@ -167,9 +173,9 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { // gateway disconnect from updates stream // it should be marked as disconnected - drop(updates_stream); + gateway.disconnect_from_updates_stream(); // wait for the background thread to handle the disconnect - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_millis(100)).await; { let gateway_map = test_server.get_gateway_map(); @@ -190,10 +196,6 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { // filtering by id // sent to multiple gateways -// test client status -// connected -// disconnected - #[sqlx::test] async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; @@ -255,7 +257,7 @@ async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) .expect("failed to send stats update"); // wait for event to be emitted - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_millis(100)).await; let grpc_event = test_server .grpc_event_rx .try_recv() @@ -338,7 +340,7 @@ async fn test_vpn_client_disconnected(_: PgPoolOptions, options: PgConnectOption .expect("failed to send stats update"); // wait for event to be emitted - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_millis(100)).await; let grpc_event = test_server .grpc_event_rx .try_recv() @@ -353,3 +355,100 @@ async fn test_vpn_client_disconnected(_: PgPoolOptions, options: PgConnectOption } if ((location.id == test_location.id) & (device.id == test_device.id)) ); } + +#[sqlx::test] +async fn test_gateway_update_routing(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (mut test_server, mut gateway_1, test_location, test_user) = + setup_test_server(pool.clone()).await; + + // setup another test location & gateway + let test_location_2 = WireguardNetwork::new( + "test location 2".to_string(), + Vec::new(), + 1000, + "endpoint2".to_string(), + None, + Vec::new(), + 100, + 100, + false, + false, + LocationMfaMode::Disabled, + ) + .save(&pool) + .await + .unwrap(); + let mut gateway_2 = MockGateway::new(test_server.client_channel.clone()).await; + + // set auth token for gateway + let token = test_location_2 + .generate_gateway_token() + .expect("failed to generate gateway token"); + gateway_2.set_token(&token); + + // set hostname for gateway + gateway_2.set_hostname("test_gateway_2"); + + // register gateways with core + let _config_1 = gateway_1.get_gateway_config().await; + let _config_2 = gateway_2.get_gateway_config().await; + + // connect gateways to the updates stream + gateway_1.connect_to_updates_stream().await; + gateway_2.connect_to_updates_stream().await; + + // send update for location 1 + test_server.send_wireguard_event(defguard_core::db::GatewayEvent::NetworkDeleted( + test_location.id, + "network name".into(), + )); + + // only one gateway should receive this update + assert!(gateway_2.receive_next_update().await.is_none()); + let update = gateway_1.receive_next_update().await.unwrap(); + let expected_update = Update { + update_type: 2, + update: Some(update::Update::Network(Configuration { + name: "network name".into(), + prvkey: String::new(), + addresses: Vec::new(), + port: 0, + peers: Vec::new(), + firewall_config: None, + })), + }; + assert_eq!(update, expected_update); + + // send update for location 2 + test_server.send_wireguard_event(defguard_core::db::GatewayEvent::NetworkDeleted( + test_location_2.id, + "network name 2".into(), + )); + + // only one gateway should receive this update + assert!(gateway_1.receive_next_update().await.is_none()); + let update = gateway_2.receive_next_update().await.unwrap(); + let expected_update = Update { + update_type: 2, + update: Some(update::Update::Network(Configuration { + name: "network name 2".into(), + prvkey: String::new(), + addresses: Vec::new(), + port: 0, + peers: Vec::new(), + firewall_config: None, + })), + }; + assert_eq!(update, expected_update); + + // send update for location which does not exist + test_server.send_wireguard_event(defguard_core::db::GatewayEvent::NetworkDeleted( + 1234, + "does not exist".into(), + )); + + // no gateway should receive this update + assert!(gateway_1.receive_next_update().await.is_none()); + assert!(gateway_2.receive_next_update().await.is_none()); +} From 3b7857d6b4bb4a18b9422e7e344f13f66982982a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 25 Aug 2025 11:03:01 +0200 Subject: [PATCH 13/16] test gateway acl config --- .../tests/integration/grpc/gateway.rs | 64 ++++++++++++++++--- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index 49e7a1ea01..ee0f87ad7e 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -14,10 +14,14 @@ use defguard_core::{ }, setup_pool, }, + enterprise::{license::set_cached_license, limits::update_counts}, events::GrpcEvent, grpc::{ gateway::{Configuration, Update, update}, - proto::gateway::{PeerStats, StatsUpdate, stats_update::Payload}, + proto::{ + enterprise::firewall::FirewallPolicy, + gateway::{PeerStats, StatsUpdate, stats_update::Payload}, + }, }, }; use sqlx::{ @@ -189,13 +193,6 @@ async fn test_gateway_status(_: PgPoolOptions, options: PgConnectOptions) { } } -// test correct config is sent to gw -// firewall rules are included - -// test updates stream -// filtering by id -// sent to multiple gateways - #[sqlx::test] async fn test_vpn_client_connected(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; @@ -452,3 +449,54 @@ async fn test_gateway_update_routing(_: PgPoolOptions, options: PgConnectOptions assert!(gateway_1.receive_next_update().await.is_none()); assert!(gateway_2.receive_next_update().await.is_none()); } + +#[sqlx::test] +async fn test_gateway_config(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + let (_test_server, mut gateway, mut test_location, _test_user) = + setup_test_server(pool.clone()).await; + + // get gateway config + let config = gateway.get_gateway_config().await.unwrap().into_inner(); + + assert_eq!(config.name, test_location.name); + assert!(config.firewall_config.is_none()); + + // enable ACL for test location + test_location.acl_enabled = true; + test_location + .save(&pool) + .await + .expect("failed to update location"); + + // get gateway config + let config = gateway.get_gateway_config().await.unwrap().into_inner(); + assert!(config.firewall_config.is_some()); + assert_eq!( + config.firewall_config.unwrap().default_policy == i32::from(FirewallPolicy::Allow), + test_location.acl_default_allow + ); + + // unset the license and create another location to exceed limits and disable enterprise features + set_cached_license(None); + let _test_location_2 = WireguardNetwork::new( + "test location 2".to_string(), + Vec::new(), + 1000, + "endpoint2".to_string(), + None, + Vec::new(), + 100, + 100, + false, + false, + LocationMfaMode::Disabled, + ) + .save(&pool) + .await + .unwrap(); + update_counts(&pool).await.unwrap(); + + let config = gateway.get_gateway_config().await.unwrap().into_inner(); + assert!(config.firewall_config.is_none()); +} From ca1a167396bab5c3e374035222c778ee5c53374c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 25 Aug 2025 11:18:21 +0200 Subject: [PATCH 14/16] lint fix --- .../defguard_core/src/db/models/wireguard.rs | 1 - .../src/grpc/gateway/client_state.rs | 2 +- .../defguard_core/src/handlers/wireguard.rs | 2 +- .../tests/integration/api/common/mod.rs | 9 ++----- .../tests/integration/grpc/common/mod.rs | 26 +++++-------------- .../tests/integration/grpc/gateway.rs | 12 +++------ 6 files changed, 14 insertions(+), 38 deletions(-) diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index bf7796c583..1996fcdd69 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -1308,7 +1308,6 @@ impl WireguardNetwork { } /// Generates auth token for a VPN gateway - #[must_use] pub fn generate_gateway_token(&self) -> Result { let location_id = self.id; diff --git a/crates/defguard_core/src/grpc/gateway/client_state.rs b/crates/defguard_core/src/grpc/gateway/client_state.rs index c45f83bb27..8eb1e600f4 100644 --- a/crates/defguard_core/src/grpc/gateway/client_state.rs +++ b/crates/defguard_core/src/grpc/gateway/client_state.rs @@ -84,7 +84,7 @@ impl ClientState { /// Helper struct used to handle connected VPN clients state /// Clients are grouped by location ID type ClientPubKey = String; -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Default, Serialize, Clone)] pub struct ClientMap(HashMap>); impl ClientMap { diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index a035d5aeb5..929bd88f05 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -21,7 +21,7 @@ use super::{ApiResponse, ApiResult, WebError, device_for_admin_or_self, user_for use crate::{ AsCsv, appstate::AppState, - auth::{AdminRole, Claims, ClaimsType, SessionInfo}, + auth::{AdminRole, SessionInfo}, db::{ AddDevice, Device, GatewayEvent, Id, WireguardNetwork, models::{ diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 9f8dafdfec..9a2b66c0c2 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -1,12 +1,8 @@ pub(crate) mod client; -use std::{ - str::FromStr, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use defguard_core::{ - SERVER_CONFIG, auth::failed_login::FailedLoginMap, build_webapp, config::DefGuardConfig, @@ -20,8 +16,7 @@ use defguard_core::{ handlers::Auth, mail::Mail, }; -use reqwest::{StatusCode, Url, header::HeaderName}; -use secrecy::ExposeSecret; +use reqwest::{StatusCode, header::HeaderName}; use serde::de::DeserializeOwned; use serde_json::{Value, json}; use sqlx::PgPool; diff --git a/crates/defguard_core/tests/integration/grpc/common/mod.rs b/crates/defguard_core/tests/integration/grpc/common/mod.rs index 1b2d54d5f1..bc764aa626 100644 --- a/crates/defguard_core/tests/integration/grpc/common/mod.rs +++ b/crates/defguard_core/tests/integration/grpc/common/mod.rs @@ -29,13 +29,9 @@ pub mod mock_gateway; pub struct TestGrpcServer { grpc_server_task_handle: JoinHandle<()>, pub grpc_event_rx: UnboundedReceiver, - // app_event_rx: UnboundedReceiver, wireguard_tx: Sender, - // mail_rx: UnboundedReceiver, - worker_state: Arc>, gateway_state: Arc>, client_state: Arc>, - failed_logins: Arc>, pub client_channel: Channel, } @@ -46,10 +42,8 @@ impl TestGrpcServer { grpc_router: Router, grpc_event_rx: UnboundedReceiver, wireguard_tx: Sender, - worker_state: Arc>, gateway_state: Arc>, client_state: Arc>, - failed_logins: Arc>, client_channel: Channel, ) -> Self { // spawn test gRPC server @@ -65,22 +59,18 @@ impl TestGrpcServer { grpc_server_task_handle, grpc_event_rx, wireguard_tx, - worker_state, gateway_state, client_state, - failed_logins, client_channel, } } - #[must_use] pub fn get_gateway_map(&self) -> std::sync::MutexGuard<'_, GatewayMap> { self.gateway_state .lock() .expect("failed to acquire lock on gateway state") } - #[must_use] pub fn get_client_map(&self) -> std::sync::MutexGuard<'_, ClientMap> { self.client_state .lock() @@ -130,10 +120,10 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> TestGrpcServer { // setup helper structs let (grpc_event_tx, grpc_event_rx) = unbounded_channel::(); - let (app_event_tx, app_event_rx) = unbounded_channel::(); + let (app_event_tx, _app_event_rx) = unbounded_channel::(); let worker_state = Arc::new(Mutex::new(WorkerState::new(app_event_tx.clone()))); - let (wg_tx, wg_rx) = broadcast::channel::(16); - let (mail_tx, mail_rx) = unbounded_channel::(); + let (wg_tx, _wg_rx) = broadcast::channel::(16); + let (mail_tx, _mail_rx) = unbounded_channel::(); let gateway_state = Arc::new(Mutex::new(GatewayMap::new())); let client_state = Arc::new(Mutex::new(ClientMap::new())); @@ -141,8 +131,8 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> TestGrpcServer { let failed_logins = Arc::new(Mutex::new(failed_logins)); let config = init_config(None); - initialize_users(&pool, &config).await; - initialize_current_settings(&pool) + initialize_users(pool, &config).await; + initialize_current_settings(pool) .await .expect("Could not initialize settings"); @@ -160,12 +150,12 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> TestGrpcServer { let grpc_router = build_grpc_service_router( server, pool.clone(), - worker_state.clone(), + worker_state, gateway_state.clone(), client_state.clone(), wg_tx.clone(), mail_tx, - failed_logins.clone(), + failed_logins, grpc_event_tx, ) .await; @@ -175,10 +165,8 @@ pub(crate) async fn make_grpc_test_server(pool: &PgPool) -> TestGrpcServer { grpc_router, grpc_event_rx, wg_tx, - worker_state, gateway_state, client_state, - failed_logins, client_channel, ) .await diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index ee0f87ad7e..b6d4593549 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -28,16 +28,10 @@ use sqlx::{ PgPool, postgres::{PgConnectOptions, PgPoolOptions}, }; -use tokio::{ - sync::mpsc::error::TryRecvError, - time::{advance, pause, sleep}, -}; -use tokio_stream::StreamExt; +use tokio::{sync::mpsc::error::TryRecvError, time::sleep}; use tonic::Code; -use crate::grpc::common::{ - TestGrpcServer, create_client_channel, make_grpc_test_server, mock_gateway::MockGateway, -}; +use crate::grpc::common::{TestGrpcServer, make_grpc_test_server, mock_gateway::MockGateway}; async fn setup_test_server( pool: PgPool, @@ -356,7 +350,7 @@ async fn test_vpn_client_disconnected(_: PgPoolOptions, options: PgConnectOption #[sqlx::test] async fn test_gateway_update_routing(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; - let (mut test_server, mut gateway_1, test_location, test_user) = + let (test_server, mut gateway_1, test_location, _test_user) = setup_test_server(pool.clone()).await; // setup another test location & gateway From fd25326801fb024fa9aee9de8273d2de693ec12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 25 Aug 2025 12:50:17 +0200 Subject: [PATCH 15/16] update dependencies --- Cargo.lock | 106 ++++++++++++++++++++++----------------------- flake.lock | 14 +++--- web/package.json | 2 +- web/pnpm-lock.yaml | 11 +++-- 4 files changed, 66 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c08bc1614..9f6109695d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,9 +442,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" dependencies = [ "serde", ] @@ -1770,7 +1770,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", "libgit2-sys", "log", @@ -1786,8 +1786,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -1796,7 +1796,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "ignore", "walkdir", ] @@ -1824,7 +1824,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap 2.11.0", "slab", "tokio", "tokio-util", @@ -2265,7 +2265,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.10", "same-file", "walkdir", "winapi-util", @@ -2284,9 +2284,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown 0.15.5", @@ -2304,11 +2304,11 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "libc", ] @@ -2376,9 +2376,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom 0.3.3", "libc", @@ -2550,7 +2550,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "libc", "redox_syscall", ] @@ -3012,7 +3012,7 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "cfg-if", "foreign-types", "libc", @@ -3283,7 +3283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.11.0", ] [[package]] @@ -3456,7 +3456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64 0.22.1", - "indexmap 2.10.0", + "indexmap 2.11.0", "quick-xml", "serde", "time", @@ -3620,7 +3620,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "getopts", "memchr", "pulldown-cmark-escape", @@ -3644,9 +3644,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.2" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d200a41a7797e6461bd04e4e95c3347053a731c32c87f066f2f0dda22dbdbba8" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" dependencies = [ "memchr", ] @@ -3792,7 +3792,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] @@ -3817,14 +3817,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", ] [[package]] @@ -3838,13 +3838,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.6", ] [[package]] @@ -3855,9 +3855,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" @@ -4045,7 +4045,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "errno", "libc", "linux-raw-sys", @@ -4190,7 +4190,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -4203,7 +4203,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4282,7 +4282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" dependencies = [ "form_urlencoded", - "indexmap 2.10.0", + "indexmap 2.11.0", "itoa", "ryu", "serde", @@ -4352,7 +4352,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.11.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -4380,7 +4380,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "itoa", "ryu", "serde", @@ -4599,7 +4599,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap 2.10.0", + "indexmap 2.11.0", "ipnetwork", "log", "memchr", @@ -4664,7 +4664,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.2", + "bitflags 2.9.3", "byteorder", "bytes", "chrono", @@ -4708,7 +4708,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.2", + "bitflags 2.9.3", "byteorder", "chrono", "crc", @@ -4927,7 +4927,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5180,7 +5180,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "toml_datetime", "winnow", ] @@ -5289,7 +5289,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.10.0", + "indexmap 2.11.0", "pin-project-lite", "slab", "sync_wrapper", @@ -5306,7 +5306,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", "bytes", "futures-core", "futures-util", @@ -5579,9 +5579,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.6" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137a3c834eaf7139b73688502f3f1141a0337c5d8e4d9b536f9b8c796e26a7c4" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -5607,7 +5607,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.11.0", "serde", "serde_json", "utoipa-gen", @@ -6309,9 +6309,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -6322,7 +6322,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.9.3", ] [[package]] @@ -6513,7 +6513,7 @@ dependencies = [ "flate2", "getrandom 0.3.3", "hmac", - "indexmap 2.10.0", + "indexmap 2.11.0", "lzma-rs", "memchr", "pbkdf2", @@ -6535,7 +6535,7 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.10.0", + "indexmap 2.11.0", "memchr", "zopfli", ] diff --git a/flake.lock b/flake.lock index 9047362a46..c5eade4b1d 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755186698, - "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", + "lastModified": 1755615617, + "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", + "rev": "20075955deac2583bb12f07151c2df830ef346b4", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1755571033, - "narHash": "sha256-V8gmZBfMiFGCyGJQx/yO81LFJ4d/I5Jxs2id96rLxrM=", + "lastModified": 1756089517, + "narHash": "sha256-KGinVKturJFPrRebgvyUB1BUNqf1y9FN+tSJaTPlnFE=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "95487740bb7ac11553445e9249041a6fa4b5eccf", + "rev": "44774c8c83cd392c50914f86e1ff75ef8619f1cd", "type": "github" }, "original": { @@ -79,4 +79,4 @@ }, "root": "root", "version": 7 -} \ No newline at end of file +} diff --git a/web/package.json b/web/package.json index 83e7009e4e..dd956aa6b6 100644 --- a/web/package.json +++ b/web/package.json @@ -131,7 +131,7 @@ "@types/react-window": "^1.8.8", "@vitejs/plugin-react-swc": "^4.0.1", "autoprefixer": "^10.4.21", - "concurrently": "^9.2.0", + "concurrently": "^9.2.1", "dotenv": "^17.2.1", "esbuild": "^0.25.9", "globals": "^16.3.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f5ca628583..9dd237555b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -268,8 +268,8 @@ importers: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) concurrently: - specifier: ^9.2.0 - version: 9.2.0 + specifier: ^9.2.1 + version: 9.2.1 dotenv: specifier: ^17.2.1 version: 17.2.1 @@ -1295,8 +1295,8 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} - concurrently@9.2.0: - resolution: {integrity: sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==} + concurrently@9.2.1: + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} engines: {node: '>=18'} hasBin: true @@ -3983,10 +3983,9 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - concurrently@9.2.0: + concurrently@9.2.1: dependencies: chalk: 4.1.2 - lodash: 4.17.21 rxjs: 7.8.2 shell-quote: 1.8.3 supports-color: 8.1.1 From a0ef76896ad286eaf124e57a461c45dd623adcda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 26 Aug 2025 09:38:26 +0200 Subject: [PATCH 16/16] review fixes --- .../tests/integration/api/common/mod.rs | 28 ------------------- .../tests/integration/grpc/gateway.rs | 4 +-- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 09f54a01f9..8da63d186d 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -40,34 +40,6 @@ pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for #[allow(clippy::declare_interior_mutable_const)] pub const X_FORWARDED_URI: HeaderName = HeaderName::from_static("x-forwarded-uri"); -// /// Allows overriding the default DefGuard URL for tests, as during the tests, the server has a random port, making the URL unpredictable beforehand. -// // TODO: Allow customizing the whole config, not just the URL -// pub(crate) fn init_config(custom_defguard_url: Option<&str>) -> DefGuardConfig { -// let url = custom_defguard_url.unwrap_or("http://localhost:8000"); -// let mut config = DefGuardConfig::new_test_config(); -// config.url = Url::from_str(url).unwrap(); -// let _ = SERVER_CONFIG.set(config.clone()); -// config -// } - -// pub(crate) async fn initialize_users(pool: &PgPool, config: &DefGuardConfig) { -// User::init_admin_user(pool, config.default_admin_password.expose_secret()) -// .await -// .unwrap(); - -// User::new( -// "hpotter", -// Some("pass123"), -// "Potter", -// "Harry", -// "h.potter@hogwart.edu.uk", -// None, -// ) -// .save(pool) -// .await -// .unwrap(); -// } - pub(crate) struct ClientState { pub pool: PgPool, pub worker_state: Arc>, diff --git a/crates/defguard_core/tests/integration/grpc/gateway.rs b/crates/defguard_core/tests/integration/grpc/gateway.rs index b6d4593549..e174c968be 100644 --- a/crates/defguard_core/tests/integration/grpc/gateway.rs +++ b/crates/defguard_core/tests/integration/grpc/gateway.rs @@ -88,7 +88,7 @@ async fn test_gateway_authorization(_: PgPoolOptions, options: PgConnectOptions) // make a request without auth token let response = gateway.get_gateway_config().await; - // check that response code is Status::Unauthenticated + // check that response code is `Code::Unauthenticated` assert!(response.is_err()); let status = response.err().unwrap(); assert_eq!(status.code(), Code::Unauthenticated); @@ -118,7 +118,7 @@ async fn test_gateway_hostname_is_required(_: PgPoolOptions, options: PgConnectO // make a request without hostname let response = gateway.get_gateway_config().await; - // check that response code is Status::Unauthenticated + // check that response code is `Code::Internal` assert!(response.is_err()); let status = response.err().unwrap(); assert_eq!(status.code(), Code::Internal);