diff --git a/.sqlx/query-96917ed09e836086c38396c5778b83ca94ffff8f5f636ef650250befbcd78ab4.json b/.sqlx/query-386714aa5e0cbc2d896edf6dcd2f4ec260d08f8feee279c41f73fd8e33aea5aa.json similarity index 87% rename from .sqlx/query-96917ed09e836086c38396c5778b83ca94ffff8f5f636ef650250befbcd78ab4.json rename to .sqlx/query-386714aa5e0cbc2d896edf6dcd2f4ec260d08f8feee279c41f73fd8e33aea5aa.json index 444299927c..2016018b0a 100644 --- a/.sqlx/query-96917ed09e836086c38396c5778b83ca94ffff8f5f636ef650250befbcd78ab4.json +++ b/.sqlx/query-386714aa5e0cbc2d896edf6dcd2f4ec260d08f8feee279c41f73fd8e33aea5aa.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"settings\" SET openid_enabled = $1, wireguard_enabled = $2, webhooks_enabled = $3, worker_enabled = $4, challenge_template = $5, instance_name = $6, main_logo_url = $7, nav_logo_url = $8, smtp_server = $9, smtp_port = $10, smtp_encryption = $11, smtp_user = $12, smtp_password = $13, smtp_sender = $14, enrollment_vpn_step_optional = $15, enrollment_welcome_message = $16, enrollment_welcome_email = $17, enrollment_welcome_email_subject = $18, enrollment_use_welcome_message_as_email = $19, uuid = $20, ldap_url = $21, ldap_bind_username = $22, ldap_bind_password = $23, ldap_group_search_base = $24, ldap_user_search_base = $25, ldap_user_obj_class = $26, ldap_group_obj_class = $27, ldap_username_attr = $28, ldap_groupname_attr = $29, ldap_group_member_attr = $30, ldap_member_attr = $31, ldap_use_starttls = $32, ldap_tls_verify_cert = $33, openid_create_account = $34, license = $35, gateway_disconnect_notifications_enabled = $36, gateway_disconnect_notifications_inactivity_threshold = $37, gateway_disconnect_notifications_reconnect_notification_enabled = $38, ldap_sync_status = $39, ldap_enabled = $40, ldap_sync_enabled = $41, ldap_is_authoritative = $42, ldap_sync_interval = $43, ldap_user_auxiliary_obj_classes = $44, ldap_uses_ad = $45, ldap_user_rdn_attr = $46, ldap_sync_groups = $47, openid_username_handling = $48, ca_key_der = $49, ca_cert_der = $50, ca_expiry = $51, defguard_url = $52, default_admin_group_name = $53, authentication_period_days = $54, mfa_code_timeout_seconds = $55, public_proxy_url = $56, default_admin_id = $57, auth_cookie_timeout_days = $58, secret_key = $59, webauthn_rp_id = $60, disable_stats_purge = $61, stats_purge_frequency_hours = $62, stats_purge_threshold_days = $63, enrollment_token_timeout_hours = $64, password_reset_token_timeout_hours = $65, enrollment_session_timeout_minutes = $66, password_reset_session_timeout_minutes = $67 WHERE id = 1", + "query": "UPDATE \"settings\" SET openid_enabled = $1, wireguard_enabled = $2, webhooks_enabled = $3, worker_enabled = $4, challenge_template = $5, instance_name = $6, main_logo_url = $7, nav_logo_url = $8, smtp_server = $9, smtp_port = $10, smtp_encryption = $11, smtp_user = $12, smtp_password = $13, smtp_sender = $14, enrollment_vpn_step_optional = $15, enrollment_welcome_message = $16, enrollment_welcome_email = $17, enrollment_welcome_email_subject = $18, enrollment_use_welcome_message_as_email = $19, uuid = $20, ldap_url = $21, ldap_bind_username = $22, ldap_bind_password = $23, ldap_group_search_base = $24, ldap_user_search_base = $25, ldap_user_obj_class = $26, ldap_group_obj_class = $27, ldap_username_attr = $28, ldap_groupname_attr = $29, ldap_group_member_attr = $30, ldap_member_attr = $31, ldap_use_starttls = $32, ldap_tls_verify_cert = $33, openid_create_account = $34, license = $35, gateway_disconnect_notifications_enabled = $36, gateway_disconnect_notifications_inactivity_threshold = $37, gateway_disconnect_notifications_reconnect_notification_enabled = $38, ldap_sync_status = $39, ldap_enabled = $40, ldap_sync_enabled = $41, ldap_is_authoritative = $42, ldap_sync_interval = $43, ldap_user_auxiliary_obj_classes = $44, ldap_uses_ad = $45, ldap_user_rdn_attr = $46, ldap_sync_groups = $47, openid_username_handling = $48, ca_key_der = $49, ca_cert_der = $50, ca_expiry = $51, defguard_url = $52, default_admin_group_name = $53, authentication_period_days = $54, mfa_code_timeout_seconds = $55, public_proxy_url = $56, default_admin_id = $57, secret_key = $58, webauthn_rp_id = $59, disable_stats_purge = $60, stats_purge_frequency_hours = $61, stats_purge_threshold_days = $62, enrollment_token_timeout_hours = $63, password_reset_token_timeout_hours = $64, enrollment_session_timeout_minutes = $65, password_reset_session_timeout_minutes = $66 WHERE id = 1", "describe": { "columns": [], "parameters": { @@ -94,7 +94,6 @@ "Int4", "Text", "Int8", - "Int4", "Text", "Text", "Bool", @@ -108,5 +107,5 @@ }, "nullable": [] }, - "hash": "96917ed09e836086c38396c5778b83ca94ffff8f5f636ef650250befbcd78ab4" + "hash": "386714aa5e0cbc2d896edf6dcd2f4ec260d08f8feee279c41f73fd8e33aea5aa" } diff --git a/.sqlx/query-a043fb7d435267b6c1bd9febbbeb0bc1d29e14daf738f64246f8deea246f2630.json b/.sqlx/query-bb022291c5a000545df91eb246c5a24916ddd638337ed9427d94db5b1dc9766a.json similarity index 95% rename from .sqlx/query-a043fb7d435267b6c1bd9febbbeb0bc1d29e14daf738f64246f8deea246f2630.json rename to .sqlx/query-bb022291c5a000545df91eb246c5a24916ddd638337ed9427d94db5b1dc9766a.json index caa5f6b3a7..0e6b201a60 100644 --- a/.sqlx/query-a043fb7d435267b6c1bd9febbbeb0bc1d29e14daf738f64246f8deea246f2630.json +++ b/.sqlx/query-bb022291c5a000545df91eb246c5a24916ddd638337ed9427d94db5b1dc9766a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, smtp_password \"smtp_password?: SecretStringWrapper\", smtp_sender, enrollment_vpn_step_optional, enrollment_welcome_message, enrollment_welcome_email, enrollment_welcome_email_subject, enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, ldap_bind_password \"ldap_bind_password?: SecretStringWrapper\", ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, ldap_group_member_attr, ldap_member_attr, openid_create_account, license, gateway_disconnect_notifications_enabled, ldap_use_starttls, ldap_tls_verify_cert, gateway_disconnect_notifications_inactivity_threshold, gateway_disconnect_notifications_reconnect_notification_enabled, ldap_sync_status \"ldap_sync_status: LdapSyncStatus\", ldap_enabled, ldap_sync_enabled, ldap_is_authoritative, ldap_sync_interval, ldap_user_auxiliary_obj_classes, ldap_uses_ad, ldap_user_rdn_attr, ldap_sync_groups, openid_username_handling \"openid_username_handling: OpenIdUsernameHandling\", ca_key_der, ca_cert_der, ca_expiry, defguard_url, default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, public_proxy_url, default_admin_id, auth_cookie_timeout_days, secret_key, webauthn_rp_id, disable_stats_purge, stats_purge_frequency_hours, stats_purge_threshold_days, enrollment_token_timeout_hours, password_reset_token_timeout_hours, enrollment_session_timeout_minutes, password_reset_session_timeout_minutes FROM \"settings\" WHERE id = 1", + "query": "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, smtp_password \"smtp_password?: SecretStringWrapper\", smtp_sender, enrollment_vpn_step_optional, enrollment_welcome_message, enrollment_welcome_email, enrollment_welcome_email_subject, enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, ldap_bind_password \"ldap_bind_password?: SecretStringWrapper\", ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, ldap_group_member_attr, ldap_member_attr, openid_create_account, license, gateway_disconnect_notifications_enabled, ldap_use_starttls, ldap_tls_verify_cert, gateway_disconnect_notifications_inactivity_threshold, gateway_disconnect_notifications_reconnect_notification_enabled, ldap_sync_status \"ldap_sync_status: LdapSyncStatus\", ldap_enabled, ldap_sync_enabled, ldap_is_authoritative, ldap_sync_interval, ldap_user_auxiliary_obj_classes, ldap_uses_ad, ldap_user_rdn_attr, ldap_sync_groups, openid_username_handling \"openid_username_handling: OpenIdUsernameHandling\", ca_key_der, ca_cert_der, ca_expiry, defguard_url, default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, public_proxy_url, default_admin_id, secret_key, webauthn_rp_id, disable_stats_purge, stats_purge_frequency_hours, stats_purge_threshold_days, enrollment_token_timeout_hours, password_reset_token_timeout_hours, enrollment_session_timeout_minutes, password_reset_session_timeout_minutes FROM \"settings\" WHERE id = 1", "describe": { "columns": [ { @@ -322,51 +322,46 @@ }, { "ordinal": 57, - "name": "auth_cookie_timeout_days", - "type_info": "Int4" - }, - { - "ordinal": 58, "name": "secret_key", "type_info": "Text" }, { - "ordinal": 59, + "ordinal": 58, "name": "webauthn_rp_id", "type_info": "Text" }, { - "ordinal": 60, + "ordinal": 59, "name": "disable_stats_purge", "type_info": "Bool" }, { - "ordinal": 61, + "ordinal": 60, "name": "stats_purge_frequency_hours", "type_info": "Int4" }, { - "ordinal": 62, + "ordinal": 61, "name": "stats_purge_threshold_days", "type_info": "Int4" }, { - "ordinal": 63, + "ordinal": 62, "name": "enrollment_token_timeout_hours", "type_info": "Int4" }, { - "ordinal": 64, + "ordinal": 63, "name": "password_reset_token_timeout_hours", "type_info": "Int4" }, { - "ordinal": 65, + "ordinal": 64, "name": "enrollment_session_timeout_minutes", "type_info": "Int4" }, { - "ordinal": 66, + "ordinal": 65, "name": "password_reset_session_timeout_minutes", "type_info": "Int4" } @@ -432,7 +427,6 @@ false, false, true, - false, true, true, false, @@ -444,5 +438,5 @@ false ] }, - "hash": "a043fb7d435267b6c1bd9febbbeb0bc1d29e14daf738f64246f8deea246f2630" + "hash": "bb022291c5a000545df91eb246c5a24916ddd638337ed9427d94db5b1dc9766a" } diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 091e82e3aa..fdd1d779c2 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -37,11 +37,6 @@ pub struct DefGuardConfig { #[arg(long, env = "DEFGUARD_LOG_FILE")] pub log_file: Option, - #[arg(long, env = "DEFGUARD_AUTH_COOKIE_TIMEOUT")] - #[serde(skip_serializing)] - #[deprecated(since = "2.0.0", note = "Use Settings.auth_cookie_timeout instead")] - pub auth_cookie_timeout: Option, - #[arg(long, env = "DEFGUARD_SECRET_KEY")] #[serde(skip_serializing)] #[deprecated(since = "2.0.0", note = "Use Settings.secret_key instead")] diff --git a/crates/defguard_common/src/db/models/settings.rs b/crates/defguard_common/src/db/models/settings.rs index 5c757d41b1..936b0575eb 100644 --- a/crates/defguard_common/src/db/models/settings.rs +++ b/crates/defguard_common/src/db/models/settings.rs @@ -180,7 +180,6 @@ pub struct Settings { pub secret_key: Option, pub webauthn_rp_id: Option, pub disable_stats_purge: bool, - auth_cookie_timeout_days: i32, stats_purge_frequency_hours: i32, stats_purge_threshold_days: i32, enrollment_token_timeout_hours: i32, @@ -332,7 +331,7 @@ impl Settings { ca_key_der, ca_cert_der, ca_expiry, defguard_url, \ default_admin_group_name, authentication_period_days, mfa_code_timeout_seconds, \ public_proxy_url, \ - default_admin_id, auth_cookie_timeout_days, secret_key, webauthn_rp_id, disable_stats_purge, \ + default_admin_id, secret_key, webauthn_rp_id, disable_stats_purge, \ stats_purge_frequency_hours, stats_purge_threshold_days, \ enrollment_token_timeout_hours, password_reset_token_timeout_hours, \ enrollment_session_timeout_minutes, password_reset_session_timeout_minutes \ @@ -422,16 +421,15 @@ impl Settings { mfa_code_timeout_seconds = $55, \ public_proxy_url = $56, \ default_admin_id = $57, \ - auth_cookie_timeout_days = $58, \ - secret_key = $59, \ - webauthn_rp_id = $60, \ - disable_stats_purge = $61, \ - stats_purge_frequency_hours = $62, \ - stats_purge_threshold_days = $63, \ - enrollment_token_timeout_hours = $64, \ - password_reset_token_timeout_hours = $65, \ - enrollment_session_timeout_minutes = $66, \ - password_reset_session_timeout_minutes = $67 \ + secret_key = $58, \ + webauthn_rp_id = $59, \ + disable_stats_purge = $60, \ + stats_purge_frequency_hours = $61, \ + stats_purge_threshold_days = $62, \ + enrollment_token_timeout_hours = $63, \ + password_reset_token_timeout_hours = $64, \ + enrollment_session_timeout_minutes = $65, \ + password_reset_session_timeout_minutes = $66 \ WHERE id = 1", self.openid_enabled, self.wireguard_enabled, @@ -490,7 +488,6 @@ impl Settings { self.mfa_code_timeout_seconds, self.public_proxy_url, self.default_admin_id, - self.auth_cookie_timeout_days, self.secret_key, self.webauthn_rp_id, self.disable_stats_purge, @@ -605,11 +602,6 @@ impl Settings { Duration::from_secs(self.authentication_period_days as u64 * 24 * 3600) } - #[must_use] - pub fn auth_cookie_timeout(&self) -> Duration { - Duration::from_secs(self.auth_cookie_timeout_days as u64 * 24 * 3600) - } - #[must_use] pub fn stats_purge_frequency(&self) -> Duration { Duration::from_secs(self.stats_purge_frequency_hours as u64 * 3600) @@ -661,9 +653,6 @@ impl Settings { let hour = minute * 60; let day = hour * 24; - if let Some(auth_cookie_timeout) = config.auth_cookie_timeout { - self.auth_cookie_timeout_days = (auth_cookie_timeout.as_secs() / day) as i32; - } if let Some(secret_key) = &config.secret_key { let secret_key = secret_key.expose_secret(); if let Err(err) = Settings::validate_secret_key(secret_key) { @@ -890,9 +879,6 @@ mod test { }; let mut config = DefGuardConfig::new_test_config(); - config.auth_cookie_timeout = Some(Duration::from(std::time::Duration::from_secs( - 3 * 24 * 3600, - ))); config.secret_key = Some(SecretString::from("a".repeat(64))); config.webauthn_rp_id = Some("rp-from-config".into()); config.enrollment_url = Some(Url::parse("https://proxy.example.com").unwrap()); @@ -917,7 +903,6 @@ mod test { settings.apply_from_config(&config); - assert_eq!(settings.auth_cookie_timeout_days, 3); assert_eq!( settings.secret_key.as_deref(), Some("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql index c74dfd7d0d..81a6d9b726 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.down.sql @@ -1,5 +1,4 @@ ALTER TABLE settings - DROP COLUMN auth_cookie_timeout_days, DROP COLUMN secret_key, DROP COLUMN openid_signing_key, DROP COLUMN webauthn_rp_id, diff --git a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql index 64858bd93f..1f51954c46 100644 --- a/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql +++ b/migrations/20260227091211_[2.0.0]_settings_in_db.up.sql @@ -1,5 +1,4 @@ ALTER TABLE settings - ADD COLUMN auth_cookie_timeout_days int4 NOT NULL DEFAULT 7, ADD COLUMN secret_key text, ADD COLUMN openid_signing_key text, ADD COLUMN webauthn_rp_id text, diff --git a/web/messages/en/settings.json b/web/messages/en/settings.json index e66da76ddd..ab83c11a69 100644 --- a/web/messages/en/settings.json +++ b/web/messages/en/settings.json @@ -3,19 +3,37 @@ "settings_page_title": "Settings", "settings_breadcrumb_general": "General", "settings_breadcrumb_instance": "Instance settings", + "settings_breadcrumb_vpn_stats": "VPN stats", + "settings_breadcrumb_enrollment": "Enrollment", + "settings_breadcrumb_client_behavior": "Client behavior", "settings_instance_title": "Instance settings", "settings_instance_subtitle": "Here you can configure general instance parameters.", "settings_instance_label_name": "Instance name", "settings_instance_label_public_proxy_url": "Public Edge Component URL", "settings_instance_label_session_duration": "Session duration", - "settings_instance_label_auth_cookie_timeout_days": "Auth cookie timeout (days)", - "settings_instance_session_duration_1": "1 day", - "settings_instance_session_duration_2": "2 days", - "settings_instance_session_duration_3": "3 days", - "settings_instance_session_duration_7": "7 days", - "settings_instance_session_duration_10": "10 days", - "settings_instance_session_duration_14": "14 days", - "settings_instance_session_duration_30": "30 days", + "settings_vpn_stats_title": "VPN stats", + "settings_vpn_stats_subtitle": "Configure statistics purge behavior for VPN data.", + "settings_vpn_stats_toggle_disable_title": "Disable stats purge", + "settings_vpn_stats_label_purge_frequency": "Stats purge frequency", + "settings_vpn_stats_label_purge_threshold": "Stats purge threshold", + "settings_enrollment_title": "Enrollment", + "settings_enrollment_subtitle": "Configure token and session timeouts for enrollment and password reset flows.", + "settings_enrollment_label_token_validity": "Enrollment token validity", + "settings_enrollment_label_password_reset_token_validity": "Password reset token validity", + "settings_enrollment_label_session_expires_in": "Enrollment session expires in", + "settings_enrollment_label_password_reset_session_expires_in": "Password reset session expires in", + "settings_general_section_instance_content": "Configure your instance name and branding settings. Add a logo to personalize the interface and make it easily recognizable to your users.", + "settings_general_section_client_behavior_content": "Manage how users interact with the Defguard client. Control device management permissions, configuration access, and traffic routing options.", + "settings_general_section_vpn_stats_content": "Configure VPN statistics cleanup behavior, including purge frequency and retention threshold.", + "settings_general_section_enrollment_content": "Set enrollment and password reset token and session timeout values.", + "settings_duration_one_day": "1 day", + "settings_duration_days": "{days} days", + "settings_duration_one_hour": "1 hour", + "settings_duration_hours": "{hours} hours", + "settings_duration_one_minute": "1 minute", + "settings_duration_minutes": "{minutes} minutes", + "settings_duration_one_week": "1 week", + "settings_duration_one_month": "1 month", "settings_activity_log_streaming_title": "Activity log streaming", "settings_activity_log_streaming_description": "Monitor and export real-time activity logs from your Defguard instance. Stream events to external systems for auditing, analytics, or security monitoring.", "settings_activity_log_streaming_no_upstreams": "You don't have any activity log upstreams.", diff --git a/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx b/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx index e45f80f13a..fffe590bfa 100644 --- a/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx +++ b/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx @@ -32,10 +32,10 @@ import { canUseBusinessFeature } from '../../../shared/utils/license'; const breadcrumbs = [ - General + {m.settings_breadcrumb_general()} , - Client behavior + {m.settings_breadcrumb_client_behavior()} , ]; diff --git a/web/src/pages/settings/SettingsEnrollmentPage/SettingsEnrollmentPage.tsx b/web/src/pages/settings/SettingsEnrollmentPage/SettingsEnrollmentPage.tsx index 81c2bbb020..ce3c04c209 100644 --- a/web/src/pages/settings/SettingsEnrollmentPage/SettingsEnrollmentPage.tsx +++ b/web/src/pages/settings/SettingsEnrollmentPage/SettingsEnrollmentPage.tsx @@ -18,6 +18,10 @@ import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../../shared/form'; import { formChangeLogic } from '../../../shared/formLogic'; import { getSettingsQueryOptions } from '../../../shared/query'; +import { + createNumericSelectOptions, + withNumericFallbackOption, +} from '../../../shared/utils/numericSelectOptions'; const breadcrumbs = [ - General + {m.settings_breadcrumb_general()} , - Enrollment + {m.settings_breadcrumb_enrollment()} , ]; export const SettingsEnrollmentPage = () => { const { data: settings } = useQuery(getSettingsQueryOptions); return ( - + {isPresent(settings) && ( @@ -64,6 +68,19 @@ const formSchema = z.object({ type FormFields = z.infer; +const enrollmentTokenTimeoutBaseOptions = createNumericSelectOptions({ + 1: m.settings_duration_one_hour(), + 12: m.settings_duration_hours({ hours: 12 }), + 24: m.settings_duration_one_day(), + 168: m.settings_duration_one_week(), +}); + +const enrollmentSessionTimeoutBaseOptions = createNumericSelectOptions({ + 10: m.settings_duration_minutes({ minutes: 10 }), + 30: m.settings_duration_minutes({ minutes: 30 }), + 60: m.settings_duration_one_hour(), +}); + const Content = ({ settings }: { settings: Settings }) => { const { mutateAsync } = useMutation({ mutationFn: api.settings.patchSettings, @@ -85,6 +102,46 @@ const Content = ({ settings }: { settings: Settings }) => { [settings], ); + const enrollmentTokenTimeoutOptions = useMemo( + () => + withNumericFallbackOption( + enrollmentTokenTimeoutBaseOptions, + defaultValues.enrollment_token_timeout_hours, + 'hours', + ), + [defaultValues.enrollment_token_timeout_hours], + ); + + const passwordResetTokenTimeoutOptions = useMemo( + () => + withNumericFallbackOption( + enrollmentTokenTimeoutBaseOptions, + defaultValues.password_reset_token_timeout_hours, + 'hours', + ), + [defaultValues.password_reset_token_timeout_hours], + ); + + const enrollmentSessionTimeoutOptions = useMemo( + () => + withNumericFallbackOption( + enrollmentSessionTimeoutBaseOptions, + defaultValues.enrollment_session_timeout_minutes, + 'minutes', + ), + [defaultValues.enrollment_session_timeout_minutes], + ); + + const passwordResetSessionTimeoutOptions = useMemo( + () => + withNumericFallbackOption( + enrollmentSessionTimeoutBaseOptions, + defaultValues.password_reset_session_timeout_minutes, + 'minutes', + ), + [defaultValues.password_reset_session_timeout_minutes], + ); + const form = useAppForm({ defaultValues, validationLogic: formChangeLogic, @@ -109,40 +166,40 @@ const Content = ({ settings }: { settings: Settings }) => { {(field) => ( - )} {(field) => ( - )} {(field) => ( - )} {(field) => ( - )} diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx index df69eff1f2..000e5a8111 100644 --- a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query'; import { Link, useNavigate } from '@tanstack/react-router'; +import { m } from '../../../../paraglide/messages'; import { businessBadgeProps } from '../../../../shared/components/badges/BusinessBadge'; import { SettingsLayout } from '../../../../shared/components/SettingsLayout/SettingsLayout'; import { SectionSelect } from '../../../../shared/defguard-ui/components/SectionSelect/SectionSelect'; @@ -20,8 +21,8 @@ export const SettingsGeneralTab = () => { @@ -39,31 +40,31 @@ export const SettingsGeneralTab = () => { - { - navigate({ to: '/settings/client' }); - }} - /> - + + { + navigate({ to: '/settings/client' }); + }} + /> ); }; diff --git a/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx b/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx index c13edd7001..781370c35f 100644 --- a/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx +++ b/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx @@ -19,6 +19,10 @@ import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../../shared/form'; import { formChangeLogic } from '../../../shared/formLogic'; import { getSettingsQueryOptions } from '../../../shared/query'; +import { + createNumericSelectOptions, + withNumericFallbackOption, +} from '../../../shared/utils/numericSelectOptions'; const breadcrumbs = [ ; -const sessionDurationOptions = [ - { key: 1, value: 1, label: m.settings_instance_session_duration_1() }, - { key: 2, value: 2, label: m.settings_instance_session_duration_2() }, - { key: 3, value: 3, label: m.settings_instance_session_duration_3() }, - { key: 7, value: 7, label: m.settings_instance_session_duration_7() }, - { - key: 10, - value: 10, - label: m.settings_instance_session_duration_10(), - }, - { - key: 14, - value: 14, - label: m.settings_instance_session_duration_14(), - }, - { - key: 30, - value: 30, - label: m.settings_instance_session_duration_30(), - }, -]; +const sessionDurationOptions = createNumericSelectOptions({ + 1: m.settings_duration_one_day(), + 2: m.settings_duration_days({ days: 2 }), + 3: m.settings_duration_days({ days: 3 }), + 7: m.settings_duration_days({ days: 7 }), + 10: m.settings_duration_days({ days: 10 }), + 14: m.settings_duration_days({ days: 14 }), + 30: m.settings_duration_days({ days: 30 }), +}); + +const sessionDurationFallbackUnit = 'days'; const Content = ({ settings }: { settings: Settings }) => { const { mutateAsync } = useMutation({ @@ -116,7 +109,6 @@ const Content = ({ settings }: { settings: Settings }) => { const defaultValues = useMemo( (): FormFields => ({ instance_name: settings.instance_name ?? '', - auth_cookie_timeout_days: settings.auth_cookie_timeout_days ?? 7, public_proxy_url: settings.public_proxy_url ?? '', authentication_period_days: settings.authentication_period_days ?? 7, }), @@ -124,10 +116,19 @@ const Content = ({ settings }: { settings: Settings }) => { settings.instance_name, settings.public_proxy_url, settings.authentication_period_days, - settings.auth_cookie_timeout_days, ], ); + const sessionDurationSelectOptions = useMemo( + () => + withNumericFallbackOption( + sessionDurationOptions, + defaultValues.authentication_period_days, + sessionDurationFallbackUnit, + ), + [defaultValues.authentication_period_days], + ); + const form = useAppForm({ defaultValues, validationLogic: formChangeLogic, @@ -170,17 +171,7 @@ const Content = ({ settings }: { settings: Settings }) => { - )} - - - - {(field) => ( - )} diff --git a/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx b/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx index d45f079dd3..2f17b6f308 100644 --- a/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx +++ b/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx @@ -18,6 +18,10 @@ import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../../shared/form'; import { formChangeLogic } from '../../../shared/formLogic'; import { getSettingsQueryOptions } from '../../../shared/query'; +import { + createNumericSelectOptions, + withNumericFallbackOption, +} from '../../../shared/utils/numericSelectOptions'; const breadcrumbs = [ - General + {m.settings_breadcrumb_general()} , - VPN stats + {m.settings_breadcrumb_vpn_stats()} , ]; export const SettingsVpnStatsPage = () => { const { data: settings } = useQuery(getSettingsQueryOptions); return ( - + {isPresent(settings) && ( @@ -63,6 +67,23 @@ const formSchema = z.object({ type FormFields = z.infer; +const statsPurgeFrequencyBaseOptions = createNumericSelectOptions({ + 1: m.settings_duration_one_hour(), + 12: m.settings_duration_hours({ hours: 12 }), + 24: m.settings_duration_one_day(), + 48: m.settings_duration_days({ days: 2 }), + 168: m.settings_duration_one_week(), + 720: m.settings_duration_one_month(), +}); + +const statsPurgeThresholdBaseOptions = createNumericSelectOptions({ + 1: m.settings_duration_one_day(), + 7: m.settings_duration_days({ days: 7 }), + 14: m.settings_duration_days({ days: 14 }), + 30: m.settings_duration_days({ days: 30 }), + 90: m.settings_duration_days({ days: 90 }), +}); + const Content = ({ settings }: { settings: Settings }) => { const { mutateAsync } = useMutation({ mutationFn: api.settings.patchSettings, @@ -80,6 +101,26 @@ const Content = ({ settings }: { settings: Settings }) => { [settings], ); + const statsPurgeFrequencyOptions = useMemo( + () => + withNumericFallbackOption( + statsPurgeFrequencyBaseOptions, + defaultValues.stats_purge_frequency_hours, + 'hours', + ), + [defaultValues.stats_purge_frequency_hours], + ); + + const statsPurgeThresholdOptions = useMemo( + () => + withNumericFallbackOption( + statsPurgeThresholdBaseOptions, + defaultValues.stats_purge_threshold_days, + 'days', + ), + [defaultValues.stats_purge_threshold_days], + ); + const form = useAppForm({ defaultValues, validationLogic: formChangeLogic, @@ -106,28 +147,27 @@ const Content = ({ settings }: { settings: Settings }) => { {(field) => ( )} {(field) => ( - )} {(field) => ( - )} diff --git a/web/src/shared/api/types.ts b/web/src/shared/api/types.ts index f52540a573..691af31f1b 100644 --- a/web/src/shared/api/types.ts +++ b/web/src/shared/api/types.ts @@ -896,7 +896,6 @@ export interface SettingsGatewayNotifications { } export interface SettingsTimeoutsAndMaintenance { - auth_cookie_timeout_days: number; disable_stats_purge: boolean; stats_purge_frequency_hours: number; stats_purge_threshold_days: number; diff --git a/web/src/shared/const/overviewPeriodOptions.ts b/web/src/shared/const/overviewPeriodOptions.ts deleted file mode 100644 index 17b3e1c682..0000000000 --- a/web/src/shared/const/overviewPeriodOptions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { SelectOption } from '../defguard-ui/components/Select/types'; - -export const overviewPeriodOptions: SelectOption[] = [ - { key: 1, label: '1h period', value: 1 }, - { key: 2, label: '2h period', value: 2 }, - { key: 6, label: '6h period', value: 6 }, - { key: 8, label: '8h period', value: 8 }, - { key: 12, label: '12h period', value: 12 }, - { key: 16, label: '16h period', value: 16 }, - { key: 24, label: '24h period', value: 24 }, -]; diff --git a/web/src/shared/utils/numericSelectOptions.ts b/web/src/shared/utils/numericSelectOptions.ts new file mode 100644 index 0000000000..329c1f428a --- /dev/null +++ b/web/src/shared/utils/numericSelectOptions.ts @@ -0,0 +1,43 @@ +import type { SelectOption } from '../defguard-ui/components/Select/types'; + +export type NumericSelectOption = SelectOption; + +export type NumericSelectOptionMap = Readonly>; + +/** Builds sorted numeric select options from a value-to-label map. */ +export const createNumericSelectOptions = ( + optionMap: NumericSelectOptionMap, +): NumericSelectOption[] => + Object.entries(optionMap) + .map(([value, label]) => { + const numericValue = Number(value); + + return { + key: numericValue, + label, + value: numericValue, + }; + }) + .sort((a, b) => a.value - b.value); + +/** Appends the current numeric value when it is missing from predefined options. */ +export const withNumericFallbackOption = ( + options: readonly NumericSelectOption[], + value: number, + unit: string, +): NumericSelectOption[] => { + if (options.some((option) => option.value === value)) { + return [...options]; + } + + const label = `${value} ${unit}`; + + return [ + ...options, + { + key: value, + label, + value, + }, + ].sort((a, b) => a.value - b.value); +};