diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index ca20e0e2e8..c9d6481721 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -5,6 +5,7 @@ use sqlx::{PgExecutor, PgPool, Type, query, query_as}; use struct_patch::Patch; use thiserror::Error; use tracing::{debug, info, warn}; +use utoipa::ToSchema; use uuid::Uuid; use crate::{global_value, secret::SecretStringWrapper}; @@ -51,7 +52,7 @@ pub enum SmtpEncryption { ImplicitTls, } -#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Type, Debug, Default, Copy)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Serialize, PartialEq, ToSchema, Type)] #[sqlx(type_name = "openid_username_handling", rename_all = "snake_case")] pub enum OpenidUsernameHandling { #[default] diff --git a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs index 575679ab3c..8279b29a2c 100644 --- a/crates/defguard_core/src/enterprise/db/models/openid_provider.rs +++ b/crates/defguard_core/src/enterprise/db/models/openid_provider.rs @@ -38,7 +38,7 @@ impl From for DirectorySyncUserBehavior { "disable" => DirectorySyncUserBehavior::Disable, "delete" => DirectorySyncUserBehavior::Delete, _ => { - warn!("Unknown directory sync user behavior passed: {}", s); + warn!("Unknown directory sync user behavior passed: {s}"); DirectorySyncUserBehavior::Keep } } @@ -79,7 +79,7 @@ impl From for DirectorySyncTarget { "users" => DirectorySyncTarget::Users, "groups" => DirectorySyncTarget::Groups, _ => { - warn!("Unknown directory sync target passed: {}", s); + warn!("Unknown directory sync target passed: {s}"); DirectorySyncTarget::All } } diff --git a/crates/defguard_core/src/enterprise/directory_sync/mod.rs b/crates/defguard_core/src/enterprise/directory_sync/mod.rs index ad3fec42a7..ea73b0b6d1 100644 --- a/crates/defguard_core/src/enterprise/directory_sync/mod.rs +++ b/crates/defguard_core/src/enterprise/directory_sync/mod.rs @@ -253,7 +253,8 @@ impl DirectorySyncClient { ) { (Some(key), Some(email), Some(admin_email)) => { debug!( - "Google directory has all the configuration needed, proceeding with creating the sync client" + "Google directory has all the configuration needed, proceeding with \ + creating the sync client" ); let client = google::GoogleDirectorySync::new(key, email, admin_email); debug!("Google directory sync client created"); @@ -279,7 +280,8 @@ impl DirectorySyncClient { provider_settings.okta_dirsync_client_id.as_ref(), ) { debug!( - "Okta directory has all the configuration needed, proceeding with creating the sync client" + "Okta directory has all the configuration needed, proceeding with creating \ + the sync client" ); let client = okta::OktaDirectorySync::new(jwk, client_id, &provider_settings.base_url); @@ -287,7 +289,8 @@ impl DirectorySyncClient { Ok(Self::Okta(client)) } else { Err(DirectorySyncError::InvalidProviderConfiguration( - "Okta provider is not configured correctly for Directory Sync. Okta private key or client id is missing." + "Okta provider is not configured correctly for Directory Sync. Okta \ + private key or client id is missing." .to_string(), )) } @@ -296,7 +299,8 @@ impl DirectorySyncClient { debug!("JumpCloud directory sync provider selected"); if let Some(key) = provider_settings.jumpcloud_api_key.as_ref() { debug!( - "JumpCloud directory has all the configuration needed, proceeding with creating the sync client" + "JumpCloud directory has all the configuration needed, proceeding with \ + creating the sync client" ); let client = jumpcloud::JumpCloudDirectorySync::new(key.clone()); debug!("JumpCloud directory sync client created"); @@ -446,7 +450,8 @@ async fn create_and_add_to_group( pool: &PgPool, ) -> Result<(), DirectorySyncError> { debug!( - "Creating group {} if it doesn't exist and adding user {group_name} to it if they are not already a member", + "Creating group {} if it doesn't exist and adding user {group_name} to it if they are not \ + already a member", user.email ); let group = if let Some(group) = Group::find_by_name(pool, group_name).await? { @@ -492,7 +497,8 @@ async fn sync_all_users_groups( match directory_sync.get_group_members(group, all_users).await { Ok(members) => { debug!( - "Group {} has {} members in the directory, adding them to the user-group mapping", + "Group {} has {} members in the directory, adding them to the user-group \ + mapping", group.name, members.len() ); @@ -541,13 +547,15 @@ async fn sync_all_users_groups( if current_group.is_admin { if admin_count == 1 { error!( - "User {} is the last admin in the system, can't remove them from an admin group {}", + "User {} is the last admin in the system, can't remove them from an \ + admin group {}", user.email, current_group.name ); continue; } debug!( - "Removing user {} from group {} as they are not a member of it in the directory", + "Removing user {} from group {} as they are not a member of it in the \ + directory", user.email, current_group.name ); user.remove_from_group(&mut *transaction, current_group) @@ -555,7 +563,8 @@ async fn sync_all_users_groups( admin_count -= 1; } else { debug!( - "Removing user {} from group {} as they are not a member of it in the directory", + "Removing user {} from group {} as they are not a member of it in the \ + directory", user.email, current_group.name ); user.remove_from_group(&mut *transaction, current_group) @@ -674,12 +683,14 @@ async fn sync_all_users_state( match &directory_user.user_details { None => { error!( - "Missing directory user details for user {directory_user:?}. Unable to create missing Defguard user." + "Missing directory user details for user {directory_user:?}. Unable to \ + create missing Defguard user." ); } Some(details) => { debug!( - "User {directory_user:?} exists in directory but not in Defguard. Creating new Defguard user.", + "User {directory_user:?} exists in directory but not in Defguard. Creating \ + new Defguard user.", ); // Extract the username from the email address @@ -728,11 +739,10 @@ async fn sync_all_users_state( .collect::>>(); debug!( - "There are {} users missing from the directory but present in Defguard, \ - deciding what to do next based on the following settings: user action: {}, admin action: {}", + "There are {} users missing from the directory but present in Defguard, deciding what to \ + do next based on the following settings: user action: {user_behavior}, admin action: \ + {admin_behavior}", missing_directory_users.len(), - user_behavior, - admin_behavior ); // Keep the admin count to prevent deleting the last admin let mut admin_count = User::find_admins(&mut *transaction).await?.len(); @@ -810,7 +820,8 @@ async fn sync_all_users_state( DirectorySyncUserBehavior::Disable => { if user.is_active { info!( - "Disabling user {} because they are not present in the directory and the user behavior setting is set to disable", + "Disabling user {} because they are not present in the directory and \ + the user behavior setting is set to disable", user.email ); disable_user(&mut user, &mut transaction, wg_tx).await.map_err(|err| { @@ -887,7 +898,7 @@ async fn sync_inactive_directory_users( .collect(); debug!( - "There are {} active Defguard users disabled in the directory. Disabling them in Defguard...", + "There are {} active Defguard users disabled in the directory. Disabling them in Defguard.", users_to_disable.len() ); @@ -933,7 +944,7 @@ async fn sync_active_directory_users( .collect(); debug!( - "There are {} inactive Defguard users enabled in the directory. Enabling them in Defguard...", + "There are {} inactive Defguard users enabled in the directory. Enabling them in Defguard.", users_to_enable.len() ); for mut user in users_to_enable { @@ -957,7 +968,8 @@ async fn sync_active_directory_users( // The default inverval for the directory sync job const DIRECTORY_SYNC_INTERVAL: u64 = 60 * 10; -/// Used to inform the utility thread how often it should perform the directory sync job. See [`run_utility_thread`] for more details. +/// Used to inform the utility thread how often it should perform the directory sync job. +/// See [`run_utility_thread`] for more details. pub(crate) async fn get_directory_sync_interval(pool: &PgPool) -> u64 { if let Ok(Some(provider_settings)) = OpenIdProvider::get_current(pool).await { provider_settings @@ -980,7 +992,8 @@ pub(crate) async fn do_directory_sync( return Ok(()); } - // TODO: Reduce the amount of times those settings are retrieved in the whole directory sync process + // TODO: Reduce the amount of times those settings are retrieved in the whole directory sync + // process. let provider = OpenIdProvider::get_current(pool).await?; if !is_directory_sync_enabled(provider.as_ref()) { @@ -995,12 +1008,12 @@ pub(crate) async fn do_directory_sync( match DirectorySyncClient::build(pool).await { Ok(mut dir_sync) => { // TODO: Directory sync's access token is dropped every time, find a way to preserve it - // Same goes for Etags, those could be used to reduce the amount of data transferred. Some way - // of preserving them should be implemented. + // Same goes for Etags, those could be used to reduce the amount of data transferred. + // Some way of preserving them should be implemented. dir_sync.prepare().await?; - // This is an optimization, both sync_all_users_state and sync_all_users_groups depend on it so we might - // as well get all users once and pass it to both functions. + // This is an optimization, both sync_all_users_state and sync_all_users_groups depend + // on it so we might as well get all users once and pass it to both functions. let mut all_users = None; if matches!( @@ -1015,12 +1028,13 @@ pub(crate) async fn do_directory_sync( sync_target, DirectorySyncTarget::All | DirectorySyncTarget::Groups ) { - // Sometimes we don't even need to query all users, this is an optimization to reduce the amount of data transferred. + // Sometimes we don't even need to query all users, this is an optimization to + // reduce the amount of data transferred. let users_to_pass = match dir_sync { DirectorySyncClient::JumpCloud(_) => { if all_users.is_none() { - // JumpCloud doesn't return emails of group members, so we need to pass all users - // to the get_user_groups method to map ids to emails. + // JumpCloud doesn't return emails of group members, so we need to pass + // all users to the get_user_groups method to map ids to emails. Some(dir_sync.get_all_users().await?) } else { all_users @@ -1041,9 +1055,9 @@ pub(crate) async fn do_directory_sync( } // Helpers shared between the directory sync providers -// -/// Parse a reqwest response and return the JSON body if the response is OK, otherwise map an error to a DirectorySyncError::RequestError +/// Parse a reqwest response and return the JSON body if the response is OK, otherwise map an error +/// to a DirectorySyncError::RequestError. /// The context_message is used to provide more context to the error message. async fn parse_response( response: reqwest::Response, diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index 7f4b9e1bf8..316bb4df68 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -799,7 +799,7 @@ fn test_last_ip_in_v6_subnet() { IpAddr::V6(Ipv6Addr::new( 0x280b, 0x47f8, 0xc9d7, 0x634c, 0xcb35, 0x11f3, 0x14e1, 0x51ff )) - ) + ); } async fn create_acl_rule( diff --git a/crates/defguard_core/src/enterprise/handlers/openid_login.rs b/crates/defguard_core/src/enterprise/handlers/openid_login.rs index ff370b093e..b2ab080333 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_login.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_login.rs @@ -764,7 +764,7 @@ mod test { // empty second part let encoded = BASE64_STANDARD.encode("csrf."); let extracted = extract_state_data(&encoded); - assert_eq!(extracted, Some("".to_string())); + assert_eq!(extracted, Some(String::new())); // multiple dots let encoded = BASE64_STANDARD.encode("csrf.data.with.dots"); diff --git a/crates/defguard_core/src/enterprise/handlers/openid_providers.rs b/crates/defguard_core/src/enterprise/handlers/openid_providers.rs index c236209b62..13519dfe48 100644 --- a/crates/defguard_core/src/enterprise/handlers/openid_providers.rs +++ b/crates/defguard_core/src/enterprise/handlers/openid_providers.rs @@ -10,6 +10,7 @@ use defguard_common::db::models::{ }; use rsa::{RsaPrivateKey, pkcs8::DecodePrivateKey}; use serde_json::json; +use utoipa::ToSchema; use super::LicenseInfo; use crate::{ @@ -22,7 +23,7 @@ use crate::{ handlers::{ApiResponse, ApiResult}, }; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct AddProviderData { pub name: String, pub base_url: String, @@ -46,12 +47,18 @@ pub struct AddProviderData { pub prefetch_users: bool, } -#[derive(Deserialize, Serialize)] -pub struct DeleteProviderData { - name: String, -} - -pub async fn add_openid_provider( +/// Add OpenID provider. +/// +/// # Returns +/// - HTTP Status "created" on success. +#[utoipa::path( + post, + path = "/api/v1/openid/provider", + responses( + (status = CREATED, description = "Add OpenID provider", body = [AddProviderData]), + ), +)] +pub(crate) async fn add_openid_provider( _license: LicenseInfo, _admin: AdminRole, session: SessionInfo, @@ -182,13 +189,30 @@ pub async fn add_openid_provider( }) } -pub async fn get_current_openid_provider( +/// Get OpenID provider by name. +/// +/// # Returns +/// - HTTP Status "OK" on success. +#[utoipa::path( + put, + path = "/api/v1/openid/provider/{name}", + responses( + (status = OK, description = "Get OpenID provider"), + ), + params( + ("name" = String, Path, description = "The name of a provider",) + ) +)] +pub(crate) async fn get_openid_provider( _license: LicenseInfo, _admin: AdminRole, State(appstate): State, + Path(name): Path, ) -> ApiResult { let settings = Settings::get_current_settings(); - match OpenIdProvider::get_current(&appstate.pool).await? { + let settings_json = json!({"create_account": settings.openid_create_account, + "username_handling": settings.openid_username_handling}); + match OpenIdProvider::find_by_name(&appstate.pool, &name).await? { Some(mut provider) => { // Get rid of it, it should stay on the backend only. provider.google_service_account_key = None; @@ -196,8 +220,7 @@ pub async fn get_current_openid_provider( Ok(ApiResponse { json: json!({ "provider": json!(provider), - "settings": json!({"create_account": settings.openid_create_account, - "username_handling": settings.openid_username_handling}), + "settings": settings_json, }), status: StatusCode::OK, }) @@ -205,28 +228,41 @@ pub async fn get_current_openid_provider( None => Ok(ApiResponse { json: json!({ "provider": null, - "settings": json!({"create_account": settings.openid_create_account, - "username_handling": settings.openid_username_handling}), + "settings": settings_json, }), status: StatusCode::NO_CONTENT, }), } } -pub async fn delete_openid_provider( +/// Delete OpenID provider. +/// +/// # Returns +/// - HTTP Status "OK" on success. +#[utoipa::path( + get, + path = "/api/v1/openid/provider/{name}", + responses( + (status = OK, description = "Delete OpenID provider"), + ), + params( + ("name" = String, Path, description = "The name of a provider",) + ) +)] +pub(crate) async fn delete_openid_provider( _license: LicenseInfo, _admin: AdminRole, session: SessionInfo, context: ApiRequestContext, State(appstate): State, - Path(provider_data): Path, + Path(name): Path, ) -> ApiResult { debug!( - "User {} deleting OpenID provider {}", - session.user.username, provider_data.name + "User {} deleting OpenID provider {name}", + session.user.username ); let mut transaction = appstate.pool.begin().await?; - let provider = OpenIdProvider::find_by_name(&mut *transaction, &provider_data.name).await?; + let provider = OpenIdProvider::find_by_name(&mut *transaction, &name).await?; if let Some(provider) = provider { provider.clone().delete(&mut *transaction).await?; // fetch all locations using external MFA @@ -258,8 +294,8 @@ pub async fn delete_openid_provider( }) } else { warn!( - "User {} failed to delete OpenID provider {}. Such provider does not exist.", - session.user.username, provider_data.name + "User {} failed to delete OpenID provider {name}. Such provider does not exist.", + session.user.username, ); Ok(ApiResponse { json: json!({}), @@ -268,10 +304,25 @@ pub async fn delete_openid_provider( } } -pub async fn modify_openid_provider( +/// Modify OpenID provider. +/// +/// # Returns +/// - HTTP Status "OK" on success. +#[utoipa::path( + put, + path = "/api/v1/openid/provider/{name}", + responses( + (status = OK, description = "Modify OpenID provider"), + ), + params( + ("name" = String, Path, description = "The name of a provider",) + ) +)] +pub(crate) async fn modify_openid_provider( _license: LicenseInfo, _admin: AdminRole, session: SessionInfo, + context: ApiRequestContext, State(appstate): State, Json(provider_data): Json, ) -> ApiResult { @@ -290,6 +341,11 @@ pub async fn modify_openid_provider( "User {} modified OpenID client {}", session.user.username, provider.name ); + appstate.emit_event(ApiEvent { + context, + event: Box::new(ApiEventType::OpenIdProviderModified { provider }), + })?; + Ok(ApiResponse { json: json!({}), status: StatusCode::OK, @@ -306,7 +362,18 @@ pub async fn modify_openid_provider( } } -pub async fn list_openid_providers( +/// List all OpenID providers. +/// +/// # Returns +/// - Array of all OpenID providers and HTTP status "OK" on success. +#[utoipa::path( + get, + path = "/api/v1/openid/provider", + responses( + (status = OK, description = "List all OpenID provider"), + ), +)] +pub(crate) async fn list_openid_providers( _license: LicenseInfo, _admin: AdminRole, State(appstate): State, @@ -318,7 +385,7 @@ pub async fn list_openid_providers( }) } -pub async fn test_dirsync_connection( +pub(crate) async fn test_dirsync_connection( _license: LicenseInfo, _admin: AdminRole, session: SessionInfo, @@ -331,8 +398,8 @@ pub async fn test_dirsync_connection( if let Err(err) = test_directory_sync_connection(&appstate.pool).await { error!( - "User {} tested directory sync connection, the connection failed: {}", - session.user.username, err + "User {} tested directory sync connection, the connection failed: {err}", + session.user.username, ); return Ok(ApiResponse { json: json!({"message": err.to_string(), "success": false}), diff --git a/crates/defguard_core/src/enterprise/ldap/test_client.rs b/crates/defguard_core/src/enterprise/ldap/test_client.rs index d82132b3b2..907a6bd4df 100644 --- a/crates/defguard_core/src/enterprise/ldap/test_client.rs +++ b/crates/defguard_core/src/enterprise/ldap/test_client.rs @@ -70,9 +70,7 @@ fn vecs_equal_unordered(vec1: &[T], vec2: &[T]) -> bool where T: PartialEq + Clone, { - if vec1.len() != vec2.len() { - false - } else { + if vec1.len() == vec2.len() { let mut vec2_copy = vec2.to_vec(); vec1.iter().all(|item| { if let Some(pos) = vec2_copy.iter().position(|x| x == item) { @@ -82,6 +80,8 @@ where false } }) + } else { + false } } @@ -386,7 +386,12 @@ impl super::LDAPConnection { dn: dn.to_string(), attrs: attrs .into_iter() - .map(|(k, v)| (k.to_string(), v.iter().map(|s| s.to_string()).collect())) + .map(|(k, v)| { + ( + k.to_string(), + v.iter().map(std::string::ToString::to_string).collect(), + ) + }) .collect(), }); Ok(()) @@ -489,7 +494,7 @@ impl super::LDAPConnection { user, "", "", - classes.iter().map(|s| s.as_str()).collect(), + classes.iter().map(std::string::String::as_str).collect(), false, &config.ldap_username_attr, &rdn_attr, @@ -498,7 +503,12 @@ impl super::LDAPConnection { dn: dn.clone(), attrs: attrs .iter() - .map(|(k, v)| (k.to_string(), v.iter().map(|s| s.to_string()).collect())) + .map(|(k, v)| { + ( + k.to_string(), + v.iter().map(std::string::ToString::to_string).collect(), + ) + }) .collect(), bin_attrs: HashMap::new(), }); @@ -561,13 +571,18 @@ pub(super) fn user_to_test_attrs( user, &ssha_password, &nt_password, - classes.iter().map(|s| s.as_str()).collect(), + classes.iter().map(std::string::String::as_str).collect(), false, &config.ldap_username_attr, &rdn_attr, ) .into_iter() - .map(|(k, v)| (k.to_string(), v.iter().map(|s| s.to_string()).collect())) + .map(|(k, v)| { + ( + k.to_string(), + v.iter().map(std::string::ToString::to_string).collect(), + ) + }) .collect() } diff --git a/crates/defguard_core/src/enterprise/ldap/tests.rs b/crates/defguard_core/src/enterprise/ldap/tests.rs index d2980b9498..2067f5a04a 100644 --- a/crates/defguard_core/src/enterprise/ldap/tests.rs +++ b/crates/defguard_core/src/enterprise/ldap/tests.rs @@ -49,7 +49,7 @@ fn test_get_rdn_attr() { // Empty string should fall back to default 'cn' let config = LDAPConfig { - ldap_user_rdn_attr: Some("".to_string()), + ldap_user_rdn_attr: Some(String::new()), ..LDAPConfig::default() }; assert_eq!(config.get_rdn_attr(), "cn"); @@ -218,7 +218,7 @@ fn test_using_username_as_rdn() { // Empty RDN attribute falls back to 'cn', so username is used let config = LDAPConfig { - ldap_user_rdn_attr: Some("".to_string()), + ldap_user_rdn_attr: Some(String::new()), ..LDAPConfig::default() }; assert!(config.using_username_as_rdn()); @@ -1965,7 +1965,7 @@ async fn test_sync_users_with_empty_paths_and_nested_ous( ); assert_eq!(added_user.username, "ldap_only_user"); assert!(added_user.from_ldap); - assert!(ldap_conn.test_client.get_events().is_empty()) + assert!(ldap_conn.test_client.get_events().is_empty()); } } @@ -2455,7 +2455,7 @@ fn test_extract_dn_value() { assert_eq!(extract_rdn_value("cn=onlyvalue"), None); assert_eq!( extract_rdn_value("cn=,dc=example,dc=com"), - Some("".to_string()) + Some(String::new()) ); assert_eq!(extract_rdn_value(""), None); } @@ -2845,7 +2845,7 @@ fn test_as_ldap_attrs() { "Smith".to_string(), "John".to_string(), "john.smith@example.com".to_string(), - Some("".to_string()), + Some(String::new()), ); let attrs = user_as_ldap_attrs( @@ -2897,7 +2897,7 @@ fn test_as_ldap_mod_with_empty_phone() { "Smith".to_string(), "John".to_string(), "john.smith@example.com".to_string(), - Some("".to_string()), + Some(String::new()), ); let config = LDAPConfig { @@ -2992,7 +2992,7 @@ fn test_extract_dn_path_various_cases() { assert_eq!(extract_dn_path(""), None); - assert_eq!(extract_dn_path("cn=abc,"), Some("".to_string())); + assert_eq!(extract_dn_path("cn=abc,"), Some(String::new())); assert_eq!( extract_dn_path("uid=cde,ou=users,ou=staff,dc=example,dc=org"), diff --git a/crates/defguard_core/src/enterprise/limits.rs b/crates/defguard_core/src/enterprise/limits.rs index 7a7c17e54f..c70bc0054c 100644 --- a/crates/defguard_core/src/enterprise/limits.rs +++ b/crates/defguard_core/src/enterprise/limits.rs @@ -87,8 +87,8 @@ impl Counts { Self { user, user_device, - location, network_device, + location, } } diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 17cdbe86c2..3024643b57 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -32,30 +32,6 @@ use defguard_common::{ use defguard_mail::Mail; use defguard_version::server::DefguardVersionLayer; use defguard_web_ui::{index, svg, web_asset}; -use enterprise::{ - handlers::{ - acl::{ - apply_acl_aliases, apply_acl_rules, create_acl_alias, create_acl_rule, - delete_acl_alias, delete_acl_rule, get_acl_alias, get_acl_rule, list_acl_aliases, - list_acl_rules, update_acl_alias, update_acl_rule, - }, - activity_log_stream::{ - create_activity_log_stream, delete_activity_log_stream, get_activity_log_stream, - modify_activity_log_stream, - }, - api_tokens::{add_api_token, delete_api_token, fetch_api_tokens, rename_api_token}, - check_enterprise_info, - enterprise_settings::{get_enterprise_settings, patch_enterprise_settings}, - openid_login::{auth_callback, get_auth_info}, - openid_providers::{ - add_openid_provider, delete_openid_provider, get_current_openid_provider, - test_dirsync_connection, - }, - }, - snat::handlers::{ - create_snat_binding, delete_snat_binding, list_snat_bindings, modify_snat_binding, - }, -}; use events::ApiEvent; use handlers::{ activity_log::get_activity_log_events, @@ -101,6 +77,30 @@ use self::{ appstate::AppState, auth::failed_login::FailedLoginMap, db::AppEvent, + enterprise::{ + handlers::{ + acl::{ + apply_acl_aliases, apply_acl_rules, create_acl_alias, create_acl_rule, + delete_acl_alias, delete_acl_rule, get_acl_alias, get_acl_rule, list_acl_aliases, + list_acl_rules, update_acl_alias, update_acl_rule, + }, + activity_log_stream::{ + create_activity_log_stream, delete_activity_log_stream, get_activity_log_stream, + modify_activity_log_stream, + }, + api_tokens::{add_api_token, delete_api_token, fetch_api_tokens, rename_api_token}, + check_enterprise_info, + enterprise_settings::{get_enterprise_settings, patch_enterprise_settings}, + openid_login::{auth_callback, get_auth_info}, + openid_providers::{ + add_openid_provider, delete_openid_provider, get_openid_provider, + modify_openid_provider, test_dirsync_connection, + }, + }, + snat::handlers::{ + create_snat_binding, delete_snat_binding, list_snat_bindings, modify_snat_binding, + }, + }, grpc::WorkerState, handlers::{ app_info::get_app_info, @@ -150,6 +150,7 @@ use self::{ }, }; use crate::{ + enterprise::handlers::openid_providers::list_openid_providers, grpc::gateway::events::GatewayEvent, handlers::wireguard::{add_gateway, change_gateway}, location_management::sync_location_allowed_devices, @@ -502,9 +503,14 @@ pub fn build_webapp( Router::new() .route( "/provider", - get(get_current_openid_provider).post(add_openid_provider), + get(list_openid_providers).post(add_openid_provider), + ) + .route( + "/provider/{name}", + get(get_openid_provider) + .put(modify_openid_provider) + .delete(delete_openid_provider), ) - .route("/provider/{name}", delete(delete_openid_provider)) .route("/callback", post(auth_callback)) .route("/auth_info", get(get_auth_info)), ); diff --git a/crates/defguard_core/tests/integration/api/auth.rs b/crates/defguard_core/tests/integration/api/auth.rs index 08b32b55cf..d331c56920 100644 --- a/crates/defguard_core/tests/integration/api/auth.rs +++ b/crates/defguard_core/tests/integration/api/auth.rs @@ -109,6 +109,20 @@ async fn test_login_bruteforce(_: PgPoolOptions, options: PgConnectOptions) { } } +async fn responses_eq(response1: TestResponse, response2: TestResponse) -> bool { + // omit date header + let mut headers1 = response1.headers().clone(); + headers1.remove("date"); + let mut headers2 = response2.headers().clone(); + headers2.remove("date"); + let headers = headers1 == headers2; + + let status = response1.status() == response2.status(); + let body = response1.bytes().await == response2.bytes().await; + + status && headers && body +} + #[sqlx::test] async fn dg25_21_test_login_enumeration(_: PgPoolOptions, options: PgConnectOptions) { let pool = setup_pool(options).await; @@ -124,20 +138,6 @@ async fn dg25_21_test_login_enumeration(_: PgPoolOptions, options: PgConnectOpti let response = client.post("/api/v1/auth").json(&user_auth).send().await; assert_eq!(response.status(), StatusCode::OK); - async fn responses_eq(response1: TestResponse, response2: TestResponse) -> bool { - // omit date header - let mut headers1 = response1.headers().clone(); - headers1.remove("date"); - let mut headers2 = response2.headers().clone(); - headers2.remove("date"); - let headers = headers1 == headers2; - - let status = response1.status() == response2.status(); - let body = response1.bytes().await == response2.bytes().await; - - status && headers && body - } - // regular user let user_auth = Auth::new("hpotter", "invalid"); let response_existing_user = client.post("/api/v1/auth").json(&user_auth).send().await; @@ -282,7 +282,7 @@ async fn test_totp(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::UNAUTHORIZED); // provide recovery code - let code = recovery_codes.codes.unwrap().first().unwrap().to_string(); + let code = recovery_codes.codes.unwrap().first().unwrap().clone(); let response = client .post("/api/v1/auth/recovery") .json(&json!({ "code": code })) diff --git a/crates/defguard_core/tests/integration/api/common/mod.rs b/crates/defguard_core/tests/integration/api/common/mod.rs index 12fbd499ca..9bbac87768 100644 --- a/crates/defguard_core/tests/integration/api/common/mod.rs +++ b/crates/defguard_core/tests/integration/api/common/mod.rs @@ -38,7 +38,10 @@ use tokio::{ }; use self::client::TestClient; -use crate::common::{init_config, initialize_users}; +use crate::{ + api::common::client::TestResponse, + common::{init_config, initialize_users}, +}; #[allow(clippy::declare_interior_mutable_const)] pub const X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host"); @@ -135,7 +138,7 @@ pub(crate) async fn make_base_client( failed_logins, api_event_tx, Version::parse(VERSION).unwrap(), - Default::default(), + Arc::default(), ); ( @@ -169,10 +172,16 @@ pub(crate) async fn exceed_enterprise_limits(client: &TestClient) { let auth = Auth::new("admin", "pass123"); client.post("/api/v1/auth").json(&auth).send().await; + make_network(client, "network1").await; + make_network(client, "network2").await; +} + +/// Create test network with a given name. +pub(crate) async fn make_network(client: &TestClient, name: &str) -> TestResponse { let response = client .post("/api/v1/network") .json(&json!({ - "name": "network1", + "name": name, "address": "10.1.1.1/24", "port": 55555, "endpoint": "192.168.4.14", @@ -189,45 +198,7 @@ pub(crate) async fn exceed_enterprise_limits(client: &TestClient) { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - - let response = client - .post("/api/v1/network") - .json(&json!({ - "name": "network2", - "address": "10.1.1.1/24", - "port": 55555, - "endpoint": "192.168.4.14", - "allowed_ips": "10.1.1.0/24", - "dns": "1.1.1.1", - "allowed_groups": [], - "keepalive_interval": 25, - "peer_disconnect_threshold": 300, - "acl_enabled": false, - "acl_default_allow": false, - "location_mfa_mode": "disabled", - "service_location_mode": "disabled" - })) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); -} - -pub(crate) fn make_network() -> Value { - json!({ - "name": "network", - "address": "10.1.1.1/24", - "port": 55555, - "endpoint": "192.168.4.14", - "allowed_ips": "10.1.1.0/24", - "dns": "1.1.1.1", - "allowed_groups": [], - "keepalive_interval": 25, - "peer_disconnect_threshold": 300, - "acl_enabled": false, - "acl_default_allow": false, - "location_mfa_mode": "disabled", - "service_location_mode": "disabled" - }) + response } /// Replaces id field in json response with NoId diff --git a/crates/defguard_core/tests/integration/api/enterprise_settings.rs b/crates/defguard_core/tests/integration/api/enterprise_settings.rs index c065dde6cc..878526329f 100644 --- a/crates/defguard_core/tests/integration/api/enterprise_settings.rs +++ b/crates/defguard_core/tests/integration/api/enterprise_settings.rs @@ -71,12 +71,7 @@ async fn test_admin_devices_management_is_enforced(_: PgPoolOptions, options: Pg exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // setup admin devices management let settings = EnterpriseSettings { @@ -167,12 +162,7 @@ async fn test_regular_user_device_management(_: PgPoolOptions, options: PgConnec exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // setup admin devices management let settings = EnterpriseSettings { @@ -255,12 +245,7 @@ async fn dg25_12_test_enforce_client_activation_only(_: PgPoolOptions, options: exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // disable manual device management let settings = EnterpriseSettings { @@ -336,12 +321,7 @@ async fn dg25_13_test_disable_device_config(_: PgPoolOptions, options: PgConnect exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // disable manual device management let settings = EnterpriseSettings { diff --git a/crates/defguard_core/tests/integration/api/openid.rs b/crates/defguard_core/tests/integration/api/openid.rs index ceb643ebe3..30d6ed5fc2 100644 --- a/crates/defguard_core/tests/integration/api/openid.rs +++ b/crates/defguard_core/tests/integration/api/openid.rs @@ -449,7 +449,7 @@ async fn http_client( Method::DELETE => client.delete(uri), _ => unimplemented!(), }; - for (key, value) in request.headers().iter() { + for (key, value) in request.headers() { test_request = test_request.header( HeaderName::from_str(key.as_str()).unwrap(), value.to_str().unwrap(), @@ -1386,7 +1386,7 @@ async fn dg25_22_test_respect_openid_scope_in_userinfo( // Clean up - delete the OAuth client client - .delete(format!("/api/v1/oauth/{}", client_id_for_cleanup)) + .delete(format!("/api/v1/oauth/{client_id_for_cleanup}")) .send() .await; diff --git a/crates/defguard_core/tests/integration/api/openid_login.rs b/crates/defguard_core/tests/integration/api/openid_login.rs index 9091b5ed34..2810c54772 100644 --- a/crates/defguard_core/tests/integration/api/openid_login.rs +++ b/crates/defguard_core/tests/integration/api/openid_login.rs @@ -102,7 +102,7 @@ async fn test_openid_providers(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::FORBIDDEN); } -// FIXME: tihs test sometimes fails because of test_openid_providers. +// FIXME: this test sometimes fails because of test_openid_providers. // The license state is possibly preserved between those two. This requires further research. #[sqlx::test] async fn test_openid_login(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_core/tests/integration/api/snat.rs b/crates/defguard_core/tests/integration/api/snat.rs index 3e0ad6428a..3207a43655 100644 --- a/crates/defguard_core/tests/integration/api/snat.rs +++ b/crates/defguard_core/tests/integration/api/snat.rs @@ -26,12 +26,7 @@ async fn test_snat_crud(_: PgPoolOptions, options: PgConnectOptions) { authenticate_admin(&mut client).await; // create location - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // list SNAT bindings (should be empty) let response = client.get("/api/v1/network/1/snat").send().await; @@ -118,12 +113,7 @@ async fn test_snat_enterprise_required(_: PgPoolOptions, options: PgConnectOptio exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // unset the license let license = get_cached_license().clone(); @@ -185,12 +175,7 @@ async fn test_snat_admin_required(_: PgPoolOptions, options: PgConnectOptions) { let response = client.post("/api/v1/auth").json(&auth).send().await; assert_eq!(response.status(), StatusCode::OK); - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // login as normal user let auth = Auth::new("hpotter", "pass123"); @@ -241,12 +226,7 @@ async fn test_snat_validation(_: PgPoolOptions, options: PgConnectOptions) { exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // try to create binding for non-existent user let new_binding = NewUserSnatBinding { @@ -318,12 +298,7 @@ async fn test_snat_multiple_bindings(_: PgPoolOptions, options: PgConnectOptions exceed_enterprise_limits(&client).await; // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // create multiple SNAT bindings for different users let binding1 = NewUserSnatBinding { diff --git a/crates/defguard_core/tests/integration/api/user.rs b/crates/defguard_core/tests/integration/api/user.rs index dfcf4fdb15..95c5fa7eb8 100644 --- a/crates/defguard_core/tests/integration/api/user.rs +++ b/crates/defguard_core/tests/integration/api/user.rs @@ -388,7 +388,7 @@ async fn test_check_username(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::CREATED); let test_user = get_db_user(&pool, username).await; - expected_events.push(ApiEventType::UserAdded { user: test_user }) + expected_events.push(ApiEventType::UserAdded { user: test_user }); } client.verify_api_events(&expected_events); @@ -550,12 +550,7 @@ async fn test_user_add_device(_: PgPoolOptions, options: PgConnectOptions) { ); // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; expected_events.push(ApiEventType::VpnLocationAdded { location: get_db_location(&state.pool, 1).await, }); diff --git a/crates/defguard_core/tests/integration/api/wireguard.rs b/crates/defguard_core/tests/integration/api/wireguard.rs index 68694abae6..80a213f5fa 100644 --- a/crates/defguard_core/tests/integration/api/wireguard.rs +++ b/crates/defguard_core/tests/integration/api/wireguard.rs @@ -44,12 +44,7 @@ async fn test_network(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::OK); // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + let response = make_network(&client, "network").await; let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); @@ -330,12 +325,7 @@ async fn test_device(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::OK); // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -369,12 +359,7 @@ async fn test_device(_: PgPoolOptions, options: PgConnectOptions) { ); // add another network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; assert_matches!(wg_rx.try_recv().unwrap(), GatewayEvent::NetworkCreated(..)); // an IP was assigned for an existing device @@ -607,12 +592,7 @@ async fn test_device_permissions(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::OK); // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // admin can add devices for other users let device = json!({ @@ -754,12 +734,7 @@ async fn test_device_pubkey(_: PgPoolOptions, options: PgConnectOptions) { assert_eq!(response.status(), StatusCode::OK); // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs index 8cfb4995de..d206b083f5 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_devices.rs @@ -388,5 +388,5 @@ async fn test_device_ip_validation(_: PgPoolOptions, options: PgConnectOptions) valid: true, } ] - ) + ); } diff --git a/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs b/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs index 21718da443..b9ea6351d5 100644 --- a/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs +++ b/crates/defguard_core/tests/integration/api/wireguard_network_stats.rs @@ -38,12 +38,7 @@ async fn test_stats(_: PgPoolOptions, options: PgConnectOptions) { let response = &client.post("/api/v1/auth").json(&auth).send().await; assert_eq!(response.status(), StatusCode::OK); // create network - let response = client - .post("/api/v1/network") - .json(&make_network()) - .send() - .await; - assert_eq!(response.status(), StatusCode::CREATED); + make_network(&client, "network").await; // create devices let device = json!({ diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e5dad9f8f7..788f93983a 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@inlang/paraglide-js': specifier: ^2.8.0 - version: 2.8.0(babel-plugin-macros@3.1.0) + version: 2.8.0 '@react-hook/resize-observer': specifier: ^2.0.2 version: 2.0.2(react@19.2.3) @@ -31,7 +31,7 @@ importers: version: 2.0.1 '@tanstack/react-devtools': specifier: ^0.9.2 - version: 0.9.2(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) + version: 0.9.2(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.9) '@tanstack/react-form': specifier: ^1.27.7 version: 1.27.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -82,7 +82,7 @@ importers: version: 4.17.22 motion: specifier: ^12.26.2 - version: 12.26.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) qrcode.react: specifier: ^4.2.0 version: 4.2.0(react@19.2.3) @@ -128,10 +128,10 @@ importers: version: 2.3.11 '@tanstack/devtools-vite': specifier: ^0.4.1 - version: 0.4.1(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1)) + version: 0.4.1(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0)) '@tanstack/router-plugin': specifier: ^1.149.3 - version: 1.149.3(@tanstack/react-router@1.149.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1)) + version: 1.149.3(@tanstack/react-router@1.149.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0)) '@types/byte-size': specifier: ^8.1.2 version: 8.1.2 @@ -155,7 +155,7 @@ importers: version: 19.2.3(@types/react@19.2.8) '@vitejs/plugin-react-swc': specifier: ^4.2.2 - version: 4.2.2(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1)) + version: 4.2.2(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0)) autoprefixer: specifier: ^10.4.23 version: 10.4.23(postcss@8.5.6) @@ -185,10 +185,10 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1) + version: 7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0) vite-plugin-image-optimizer: specifier: ^2.0.3 - version: 2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1)) + version: 2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0)) packages: @@ -269,10 +269,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -377,12 +373,6 @@ packages: '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@emotion/is-prop-valid@1.4.0': - resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} - - '@emotion/memoize@0.9.0': - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -718,9 +708,6 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1370,9 +1357,6 @@ packages: '@types/node@25.0.8': resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==} - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -1471,10 +1455,6 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -1498,9 +1478,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - byte-size@9.0.1: resolution: {integrity: sha512-YLe9x3rabBrcI0cueCdLS2l5ONUKywcRpTs02B8KP9/Cimhj7o3ZccGrPnRvcbyHMbb7W79/3MUJl7iGgTXKEw==} engines: {node: '>=12.17'} @@ -1580,9 +1557,6 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - comment-json@4.5.1: resolution: {integrity: sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==} engines: {node: '>= 6'} @@ -1600,10 +1574,6 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -2025,10 +1995,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} @@ -2066,10 +2032,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} - hasBin: true - js-sha256@0.11.1: resolution: {integrity: sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==} @@ -2304,9 +2266,6 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2480,11 +2439,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2573,16 +2527,13 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - solid-js@1.9.10: - resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} + solid-js@1.9.9: + resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -2667,10 +2618,6 @@ packages: resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} engines: {node: '>=14.18'} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} @@ -2681,11 +2628,6 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - terser@5.37.0: - resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} - engines: {node: '>=10'} - hasBin: true - text-camel-case@1.2.9: resolution: {integrity: sha512-wKYs9SgRxYizJE1mneR7BbLNlGw2IYzJAS8XwkWIry0CTbO1gvvPkFsx5Z1/hr+VqUaBqx9q3yKd30HpZLdMsQ==} @@ -2926,15 +2868,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yaml@2.6.1: - resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} - engines: {node: '>= 14'} - hasBin: true - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -3058,9 +2991,6 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/runtime@7.28.6': - optional: true - '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -3155,14 +3085,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emotion/is-prop-valid@1.4.0': - dependencies: - '@emotion/memoize': 0.9.0 - optional: true - - '@emotion/memoize@0.9.0': - optional: true - '@esbuild/aix-ppc64@0.27.2': optional: true @@ -3362,10 +3284,10 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inlang/paraglide-js@2.8.0(babel-plugin-macros@3.1.0)': + '@inlang/paraglide-js@2.8.0': dependencies: '@inlang/recommend-sherlock': 0.2.1 - '@inlang/sdk': 2.6.0(babel-plugin-macros@3.1.0) + '@inlang/sdk': 2.6.0 commander: 11.1.0 consola: 3.4.0 json5: 2.2.3 @@ -3378,9 +3300,9 @@ snapshots: dependencies: comment-json: 4.5.1 - '@inlang/sdk@2.6.0(babel-plugin-macros@3.1.0)': + '@inlang/sdk@2.6.0': dependencies: - '@lix-js/sdk': 0.4.7(babel-plugin-macros@3.1.0) + '@lix-js/sdk': 0.4.7 '@sinclair/typebox': 0.31.28 kysely: 0.27.6 sqlite-wasm-kysely: 0.3.0(kysely@0.27.6) @@ -3400,12 +3322,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -3421,10 +3337,10 @@ snapshots: '@keyv/serialize@1.1.1': {} - '@lix-js/sdk@0.4.7(babel-plugin-macros@3.1.0)': + '@lix-js/sdk@0.4.7': dependencies: '@lix-js/server-protocol-schema': 0.1.1 - dedent: 1.5.1(babel-plugin-macros@3.1.0) + dedent: 1.5.1 human-id: 4.1.3 js-sha256: 0.11.1 kysely: 0.27.6 @@ -3615,39 +3531,39 @@ snapshots: '@sinclair/typebox@0.31.28': {} - '@solid-primitives/event-listener@2.4.3(solid-js@1.9.10)': + '@solid-primitives/event-listener@2.4.3(solid-js@1.9.9)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 - '@solid-primitives/keyboard@1.3.3(solid-js@1.9.10)': + '@solid-primitives/keyboard@1.3.3(solid-js@1.9.9)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.9) + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 - '@solid-primitives/resize-observer@2.1.3(solid-js@1.9.10)': + '@solid-primitives/resize-observer@2.1.3(solid-js@1.9.9)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) - '@solid-primitives/static-store': 0.1.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.9) + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.9) + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 - '@solid-primitives/rootless@1.5.2(solid-js@1.9.10)': + '@solid-primitives/rootless@1.5.2(solid-js@1.9.9)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 - '@solid-primitives/static-store@0.1.2(solid-js@1.9.10)': + '@solid-primitives/static-store@0.1.2(solid-js@1.9.9)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 - '@solid-primitives/utils@6.3.2(solid-js@1.9.10)': + '@solid-primitives/utils@6.3.2(solid-js@1.9.9)': dependencies: - solid-js: 1.9.10 + solid-js: 1.9.9 '@sqlite.org/sqlite-wasm@3.48.0-build4': {} @@ -3747,15 +3663,15 @@ snapshots: '@tanstack/devtools-event-client@0.4.0': {} - '@tanstack/devtools-ui@0.4.4(csstype@3.2.3)(solid-js@1.9.10)': + '@tanstack/devtools-ui@0.4.4(csstype@3.2.3)(solid-js@1.9.9)': dependencies: clsx: 2.1.1 goober: 2.1.18(csstype@3.2.3) - solid-js: 1.9.10 + solid-js: 1.9.9 transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.4.1(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1))': + '@tanstack/devtools-vite@0.4.1(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0))': dependencies: '@babel/core': 7.28.6 '@babel/generator': 7.28.6 @@ -3767,23 +3683,23 @@ snapshots: chalk: 5.6.2 launch-editor: 2.12.0 picomatch: 4.0.3 - vite: 7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1) + vite: 7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@tanstack/devtools@0.10.3(csstype@3.2.3)(solid-js@1.9.10)': + '@tanstack/devtools@0.10.3(csstype@3.2.3)(solid-js@1.9.9)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.10) - '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.10) + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) + '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.9) + '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.9) '@tanstack/devtools-client': 0.0.5 '@tanstack/devtools-event-bus': 0.4.0 - '@tanstack/devtools-ui': 0.4.4(csstype@3.2.3)(solid-js@1.9.10) + '@tanstack/devtools-ui': 0.4.4(csstype@3.2.3)(solid-js@1.9.9) clsx: 2.1.1 goober: 2.1.18(csstype@3.2.3) - solid-js: 1.9.10 + solid-js: 1.9.9 transitivePeerDependencies: - bufferutil - csstype @@ -3803,9 +3719,9 @@ snapshots: '@tanstack/query-devtools@5.92.0': {} - '@tanstack/react-devtools@0.9.2(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)': + '@tanstack/react-devtools@0.9.2(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.9)': dependencies: - '@tanstack/devtools': 0.10.3(csstype@3.2.3)(solid-js@1.9.10) + '@tanstack/devtools': 0.10.3(csstype@3.2.3)(solid-js@1.9.9) '@types/react': 19.2.8 '@types/react-dom': 19.2.3(@types/react@19.2.8) react: 19.2.3 @@ -3908,7 +3824,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.149.3(@tanstack/react-router@1.149.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1))': + '@tanstack/router-plugin@1.149.3(@tanstack/react-router@1.149.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0))': dependencies: '@babel/core': 7.28.6 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) @@ -3926,7 +3842,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.149.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - vite: 7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1) + vite: 7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - supports-color @@ -4010,9 +3926,6 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/parse-json@4.0.2': - optional: true - '@types/qs@6.14.0': {} '@types/react-dom@19.2.3(@types/react@19.2.8)': @@ -4036,11 +3949,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@4.2.2(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1))': + '@vitejs/plugin-react-swc@4.2.2(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.47 '@swc/core': 1.15.8 - vite: 7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1) + vite: 7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - '@swc/helpers' @@ -4108,13 +4021,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-macros@3.1.0: - dependencies: - '@babel/runtime': 7.28.6 - cosmiconfig: 7.1.0 - resolve: 1.22.11 - optional: true - bail@2.0.2: {} balanced-match@2.0.0: {} @@ -4135,9 +4041,6 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) - buffer-from@1.1.2: - optional: true - byte-size@9.0.1: {} cacheable@2.3.1: @@ -4208,9 +4111,6 @@ snapshots: commander@11.1.0: {} - commander@2.20.3: - optional: true - comment-json@4.5.1: dependencies: array-timsort: 1.0.3 @@ -4225,15 +4125,6 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - optional: true - cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 @@ -4304,9 +4195,7 @@ snapshots: dependencies: character-entities: 2.0.2 - dedent@1.5.1(babel-plugin-macros@3.1.0): - optionalDependencies: - babel-plugin-macros: 3.1.0 + dedent@1.5.1: {} delayed-stream@1.0.0: {} @@ -4450,13 +4339,12 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.26.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + framer-motion@12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: motion-dom: 12.26.2 motion-utils: 12.24.10 tslib: 2.8.1 optionalDependencies: - '@emotion/is-prop-valid': 1.4.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -4661,11 +4549,6 @@ snapshots: dependencies: binary-extensions: 2.3.0 - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - optional: true - is-decimal@2.0.1: {} is-extglob@2.1.1: {} @@ -4688,9 +4571,6 @@ snapshots: isexe@2.0.0: {} - jiti@2.4.2: - optional: true - js-sha256@0.11.1: {} js-tokens@4.0.0: {} @@ -4985,12 +4865,11 @@ snapshots: motion-utils@12.24.10: {} - motion@12.26.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + motion@12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - framer-motion: 12.26.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + framer-motion: 12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tslib: 2.8.1 optionalDependencies: - '@emotion/is-prop-valid': 1.4.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5032,9 +4911,6 @@ snapshots: dependencies: entities: 6.0.1 - path-parse@1.0.7: - optional: true - path-type@4.0.0: {} pathe@2.0.3: {} @@ -5209,13 +5085,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - optional: true - reusify@1.1.0: {} rollup@4.55.1: @@ -5354,7 +5223,7 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - solid-js@1.9.10: + solid-js@1.9.9: dependencies: csstype: 3.2.3 seroval: 1.3.2 @@ -5362,12 +5231,6 @@ snapshots: source-map-js@1.2.1: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - optional: true - source-map@0.6.1: {} source-map@0.7.6: {} @@ -5494,9 +5357,6 @@ snapshots: has-flag: 4.0.0 supports-color: 7.2.0 - supports-preserve-symlinks-flag@1.0.0: - optional: true - svg-tags@1.0.0: {} tabbable@6.4.0: {} @@ -5509,14 +5369,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - terser@5.37.0: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true - text-camel-case@1.2.9: dependencies: text-pascal-case: 1.2.9 @@ -5730,15 +5582,15 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1)): + vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0)): dependencies: ansi-colors: 4.1.3 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1) + vite: 7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0) optionalDependencies: sharp: 0.34.5 - vite@7.3.1(@types/node@25.0.8)(jiti@2.4.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.21.0)(yaml@2.6.1): + vite@7.3.1(@types/node@25.0.8)(sass@1.97.2)(tsx@4.21.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -5749,11 +5601,8 @@ snapshots: optionalDependencies: '@types/node': 25.0.8 fsevents: 2.3.3 - jiti: 2.4.2 sass: 1.97.2 - terser: 5.37.0 tsx: 4.21.0 - yaml: 2.6.1 web-namespaces@2.0.1: {} @@ -5772,12 +5621,6 @@ snapshots: yallist@3.1.1: {} - yaml@1.10.2: - optional: true - - yaml@2.6.1: - optional: true - zod@3.25.76: {} zod@4.3.5: {}