From 03894c268cda14d260e318485681dea3753e9fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Tue, 5 Aug 2025 07:59:17 +0200 Subject: [PATCH 01/84] biometric mfa poc (#1368) * init model * handle register mobile auth request * add biometric mfa flow * fix location mfa compatibility check for biometrc method * review changes * Potential fix for code scanning alert no. 34: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * sqlx --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- ...f6c57a40cd9f4d57e192f7cb9e544f8831e8b.json | 14 +++ ...04d1d499ee1defd1557b08ec0f48d9bbd1ac3.json | 34 +++++ ...1f733da54b9660e287cbf44534f7793bf2db6.json | 32 +++++ ...7eb60ad39c2e52f23525bb9a1d656bd45ac2a.json | 34 +++++ ...910766a23aaefc6ba173b1327145fc5280716.json | 23 ++++ ...d50c63d1fbcf5aedc1448ab445bf223f6c612.json | 16 +++ Cargo.lock | 1 + crates/defguard_core/Cargo.toml | 1 + .../src/db/models/biometric_auth.rs | 119 ++++++++++++++++++ crates/defguard_core/src/db/models/mod.rs | 1 + crates/defguard_core/src/db/models/user.rs | 1 + .../src/enterprise/grpc/desktop_client_mfa.rs | 4 +- crates/defguard_core/src/events.rs | 1 + .../{desktop_client_mfa.rs => client_mfa.rs} | 87 ++++++++++++- crates/defguard_core/src/grpc/enrollment.rs | 43 ++++++- crates/defguard_core/src/grpc/mod.rs | 14 ++- .../20250731063659_biometric_auth.down.sql | 2 + .../20250731063659_biometric_auth.up.sql | 7 ++ proto | 2 +- 19 files changed, 427 insertions(+), 9 deletions(-) create mode 100644 .sqlx/query-1817d2513210c2b128336ecf4fff6c57a40cd9f4d57e192f7cb9e544f8831e8b.json create mode 100644 .sqlx/query-41d50b33737847c6a7639125b3d04d1d499ee1defd1557b08ec0f48d9bbd1ac3.json create mode 100644 .sqlx/query-68009569c58b64947a3b3ce7fbc1f733da54b9660e287cbf44534f7793bf2db6.json create mode 100644 .sqlx/query-858c7323ca15f68c7c22c40987d7eb60ad39c2e52f23525bb9a1d656bd45ac2a.json create mode 100644 .sqlx/query-9cfebbfe0253249ee1b2d65ff6a910766a23aaefc6ba173b1327145fc5280716.json create mode 100644 .sqlx/query-c398c1d38f7343ddf026d8fca19d50c63d1fbcf5aedc1448ab445bf223f6c612.json create mode 100644 crates/defguard_core/src/db/models/biometric_auth.rs rename crates/defguard_core/src/grpc/{desktop_client_mfa.rs => client_mfa.rs} (83%) create mode 100644 migrations/20250731063659_biometric_auth.down.sql create mode 100644 migrations/20250731063659_biometric_auth.up.sql diff --git a/.sqlx/query-1817d2513210c2b128336ecf4fff6c57a40cd9f4d57e192f7cb9e544f8831e8b.json b/.sqlx/query-1817d2513210c2b128336ecf4fff6c57a40cd9f4d57e192f7cb9e544f8831e8b.json new file mode 100644 index 0000000000..3871130f44 --- /dev/null +++ b/.sqlx/query-1817d2513210c2b128336ecf4fff6c57a40cd9f4d57e192f7cb9e544f8831e8b.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM \"biometric_auth\" WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "1817d2513210c2b128336ecf4fff6c57a40cd9f4d57e192f7cb9e544f8831e8b" +} diff --git a/.sqlx/query-41d50b33737847c6a7639125b3d04d1d499ee1defd1557b08ec0f48d9bbd1ac3.json b/.sqlx/query-41d50b33737847c6a7639125b3d04d1d499ee1defd1557b08ec0f48d9bbd1ac3.json new file mode 100644 index 0000000000..51ac03b6f6 --- /dev/null +++ b/.sqlx/query-41d50b33737847c6a7639125b3d04d1d499ee1defd1557b08ec0f48d9bbd1ac3.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, pub_key, device_id FROM biometric_auth WHERE device_id=$1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "pub_key", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "41d50b33737847c6a7639125b3d04d1d499ee1defd1557b08ec0f48d9bbd1ac3" +} diff --git a/.sqlx/query-68009569c58b64947a3b3ce7fbc1f733da54b9660e287cbf44534f7793bf2db6.json b/.sqlx/query-68009569c58b64947a3b3ce7fbc1f733da54b9660e287cbf44534f7793bf2db6.json new file mode 100644 index 0000000000..577736725f --- /dev/null +++ b/.sqlx/query-68009569c58b64947a3b3ce7fbc1f733da54b9660e287cbf44534f7793bf2db6.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"pub_key\",\"device_id\" FROM \"biometric_auth\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "pub_key", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "68009569c58b64947a3b3ce7fbc1f733da54b9660e287cbf44534f7793bf2db6" +} diff --git a/.sqlx/query-858c7323ca15f68c7c22c40987d7eb60ad39c2e52f23525bb9a1d656bd45ac2a.json b/.sqlx/query-858c7323ca15f68c7c22c40987d7eb60ad39c2e52f23525bb9a1d656bd45ac2a.json new file mode 100644 index 0000000000..d33d89c315 --- /dev/null +++ b/.sqlx/query-858c7323ca15f68c7c22c40987d7eb60ad39c2e52f23525bb9a1d656bd45ac2a.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"pub_key\",\"device_id\" FROM \"biometric_auth\" WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "pub_key", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "858c7323ca15f68c7c22c40987d7eb60ad39c2e52f23525bb9a1d656bd45ac2a" +} diff --git a/.sqlx/query-9cfebbfe0253249ee1b2d65ff6a910766a23aaefc6ba173b1327145fc5280716.json b/.sqlx/query-9cfebbfe0253249ee1b2d65ff6a910766a23aaefc6ba173b1327145fc5280716.json new file mode 100644 index 0000000000..03e8434ef6 --- /dev/null +++ b/.sqlx/query-9cfebbfe0253249ee1b2d65ff6a910766a23aaefc6ba173b1327145fc5280716.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO \"biometric_auth\" (\"pub_key\",\"device_id\") VALUES ($1,$2) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "9cfebbfe0253249ee1b2d65ff6a910766a23aaefc6ba173b1327145fc5280716" +} diff --git a/.sqlx/query-c398c1d38f7343ddf026d8fca19d50c63d1fbcf5aedc1448ab445bf223f6c612.json b/.sqlx/query-c398c1d38f7343ddf026d8fca19d50c63d1fbcf5aedc1448ab445bf223f6c612.json new file mode 100644 index 0000000000..322057ab6d --- /dev/null +++ b/.sqlx/query-c398c1d38f7343ddf026d8fca19d50c63d1fbcf5aedc1448ab445bf223f6c612.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"biometric_auth\" SET \"pub_key\" = $2,\"device_id\" = $3 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Text", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "c398c1d38f7343ddf026d8fca19d50c63d1fbcf5aedc1448ab445bf223f6c612" +} diff --git a/Cargo.lock b/Cargo.lock index 61aabb2118..795ec0a755 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1090,6 +1090,7 @@ dependencies = [ "clap", "defguard_web_ui", "dotenvy", + "ed25519-dalek", "humantime", "ipnetwork", "jsonwebkey", diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index 0e07be4195..c141334dba 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -83,6 +83,7 @@ x25519-dalek = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } bytes = { workspace = true } +ed25519-dalek = "2.2.0" # https://github.com/juhaku/utoipa/issues/1345 [dependencies.zip] diff --git a/crates/defguard_core/src/db/models/biometric_auth.rs b/crates/defguard_core/src/db/models/biometric_auth.rs new file mode 100644 index 0000000000..45e3e4d5ee --- /dev/null +++ b/crates/defguard_core/src/db/models/biometric_auth.rs @@ -0,0 +1,119 @@ +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; +use base64::Engine; +use base64::engine::general_purpose; +use ed25519_dalek::Verifier; +use ed25519_dalek::{Signature, VerifyingKey}; +use model_derive::Model; +use sqlx::{PgExecutor, query_as}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum BiometricAuthError { + #[error("Public key is not valid ed25519")] + InvalidPublicKey, + #[error("Signature invalid")] + InvalidSignature, + #[error("Verification of submitted challenge failed. {0}")] + ChallengeFailed(String), +} + +#[derive(Model, Clone)] +#[table(biometric_auth)] +pub struct BiometricAuth { + pub id: I, + pub pub_key: String, + pub device_id: Id, +} + +impl BiometricAuth { + pub fn new(device_id: Id, pub_key: String) -> Self { + Self { + id: NoId, + device_id, + pub_key, + } + } +} + +impl BiometricAuth { + pub(crate) async fn find_by_device_id<'e, E>( + executor: E, + device_id: Id, + ) -> Result, sqlx::Error> + where + E: PgExecutor<'e>, + { + query_as!( + Self, + "SELECT id, pub_key, device_id FROM biometric_auth WHERE device_id=$1", + &device_id + ) + .fetch_optional(executor) + .await + } +} + +#[derive(Clone, Debug)] +pub struct BiometricChallenge { + pub auth_pub_key: Option, + pub challenge: String, +} + +fn decode_pub_key(public_key: &str) -> Result { + let pub_bytes: [u8; 32] = general_purpose::STANDARD + .decode(public_key) + .map_err(|_| BiometricAuthError::InvalidPublicKey)? + .try_into() + .map_err(|_| BiometricAuthError::InvalidPublicKey)?; + let verifying_key = + VerifyingKey::from_bytes(&pub_bytes).map_err(|_| BiometricAuthError::InvalidPublicKey)?; + Ok(verifying_key) +} + +impl BiometricChallenge { + pub fn new(auth_pub_key: Option) -> Result { + if let Some(pub_key) = &auth_pub_key { + let _ = decode_pub_key(pub_key.as_str())?; + } + let challenge = gen_alphanumeric(44); + Ok(Self { + challenge, + auth_pub_key, + }) + } + + #[must_use] + pub fn verify(&self, signed_challenge: &str) -> bool { + if let Some(auth_pub_key) = &self.auth_pub_key { + return match verify(signed_challenge, auth_pub_key.as_str(), &self.challenge) { + Ok(res) => res, + Err(e) => { + error!("Biometric auth verification failed!\n Reason: {e}"); + false + } + }; + } + false + } +} + +fn verify( + signed_challenge: &str, + public_key: &str, + original_challenge: &str, +) -> Result { + let verifying_key = decode_pub_key(public_key)?; + let sig_bytes: [u8; 64] = general_purpose::STANDARD + .decode(signed_challenge) + .map_err(|_| BiometricAuthError::InvalidSignature)? + .try_into() + .map_err(|_| BiometricAuthError::InvalidSignature)?; + let signature = Signature::from_bytes(&sig_bytes); + match verifying_key.verify(original_challenge.as_bytes(), &signature) { + Ok(()) => Ok(true), + Err(_) => Ok(false), + } +} diff --git a/crates/defguard_core/src/db/models/mod.rs b/crates/defguard_core/src/db/models/mod.rs index 2088d31a2d..369b114b7e 100644 --- a/crates/defguard_core/src/db/models/mod.rs +++ b/crates/defguard_core/src/db/models/mod.rs @@ -2,6 +2,7 @@ pub mod activity_log; #[cfg(feature = "openid")] pub mod auth_code; pub mod authentication_key; +pub mod biometric_auth; pub mod device; pub mod device_login; pub mod enrollment; diff --git a/crates/defguard_core/src/db/models/user.rs b/crates/defguard_core/src/db/models/user.rs index 80df0cb658..3896dc26b3 100644 --- a/crates/defguard_core/src/db/models/user.rs +++ b/crates/defguard_core/src/db/models/user.rs @@ -80,6 +80,7 @@ impl fmt::Display for MfaMethod { MfaMethod::Totp => "TOTP", MfaMethod::Email => "Email", MfaMethod::Oidc => "OIDC", + MfaMethod::Biometric => "Biometric", } ) } diff --git a/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs b/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs index ec7187f037..f580779d5f 100644 --- a/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs +++ b/crates/defguard_core/src/enterprise/grpc/desktop_client_mfa.rs @@ -9,7 +9,7 @@ use crate::{ }, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, DesktopClientMfaEvent}, grpc::{ - desktop_client_mfa::{ClientLoginSession, ClientMfaServer}, + client_mfa::{ClientLoginSession, ClientMfaServer}, proto::proxy::{ClientMfaOidcAuthenticateRequest, DeviceInfo, MfaMethod}, utils::parse_client_info, }, @@ -52,6 +52,7 @@ impl ClientMfaServer { location, user, openid_auth_completed, + biometric_challenge: _, } = session; if openid_auth_completed { @@ -146,6 +147,7 @@ impl ClientMfaServer { location: location.clone(), user: user.clone(), openid_auth_completed: true, + biometric_challenge: None, }, ); diff --git a/crates/defguard_core/src/events.rs b/crates/defguard_core/src/events.rs index 9b5b1d571b..c1ac9c892b 100644 --- a/crates/defguard_core/src/events.rs +++ b/crates/defguard_core/src/events.rs @@ -389,6 +389,7 @@ impl Serialize for ClientMFAMethod { MfaMethod::Totp => serializer.serialize_unit_variant("MfaMethod", 0, "Totp"), MfaMethod::Email => serializer.serialize_unit_variant("MfaMethod", 1, "Email"), MfaMethod::Oidc => serializer.serialize_unit_variant("MfaMethod", 2, "Oidc"), + MfaMethod::Biometric => serializer.serialize_unit_variant("MfaMethod", 3, "Biometric"), } } } diff --git a/crates/defguard_core/src/grpc/desktop_client_mfa.rs b/crates/defguard_core/src/grpc/client_mfa.rs similarity index 83% rename from crates/defguard_core/src/grpc/desktop_client_mfa.rs rename to crates/defguard_core/src/grpc/client_mfa.rs index ddcf0c3c7f..d92abb0f75 100644 --- a/crates/defguard_core/src/grpc/desktop_client_mfa.rs +++ b/crates/defguard_core/src/grpc/client_mfa.rs @@ -18,6 +18,7 @@ use crate::{ db::{ Device, GatewayEvent, Id, User, UserInfo, WireguardNetwork, models::{ + biometric_auth::{BiometricAuth, BiometricChallenge}, device::{DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, wireguard::LocationMfaMode, }, @@ -50,6 +51,7 @@ pub(crate) struct ClientLoginSession { pub(crate) device: Device, pub(crate) user: User, pub(crate) openid_auth_completed: bool, + pub(crate) biometric_challenge: Option, } pub(crate) struct ClientMfaServer { @@ -162,7 +164,8 @@ impl ClientMfaServer { // MFA enabled status is already verified (LocationMfaMode::Disabled, _) => unreachable!(), (LocationMfaMode::Internal, MfaMethod::Totp) - | (LocationMfaMode::Internal, MfaMethod::Email) => { + | (LocationMfaMode::Internal, MfaMethod::Email) + | (LocationMfaMode::Internal, MfaMethod::Biometric) => { debug!("Location uses internal MFA. Selected method: {selected_method}") } (LocationMfaMode::External, MfaMethod::Oidc) => { @@ -180,8 +183,22 @@ impl ClientMfaServer { } } + let mut selected_mobile_auth: Option> = None; + // check if selected method is configured match selected_method { + MfaMethod::Biometric => { + if let Some(found) = BiometricAuth::find_by_device_id(&self.pool, device.id) + .await + .map_err(|_| Status::internal("unexpected_error"))? + { + selected_mobile_auth = Some(found); + } else { + return Err(Status::invalid_argument( + "Select MFA method not available for the device.", + )); + } + } MfaMethod::Totp => { if !user.totp_enabled { error!("TOTP not enabled for user {}", user.username); @@ -238,6 +255,28 @@ impl ClientMfaServer { user.username, location.name ); + let biometric_challenge: Option = match selected_method { + MfaMethod::Biometric => match selected_mobile_auth { + Some(mobile_auth) => match BiometricChallenge::new(Some(mobile_auth.pub_key)) { + Ok(challenge) => Some(challenge), + Err(e) => { + error!( + "Start biometric mfa failed ! Challenge creation failed ! Reason: {e}" + ); + return Err(Status::invalid_argument("Invalid public key")); + } + }, + None => { + return Err(Status::internal("unexpected error")); + } + }, + _ => None, + }; + + let response_challenge = biometric_challenge + .as_ref() + .map(|challenge| challenge.challenge.clone()); + // store login session self.sessions.insert( request.pubkey, @@ -247,10 +286,14 @@ impl ClientMfaServer { device, user, openid_auth_completed: false, + biometric_challenge, }, ); - Ok(ClientMfaStartResponse { token }) + Ok(ClientMfaStartResponse { + token, + challenge: response_challenge, + }) } /// Checks if given user is allowed to access a location @@ -312,6 +355,7 @@ impl ClientMfaServer { location, user, openid_auth_completed, + biometric_challenge, } = session; // Prepare event context @@ -325,6 +369,39 @@ impl ClientMfaServer { // validate code match method { + MfaMethod::Biometric => { + if let Some(challenge) = biometric_challenge { + if let Some(signed_challenge) = request.code { + match challenge.verify(signed_challenge.as_str()) { + // verification passed + true => { + debug!("Signed challenge verified successfully."); + } + // challenge rejected + false => { + self.emit_event(BidiStreamEvent { + context, + event: BidiStreamEventType::DesktopClientMfa(Box::new( + DesktopClientMfaEvent::Failed { + location: location.clone(), + device: device.clone(), + method: *method, + message: "Signed challenge rejected".to_string(), + }, + )), + })?; + return Err(Status::unauthenticated("unauthorized")); + } + } + } else { + error!("Signed challenge not found in request"); + return Err(Status::invalid_argument("Challenge not found in request")); + } + } else { + error!("Challenge not found in MFA session !"); + return Err(Status::internal("Challenge not found in MFA session")); + } + } MfaMethod::Totp => { let code = if let Some(code) = request.code { code.to_string() @@ -469,8 +546,10 @@ impl ClientMfaServer { })?; info!( - "Desktop client login finished for {} at location {}", - user.username, location.name + "Desktop client login finished for {} at location {} with method {}", + user.username, + location.name, + method.as_str_name() ); self.emit_event(BidiStreamEvent { context, diff --git a/crates/defguard_core/src/grpc/enrollment.rs b/crates/defguard_core/src/grpc/enrollment.rs index 48a8dcddd5..5da67fa101 100644 --- a/crates/defguard_core/src/grpc/enrollment.rs +++ b/crates/defguard_core/src/grpc/enrollment.rs @@ -20,6 +20,7 @@ use crate::{ db::{ Device, GatewayEvent, Id, Settings, User, WireguardNetwork, models::{ + biometric_auth::BiometricAuth, device::{DeviceConfig, DeviceInfo, DeviceType}, enrollment::{ENROLLMENT_TOKEN_TYPE, Token, TokenError}, polling_token::PollingToken, @@ -33,7 +34,7 @@ use crate::{ }, events::{BidiRequestContext, BidiStreamEvent, BidiStreamEventType, EnrollmentEvent}, grpc::{ - proto::proxy::LocationMfaMode as ProtoLocationMfaMode, + proto::proxy::{LocationMfaMode as ProtoLocationMfaMode, RegisterMobileAuthRequest}, utils::{build_device_config_response, new_polling_token, parse_client_info}, }, handlers::{mail::send_new_device_added_email, user::check_password_strength}, @@ -284,6 +285,46 @@ impl EnrollmentServer { } } + #[instrument(skip_all)] + pub async fn register_mobile_auth( + &self, + request: RegisterMobileAuthRequest, + ) -> Result<(), Status> { + debug!("Register mobile auth started"); + let enrollment = self.validate_session(Some(&request.token)).await?; + let user = enrollment.fetch_user(&self.pool).await?; + Device::validate_pubkey(&request.device_pub_key).map_err(|err| { + error!( + "Invalid pubkey {}, device won't be registered as mobile mfa auth for user {}({:?}): {err}", + request.device_pub_key, user.username, user.id + ); + Status::invalid_argument("invalid pubkey") + })?; + let device = match Device::find_by_pubkey(&self.pool, &request.device_pub_key) + .await + .map_err(|err| { + error!("Failed to read devices from db : {err}"); + Status::internal("Something went wrong") + })? { + Some(d) => d, + None => { + return Err(Status::invalid_argument( + "Device with given public key doesn't exist", + )); + } + }; + let mobile_auth = BiometricAuth::new(device.id, request.auth_pub_key); + let _ = mobile_auth.save(&self.pool).await.map_err(|err| { + error!("Failed to save mobile auth into db : {err}"); + Status::internal("Failed to save results") + })?; + info!( + "User {}({}) registered mobile auth for device {}({})", + user.username, user.id, device.name, device.id + ); + Ok(()) + } + #[instrument(skip_all)] pub async fn activate_user( &self, diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 89e7e0b16c..c75c21c0d8 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -36,7 +36,7 @@ use uuid::Uuid; use self::gateway::{GatewayServer, gateway_service_server::GatewayServiceServer}; use self::{ auth::{AuthServer, auth_service_server::AuthServiceServer}, - desktop_client_mfa::ClientMfaServer, + client_mfa::ClientMfaServer, enrollment::EnrollmentServer, password_reset::PasswordResetServer, proto::proxy::core_response, @@ -69,7 +69,7 @@ use crate::{ }; mod auth; -pub(crate) mod desktop_client_mfa; +pub(crate) mod client_mfa; pub mod enrollment; #[cfg(feature = "wireguard")] pub(crate) mod gateway; @@ -528,6 +528,16 @@ pub async fn run_grpc_bidi_stream( info!("Received message from proxy."); debug!("Received the following message from proxy: {received:?}"); let payload = match received.payload { + // rpc RegisterMobileAuth (RegisterMobileAuthRequest) return (google.protobuf.Empty) + Some(core_request::Payload::RegisterMobileAuth(request)) => { + match enrollment_server.register_mobile_auth(request).await { + Ok(()) => Some(core_response::Payload::Empty(())), + Err(err) => { + error!("Register mobile auth error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } // rpc StartEnrollment (EnrollmentStartRequest) returns (EnrollmentStartResponse) Some(core_request::Payload::EnrollmentStart(request)) => { match enrollment_server diff --git a/migrations/20250731063659_biometric_auth.down.sql b/migrations/20250731063659_biometric_auth.down.sql new file mode 100644 index 0000000000..dfb981387f --- /dev/null +++ b/migrations/20250731063659_biometric_auth.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS biometric_auth; +DROP CONSTRAINT biometric_auth_device; diff --git a/migrations/20250731063659_biometric_auth.up.sql b/migrations/20250731063659_biometric_auth.up.sql new file mode 100644 index 0000000000..885371af1d --- /dev/null +++ b/migrations/20250731063659_biometric_auth.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE biometric_auth ( + id bigserial PRIMARY KEY, + pub_key text NOT NULL, + device_id bigint NOT NULL, + FOREIGN KEY(device_id) REFERENCES "device"(id) ON DELETE CASCADE, + CONSTRAINT biometric_auth_device UNIQUE (device_id) +); diff --git a/proto b/proto index b9f24ac413..54b7b78dde 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit b9f24ac41326bffe4c7e72019ccc8a785f6bd343 +Subproject commit 54b7b78dde005e6abcf783633e8af92f7e20db4b From dcbcc88c465077761f9df247c699a79dcd77406c Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:42:40 +0200 Subject: [PATCH 02/84] fix workflow permissions (#1379) --- .github/workflows/current.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index 490f5e6aaa..62bb823050 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -1,6 +1,8 @@ name: Build current image permissions: contents: read + id-token: write + packages: write on: push: branches: From 2643dbd1111d420a242a9c5b7931d0d4c79d4dcf Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 6 Aug 2025 10:33:53 +0200 Subject: [PATCH 03/84] Change "Gateway address" field in VPN configuration (#1381) --- web/package.json | 24 +- web/pnpm-lock.yaml | 546 +++++++++--------- web/src/i18n/en/index.ts | 3 +- web/src/i18n/i18n-types.ts | 6 +- web/src/i18n/pl/index.ts | 3 +- .../NetworkEditForm/NetworkEditForm.tsx | 3 + 6 files changed, 296 insertions(+), 289 deletions(-) diff --git a/web/package.json b/web/package.json index 04fc499c22..f76351ddea 100644 --- a/web/package.json +++ b/web/package.json @@ -42,15 +42,15 @@ ] }, "dependencies": { - "@floating-ui/react": "^0.27.13", + "@floating-ui/react": "^0.27.15", "@github/webauthn-json": "^2.1.1", - "@hookform/resolvers": "^5.1.1", + "@hookform/resolvers": "^5.2.1", "@react-hook/resize-observer": "^2.0.2", "@react-rxjs/core": "^0.10.8", "@stablelib/base64": "^2.0.1", "@stablelib/x25519": "^2.0.1", - "@tanstack/query-core": "^5.83.0", - "@tanstack/react-query": "^5.83.0", + "@tanstack/query-core": "^5.83.1", + "@tanstack/react-query": "^5.84.1", "@tanstack/react-virtual": "3.13.12", "@tanstack/virtual-core": "3.13.12", "@use-gesture/react": "^10.3.1", @@ -66,7 +66,7 @@ "events": "^3.3.0", "fast-deep-equal": "^3.1.3", "file-saver": "^2.0.5", - "framer-motion": "^12.23.7", + "framer-motion": "^12.23.12", "fuse.js": "^7.1.0", "get-text-width": "^1.0.3", "hex-rgb": "^5.0.0", @@ -86,10 +86,10 @@ "react-click-away-listener": "^2.4.0", "react-datepicker": "^8.4.0", "react-dom": "^18.3.1", - "react-hook-form": "^7.61.0", + "react-hook-form": "^7.62.0", "react-idle-timer": "^5.7.2", "react-intersection-observer": "^9.16.0", - "react-is": "^19.1.0", + "react-is": "^19.1.1", "react-loading-skeleton": "^3.5.0", "react-markdown": "^10.1.0", "react-qr-code": "^2.0.18", @@ -99,7 +99,7 @@ "react-tracked": "^2.0.1", "react-virtualized-auto-sizer": "^1.0.26", "react-window": "^1.8.11", - "recharts": "^3.1.0", + "recharts": "^3.1.2", "rehype-external-links": "^3.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", @@ -109,7 +109,7 @@ "typesafe-i18n": "^5.26.2", "use-breakpoint": "^4.0.6", "zod": "^3.25.76", - "zustand": "^5.0.6" + "zustand": "^5.0.7" }, "devDependencies": { "@babel/core": "^7.28.0", @@ -117,12 +117,12 @@ "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@hookform/devtools": "^4.4.0", - "@tanstack/react-query-devtools": "^5.83.0", + "@tanstack/react-query-devtools": "^5.84.1", "@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.1.0", + "@types/node": "^24.2.0", "@types/qs": "^6.14.0", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", @@ -131,7 +131,7 @@ "@vitejs/plugin-react-swc": "^3.11.0", "autoprefixer": "^10.4.21", "concurrently": "^9.2.0", - "dotenv": "^17.2.0", + "dotenv": "^17.2.1", "esbuild": "^0.25.8", "globals": "^16.3.0", "postcss": "^8.5.6", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index bae5c4a083..80ee19bb71 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: '@floating-ui/react': - specifier: ^0.27.13 - version: 0.27.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^0.27.15 + version: 0.27.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@github/webauthn-json': specifier: ^2.1.1 version: 2.1.1 '@hookform/resolvers': - specifier: ^5.1.1 - version: 5.1.1(react-hook-form@7.61.0(react@18.3.1)) + specifier: ^5.2.1 + version: 5.2.1(react-hook-form@7.62.0(react@18.3.1)) '@react-hook/resize-observer': specifier: ^2.0.2 version: 2.0.2(react@18.3.1) @@ -30,11 +30,11 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tanstack/query-core': - specifier: ^5.83.0 - version: 5.83.0 + specifier: ^5.83.1 + version: 5.83.1 '@tanstack/react-query': - specifier: ^5.83.0 - version: 5.83.0(react@18.3.1) + specifier: ^5.84.1 + version: 5.84.1(react@18.3.1) '@tanstack/react-virtual': specifier: 3.13.12 version: 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -81,8 +81,8 @@ importers: specifier: ^2.0.5 version: 2.0.5 framer-motion: - specifier: ^12.23.7 - version: 12.23.7(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^12.23.12 + version: 12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fuse.js: specifier: ^7.1.0 version: 7.1.0 @@ -141,8 +141,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-hook-form: - specifier: ^7.61.0 - version: 7.61.0(react@18.3.1) + specifier: ^7.62.0 + version: 7.62.0(react@18.3.1) react-idle-timer: specifier: ^5.7.2 version: 5.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -150,8 +150,8 @@ importers: specifier: ^9.16.0 version: 9.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-is: - specifier: ^19.1.0 - version: 19.1.0 + specifier: ^19.1.1 + version: 19.1.1 react-loading-skeleton: specifier: ^3.5.0 version: 3.5.0(react@18.3.1) @@ -180,8 +180,8 @@ importers: specifier: ^1.8.11 version: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recharts: - specifier: ^3.1.0 - version: 3.1.0(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react-is@19.1.0)(react@18.3.1)(redux@5.0.1) + specifier: ^3.1.2 + version: 3.1.2(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react-is@19.1.1)(react@18.3.1)(redux@5.0.1) rehype-external-links: specifier: ^3.0.0 version: 3.0.0 @@ -210,8 +210,8 @@ importers: specifier: ^3.25.76 version: 3.25.76 zustand: - specifier: ^5.0.6 - version: 5.0.6(@types/react@18.3.23)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) + specifier: ^5.0.7 + version: 5.0.7(@types/react@18.3.23)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) devDependencies: '@babel/core': specifier: ^7.28.0 @@ -229,8 +229,8 @@ importers: specifier: ^4.4.0 version: 4.4.0(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query-devtools': - specifier: ^5.83.0 - version: 5.83.0(@tanstack/react-query@5.83.0(react@18.3.1))(react@18.3.1) + specifier: ^5.84.1 + version: 5.84.1(@tanstack/react-query@5.84.1(react@18.3.1))(react@18.3.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.1.0 - version: 24.1.0 + specifier: ^24.2.0 + version: 24.2.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: ^3.11.0 - version: 3.11.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) + version: 3.11.0(vite@7.0.6(@types/node@24.2.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) @@ -271,8 +271,8 @@ importers: specifier: ^9.2.0 version: 9.2.0 dotenv: - specifier: ^17.2.0 - version: 17.2.0 + specifier: ^17.2.1 + version: 17.2.1 esbuild: specifier: ^0.25.8 version: 0.25.8 @@ -299,10 +299,10 @@ importers: version: 5.8.3 vite: specifier: ^7.0.6 - version: 7.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + version: 7.0.6(@types/node@24.2.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.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) + version: 1.1.0(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) packages: @@ -356,8 +356,8 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.6': - resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} engines: {node: '>=6.9.0'} '@babel/parser@7.28.0': @@ -365,8 +365,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.27.6': - resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + '@babel/runtime@7.28.2': + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': @@ -377,8 +377,8 @@ packages: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.1': - resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} '@biomejs/biome@2.1.1': @@ -654,20 +654,20 @@ packages: cpu: [x64] os: [win32] - '@floating-ui/core@1.7.2': - resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - '@floating-ui/dom@1.7.2': - resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + '@floating-ui/dom@1.7.3': + resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==} - '@floating-ui/react-dom@2.1.4': - resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==} + '@floating-ui/react-dom@2.1.5': + resolution: {integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react@0.27.13': - resolution: {integrity: sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA==} + '@floating-ui/react@0.27.15': + resolution: {integrity: sha512-0LGxhBi3BB1DwuSNQAmuaSuertFzNAerlMdPbotjTVnvPtdOs7CkrHLaev5NIXemhzDXNC0tFzuseut7cWA5mw==} peerDependencies: react: '>=17.0.0' react-dom: '>=17.0.0' @@ -685,8 +685,8 @@ packages: react: ^16.8.0 || ^17 || ^18 || ^19 react-dom: ^16.8.0 || ^17 || ^18 || ^19 - '@hookform/resolvers@5.1.1': - resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} + '@hookform/resolvers@5.2.1': + resolution: {integrity: sha512-u0+6X58gkjMcxur1wRWokA7XsiiBJ6aK17aPZxhkoYiK5J+HcTx0Vhu9ovXe6H+dVpO6cjrn2FkJTryXEMlryQ==} peerDependencies: react-hook-form: ^7.55.0 @@ -749,103 +749,103 @@ packages: '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rollup/rollup-android-arm-eabi@4.45.1': - resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==} + '@rollup/rollup-android-arm-eabi@4.46.2': + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.45.1': - resolution: {integrity: sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==} + '@rollup/rollup-android-arm64@4.46.2': + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.45.1': - resolution: {integrity: sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==} + '@rollup/rollup-darwin-arm64@4.46.2': + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.45.1': - resolution: {integrity: sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==} + '@rollup/rollup-darwin-x64@4.46.2': + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.45.1': - resolution: {integrity: sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==} + '@rollup/rollup-freebsd-arm64@4.46.2': + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.45.1': - resolution: {integrity: sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==} + '@rollup/rollup-freebsd-x64@4.46.2': + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.45.1': - resolution: {integrity: sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.45.1': - resolution: {integrity: sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==} + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.45.1': - resolution: {integrity: sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==} + '@rollup/rollup-linux-arm64-gnu@4.46.2': + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.45.1': - resolution: {integrity: sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==} + '@rollup/rollup-linux-arm64-musl@4.46.2': + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.45.1': - resolution: {integrity: sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==} + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.45.1': - resolution: {integrity: sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==} + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.45.1': - resolution: {integrity: sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==} + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.45.1': - resolution: {integrity: sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==} + '@rollup/rollup-linux-riscv64-musl@4.46.2': + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.45.1': - resolution: {integrity: sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==} + '@rollup/rollup-linux-s390x-gnu@4.46.2': + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.45.1': - resolution: {integrity: sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==} + '@rollup/rollup-linux-x64-gnu@4.46.2': + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.45.1': - resolution: {integrity: sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==} + '@rollup/rollup-linux-x64-musl@4.46.2': + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.45.1': - resolution: {integrity: sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==} + '@rollup/rollup-win32-arm64-msvc@4.46.2': + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.45.1': - resolution: {integrity: sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==} + '@rollup/rollup-win32-ia32-msvc@4.46.2': + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.45.1': - resolution: {integrity: sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==} + '@rollup/rollup-win32-x64-msvc@4.46.2': + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} cpu: [x64] os: [win32] @@ -884,68 +884,68 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@swc/core-darwin-arm64@1.13.2': - resolution: {integrity: sha512-44p7ivuLSGFJ15Vly4ivLJjg3ARo4879LtEBAabcHhSZygpmkP8eyjyWxrH3OxkY1eRZSIJe8yRZPFw4kPXFPw==} + '@swc/core-darwin-arm64@1.13.3': + resolution: {integrity: sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.13.2': - resolution: {integrity: sha512-Lb9EZi7X2XDAVmuUlBm2UvVAgSCbD3qKqDCxSI4jEOddzVOpNCnyZ/xEampdngUIyDDhhJLYU9duC+Mcsv5Y+A==} + '@swc/core-darwin-x64@1.13.3': + resolution: {integrity: sha512-p0X6yhxmNUOMZrbeZ3ZNsPige8lSlSe1llllXvpCLkKKxN/k5vZt1sULoq6Nj4eQ7KeHQVm81/+AwKZyf/e0TA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.13.2': - resolution: {integrity: sha512-9TDe/92ee1x57x+0OqL1huG4BeljVx0nWW4QOOxp8CCK67Rpc/HHl2wciJ0Kl9Dxf2NvpNtkPvqj9+BUmM9WVA==} + '@swc/core-linux-arm-gnueabihf@1.13.3': + resolution: {integrity: sha512-OmDoiexL2fVWvQTCtoh0xHMyEkZweQAlh4dRyvl8ugqIPEVARSYtaj55TBMUJIP44mSUOJ5tytjzhn2KFxFcBA==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.13.2': - resolution: {integrity: sha512-KJUSl56DBk7AWMAIEcU83zl5mg3vlQYhLELhjwRFkGFMvghQvdqQ3zFOYa4TexKA7noBZa3C8fb24rI5sw9Exg==} + '@swc/core-linux-arm64-gnu@1.13.3': + resolution: {integrity: sha512-STfKku3QfnuUj6k3g9ld4vwhtgCGYIFQmsGPPgT9MK/dI3Lwnpe5Gs5t1inoUIoGNP8sIOLlBB4HV4MmBjQuhw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.13.2': - resolution: {integrity: sha512-teU27iG1oyWpNh9CzcGQ48ClDRt/RCem7mYO7ehd2FY102UeTws2+OzLESS1TS1tEZipq/5xwx3FzbVgiolCiQ==} + '@swc/core-linux-arm64-musl@1.13.3': + resolution: {integrity: sha512-bc+CXYlFc1t8pv9yZJGus372ldzOVscBl7encUBlU1m/Sig0+NDJLz6cXXRcFyl6ABNOApWeR4Yl7iUWx6C8og==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.13.2': - resolution: {integrity: sha512-dRPsyPyqpLD0HMRCRpYALIh4kdOir8pPg4AhNQZLehKowigRd30RcLXGNVZcc31Ua8CiPI4QSgjOIxK+EQe4LQ==} + '@swc/core-linux-x64-gnu@1.13.3': + resolution: {integrity: sha512-dFXoa0TEhohrKcxn/54YKs1iwNeW6tUkHJgXW33H381SvjKFUV53WR231jh1sWVJETjA3vsAwxKwR23s7UCmUA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.13.2': - resolution: {integrity: sha512-CCxETW+KkYEQDqz1SYC15YIWYheqFC+PJVOW76Maa/8yu8Biw+HTAcblKf2isrlUtK8RvrQN94v3UXkC2NzCEw==} + '@swc/core-linux-x64-musl@1.13.3': + resolution: {integrity: sha512-ieyjisLB+ldexiE/yD8uomaZuZIbTc8tjquYln9Quh5ykOBY7LpJJYBWvWtm1g3pHv6AXlBI8Jay7Fffb6aLfA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.13.2': - resolution: {integrity: sha512-Wv/QTA6PjyRLlmKcN6AmSI4jwSMRl0VTLGs57PHTqYRwwfwd7y4s2fIPJVBNbAlXd795dOEP6d/bGSQSyhOX3A==} + '@swc/core-win32-arm64-msvc@1.13.3': + resolution: {integrity: sha512-elTQpnaX5vESSbhCEgcwXjpMsnUbqqHfEpB7ewpkAsLzKEXZaK67ihSRYAuAx6ewRQTo7DS5iTT6X5aQD3MzMw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.13.2': - resolution: {integrity: sha512-PuCdtNynEkUNbUXX/wsyUC+t4mamIU5y00lT5vJcAvco3/r16Iaxl5UCzhXYaWZSNVZMzPp9qN8NlSL8M5pPxw==} + '@swc/core-win32-ia32-msvc@1.13.3': + resolution: {integrity: sha512-nvehQVEOdI1BleJpuUgPLrclJ0TzbEMc+MarXDmmiRFwEUGqj+pnfkTSb7RZyS1puU74IXdK/YhTirHurtbI9w==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.13.2': - resolution: {integrity: sha512-qlmMkFZJus8cYuBURx1a3YAG2G7IW44i+FEYV5/32ylKkzGNAr9tDJSA53XNnNXkAB5EXSPsOz7bn5C3JlEtdQ==} + '@swc/core-win32-x64-msvc@1.13.3': + resolution: {integrity: sha512-A+JSKGkRbPLVV2Kwx8TaDAV0yXIXm/gc8m98hSkVDGlPBBmydgzNdWy3X7HTUBM7IDk7YlWE7w2+RUGjdgpTmg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.13.2': - resolution: {integrity: sha512-YWqn+0IKXDhqVLKoac4v2tV6hJqB/wOh8/Br8zjqeqBkKa77Qb0Kw2i7LOFzjFNZbZaPH6AlMGlBwNrxaauaAg==} + '@swc/core@1.13.3': + resolution: {integrity: sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -959,20 +959,20 @@ packages: '@swc/types@0.1.23': resolution: {integrity: sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==} - '@tanstack/query-core@5.83.0': - resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==} + '@tanstack/query-core@5.83.1': + resolution: {integrity: sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==} - '@tanstack/query-devtools@5.81.2': - resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==} + '@tanstack/query-devtools@5.84.0': + resolution: {integrity: sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ==} - '@tanstack/react-query-devtools@5.83.0': - resolution: {integrity: sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==} + '@tanstack/react-query-devtools@5.84.1': + resolution: {integrity: sha512-nle+OQ9B3Z3EG2R3ixvaNcJ6OeqGwmAc5iMDW6Vj+emLZkWRrN3BDsrzZQu414n34lpxplnC7z1jmKuU/scHCQ==} peerDependencies: - '@tanstack/react-query': ^5.83.0 + '@tanstack/react-query': ^5.84.1 react: ^18 || ^19 - '@tanstack/react-query@5.83.0': - resolution: {integrity: sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==} + '@tanstack/react-query@5.84.1': + resolution: {integrity: sha512-zo7EUygcWJMQfFNWDSG7CBhy8irje/XY0RDVKKV4IQJAysb+ZJkkJPcnQi+KboyGUgT+SQebRFoTqLuTtfoDLw==} peerDependencies: react: ^18 || ^19 @@ -1051,8 +1051,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.1.0': - resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + '@types/node@24.2.0': + resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1217,8 +1217,8 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - caniuse-lite@1.0.30001727: - resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + caniuse-lite@1.0.30001731: + resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1518,8 +1518,8 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} - dotenv@17.2.0: - resolution: {integrity: sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==} + dotenv@17.2.1: + resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==} engines: {node: '>=12'} dotgitignore@2.1.0: @@ -1530,8 +1530,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.190: - resolution: {integrity: sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==} + electron-to-chromium@1.5.197: + resolution: {integrity: sha512-m1xWB3g7vJ6asIFz+2pBUbq3uGmfmln1M9SSvBe4QIFWYrRHylP73zL/3nMjDmwz8V+1xAXQDfBd6+HPW0WvDQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1563,8 +1563,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.39.7: - resolution: {integrity: sha512-ek/wWryKouBrZIjkwW2BFf91CWOIMvoy2AE5YYgUrfWsJQM2Su1LoLtrw8uusEpN9RfqLlV/0FVNjT0WMv8Bxw==} + es-toolkit@1.39.8: + resolution: {integrity: sha512-A8QO9TfF+rltS8BXpdu8OS+rpGgEdnRhqIVxO/ZmNvnXBYgOdSsxukT55ELyP94gZIntWJ+Li9QRrT2u1Kitpg==} esbuild@0.25.8: resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} @@ -1637,8 +1637,8 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1653,8 +1653,8 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - framer-motion@12.23.7: - resolution: {integrity: sha512-Qs+zNG9D/3c9C0riom1iXVVOOOaY3T32LIofgbQJz9APY/CUE5v6G41WkcZl2lVhaAgQDQcNq94f8qzLf3rTZA==} + framer-motion@12.23.12: + resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2157,8 +2157,8 @@ packages: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} - motion-dom@12.23.7: - resolution: {integrity: sha512-AyJR07/YxObtK3NyGLCfebUe0k9UZGhik+2eIPUoKz76cKRRSkMeifmIxfztIvOaKb/Smu9IfVHkmx+mV+iFmQ==} + motion-dom@12.23.12: + resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} motion-utils@12.23.6: resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} @@ -2377,8 +2377,8 @@ packages: peerDependencies: react: ^18.3.1 - react-hook-form@7.61.0: - resolution: {integrity: sha512-o8S/HcCeuaAQVib36fPCgOLaaQN/v7Anj8zlYjcLMcz+4FnNfMsoDAEvVCefLb3KDnS43wq3pwcifehhkwowuQ==} + react-hook-form@7.62.0: + resolution: {integrity: sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -2401,8 +2401,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@19.1.0: - resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} + react-is@19.1.1: + resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==} react-loading-skeleton@3.5.0: resolution: {integrity: sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==} @@ -2508,8 +2508,8 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - recharts@3.1.0: - resolution: {integrity: sha512-NqAqQcGBmLrfDs2mHX/bz8jJCQtG2FeXfE0GqpZmIuXIjkpIwj8sd9ad0WyvKiBKPd8ZgNG0hL85c8sFDwascw==} + recharts@3.1.2: + resolution: {integrity: sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2562,8 +2562,8 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rollup@4.45.1: - resolution: {integrity: sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==} + rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2862,8 +2862,8 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - undici-types@7.8.0: - resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -2925,8 +2925,8 @@ packages: vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} @@ -3053,8 +3053,8 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zustand@5.0.6: - resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} + zustand@5.0.7: + resolution: {integrity: sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -3096,11 +3096,11 @@ snapshots: '@babel/generator': 7.28.0 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.27.6 + '@babel/helpers': 7.28.2 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -3112,7 +3112,7 @@ snapshots: '@babel/generator@7.28.0': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 @@ -3130,7 +3130,7 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -3149,22 +3149,22 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.27.6': + '@babel/helpers@7.28.2': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 - '@babel/runtime@7.27.6': {} + '@babel/runtime@7.28.2': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@babel/traverse@7.28.0': dependencies: @@ -3173,12 +3173,12 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 debug: 4.4.1 transitivePeerDependencies: - supports-color - '@babel/types@7.28.1': + '@babel/types@7.28.2': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -3227,7 +3227,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.27.1 - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -3258,7 +3258,7 @@ snapshots: '@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 @@ -3284,7 +3284,7 @@ snapshots: '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react@18.3.1)': dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 '@emotion/react': 11.14.0(@types/react@18.3.23)(react@18.3.1) @@ -3385,24 +3385,24 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true - '@floating-ui/core@1.7.2': + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.7.2': + '@floating-ui/dom@1.7.3': dependencies: - '@floating-ui/core': 1.7.2 + '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react-dom@2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@floating-ui/dom': 1.7.2 + '@floating-ui/dom': 1.7.3 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@floating-ui/react@0.27.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react@0.27.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@floating-ui/react-dom': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/react-dom': 2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@floating-ui/utils': 0.2.10 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3428,10 +3428,10 @@ snapshots: - '@types/react' - supports-color - '@hookform/resolvers@5.1.1(react-hook-form@7.61.0(react@18.3.1))': + '@hookform/resolvers@5.2.1(react-hook-form@7.62.0(react@18.3.1))': dependencies: '@standard-schema/utils': 0.3.0 - react-hook-form: 7.61.0(react@18.3.1) + react-hook-form: 7.62.0(react@18.3.1) '@hutson/parse-repository-url@3.0.2': {} @@ -3492,64 +3492,64 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rollup/rollup-android-arm-eabi@4.45.1': + '@rollup/rollup-android-arm-eabi@4.46.2': optional: true - '@rollup/rollup-android-arm64@4.45.1': + '@rollup/rollup-android-arm64@4.46.2': optional: true - '@rollup/rollup-darwin-arm64@4.45.1': + '@rollup/rollup-darwin-arm64@4.46.2': optional: true - '@rollup/rollup-darwin-x64@4.45.1': + '@rollup/rollup-darwin-x64@4.46.2': optional: true - '@rollup/rollup-freebsd-arm64@4.45.1': + '@rollup/rollup-freebsd-arm64@4.46.2': optional: true - '@rollup/rollup-freebsd-x64@4.45.1': + '@rollup/rollup-freebsd-x64@4.46.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.45.1': + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.45.1': + '@rollup/rollup-linux-arm-musleabihf@4.46.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.45.1': + '@rollup/rollup-linux-arm64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.45.1': + '@rollup/rollup-linux-arm64-musl@4.46.2': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.45.1': + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.45.1': + '@rollup/rollup-linux-ppc64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.45.1': + '@rollup/rollup-linux-riscv64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.45.1': + '@rollup/rollup-linux-riscv64-musl@4.46.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.45.1': + '@rollup/rollup-linux-s390x-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.45.1': + '@rollup/rollup-linux-x64-gnu@4.46.2': optional: true - '@rollup/rollup-linux-x64-musl@4.45.1': + '@rollup/rollup-linux-x64-musl@4.46.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.45.1': + '@rollup/rollup-win32-arm64-msvc@4.46.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.45.1': + '@rollup/rollup-win32-ia32-msvc@4.46.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.45.1': + '@rollup/rollup-win32-x64-msvc@4.46.2': optional: true '@rx-state/core@0.1.4(rxjs@7.8.2)': @@ -3587,51 +3587,51 @@ snapshots: '@standard-schema/utils@0.3.0': {} - '@swc/core-darwin-arm64@1.13.2': + '@swc/core-darwin-arm64@1.13.3': optional: true - '@swc/core-darwin-x64@1.13.2': + '@swc/core-darwin-x64@1.13.3': optional: true - '@swc/core-linux-arm-gnueabihf@1.13.2': + '@swc/core-linux-arm-gnueabihf@1.13.3': optional: true - '@swc/core-linux-arm64-gnu@1.13.2': + '@swc/core-linux-arm64-gnu@1.13.3': optional: true - '@swc/core-linux-arm64-musl@1.13.2': + '@swc/core-linux-arm64-musl@1.13.3': optional: true - '@swc/core-linux-x64-gnu@1.13.2': + '@swc/core-linux-x64-gnu@1.13.3': optional: true - '@swc/core-linux-x64-musl@1.13.2': + '@swc/core-linux-x64-musl@1.13.3': optional: true - '@swc/core-win32-arm64-msvc@1.13.2': + '@swc/core-win32-arm64-msvc@1.13.3': optional: true - '@swc/core-win32-ia32-msvc@1.13.2': + '@swc/core-win32-ia32-msvc@1.13.3': optional: true - '@swc/core-win32-x64-msvc@1.13.2': + '@swc/core-win32-x64-msvc@1.13.3': optional: true - '@swc/core@1.13.2': + '@swc/core@1.13.3': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.23 optionalDependencies: - '@swc/core-darwin-arm64': 1.13.2 - '@swc/core-darwin-x64': 1.13.2 - '@swc/core-linux-arm-gnueabihf': 1.13.2 - '@swc/core-linux-arm64-gnu': 1.13.2 - '@swc/core-linux-arm64-musl': 1.13.2 - '@swc/core-linux-x64-gnu': 1.13.2 - '@swc/core-linux-x64-musl': 1.13.2 - '@swc/core-win32-arm64-msvc': 1.13.2 - '@swc/core-win32-ia32-msvc': 1.13.2 - '@swc/core-win32-x64-msvc': 1.13.2 + '@swc/core-darwin-arm64': 1.13.3 + '@swc/core-darwin-x64': 1.13.3 + '@swc/core-linux-arm-gnueabihf': 1.13.3 + '@swc/core-linux-arm64-gnu': 1.13.3 + '@swc/core-linux-arm64-musl': 1.13.3 + '@swc/core-linux-x64-gnu': 1.13.3 + '@swc/core-linux-x64-musl': 1.13.3 + '@swc/core-win32-arm64-msvc': 1.13.3 + '@swc/core-win32-ia32-msvc': 1.13.3 + '@swc/core-win32-x64-msvc': 1.13.3 '@swc/counter@0.1.3': {} @@ -3639,19 +3639,19 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.83.0': {} + '@tanstack/query-core@5.83.1': {} - '@tanstack/query-devtools@5.81.2': {} + '@tanstack/query-devtools@5.84.0': {} - '@tanstack/react-query-devtools@5.83.0(@tanstack/react-query@5.83.0(react@18.3.1))(react@18.3.1)': + '@tanstack/react-query-devtools@5.84.1(@tanstack/react-query@5.84.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/query-devtools': 5.81.2 - '@tanstack/react-query': 5.83.0(react@18.3.1) + '@tanstack/query-devtools': 5.84.0 + '@tanstack/react-query': 5.84.1(react@18.3.1) react: 18.3.1 - '@tanstack/react-query@5.83.0(react@18.3.1)': + '@tanstack/react-query@5.84.1(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.83.0 + '@tanstack/query-core': 5.83.1 react: 18.3.1 '@tanstack/react-virtual@3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -3722,9 +3722,9 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@24.1.0': + '@types/node@24.2.0': dependencies: - undici-types: 7.8.0 + undici-types: 7.10.0 '@types/normalize-package-data@2.4.4': {} @@ -3773,11 +3773,11 @@ snapshots: '@use-gesture/core': 10.3.1 react: 18.3.1 - '@vitejs/plugin-react-swc@3.11.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1))': + '@vitejs/plugin-react-swc@3.11.0(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.27 - '@swc/core': 1.13.2 - vite: 7.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + '@swc/core': 1.13.3 + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) transitivePeerDependencies: - '@swc/helpers' @@ -3815,7 +3815,7 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.1 - caniuse-lite: 1.0.30001727 + caniuse-lite: 1.0.30001731 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -3824,7 +3824,7 @@ snapshots: axios@1.11.0: dependencies: - follow-redirects: 1.15.9 + follow-redirects: 1.15.11 form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -3832,7 +3832,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -3855,8 +3855,8 @@ snapshots: browserslist@4.25.1: dependencies: - caniuse-lite: 1.0.30001727 - electron-to-chromium: 1.5.190 + caniuse-lite: 1.0.30001731 + electron-to-chromium: 1.5.197 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) @@ -3884,7 +3884,7 @@ snapshots: camelcase@5.3.1: {} - caniuse-lite@1.0.30001727: {} + caniuse-lite@1.0.30001731: {} ccount@2.0.1: {} @@ -4221,7 +4221,7 @@ snapshots: dependencies: is-obj: 2.0.0 - dotenv@17.2.0: {} + dotenv@17.2.1: {} dotgitignore@2.1.0: dependencies: @@ -4234,7 +4234,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.190: {} + electron-to-chromium@1.5.197: {} emoji-regex@8.0.0: {} @@ -4261,7 +4261,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.39.7: {} + es-toolkit@1.39.8: {} esbuild@0.25.8: optionalDependencies: @@ -4342,7 +4342,7 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - follow-redirects@1.15.9: {} + follow-redirects@1.15.11: {} form-data@4.0.4: dependencies: @@ -4354,9 +4354,9 @@ snapshots: fraction.js@4.3.7: {} - framer-motion@12.23.7(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - motion-dom: 12.23.7 + motion-dom: 12.23.12 motion-utils: 12.23.6 tslib: 2.8.1 optionalDependencies: @@ -4516,7 +4516,7 @@ snapshots: space-separated-tokens: 2.0.2 style-to-js: 1.1.17 unist-util-position: 5.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 transitivePeerDependencies: - supports-color @@ -4769,7 +4769,7 @@ snapshots: parse-entities: 4.0.2 stringify-entities: 4.0.4 unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 + vfile-message: 4.0.3 transitivePeerDependencies: - supports-color @@ -4996,7 +4996,7 @@ snapshots: modify-values@1.0.1: {} - motion-dom@12.23.7: + motion-dom@12.23.12: dependencies: motion-utils: 12.23.6 @@ -5175,7 +5175,7 @@ snapshots: react-datepicker@8.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@floating-ui/react': 0.27.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/react': 0.27.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 date-fns: 4.1.0 react: 18.3.1 @@ -5187,7 +5187,7 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-hook-form@7.61.0(react@18.3.1): + react-hook-form@7.62.0(react@18.3.1): dependencies: react: 18.3.1 @@ -5204,7 +5204,7 @@ snapshots: react-is@16.13.1: {} - react-is@19.1.0: {} + react-is@19.1.1: {} react-loading-skeleton@3.5.0(react@18.3.1): dependencies: @@ -5280,7 +5280,7 @@ snapshots: react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 memoize-one: 5.2.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -5333,17 +5333,17 @@ snapshots: dependencies: picomatch: 2.3.1 - recharts@3.1.0(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react-is@19.1.0)(react@18.3.1)(redux@5.0.1): + recharts@3.1.2(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react-is@19.1.1)(react@18.3.1)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.8.2(react-redux@9.2.0(@types/react@18.3.23)(react@18.3.1)(redux@5.0.1))(react@18.3.1) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.39.7 + es-toolkit: 1.39.8 eventemitter3: 5.0.1 immer: 10.1.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-is: 19.1.0 + react-is: 19.1.1 react-redux: 9.2.0(@types/react@18.3.23)(react@18.3.1)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 @@ -5415,30 +5415,30 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup@4.45.1: + rollup@4.46.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.45.1 - '@rollup/rollup-android-arm64': 4.45.1 - '@rollup/rollup-darwin-arm64': 4.45.1 - '@rollup/rollup-darwin-x64': 4.45.1 - '@rollup/rollup-freebsd-arm64': 4.45.1 - '@rollup/rollup-freebsd-x64': 4.45.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.45.1 - '@rollup/rollup-linux-arm-musleabihf': 4.45.1 - '@rollup/rollup-linux-arm64-gnu': 4.45.1 - '@rollup/rollup-linux-arm64-musl': 4.45.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.45.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.45.1 - '@rollup/rollup-linux-riscv64-gnu': 4.45.1 - '@rollup/rollup-linux-riscv64-musl': 4.45.1 - '@rollup/rollup-linux-s390x-gnu': 4.45.1 - '@rollup/rollup-linux-x64-gnu': 4.45.1 - '@rollup/rollup-linux-x64-musl': 4.45.1 - '@rollup/rollup-win32-arm64-msvc': 4.45.1 - '@rollup/rollup-win32-ia32-msvc': 4.45.1 - '@rollup/rollup-win32-x64-msvc': 4.45.1 + '@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 fsevents: 2.3.3 rxjs@7.8.2: @@ -5758,7 +5758,7 @@ snapshots: uglify-js@3.19.3: optional: true - undici-types@7.8.0: {} + undici-types@7.10.0: {} unified@11.0.5: dependencies: @@ -5811,7 +5811,7 @@ snapshots: use-deep-compare-effect@1.8.1(react@18.3.1): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 dequal: 2.0.3 react: 18.3.1 @@ -5833,7 +5833,7 @@ snapshots: '@types/unist': 3.0.3 vfile: 6.0.3 - vfile-message@4.0.2: + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 @@ -5841,7 +5841,7 @@ snapshots: vfile@6.0.3: dependencies: '@types/unist': 3.0.3 - vfile-message: 4.0.2 + vfile-message: 4.0.3 victory-vendor@37.3.6: dependencies: @@ -5860,20 +5860,20 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-package-version@1.1.0(vite@7.0.6(@types/node@24.1.0)(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.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)): dependencies: - vite: 7.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) - vite@7.0.6(@types/node@24.1.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1): + vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.45.1 + rollup: 4.46.2 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 24.1.0 + '@types/node': 24.2.0 fsevents: 2.3.3 jiti: 2.4.2 sass: 1.70.0 @@ -5960,7 +5960,7 @@ snapshots: zod@3.25.76: {} - zustand@5.0.6(@types/react@18.3.23)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): + zustand@5.0.7(@types/react@18.3.23)(immer@10.1.1)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): optionalDependencies: '@types/react': 18.3.23 immer: 10.1.1 diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts index 32d53a4102..1d83832719 100644 --- a/web/src/i18n/en/index.ts +++ b/web/src/i18n/en/index.ts @@ -1986,6 +1986,7 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do helpers: { address: 'Based on this address VPN network address will be defined, eg. 10.10.10.1/24 (and VPN network will be: 10.10.10.0/24). You can optionally specify multiple addresses separated by a comma. The first address is the primary address, and this one will be used for IP address assignment for devices. The other IP addresses are auxiliary and are not managed by Defguard.', + endpoint: 'Public IP address or domain name to which the remote peers/users will connect to. This address will be used in the configuration for the clients, but Defguard Gateways do not bind to this address.', gateway: 'Gateway public address, used by VPN users to connect', dns: 'Specify the DNS resolvers to query when the wireguard interface is up.', allowedIps: @@ -2024,7 +2025,7 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do label: 'Gateway VPN IP address and netmask', }, endpoint: { - label: 'Gateway address', + label: 'Gateway IP address or domain name', }, allowedIps: { label: 'Allowed Ips', diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index af7be4fe0c..7101a4be28 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -4774,6 +4774,7 @@ type RootTranslation = { * B​a​s​e​d​ ​o​n​ ​t​h​i​s​ ​a​d​d​r​e​s​s​ ​V​P​N​ ​n​e​t​w​o​r​k​ ​a​d​d​r​e​s​s​ ​w​i​l​l​ ​b​e​ ​d​e​f​i​n​e​d​,​ ​e​g​.​ ​1​0​.​1​0​.​1​0​.​1​/​2​4​ ​(​a​n​d​ ​V​P​N​ ​n​e​t​w​o​r​k​ ​w​i​l​l​ ​b​e​:​ ​1​0​.​1​0​.​1​0​.​0​/​2​4​)​.​ ​Y​o​u​ ​c​a​n​ ​o​p​t​i​o​n​a​l​l​y​ ​s​p​e​c​i​f​y​ ​m​u​l​t​i​p​l​e​ ​a​d​d​r​e​s​s​e​s​ ​s​e​p​a​r​a​t​e​d​ ​b​y​ ​a​ ​c​o​m​m​a​.​ ​T​h​e​ ​f​i​r​s​t​ ​a​d​d​r​e​s​s​ ​i​s​ ​t​h​e​ ​p​r​i​m​a​r​y​ ​a​d​d​r​e​s​s​,​ ​a​n​d​ ​t​h​i​s​ ​o​n​e​ ​w​i​l​l​ ​b​e​ ​u​s​e​d​ ​f​o​r​ ​I​P​ ​a​d​d​r​e​s​s​ ​a​s​s​i​g​n​m​e​n​t​ ​f​o​r​ ​d​e​v​i​c​e​s​.​ ​T​h​e​ ​o​t​h​e​r​ ​I​P​ ​a​d​d​r​e​s​s​e​s​ ​a​r​e​ ​a​u​x​i​l​i​a​r​y​ ​a​n​d​ ​a​r​e​ ​n​o​t​ ​m​a​n​a​g​e​d​ ​b​y​ ​D​e​f​g​u​a​r​d​. */ address: string + endpoint: string /** * G​a​t​e​w​a​y​ ​p​u​b​l​i​c​ ​a​d​d​r​e​s​s​,​ ​u​s​e​d​ ​b​y​ ​V​P​N​ ​u​s​e​r​s​ ​t​o​ ​c​o​n​n​e​c​t */ @@ -4852,7 +4853,7 @@ type RootTranslation = { } endpoint: { /** - * G​a​t​e​w​a​y​ ​a​d​d​r​e​s​s + * G​a​t​e​w​a​y​ ​I​P​ ​a​d​d​r​e​s​s​ ​o​r​ ​d​o​m​a​i​n​ ​n​a​m​e */ label: string } @@ -11369,6 +11370,7 @@ export type TranslationFunctions = { * Based on this address VPN network address will be defined, eg. 10.10.10.1/24 (and VPN network will be: 10.10.10.0/24). You can optionally specify multiple addresses separated by a comma. The first address is the primary address, and this one will be used for IP address assignment for devices. The other IP addresses are auxiliary and are not managed by Defguard. */ address: () => LocalizedString + endpoint: () => LocalizedString /** * Gateway public address, used by VPN users to connect */ @@ -11447,7 +11449,7 @@ export type TranslationFunctions = { } endpoint: { /** - * Gateway address + * Gateway IP address or domain name */ label: () => LocalizedString } diff --git a/web/src/i18n/pl/index.ts b/web/src/i18n/pl/index.ts index 61277e4ccb..2ced36f4b7 100644 --- a/web/src/i18n/pl/index.ts +++ b/web/src/i18n/pl/index.ts @@ -1771,6 +1771,7 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe helpers: { address: 'Na podstawie tego adresu będzie stworzona sieć VPN, np. 10.10.10.1/24 (sieć VPN: 10.10.10.0/24). Opcjonalnie możesz podać wiele adresów, oddzielając je przecinkiem. Pierwszy adres będzie adresem głównym i zostanie użyty do przypisywania adresów IP urządzeniom. Pozostałe adresy są dodatkowe i nie będą zarządzane przez Defguarda.', + endpoint: 'Publiczny adres IP lub domena internetowa, do której będą łączyć się użytkownicy/urządzenia. Ten adres zostanie użyty w konfiguracji klientów, ale Gatewaye Defguard nie wiążą się z tym adresem.', gateway: 'Adres publiczny Gatewaya, używany przez użytkowników VPN do łączenia się.', dns: 'Określ resolwery DNS, które mają odpytywać, gdy interfejs WireGuard jest aktywny.', @@ -1790,7 +1791,7 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe label: 'Adres i maska sieci VPN', }, endpoint: { - label: 'Adres gatewaya', + label: 'Adres IP lub domena internetowa Gatewaya', }, allowedIps: { label: 'Dozwolone adresy IP', diff --git a/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx b/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx index 4187f48063..fdac477563 100644 --- a/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx +++ b/web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx @@ -295,6 +295,9 @@ export const NetworkEditForm = () => { controller={{ control, name: 'endpoint' }} label={LL.networkConfiguration.form.fields.endpoint.label()} /> + +

{LL.networkConfiguration.form.helpers.endpoint()}

+
Date: Wed, 6 Aug 2025 13:17:07 +0200 Subject: [PATCH 04/84] add biometry enabled indicator in profile devices list (#1383) --- ...14e72dbffca5448b0aef3e0b0e3f60af6b5f7.json | 34 ++++++ .../src/db/models/biometric_auth.rs | 15 +++ crates/defguard_core/src/db/models/device.rs | 2 +- crates/defguard_core/src/db/models/mod.rs | 10 +- web/biome.json | 2 +- web/package.json | 4 +- web/pnpm-lock.yaml | 100 +++++++++--------- .../UserDevices/DeviceCard/DeviceCard.tsx | 35 +++++- .../UserDevices/DeviceCard/style.scss | 24 ++++- .../UserProfile/UserDevices/UserDevices.tsx | 3 + web/src/shared/defguard-ui | 2 +- web/src/shared/types.ts | 1 + 12 files changed, 170 insertions(+), 62 deletions(-) create mode 100644 .sqlx/query-e770f574ecb1c8fc700e610334914e72dbffca5448b0aef3e0b0e3f60af6b5f7.json diff --git a/.sqlx/query-e770f574ecb1c8fc700e610334914e72dbffca5448b0aef3e0b0e3f60af6b5f7.json b/.sqlx/query-e770f574ecb1c8fc700e610334914e72dbffca5448b0aef3e0b0e3f60af6b5f7.json new file mode 100644 index 0000000000..e08b8d9d28 --- /dev/null +++ b/.sqlx/query-e770f574ecb1c8fc700e610334914e72dbffca5448b0aef3e0b0e3f60af6b5f7.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT b.id, b.pub_key, b.device_id FROM biometric_auth as b JOIN device d ON b.device_id = d.id WHERE d.user_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "pub_key", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "e770f574ecb1c8fc700e610334914e72dbffca5448b0aef3e0b0e3f60af6b5f7" +} diff --git a/crates/defguard_core/src/db/models/biometric_auth.rs b/crates/defguard_core/src/db/models/biometric_auth.rs index 45e3e4d5ee..74d06fb944 100644 --- a/crates/defguard_core/src/db/models/biometric_auth.rs +++ b/crates/defguard_core/src/db/models/biometric_auth.rs @@ -54,6 +54,21 @@ impl BiometricAuth { .fetch_optional(executor) .await } + + pub(crate) async fn find_by_user_id<'e, E>( + executor: E, + user_id: Id, + ) -> Result, sqlx::Error> + where + E: PgExecutor<'e>, + { + query_as!( + Self, + "SELECT b.id, b.pub_key, b.device_id FROM biometric_auth as b JOIN device d ON b.device_id = d.id WHERE d.user_id = $1", &user_id + ) + .fetch_all(executor) + .await + } } #[derive(Clone, Debug)] diff --git a/crates/defguard_core/src/db/models/device.rs b/crates/defguard_core/src/db/models/device.rs index 84785f64f5..c45c371dae 100644 --- a/crates/defguard_core/src/db/models/device.rs +++ b/crates/defguard_core/src/db/models/device.rs @@ -46,7 +46,7 @@ pub struct DeviceConfig { // The type of a device: // User: A device of a user, which may be in multiple networks, e.g. a laptop -// Network: A standalone device added by a user permamently bound to one network, e.g. a printer +// Network: A stand-alone device added by a user permanently bound to one network, e.g. a printer #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema, Type)] #[sqlx(type_name = "device_type", rename_all = "snake_case")] pub enum DeviceType { diff --git a/crates/defguard_core/src/db/models/mod.rs b/crates/defguard_core/src/db/models/mod.rs index 369b114b7e..b2b2f00732 100644 --- a/crates/defguard_core/src/db/models/mod.rs +++ b/crates/defguard_core/src/db/models/mod.rs @@ -29,6 +29,8 @@ use std::collections::HashSet; use sqlx::{Error as SqlxError, PgConnection, PgPool, query_as}; use utoipa::ToSchema; +use crate::db::models::biometric_auth::BiometricAuth; + use self::{ device::UserDevice, user::{MFAMethod, User}, @@ -204,6 +206,7 @@ pub struct UserDetails { pub user: UserInfo, #[serde(default)] pub devices: Vec, + pub biometric_enabled_devices: Vec, #[serde(default)] pub security_keys: Vec, } @@ -212,11 +215,16 @@ impl UserDetails { pub async fn from_user(pool: &PgPool, user: &User) -> Result { let devices = user.user_devices(pool).await?; let security_keys = user.security_keys(pool).await?; - + let biometric_enabled_devices = BiometricAuth::find_by_user_id(pool, user.id) + .await? + .iter() + .map(|a| a.device_id) + .collect::>(); Ok(Self { user: UserInfo::from_user(pool, user).await?, devices, security_keys, + biometric_enabled_devices, }) } } diff --git a/web/biome.json b/web/biome.json index 47dc2cac51..e9cea92c71 100644 --- a/web/biome.json +++ b/web/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.1.1/schema.json", + "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json", "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, "files": { "ignoreUnknown": false, diff --git a/web/package.json b/web/package.json index f76351ddea..21e02132bb 100644 --- a/web/package.json +++ b/web/package.json @@ -113,7 +113,7 @@ }, "devDependencies": { "@babel/core": "^7.28.0", - "@biomejs/biome": "2.1.1", + "@biomejs/biome": "2.1.3", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@hookform/devtools": "^4.4.0", @@ -139,7 +139,7 @@ "sass": "~1.70.0", "standard-version": "^9.5.0", "type-fest": "^4.41.0", - "typescript": "~5.8.3", + "typescript": "~5.9.2", "vite": "^7.0.6", "vite-plugin-package-version": "^1.1.0" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 80ee19bb71..fc7698f978 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -202,7 +202,7 @@ importers: version: 1.2.4 typesafe-i18n: specifier: ^5.26.2 - version: 5.26.2(typescript@5.8.3) + version: 5.26.2(typescript@5.9.2) use-breakpoint: specifier: ^4.0.6 version: 4.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -217,8 +217,8 @@ importers: specifier: ^7.28.0 version: 7.28.0 '@biomejs/biome': - specifier: 2.1.1 - version: 2.1.1 + specifier: 2.1.3 + version: 2.1.3 '@csstools/css-parser-algorithms': specifier: ^3.0.5 version: 3.0.5(@csstools/css-tokenizer@3.0.4) @@ -295,8 +295,8 @@ importers: specifier: ^4.41.0 version: 4.41.0 typescript: - specifier: ~5.8.3 - version: 5.8.3 + specifier: ~5.9.2 + version: 5.9.2 vite: specifier: ^7.0.6 version: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) @@ -381,55 +381,55 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.1.1': - resolution: {integrity: sha512-HFGYkxG714KzG+8tvtXCJ1t1qXQMzgWzfvQaUjxN6UeKv+KvMEuliInnbZLJm6DXFXwqVi6446EGI0sGBLIYng==} + '@biomejs/biome@2.1.3': + resolution: {integrity: sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.1.1': - resolution: {integrity: sha512-2Muinu5ok4tWxq4nu5l19el48cwCY/vzvI7Vjbkf3CYIQkjxZLyj0Ad37Jv2OtlXYaLvv+Sfu1hFeXt/JwRRXQ==} + '@biomejs/cli-darwin-arm64@2.1.3': + resolution: {integrity: sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.1.1': - resolution: {integrity: sha512-cC8HM5lrgKQXLAK+6Iz2FrYW5A62pAAX6KAnRlEyLb+Q3+Kr6ur/sSuoIacqlp1yvmjHJqjYfZjPvHWnqxoEIA==} + '@biomejs/cli-darwin-x64@2.1.3': + resolution: {integrity: sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.1.1': - resolution: {integrity: sha512-/7FBLnTswu4jgV9ttI3AMIdDGqVEPIZd8I5u2D4tfCoj8rl9dnjrEQbAIDlWhUXdyWlFSz8JypH3swU9h9P+2A==} + '@biomejs/cli-linux-arm64-musl@2.1.3': + resolution: {integrity: sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.1.1': - resolution: {integrity: sha512-tw4BEbhAUkWPe4WBr6IX04DJo+2jz5qpPzpW/SWvqMjb9QuHY8+J0M23V8EPY/zWU4IG8Ui0XESapR1CB49Q7g==} + '@biomejs/cli-linux-arm64@2.1.3': + resolution: {integrity: sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.1.1': - resolution: {integrity: sha512-kUu+loNI3OCD2c12cUt7M5yaaSjDnGIksZwKnueubX6c/HWUyi/0mPbTBHR49Me3F0KKjWiKM+ZOjsmC+lUt9g==} + '@biomejs/cli-linux-x64-musl@2.1.3': + resolution: {integrity: sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.1.1': - resolution: {integrity: sha512-3WJ1GKjU7NzZb6RTbwLB59v9cTIlzjbiFLDB0z4376TkDqoNYilJaC37IomCr/aXwuU8QKkrYoHrgpSq5ffJ4Q==} + '@biomejs/cli-linux-x64@2.1.3': + resolution: {integrity: sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.1.1': - resolution: {integrity: sha512-vEHK0v0oW+E6RUWLoxb2isI3rZo57OX9ZNyyGH701fZPj6Il0Rn1f5DMNyCmyflMwTnIQstEbs7n2BxYSqQx4Q==} + '@biomejs/cli-win32-arm64@2.1.3': + resolution: {integrity: sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.1.1': - resolution: {integrity: sha512-i2PKdn70kY++KEF/zkQFvQfX1e8SkA8hq4BgC+yE9dZqyLzB/XStY2MvwI3qswlRgnGpgncgqe0QYKVS1blksg==} + '@biomejs/cli-win32-x64@2.1.3': + resolution: {integrity: sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -956,8 +956,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/types@0.1.23': - resolution: {integrity: sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==} + '@swc/types@0.1.24': + resolution: {integrity: sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==} '@tanstack/query-core@5.83.1': resolution: {integrity: sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==} @@ -2852,8 +2852,8 @@ packages: peerDependencies: typescript: '>=3.5.1' - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -3183,39 +3183,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@biomejs/biome@2.1.1': + '@biomejs/biome@2.1.3': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.1.1 - '@biomejs/cli-darwin-x64': 2.1.1 - '@biomejs/cli-linux-arm64': 2.1.1 - '@biomejs/cli-linux-arm64-musl': 2.1.1 - '@biomejs/cli-linux-x64': 2.1.1 - '@biomejs/cli-linux-x64-musl': 2.1.1 - '@biomejs/cli-win32-arm64': 2.1.1 - '@biomejs/cli-win32-x64': 2.1.1 - - '@biomejs/cli-darwin-arm64@2.1.1': + '@biomejs/cli-darwin-arm64': 2.1.3 + '@biomejs/cli-darwin-x64': 2.1.3 + '@biomejs/cli-linux-arm64': 2.1.3 + '@biomejs/cli-linux-arm64-musl': 2.1.3 + '@biomejs/cli-linux-x64': 2.1.3 + '@biomejs/cli-linux-x64-musl': 2.1.3 + '@biomejs/cli-win32-arm64': 2.1.3 + '@biomejs/cli-win32-x64': 2.1.3 + + '@biomejs/cli-darwin-arm64@2.1.3': optional: true - '@biomejs/cli-darwin-x64@2.1.1': + '@biomejs/cli-darwin-x64@2.1.3': optional: true - '@biomejs/cli-linux-arm64-musl@2.1.1': + '@biomejs/cli-linux-arm64-musl@2.1.3': optional: true - '@biomejs/cli-linux-arm64@2.1.1': + '@biomejs/cli-linux-arm64@2.1.3': optional: true - '@biomejs/cli-linux-x64-musl@2.1.1': + '@biomejs/cli-linux-x64-musl@2.1.3': optional: true - '@biomejs/cli-linux-x64@2.1.1': + '@biomejs/cli-linux-x64@2.1.3': optional: true - '@biomejs/cli-win32-arm64@2.1.1': + '@biomejs/cli-win32-arm64@2.1.3': optional: true - '@biomejs/cli-win32-x64@2.1.1': + '@biomejs/cli-win32-x64@2.1.3': optional: true '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': @@ -3620,7 +3620,7 @@ snapshots: '@swc/core@1.13.3': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.23 + '@swc/types': 0.1.24 optionalDependencies: '@swc/core-darwin-arm64': 1.13.3 '@swc/core-darwin-x64': 1.13.3 @@ -3635,7 +3635,7 @@ snapshots: '@swc/counter@0.1.3': {} - '@swc/types@0.1.23': + '@swc/types@0.1.24': dependencies: '@swc/counter': 0.1.3 @@ -5749,11 +5749,11 @@ snapshots: typedarray@0.0.6: {} - typesafe-i18n@5.26.2(typescript@5.8.3): + typesafe-i18n@5.26.2(typescript@5.9.2): dependencies: - typescript: 5.8.3 + typescript: 5.9.2 - typescript@5.8.3: {} + typescript@5.9.2: {} uglify-js@3.19.3: optional: true diff --git a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx b/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx index cf73aba967..1cfcafd433 100644 --- a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx +++ b/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx @@ -1,11 +1,11 @@ import './style.scss'; import classNames from 'classnames'; +import clsx from 'clsx'; import dayjs from 'dayjs'; import type { TargetAndTransition } from 'framer-motion'; import { isUndefined, orderBy } from 'lodash-es'; import { useMemo, useState } from 'react'; - import { useI18nContext } from '../../../../../i18n/i18n-react'; import { ListCellTags } from '../../../../../shared/components/Layout/ListCellTags/ListCellTags'; import IconClip from '../../../../../shared/components/svg/IconClip'; @@ -21,6 +21,7 @@ import { EditButtonOption } from '../../../../../shared/defguard-ui/components/L import { EditButtonOptionStyleVariant } from '../../../../../shared/defguard-ui/components/Layout/EditButton/types'; import { Label } from '../../../../../shared/defguard-ui/components/Layout/Label/Label'; import { LimitedText } from '../../../../../shared/defguard-ui/components/Layout/LimitedText/LimitedText'; +import { ListCellText } from '../../../../../shared/defguard-ui/components/Layout/ListCellText/ListCellText'; import { NoData } from '../../../../../shared/defguard-ui/components/Layout/NoData/NoData'; import { useAppStore } from '../../../../../shared/hooks/store/useAppStore'; import { useUserProfileStore } from '../../../../../shared/hooks/store/useUserProfileStore'; @@ -40,10 +41,11 @@ const formatDate = (date: string): string => { interface Props { device: Device; + biometricEnabled: boolean; modifiable: boolean; } -export const DeviceCard = ({ device, modifiable }: Props) => { +export const DeviceCard = ({ device, modifiable, biometricEnabled }: Props) => { const [hovered, setHovered] = useState(false); const [expanded, setExpanded] = useState(false); const { LL } = useI18nContext(); @@ -105,9 +107,14 @@ export const DeviceCard = ({ device, modifiable }: Props) => { onMouseOut={() => setHovered(false)} >
-
+
-

{device.name}

+ {biometricEnabled && } +
@@ -255,7 +262,7 @@ const DeviceLocation = ({
-

{network_name}

+ {!isUndefined(network_gateway_ip) && }
@@ -312,3 +319,21 @@ const ExpandButton = ({ expanded, onClick }: ExpandButtonProps) => { ); }; + +const IconBiometry = () => { + return ( + + + + ); +}; diff --git a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss b/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss index 630c435a38..b30a20f760 100644 --- a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss +++ b/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss @@ -15,7 +15,6 @@ h3 { @include small-header; - @include text-overflow-dots; user-select: none; } @@ -32,10 +31,32 @@ max-width: 120px; } + .list-cell-text { + display: flex; + flex-flow: row; + align-items: center; + justify-content: flex-start; + + h3 { + display: inline-block; + } + } + .main-info { & > header { grid-template-rows: 40px; grid-template-columns: 40px 1fr 55px; + max-width: 100%; + overflow: hidden; + + &.biometry { + grid-template-columns: 40px 12px 1fr 55px; + } + + .biometry-icon { + width: 12px; + height: 12px; + } & > .avatar-icon { grid-row: 1; @@ -59,6 +80,7 @@ .location { max-width: 100%; overflow: hidden; + & > header { grid-template-rows: 40px; grid-template-columns: 22px 1fr; diff --git a/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx b/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx index 0e172be584..e01ae311ce 100644 --- a/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx +++ b/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx @@ -48,6 +48,9 @@ export const UserDevices = () => { key={device.id} device={device} modifiable={canManageDevices} + biometricEnabled={userProfile.biometric_enabled_devices.includes( + device.id, + )} /> ))}
diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui index 99042918cf..e8958406e5 160000 --- a/web/src/shared/defguard-ui +++ b/web/src/shared/defguard-ui @@ -1 +1 @@ -Subproject commit 99042918cf99655c2598c1d7e18d8e19b4abdf2c +Subproject commit e8958406e528679302751bdf10c0fa68d73e5897 diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts index 5035b7823f..b8d846af63 100644 --- a/web/src/shared/types.ts +++ b/web/src/shared/types.ts @@ -61,6 +61,7 @@ export type UserProfile = { user: User; devices: Device[]; security_keys: SecurityKey[]; + biometric_enabled_devices: number[]; }; export interface OAuth2AuthorizedApps { From 22fbd095fd2b1a6e4ee8a2228c3dadf1b6c82988 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 7 Aug 2025 11:45:06 +0200 Subject: [PATCH 05/84] Avoid HTTP return code: 204 No Content (#1384) --- Cargo.lock | 85 ++++++++++--------- .../src/db/models/biometric_auth.rs | 3 +- crates/defguard_core/src/db/models/mod.rs | 1 + .../defguard_core/src/db/models/settings.rs | 1 + .../defguard_core/src/db/models/wireguard.rs | 3 + .../activity_log_stream_manager.rs | 5 +- .../activity_log_stream/http_stream.rs | 2 +- .../defguard_core/src/enterprise/ldap/mod.rs | 1 - crates/defguard_core/src/grpc/client_mfa.rs | 25 +++--- crates/defguard_core/src/handlers/updates.rs | 22 +++-- .../defguard_core/src/handlers/wireguard.rs | 2 +- crates/defguard_core/src/lib.rs | 2 +- crates/defguard_core/src/updates.rs | 10 ++- crates/defguard_core/src/utility_thread.rs | 2 +- crates/defguard_event_router/src/lib.rs | 2 +- web/src/shared/hooks/api/api.ts | 2 +- 16 files changed, 89 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 795ec0a755..50a819a298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -135,22 +135,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -613,9 +613,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -708,9 +708,9 @@ checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18" [[package]] name = "clap" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", "clap_derive", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", @@ -1385,9 +1385,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "eax" @@ -1524,9 +1524,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2520,9 +2520,9 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb2a0354e9ece2fcdcf9fa53417f6de587230c0c248068eb058fa26c4a753179" +checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" dependencies = [ "async-trait", "base64 0.22.1", @@ -2540,7 +2540,7 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tokio-native-tls", "url", @@ -2615,9 +2615,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -3764,9 +3764,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -3987,9 +3987,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -4030,9 +4030,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "log", "once_cell", @@ -4275,9 +4275,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -5080,9 +5080,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -5092,9 +5092,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "slab", - "socket2 0.5.10", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5142,9 +5142,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -6097,7 +6097,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -6133,10 +6133,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -6454,9 +6455,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/crates/defguard_core/src/db/models/biometric_auth.rs b/crates/defguard_core/src/db/models/biometric_auth.rs index 74d06fb944..58acc2fa02 100644 --- a/crates/defguard_core/src/db/models/biometric_auth.rs +++ b/crates/defguard_core/src/db/models/biometric_auth.rs @@ -29,6 +29,7 @@ pub struct BiometricAuth { } impl BiometricAuth { + #[must_use] pub fn new(device_id: Id, pub_key: String) -> Self { Self { id: NoId, @@ -95,8 +96,8 @@ impl BiometricChallenge { } let challenge = gen_alphanumeric(44); Ok(Self { - challenge, auth_pub_key, + challenge, }) } diff --git a/crates/defguard_core/src/db/models/mod.rs b/crates/defguard_core/src/db/models/mod.rs index b2b2f00732..bbf459faeb 100644 --- a/crates/defguard_core/src/db/models/mod.rs +++ b/crates/defguard_core/src/db/models/mod.rs @@ -88,6 +88,7 @@ pub struct GroupDiff { } impl GroupDiff { + #[must_use] pub fn changed(&self) -> bool { !self.added.is_empty() || !self.removed.is_empty() } diff --git a/crates/defguard_core/src/db/models/settings.rs b/crates/defguard_core/src/db/models/settings.rs index 9eb88aa8a5..deb1ff79b4 100644 --- a/crates/defguard_core/src/db/models/settings.rs +++ b/crates/defguard_core/src/db/models/settings.rs @@ -326,6 +326,7 @@ impl Settings { && self.smtp_sender != Some(String::new()) } + #[must_use] pub fn ldap_using_username_as_rdn(&self) -> bool { self.ldap_user_rdn_attr .as_deref() diff --git a/crates/defguard_core/src/db/models/wireguard.rs b/crates/defguard_core/src/db/models/wireguard.rs index d58d36f1bc..580fd82e4c 100644 --- a/crates/defguard_core/src/db/models/wireguard.rs +++ b/crates/defguard_core/src/db/models/wireguard.rs @@ -509,6 +509,7 @@ impl WireguardNetwork { } /// Checks if all device addresses are contained in at least one of the network addresses + #[must_use] pub fn contains_all(&self, addresses: &[IpAddr]) -> bool { addresses .iter() @@ -516,6 +517,7 @@ impl WireguardNetwork { } /// Finds [`IpNetwork`] containing given [`IpAddr`] + #[must_use] pub fn get_containing_network(&self, addr: IpAddr) -> Option { self.address.iter().find(|net| net.contains(addr)).copied() } @@ -1273,6 +1275,7 @@ impl WireguardNetwork { Ok(()) } + #[must_use] pub fn mfa_enabled(&self) -> bool { match self.location_mfa_mode { LocationMfaMode::Internal | LocationMfaMode::External => true, diff --git a/crates/defguard_core/src/enterprise/activity_log_stream/activity_log_stream_manager.rs b/crates/defguard_core/src/enterprise/activity_log_stream/activity_log_stream_manager.rs index 7870c9f4f9..3faff81ab0 100644 --- a/crates/defguard_core/src/enterprise/activity_log_stream/activity_log_stream_manager.rs +++ b/crates/defguard_core/src/enterprise/activity_log_stream/activity_log_stream_manager.rs @@ -66,13 +66,12 @@ pub async fn run_activity_log_stream_manager( cancel_token.clone(), )); } - }; + } } else { error!( "Failed to deserialize config for activity log stream {0}", &activity_log_stream.name ); - continue; } } } else { @@ -87,7 +86,7 @@ pub async fn run_activity_log_stream_manager( // - streaming task terminated early loop { tokio::select! { - _ = notification.notified() => { + () = notification.notified() => { info!( "Activity log stream manager configuration refresh notification received, reloading streaming tasks." ); diff --git a/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs b/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs index d0b3747bb2..d835607f83 100644 --- a/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs +++ b/crates/defguard_core/src/enterprise/activity_log_stream/http_stream.rs @@ -38,7 +38,7 @@ pub(super) async fn run_http_stream_task( }; loop { tokio::select! { - _ = cancel_token.cancelled() => { + () = cancel_token.cancelled() => { debug!("Activity log stream ({stream_name}) task received cancellation signal."); break; }, diff --git a/crates/defguard_core/src/enterprise/ldap/mod.rs b/crates/defguard_core/src/enterprise/ldap/mod.rs index 0057a1ac9e..c4e5369f54 100644 --- a/crates/defguard_core/src/enterprise/ldap/mod.rs +++ b/crates/defguard_core/src/enterprise/ldap/mod.rs @@ -389,7 +389,6 @@ impl LDAPConnection { ); self.sync_user_data(user, pool).await?; debug!("User {user} data synchronized"); - continue; } } diff --git a/crates/defguard_core/src/grpc/client_mfa.rs b/crates/defguard_core/src/grpc/client_mfa.rs index d92abb0f75..64302e44f5 100644 --- a/crates/defguard_core/src/grpc/client_mfa.rs +++ b/crates/defguard_core/src/grpc/client_mfa.rs @@ -163,9 +163,10 @@ impl ClientMfaServer { match (&location.location_mfa_mode, selected_method) { // MFA enabled status is already verified (LocationMfaMode::Disabled, _) => unreachable!(), - (LocationMfaMode::Internal, MfaMethod::Totp) - | (LocationMfaMode::Internal, MfaMethod::Email) - | (LocationMfaMode::Internal, MfaMethod::Biometric) => { + ( + LocationMfaMode::Internal, + MfaMethod::Totp | MfaMethod::Email | MfaMethod::Biometric, + ) => { debug!("Location uses internal MFA. Selected method: {selected_method}") } (LocationMfaMode::External, MfaMethod::Oidc) => { @@ -173,7 +174,8 @@ impl ClientMfaServer { } _ => { error!( - "Selected MFA method ({selected_method}) is not supported by location {location} which uses {}", + "Selected MFA method ({selected_method}) is not supported by location \ + {location} which uses {}", location.location_mfa_mode ); @@ -473,7 +475,8 @@ impl ClientMfaServer { MfaMethod::Oidc => { if !*openid_auth_completed { debug!( - "User {user} tried to finish OIDC MFA login but they haven't completed the OIDC authentication yet." + "User {user} tried to finish OIDC MFA login but they haven't completed \ + the OIDC authentication yet." ); self.emit_event(BidiStreamEvent { context, @@ -482,18 +485,20 @@ impl ClientMfaServer { location: location.clone(), device: device.clone(), method: *method, - message: "tried to finish OIDC MFA login but they haven't completed OIDC authentication yet".to_string() + message: "tried to finish OIDC MFA login but they haven't \ + completed OIDC authentication yet" + .to_string(), }, )), })?; return Err(Status::failed_precondition( "OIDC authentication not completed yet", )); - } else { - debug!( - "User {user} is trying to finish OIDC MFA login and the OIDC authentication has already been completed; proceeding." - ); } + debug!( + "User {user} is trying to finish OIDC MFA login and the OIDC authentication \ + has already been completed; proceeding." + ); } } diff --git a/crates/defguard_core/src/handlers/updates.rs b/crates/defguard_core/src/handlers/updates.rs index 10e9584ae5..da96076b3e 100644 --- a/crates/defguard_core/src/handlers/updates.rs +++ b/crates/defguard_core/src/handlers/updates.rs @@ -1,5 +1,5 @@ use axum::http::StatusCode; -use serde_json::json; +use serde_json::{Value, json}; use super::{ApiResponse, ApiResult}; use crate::{ @@ -12,18 +12,16 @@ pub async fn check_new_version(_admin: AdminRole, session: SessionInfo) -> ApiRe "User {} is checking if there is a new version available", session.user.username ); - let update = get_update(); - if let Some(update) = update.as_ref() { + let json = if let Some(update) = get_update().as_ref() { debug!("A new version is available, returning the update information"); - Ok(ApiResponse { - json: json!(update), - status: StatusCode::OK, - }) + json!(update) } else { debug!("No new version available"); - Ok(ApiResponse { - json: serde_json::json!({ "message": "No updates available" }), - status: StatusCode::NO_CONTENT, - }) - } + // Front-end expects empty JSON. + Value::Null + }; + Ok(ApiResponse { + json, + status: StatusCode::OK, + }) } diff --git a/crates/defguard_core/src/handlers/wireguard.rs b/crates/defguard_core/src/handlers/wireguard.rs index ab4f3d9852..b603f81294 100644 --- a/crates/defguard_core/src/handlers/wireguard.rs +++ b/crates/defguard_core/src/handlers/wireguard.rs @@ -1079,7 +1079,7 @@ pub(crate) async fn delete_device( ); } } - }; + } transaction.commit().await?; info!("User {username} deleted device {device_id}"); diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 1f8d578d1e..fec31d6c10 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -862,7 +862,7 @@ pub async fn init_vpn_location( return Err(anyhow!( "Failed to initialize first VPN location. Location already exists." )); - }; + } // create a new network WireguardNetwork::new( diff --git a/crates/defguard_core/src/updates.rs b/crates/defguard_core/src/updates.rs index 934a2a44c9..2cbfb96d71 100644 --- a/crates/defguard_core/src/updates.rs +++ b/crates/defguard_core/src/updates.rs @@ -1,4 +1,4 @@ -use std::env; +use std::{env, time::Duration}; use chrono::NaiveDate; use semver::Version; @@ -8,6 +8,7 @@ use crate::global_value; const PRODUCT_NAME: &str = "Defguard"; const UPDATES_URL: &str = "https://pkgs.defguard.net/api/update/check"; const VERSION: &str = env!("CARGO_PKG_VERSION"); +const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); #[derive(Deserialize, Debug, Serialize)] #[cfg_attr(test, derive(Clone))] @@ -31,21 +32,22 @@ async fn fetch_update() -> Result { let response = reqwest::Client::new() .post(UPDATES_URL) .json(&body) - .timeout(std::time::Duration::from_secs(10)) + .timeout(REQUEST_TIMEOUT) .send() .await?; Ok(response.json::().await?) } pub(crate) async fn do_new_version_check() -> Result<(), anyhow::Error> { - debug!("Checking for new version of Defguard ..."); + debug!("Checking for new version of Defguard."); let update = fetch_update().await?; let current_version = Version::parse(VERSION)?; let new_version = Version::parse(&update.version)?; if new_version > current_version { if update.critical { warn!( - "There is a new critical Defguard update available: {} (Released on {}). It's recommended to update as soon as possible.", + "There is a new critical Defguard update available: {} (Released on {}). It's \ + recommended to update as soon as possible.", update.version, update.release_date ); } else { diff --git a/crates/defguard_core/src/utility_thread.rs b/crates/defguard_core/src/utility_thread.rs index c972e819ce..c552bf2576 100644 --- a/crates/defguard_core/src/utility_thread.rs +++ b/crates/defguard_core/src/utility_thread.rs @@ -83,7 +83,7 @@ pub async fn run_utility_thread( .await { error!("Failed to check expired ACL rules: {err}"); - }; + } }; directory_sync_task().await; diff --git a/crates/defguard_event_router/src/lib.rs b/crates/defguard_event_router/src/lib.rs index 8ecc8c0c26..c6807d9106 100644 --- a/crates/defguard_event_router/src/lib.rs +++ b/crates/defguard_event_router/src/lib.rs @@ -159,7 +159,7 @@ impl EventRouter { Event::Grpc(grpc_event) => self.handle_grpc_event(*grpc_event)?, Event::Bidi(bidi_event) => self.handle_bidi_event(bidi_event)?, Event::Internal(internal_event) => self.handle_internal_event(*internal_event)?, - }; + } } } } diff --git a/web/src/shared/hooks/api/api.ts b/web/src/shared/hooks/api/api.ts index 4533c84bbb..40aa94e3c2 100644 --- a/web/src/shared/hooks/api/api.ts +++ b/web/src/shared/hooks/api/api.ts @@ -425,7 +425,7 @@ export const buildApi = (client: Axios): Api => { const getNewVersion: Api['getNewVersion'] = () => client.get('/updates').then((res) => { - if (res.status === 204) { + if (res.data === null) { return null; } return res.data as UpdateInfo; From af9469aaf0aed3f7306932420c92423f85479696 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:14:41 +0200 Subject: [PATCH 06/84] fix overview stats period labels (#1393) * remove unused component * remove duplicate time filter --- web/src/i18n/i18n-types.ts | 6 ++ .../overview/OverviewStats/OverviewStats.tsx | 4 +- .../OverviewStatsFilterSelect.tsx | 74 ------------------- .../overview/hooks/store/useOverviewStore.ts | 1 - web/src/shared/types.ts | 1 - 5 files changed, 8 insertions(+), 78 deletions(-) delete mode 100644 web/src/pages/overview/OverviewStatsFilterSelect/OverviewStatsFilterSelect.tsx diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index 7101a4be28..ca8c7751ba 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -4774,6 +4774,9 @@ type RootTranslation = { * B​a​s​e​d​ ​o​n​ ​t​h​i​s​ ​a​d​d​r​e​s​s​ ​V​P​N​ ​n​e​t​w​o​r​k​ ​a​d​d​r​e​s​s​ ​w​i​l​l​ ​b​e​ ​d​e​f​i​n​e​d​,​ ​e​g​.​ ​1​0​.​1​0​.​1​0​.​1​/​2​4​ ​(​a​n​d​ ​V​P​N​ ​n​e​t​w​o​r​k​ ​w​i​l​l​ ​b​e​:​ ​1​0​.​1​0​.​1​0​.​0​/​2​4​)​.​ ​Y​o​u​ ​c​a​n​ ​o​p​t​i​o​n​a​l​l​y​ ​s​p​e​c​i​f​y​ ​m​u​l​t​i​p​l​e​ ​a​d​d​r​e​s​s​e​s​ ​s​e​p​a​r​a​t​e​d​ ​b​y​ ​a​ ​c​o​m​m​a​.​ ​T​h​e​ ​f​i​r​s​t​ ​a​d​d​r​e​s​s​ ​i​s​ ​t​h​e​ ​p​r​i​m​a​r​y​ ​a​d​d​r​e​s​s​,​ ​a​n​d​ ​t​h​i​s​ ​o​n​e​ ​w​i​l​l​ ​b​e​ ​u​s​e​d​ ​f​o​r​ ​I​P​ ​a​d​d​r​e​s​s​ ​a​s​s​i​g​n​m​e​n​t​ ​f​o​r​ ​d​e​v​i​c​e​s​.​ ​T​h​e​ ​o​t​h​e​r​ ​I​P​ ​a​d​d​r​e​s​s​e​s​ ​a​r​e​ ​a​u​x​i​l​i​a​r​y​ ​a​n​d​ ​a​r​e​ ​n​o​t​ ​m​a​n​a​g​e​d​ ​b​y​ ​D​e​f​g​u​a​r​d​. */ address: string + /** + * P​u​b​l​i​c​ ​I​P​ ​a​d​d​r​e​s​s​ ​o​r​ ​d​o​m​a​i​n​ ​n​a​m​e​ ​t​o​ ​w​h​i​c​h​ ​t​h​e​ ​r​e​m​o​t​e​ ​p​e​e​r​s​/​u​s​e​r​s​ ​w​i​l​l​ ​c​o​n​n​e​c​t​ ​t​o​.​ ​T​h​i​s​ ​a​d​d​r​e​s​s​ ​w​i​l​l​ ​b​e​ ​u​s​e​d​ ​i​n​ ​t​h​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​o​r​ ​t​h​e​ ​c​l​i​e​n​t​s​,​ ​b​u​t​ ​D​e​f​g​u​a​r​d​ ​G​a​t​e​w​a​y​s​ ​d​o​ ​n​o​t​ ​b​i​n​d​ ​t​o​ ​t​h​i​s​ ​a​d​d​r​e​s​s​. + */ endpoint: string /** * G​a​t​e​w​a​y​ ​p​u​b​l​i​c​ ​a​d​d​r​e​s​s​,​ ​u​s​e​d​ ​b​y​ ​V​P​N​ ​u​s​e​r​s​ ​t​o​ ​c​o​n​n​e​c​t @@ -11370,6 +11373,9 @@ export type TranslationFunctions = { * Based on this address VPN network address will be defined, eg. 10.10.10.1/24 (and VPN network will be: 10.10.10.0/24). You can optionally specify multiple addresses separated by a comma. The first address is the primary address, and this one will be used for IP address assignment for devices. The other IP addresses are auxiliary and are not managed by Defguard. */ address: () => LocalizedString + /** + * Public IP address or domain name to which the remote peers/users will connect to. This address will be used in the configuration for the clients, but Defguard Gateways do not bind to this address. + */ endpoint: () => LocalizedString /** * Gateway public address, used by VPN users to connect diff --git a/web/src/pages/overview/OverviewStats/OverviewStats.tsx b/web/src/pages/overview/OverviewStats/OverviewStats.tsx index b2ff3a5264..f4ebc26d88 100644 --- a/web/src/pages/overview/OverviewStats/OverviewStats.tsx +++ b/web/src/pages/overview/OverviewStats/OverviewStats.tsx @@ -14,7 +14,7 @@ import { NetworkSpeed } from '../../../shared/defguard-ui/components/Layout/Netw import { NetworkDirection } from '../../../shared/defguard-ui/components/Layout/NetworkSpeed/types'; import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; import type { WireguardNetworkStats } from '../../../shared/types'; -import { useOverviewStore } from '../hooks/store/useOverviewStore'; +import { useOverviewTimeSelection } from '../../overview-index/components/hooks/useOverviewTimeSelection'; import { NetworkUsageChart } from '../OverviewConnectedUsers/shared/components/NetworkUsageChart/NetworkUsageChart'; import { networkTrafficToChartData } from './utils'; @@ -24,7 +24,7 @@ interface Props { export const OverviewStats = forwardRef( ({ networkStats }, ref) => { - const filterValue = useOverviewStore((state) => state.statsFilter); + const { from: filterValue } = useOverviewTimeSelection(); const peakDownload = useMemo(() => { const sorted = orderBy(networkStats.transfer_series, (stats) => stats.download, [ 'desc', diff --git a/web/src/pages/overview/OverviewStatsFilterSelect/OverviewStatsFilterSelect.tsx b/web/src/pages/overview/OverviewStatsFilterSelect/OverviewStatsFilterSelect.tsx deleted file mode 100644 index 493455b749..0000000000 --- a/web/src/pages/overview/OverviewStatsFilterSelect/OverviewStatsFilterSelect.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useCallback } from 'react'; - -import { Select } from '../../../shared/defguard-ui/components/Layout/Select/Select'; -import { - type SelectOption, - type SelectSelectedValue, - SelectSizeVariant, -} from '../../../shared/defguard-ui/components/Layout/Select/types'; -import { useOverviewStore } from '../hooks/store/useOverviewStore'; - -export const OverviewStatsFilterSelect = () => { - const filterValue = useOverviewStore((state) => state.statsFilter); - const setOverviewStore = useOverviewStore((state) => state.setState); - - const renderSelected = useCallback((selected: number): SelectSelectedValue => { - return { - key: selected, - displayValue: `${selected}H`, - }; - }, []); - - return ( -