From 1c133b6f6e97202e7dcb964ed5541e96b27089c1 Mon Sep 17 00:00:00 2001 From: h44z Date: Thu, 16 Apr 2026 21:55:41 +0200 Subject: [PATCH] Improved default peer handling (#674) * create default peers for newly created interfaces (#666) * allow to manually create default peers for an interface (#666) --- config.yml.sample | 5 +- docs/documentation/configuration/examples.md | 2 +- docs/documentation/configuration/overview.md | 26 +- frontend/package-lock.json | 523 +++++++++--------- frontend/package.json | 10 +- .../src/components/InterfaceEditModal.vue | 46 +- frontend/src/lang/translations/de.json | 1 + frontend/src/lang/translations/en.json | 1 + frontend/src/lang/translations/es.json | 1 + frontend/src/lang/translations/fr.json | 1 + frontend/src/lang/translations/ko.json | 1 + frontend/src/lang/translations/pt.json | 1 + frontend/src/lang/translations/ru.json | 1 + frontend/src/lang/translations/uk.json | 1 + frontend/src/lang/translations/vi.json | 1 + frontend/src/lang/translations/zh.json | 1 + frontend/src/stores/interfaces.js | 12 + internal/adapters/database.go | 2 +- .../app/api/v0/backend/interface_service.go | 9 + .../app/api/v0/handlers/endpoint_config.go | 2 +- .../api/v0/handlers/endpoint_interfaces.go | 34 ++ internal/app/wireguard/wireguard.go | 45 +- .../app/wireguard/wireguard_interfaces.go | 2 +- .../wireguard/wireguard_interfaces_test.go | 7 - internal/app/wireguard/wireguard_peers.go | 129 ++++- .../app/wireguard/wireguard_peers_test.go | 12 +- internal/config/config.go | 54 +- internal/domain/interface.go | 12 + internal/domain/interface_test.go | 26 + internal/domain/user.go | 12 + internal/domain/user_test.go | 14 + 31 files changed, 658 insertions(+), 336 deletions(-) diff --git a/config.yml.sample b/config.yml.sample index 51a9680..7033456 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -6,8 +6,9 @@ advanced: core: admin_user: test@test.de admin_password: secret - create_default_peer: true - create_default_peer_on_creation: false + create_default_peer_on_login: true + create_default_peer_on_user_creation: false + create_default_peer_on_interface_creation: false web: external_url: http://localhost:8888 diff --git a/docs/documentation/configuration/examples.md b/docs/documentation/configuration/examples.md index 396c4c1..ea4d1e0 100644 --- a/docs/documentation/configuration/examples.md +++ b/docs/documentation/configuration/examples.md @@ -8,7 +8,7 @@ core: admin_password: password admin_api_token: super-s3cr3t-api-token-or-a-UUID import_existing: false - create_default_peer: true + create_default_peer_on_login: true self_provisioning_allowed: true backend: diff --git a/docs/documentation/configuration/overview.md b/docs/documentation/configuration/overview.md index 0b96c38..af3c632 100644 --- a/docs/documentation/configuration/overview.md +++ b/docs/documentation/configuration/overview.md @@ -155,17 +155,33 @@ More advanced options are found in the subsequent `Advanced` section. - **Environment Variable:** `WG_PORTAL_CORE_EDITABLE_KEYS` - **Description:** Allow editing of WireGuard key-pairs directly in the UI. -### `create_default_peer` +### `create_default_peer` (deprecated) +- **Default:** `false` +- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER` +- **Description:** **DEPRECATED** in favor of [create_default_peer_on_login](#create_default_peer_on_login). If set to `true`, this option is equivalent to enabling `create_default_peer_on_login`. It will be removed in a future release (2.4). + +### `create_default_peer_on_creation` (deprecated) +- **Default:** `false` +- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION` +- **Description:** **DEPRECATED** in favor of [create_default_peer_on_user_creation](#create_default_peer_on_user_creation) and [create_default_peer_on_interface_creation](#create_default_peer_on_interface_creation). If set to `true`, both of those options are enabled. It will be removed in a future release (2.4). + +### `create_default_peer_on_login` - **Default:** `false` - **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER` - **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set. - **Important:** This option is only effective for interfaces where the "Create default peer" flag is set (via the UI). -### `create_default_peer_on_creation` +### `create_default_peer_on_user_creation` - **Default:** `false` -- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION` -- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set. -- **Important:** This option requires [create_default_peer](#create_default_peer) to be enabled. +- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_USER_CREATION` +- **Description:** If a new user is created (e.g., through LDAP sync or registration) and has no peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set. +- **Important:** This option is only effective for interfaces where the "Create default peer" flag is set (via the UI). + +### `create_default_peer_on_interface_creation` +- **Default:** `false` +- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_INTERFACE_CREATION` +- **Description:** When a new server interface is created with the "Create default peer" flag set, automatically create a default WireGuard peer on that interface for every existing user who does not yet have a peer on it. +- **Important:** This option is only effective for interfaces where the "Create default peer" flag is set (via the UI). ### `re_enable_peer_after_user_enable` - **Default:** `true` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b557f2c..dab5357 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,15 +23,15 @@ "is-ip": "^5.0.1", "pinia": "^3.0.4", "prismjs": "^1.30.0", - "vue": "^3.5.31", - "vue-i18n": "^11.3.0", + "vue": "^3.5.32", + "vue-i18n": "^11.3.2", "vue-prism-component": "github:h44z/vue-prism-component", "vue-router": "^5.0.4" }, "devDependencies": { - "@vitejs/plugin-vue": "^6.0.5", - "sass-embedded": "^1.98.0", - "vite": "^8.0.3" + "@vitejs/plugin-vue": "^6.0.6", + "sass-embedded": "^1.99.0", + "vite": "^8.0.8" } }, "node_modules/@babel/generator": { @@ -104,38 +104,35 @@ "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -159,14 +156,14 @@ } }, "node_modules/@intlify/core-base": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.3.0.tgz", - "integrity": "sha512-NNX5jIwF4TJBe7RtSKDMOA6JD9mp2mRcBHAwt2X+Q8PvnZub0yj5YYXlFu2AcESdgQpEv/5Yx2uOCV/yh7YkZg==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.3.2.tgz", + "integrity": "sha512-cgsUaV/dyD6aS49UPgerIblrWeXAZHNaDWqm4LujOGC7IafSyhghGXEiSVvuDYaDPiQTP+tSFSTM1HIu7Yp1nA==", "license": "MIT", "dependencies": { - "@intlify/devtools-types": "11.3.0", - "@intlify/message-compiler": "11.3.0", - "@intlify/shared": "11.3.0" + "@intlify/devtools-types": "11.3.2", + "@intlify/message-compiler": "11.3.2", + "@intlify/shared": "11.3.2" }, "engines": { "node": ">= 16" @@ -176,13 +173,13 @@ } }, "node_modules/@intlify/devtools-types": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.3.0.tgz", - "integrity": "sha512-G9CNL4WpANWVdUjubOIIS7/D2j/0j+1KJmhBJxHilWNKr9mmt3IjFV3Hq4JoBP23uOoC5ynxz/FHZ42M+YxfGw==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.3.2.tgz", + "integrity": "sha512-q96G2ZZw0FNoXzejbjIf9dbfgz1xyYBZu6ZT4b5TE/55j8d1O9X5jv0k+U+L3fVe7uebPcqRQFD0ffm30i5mJA==", "license": "MIT", "dependencies": { - "@intlify/core-base": "11.3.0", - "@intlify/shared": "11.3.0" + "@intlify/core-base": "11.3.2", + "@intlify/shared": "11.3.2" }, "engines": { "node": ">= 16" @@ -192,12 +189,12 @@ } }, "node_modules/@intlify/message-compiler": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.3.0.tgz", - "integrity": "sha512-RAJp3TMsqohg/Wa7bVF3cChRhecSYBLrTCQSj7j0UtWVFLP+6iEJoE2zb7GU5fp+fmG5kCbUdzhmlAUCWXiUJw==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.3.2.tgz", + "integrity": "sha512-d/awyHUkNSaGPxBxT/qlUpfRizxHX9dt55CnW03xx5p1KmMyfYHKupCnvzINX+Na8JR8LAR7y32lPKjoeQGmzA==", "license": "MIT", "dependencies": { - "@intlify/shared": "11.3.0", + "@intlify/shared": "11.3.2", "source-map-js": "^1.0.2" }, "engines": { @@ -208,9 +205,9 @@ } }, "node_modules/@intlify/shared": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.0.tgz", - "integrity": "sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.2.tgz", + "integrity": "sha512-x66fjdH6i+lNYPae5URSQGTjBL68Av6hi09jvC5Ci96iTkwfqrPhCj46aylQZmgMaG89rOZCIKqS7ApC8ZDVjg==", "license": "MIT", "engines": { "node": ">= 16" @@ -274,9 +271,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, @@ -293,9 +290,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", "dev": true, "license": "MIT", "funding": { @@ -641,9 +638,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", "cpu": [ "arm64" ], @@ -658,9 +655,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -675,9 +672,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -692,9 +689,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ "x64" ], @@ -709,9 +706,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ "arm" ], @@ -726,9 +723,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ "arm64" ], @@ -746,9 +743,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], @@ -766,9 +763,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ "ppc64" ], @@ -786,9 +783,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ "s390x" ], @@ -806,9 +803,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", "cpu": [ "x64" ], @@ -826,9 +823,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", "cpu": [ "x64" ], @@ -846,9 +843,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ "arm64" ], @@ -863,9 +860,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", "cpu": [ "wasm32" ], @@ -873,16 +870,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", "cpu": [ "arm64" ], @@ -897,9 +896,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -914,9 +913,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", + "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", "dev": true, "license": "MIT" }, @@ -938,13 +937,13 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz", - "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.6.tgz", + "integrity": "sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.2" + "@rolldown/pluginutils": "1.0.0-rc.13" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -996,39 +995,39 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz", - "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz", + "integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.2", - "@vue/shared": "3.5.31", + "@vue/shared": "3.5.32", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz", - "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz", + "integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.31", - "@vue/shared": "3.5.31" + "@vue/compiler-core": "3.5.32", + "@vue/shared": "3.5.32" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz", - "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz", + "integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.2", - "@vue/compiler-core": "3.5.31", - "@vue/compiler-dom": "3.5.31", - "@vue/compiler-ssr": "3.5.31", - "@vue/shared": "3.5.31", + "@vue/compiler-core": "3.5.32", + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", @@ -1036,13 +1035,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz", - "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz", + "integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.31", - "@vue/shared": "3.5.31" + "@vue/compiler-dom": "3.5.32", + "@vue/shared": "3.5.32" } }, "node_modules/@vue/devtools-api": { @@ -1079,53 +1078,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz", - "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.32.tgz", + "integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.31" + "@vue/shared": "3.5.32" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz", - "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.32.tgz", + "integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.31", - "@vue/shared": "3.5.31" + "@vue/reactivity": "3.5.32", + "@vue/shared": "3.5.32" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz", - "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz", + "integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.31", - "@vue/runtime-core": "3.5.31", - "@vue/shared": "3.5.31", + "@vue/reactivity": "3.5.32", + "@vue/runtime-core": "3.5.32", + "@vue/shared": "3.5.32", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz", - "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.32.tgz", + "integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.31", - "@vue/shared": "3.5.31" + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32" }, "peerDependencies": { - "vue": "3.5.31" + "vue": "3.5.32" } }, "node_modules/@vue/shared": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz", - "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz", + "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==", "license": "MIT" }, "node_modules/acorn": { @@ -2067,14 +2066,14 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" @@ -2083,27 +2082,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", "dev": true, "license": "MIT" }, @@ -2118,9 +2117,9 @@ } }, "node_modules/sass": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", - "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", "dev": true, "license": "MIT", "optional": true, @@ -2140,9 +2139,9 @@ } }, "node_modules/sass-embedded": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.98.0.tgz", - "integrity": "sha512-Do7u6iRb6K+lrllcTkB1BXcHwOxcKe3rEfOF/GcCLE2w3WpddakRAosJOHFUR37DpsvimQXEt5abs3NzUjEIqg==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.99.0.tgz", + "integrity": "sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==", "dev": true, "license": "MIT", "dependencies": { @@ -2161,30 +2160,30 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "sass-embedded-all-unknown": "1.98.0", - "sass-embedded-android-arm": "1.98.0", - "sass-embedded-android-arm64": "1.98.0", - "sass-embedded-android-riscv64": "1.98.0", - "sass-embedded-android-x64": "1.98.0", - "sass-embedded-darwin-arm64": "1.98.0", - "sass-embedded-darwin-x64": "1.98.0", - "sass-embedded-linux-arm": "1.98.0", - "sass-embedded-linux-arm64": "1.98.0", - "sass-embedded-linux-musl-arm": "1.98.0", - "sass-embedded-linux-musl-arm64": "1.98.0", - "sass-embedded-linux-musl-riscv64": "1.98.0", - "sass-embedded-linux-musl-x64": "1.98.0", - "sass-embedded-linux-riscv64": "1.98.0", - "sass-embedded-linux-x64": "1.98.0", - "sass-embedded-unknown-all": "1.98.0", - "sass-embedded-win32-arm64": "1.98.0", - "sass-embedded-win32-x64": "1.98.0" + "sass-embedded-all-unknown": "1.99.0", + "sass-embedded-android-arm": "1.99.0", + "sass-embedded-android-arm64": "1.99.0", + "sass-embedded-android-riscv64": "1.99.0", + "sass-embedded-android-x64": "1.99.0", + "sass-embedded-darwin-arm64": "1.99.0", + "sass-embedded-darwin-x64": "1.99.0", + "sass-embedded-linux-arm": "1.99.0", + "sass-embedded-linux-arm64": "1.99.0", + "sass-embedded-linux-musl-arm": "1.99.0", + "sass-embedded-linux-musl-arm64": "1.99.0", + "sass-embedded-linux-musl-riscv64": "1.99.0", + "sass-embedded-linux-musl-x64": "1.99.0", + "sass-embedded-linux-riscv64": "1.99.0", + "sass-embedded-linux-x64": "1.99.0", + "sass-embedded-unknown-all": "1.99.0", + "sass-embedded-win32-arm64": "1.99.0", + "sass-embedded-win32-x64": "1.99.0" } }, "node_modules/sass-embedded-all-unknown": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.98.0.tgz", - "integrity": "sha512-6n4RyK7/1mhdfYvpP3CClS3fGoYqDvRmLClCESS6I7+SAzqjxvGG6u5Fo+cb1nrPNbbilgbM4QKdgcgWHO9NCA==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.99.0.tgz", + "integrity": "sha512-qPIRG8Uhjo6/OKyAKixTnwMliTz+t9K6Duk0mx5z+K7n0Ts38NSJz2sjDnc7cA/8V9Lb3q09H38dZ1CLwD+ssw==", "cpu": [ "!arm", "!arm64", @@ -2195,13 +2194,13 @@ "license": "MIT", "optional": true, "dependencies": { - "sass": "1.98.0" + "sass": "1.99.0" } }, "node_modules/sass-embedded-android-arm": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.98.0.tgz", - "integrity": "sha512-LjGiMhHgu7VL1n7EJxTCre1x14bUsWd9d3dnkS2rku003IWOI/fxc7OXgaKagoVzok1kv09rzO3vFXJR5ZeONQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.99.0.tgz", + "integrity": "sha512-EHvJ0C7/VuP78Qr6f8gIUVUmCqIorEQpw2yp3cs3SMg02ZuumlhjXvkTcFBxHmFdFR23vTNk1WnhY6QSeV1nFQ==", "cpu": [ "arm" ], @@ -2216,9 +2215,9 @@ } }, "node_modules/sass-embedded-android-arm64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.98.0.tgz", - "integrity": "sha512-M9Ra98A6vYJHpwhoC/5EuH1eOshQ9ZyNwC8XifUDSbRl/cGeQceT1NReR9wFj3L7s1pIbmes1vMmaY2np0uAKQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.99.0.tgz", + "integrity": "sha512-fNHhdnP23yqqieCbAdym4N47AleSwjbNt6OYIYx4DdACGdtERjQB4iOX/TaKsW034MupfF7SjnAAK8w7Ptldtg==", "cpu": [ "arm64" ], @@ -2233,9 +2232,9 @@ } }, "node_modules/sass-embedded-android-riscv64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.98.0.tgz", - "integrity": "sha512-WPe+0NbaJIZE1fq/RfCZANMeIgmy83x4f+SvFOG7LhUthHpZWcOcrPTsCKKmN3xMT3iw+4DXvqTYOCYGRL3hcQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.99.0.tgz", + "integrity": "sha512-4zqDFRvgGDTL5vTHuIhRxUpXFoh0Cy7Gm5Ywk19ASd8Settmd14YdPRZPmMxfgS1GH292PofV1fq1ifiSEJWBw==", "cpu": [ "riscv64" ], @@ -2250,9 +2249,9 @@ } }, "node_modules/sass-embedded-android-x64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.98.0.tgz", - "integrity": "sha512-zrD25dT7OHPEgLWuPEByybnIfx4rnCtfge4clBgjZdZ3lF6E7qNLRBtSBmoFflh6Vg0RlEjJo5VlpnTMBM5MQQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.99.0.tgz", + "integrity": "sha512-Uk53k/dGYt04RjOL4gFjZ0Z9DH9DKh8IA8WsXUkNqsxerAygoy3zqRBS2zngfE9K2jiOM87q+1R1p87ory9oQQ==", "cpu": [ "x64" ], @@ -2267,9 +2266,9 @@ } }, "node_modules/sass-embedded-darwin-arm64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.98.0.tgz", - "integrity": "sha512-cgr1z9rBnCdMf8K+JabIaYd9Rag2OJi5mjq08XJfbJGMZV/TA6hFJCLGkr5/+ZOn4/geTM5/3aSfQ8z5EIJAOg==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.99.0.tgz", + "integrity": "sha512-u61/7U3IGLqoO6gL+AHeiAtlTPFwJK1+964U8gp45ZN0hzh1yrARf5O1mivXv8NnNgJvbG2wWJbiNZP0lG/lTg==", "cpu": [ "arm64" ], @@ -2284,9 +2283,9 @@ } }, "node_modules/sass-embedded-darwin-x64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.98.0.tgz", - "integrity": "sha512-OLBOCs/NPeiMqTdOrMFbVHBQFj19GS3bSVSxIhcCq16ZyhouUkYJEZjxQgzv9SWA2q6Ki8GCqp4k6jMeUY9dcA==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.99.0.tgz", + "integrity": "sha512-j/kkk/NcXdIameLezSfXjgCiBkVcA+G60AXrX768/3g0miK1g7M9dj7xOhCb1i7/wQeiEI3rw2LLuO63xRIn4A==", "cpu": [ "x64" ], @@ -2301,9 +2300,9 @@ } }, "node_modules/sass-embedded-linux-arm": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.98.0.tgz", - "integrity": "sha512-03baQZCxVyEp8v1NWBRlzGYrmVT/LK7ZrHlF1piscGiGxwfdxoLXVuxsylx3qn/dD/4i/rh7Bzk7reK1br9jvQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.99.0.tgz", + "integrity": "sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==", "cpu": [ "arm" ], @@ -2319,9 +2318,9 @@ } }, "node_modules/sass-embedded-linux-arm64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.98.0.tgz", - "integrity": "sha512-axOE3t2MTBwCtkUCbrdM++Gj0gC0fdHJPrgzQ+q1WUmY9NoNMGqflBtk5mBZaWUeha2qYO3FawxCB8lctFwCtw==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.99.0.tgz", + "integrity": "sha512-btNcFpItcB56L40n8hDeL7sRSMLDXQ56nB5h2deddJx1n60rpKSElJmkaDGHtpkrY+CTtDRV0FZDjHeTJddYew==", "cpu": [ "arm64" ], @@ -2337,9 +2336,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.98.0.tgz", - "integrity": "sha512-OBkjTDPYR4hSaueOGIM6FDpl9nt/VZwbSRpbNu9/eEJcxE8G/vynRugW8KRZmCFjPy8j/jkGBvvS+k9iOqKV3g==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.99.0.tgz", + "integrity": "sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==", "cpu": [ "arm" ], @@ -2355,9 +2354,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.98.0.tgz", - "integrity": "sha512-LeqNxQA8y4opjhe68CcFvMzCSrBuJqYVFbwElEj9bagHXQHTp9xVPJRn6VcrC+0VLEDq13HVXMv7RslIuU0zmA==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.99.0.tgz", + "integrity": "sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==", "cpu": [ "arm64" ], @@ -2373,9 +2372,9 @@ } }, "node_modules/sass-embedded-linux-musl-riscv64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.98.0.tgz", - "integrity": "sha512-7w6hSuOHKt8FZsmjRb3iGSxEzM87fO9+M8nt5JIQYMhHTj5C+JY/vcske0v715HCVj5e1xyTnbGXf8FcASeAIw==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.99.0.tgz", + "integrity": "sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==", "cpu": [ "riscv64" ], @@ -2391,9 +2390,9 @@ } }, "node_modules/sass-embedded-linux-musl-x64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.98.0.tgz", - "integrity": "sha512-QikNyDEJOVqPmxyCFkci8ZdCwEssdItfjQFJB+D+Uy5HFqcS5Lv3d3GxWNX/h1dSb23RPyQdQc267ok5SbEyJw==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.99.0.tgz", + "integrity": "sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==", "cpu": [ "x64" ], @@ -2409,9 +2408,9 @@ } }, "node_modules/sass-embedded-linux-riscv64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.98.0.tgz", - "integrity": "sha512-E7fNytc/v4xFBQKzgzBddV/jretA4ULAPO6XmtBiQu4zZBdBozuSxsQLe2+XXeb0X4S2GIl72V7IPABdqke/vA==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.99.0.tgz", + "integrity": "sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==", "cpu": [ "riscv64" ], @@ -2427,9 +2426,9 @@ } }, "node_modules/sass-embedded-linux-x64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.98.0.tgz", - "integrity": "sha512-VsvP0t/uw00mMNPv3vwyYKUrFbqzxQHnRMO+bHdAMjvLw4NFf6mscpym9Bzf+NXwi1ZNKnB6DtXjmcpcvqFqYg==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.99.0.tgz", + "integrity": "sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==", "cpu": [ "x64" ], @@ -2445,9 +2444,9 @@ } }, "node_modules/sass-embedded-unknown-all": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.98.0.tgz", - "integrity": "sha512-C4MMzcAo3oEDQnW7L8SBgB9F2Fq5qHPnaYTZRMOH3Mp/7kM4OooBInXpCiiFjLnjY95hzP4KyctVx0uYR6MYlQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.99.0.tgz", + "integrity": "sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==", "dev": true, "license": "MIT", "optional": true, @@ -2458,13 +2457,13 @@ "!win32" ], "dependencies": { - "sass": "1.98.0" + "sass": "1.99.0" } }, "node_modules/sass-embedded-win32-arm64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.98.0.tgz", - "integrity": "sha512-nP/10xbAiPbhQkMr3zQfXE4TuOxPzWRQe1Hgbi90jv2R4TbzbqQTuZVOaJf7KOAN4L2Bo6XCTRjK5XkVnwZuwQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.99.0.tgz", + "integrity": "sha512-8whpsW7S+uO8QApKfQuc36m3P9EISzbVZOgC79goob4qGy09u8Gz/rYvw8h1prJDSjltpHGhOzBE6LDz7WvzVw==", "cpu": [ "arm64" ], @@ -2479,9 +2478,9 @@ } }, "node_modules/sass-embedded-win32-x64": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.98.0.tgz", - "integrity": "sha512-/lbrVsfbcbdZQ5SJCWcV0NVPd6YRs+FtAnfedp4WbCkO/ZO7Zt/58MvI4X2BVpRY/Nt5ZBo1/7v2gYcQ+J4svQ==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.99.0.tgz", + "integrity": "sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg==", "cpu": [ "x64" ], @@ -2675,16 +2674,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", + "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "bin": { @@ -2702,7 +2701,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -2753,16 +2752,16 @@ } }, "node_modules/vue": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", - "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz", + "integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.31", - "@vue/compiler-sfc": "3.5.31", - "@vue/runtime-dom": "3.5.31", - "@vue/server-renderer": "3.5.31", - "@vue/shared": "3.5.31" + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-sfc": "3.5.32", + "@vue/runtime-dom": "3.5.32", + "@vue/server-renderer": "3.5.32", + "@vue/shared": "3.5.32" }, "peerDependencies": { "typescript": "*" @@ -2774,14 +2773,14 @@ } }, "node_modules/vue-i18n": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.0.tgz", - "integrity": "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.2.tgz", + "integrity": "sha512-gmFrvM+iuf2AH4ygligw/pC7PRJ63AdRNE68E0GPlQ83Mzfyck6g6cRQC3KzkYXr+ZidR91wq+5YBmAMpkgE1A==", "license": "MIT", "dependencies": { - "@intlify/core-base": "11.3.0", - "@intlify/devtools-types": "11.3.0", - "@intlify/shared": "11.3.0", + "@intlify/core-base": "11.3.2", + "@intlify/devtools-types": "11.3.2", + "@intlify/shared": "11.3.2", "@vue/devtools-api": "^6.5.0" }, "engines": { diff --git a/frontend/package.json b/frontend/package.json index b57459e..e0396d3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,14 +23,14 @@ "is-ip": "^5.0.1", "pinia": "^3.0.4", "prismjs": "^1.30.0", - "vue": "^3.5.31", - "vue-i18n": "^11.3.0", + "vue": "^3.5.32", + "vue-i18n": "^11.3.2", "vue-prism-component": "github:h44z/vue-prism-component", "vue-router": "^5.0.4" }, "devDependencies": { - "@vitejs/plugin-vue": "^6.0.5", - "sass-embedded": "^1.98.0", - "vite": "^8.0.3" + "@vitejs/plugin-vue": "^6.0.6", + "sass-embedded": "^1.99.0", + "vite": "^8.0.8" } } diff --git a/frontend/src/components/InterfaceEditModal.vue b/frontend/src/components/InterfaceEditModal.vue index 65f5a94..a078e84 100644 --- a/frontend/src/components/InterfaceEditModal.vue +++ b/frontend/src/components/InterfaceEditModal.vue @@ -53,6 +53,7 @@ const formData = ref(freshInterface()) const isSaving = ref(false) const isDeleting = ref(false) const isApplyingDefaults = ref(false) +const isCreatingDefaultPeers = ref(false) const isBackendValid = computed(() => { if (!props.visible || !selectedInterface.value) { @@ -313,6 +314,39 @@ async function applyPeerDefaults() { } } +async function createDefaultPeers() { + if (props.interfaceId==='#NEW#') { + return; // do nothing for new interfaces + } + + if (!formData.value.CreateDefaultPeer) { + return; // only allowed if the interface flag is set + } + + if (isCreatingDefaultPeers.value) return + isCreatingDefaultPeers.value = true + try { + await interfaces.CreateDefaultPeers(selectedInterface.value.Identifier) + + notify({ + title: "Default Peers Created", + text: "Created default peers for all users on this interface.", + type: 'success', + }) + + await peers.LoadPeers(selectedInterface.value.Identifier) // reload peers list + } catch (e) { + console.log(e) + notify({ + title: "Failed to create default peers!", + text: e.toString(), + type: 'error', + }) + } finally { + isCreatingDefaultPeers.value = false + } +} + async function del() { if (isDeleting.value) return if (!confirm(t('modals.interface-edit.confirm-delete', {id: selectedInterface.value.Identifier}))) return @@ -490,9 +524,15 @@ async function del() { -
- - +
+
+ + +
+
diff --git a/frontend/src/lang/translations/de.json b/frontend/src/lang/translations/de.json index 20c42f5..efbbf84 100644 --- a/frontend/src/lang/translations/de.json +++ b/frontend/src/lang/translations/de.json @@ -505,6 +505,7 @@ } }, "button-apply-defaults": "Peer-Standardeinstellungen anwenden", + "button-create-default-peers": "Standard-Peers erstellen", "confirm-delete": "Interface '{id}' wirklich löschen?" }, "peer-view": { diff --git a/frontend/src/lang/translations/en.json b/frontend/src/lang/translations/en.json index 2e93614..ba3a891 100644 --- a/frontend/src/lang/translations/en.json +++ b/frontend/src/lang/translations/en.json @@ -505,6 +505,7 @@ } }, "button-apply-defaults": "Apply Peer Defaults", + "button-create-default-peers": "Create Default Peers", "confirm-delete": "Are you sure you want to delete interface '{id}'?" }, "peer-view": { diff --git a/frontend/src/lang/translations/es.json b/frontend/src/lang/translations/es.json index e5974d2..eca0290 100644 --- a/frontend/src/lang/translations/es.json +++ b/frontend/src/lang/translations/es.json @@ -495,6 +495,7 @@ } }, "button-apply-defaults": "Aplicar Valores Predeterminados de peers", + "button-create-default-peers": "Crear Peers Predeterminados", "confirm-delete": "Seguro que desea eliminar la interfaz '{id}'?" }, "peer-view": { diff --git a/frontend/src/lang/translations/fr.json b/frontend/src/lang/translations/fr.json index 23f6a5b..4c4df6a 100644 --- a/frontend/src/lang/translations/fr.json +++ b/frontend/src/lang/translations/fr.json @@ -377,6 +377,7 @@ } }, "button-apply-defaults": "Appliquer les valeurs par défaut des pairs", + "button-create-default-peers": "Créer les pairs par défaut", "confirm-delete": "Voulez-vous vraiment supprimer l'interface \"{id}\" ?" }, "peer-view": { diff --git a/frontend/src/lang/translations/ko.json b/frontend/src/lang/translations/ko.json index 8f9bad1..979f5d0 100644 --- a/frontend/src/lang/translations/ko.json +++ b/frontend/src/lang/translations/ko.json @@ -395,6 +395,7 @@ } }, "button-apply-defaults": "피어 기본값 적용", + "button-create-default-peers": "기본 피어 생성", "confirm-delete": "인터페이스 '{id}'를 삭제하시겠습니까?" }, "peer-view": { diff --git a/frontend/src/lang/translations/pt.json b/frontend/src/lang/translations/pt.json index c5a86b5..3e1ea96 100644 --- a/frontend/src/lang/translations/pt.json +++ b/frontend/src/lang/translations/pt.json @@ -415,6 +415,7 @@ } }, "button-apply-defaults": "Aplicar Padrões de Peer", + "button-create-default-peers": "Criar Peers Padrão", "confirm-delete": "Tem certeza que deseja excluir a interface '{id}'?" }, "peer-view": { diff --git a/frontend/src/lang/translations/ru.json b/frontend/src/lang/translations/ru.json index eeaa254..068ed48 100644 --- a/frontend/src/lang/translations/ru.json +++ b/frontend/src/lang/translations/ru.json @@ -486,6 +486,7 @@ } }, "button-apply-defaults": "Применить настройки пира по умолчанию", + "button-create-default-peers": "Создать пиров по умолчанию", "confirm-delete": "Вы уверены, что хотите удалить интерфейс «{id}»?" }, "peer-view": { diff --git a/frontend/src/lang/translations/uk.json b/frontend/src/lang/translations/uk.json index 1f0982a..e87d302 100644 --- a/frontend/src/lang/translations/uk.json +++ b/frontend/src/lang/translations/uk.json @@ -377,6 +377,7 @@ } }, "button-apply-defaults": "Застосувати значення за замовчуванням для пірів", + "button-create-default-peers": "Створити пірів за замовчуванням", "confirm-delete": "Ви впевнені, що хочете видалити інтерфейс «{id}»?" }, "peer-view": { diff --git a/frontend/src/lang/translations/vi.json b/frontend/src/lang/translations/vi.json index 27a015f..a283766 100644 --- a/frontend/src/lang/translations/vi.json +++ b/frontend/src/lang/translations/vi.json @@ -355,6 +355,7 @@ } }, "button-apply-defaults": "Áp dụng Cài đặt Mặc định của Peer", + "button-create-default-peers": "Tạo Peer Mặc định", "confirm-delete": "Ban co chac muon xoa giao dien '{id}' khong?" }, "peer-view": { diff --git a/frontend/src/lang/translations/zh.json b/frontend/src/lang/translations/zh.json index f1993fb..a76c2ba 100644 --- a/frontend/src/lang/translations/zh.json +++ b/frontend/src/lang/translations/zh.json @@ -355,6 +355,7 @@ } }, "button-apply-defaults": "应用节点默认值", + "button-create-default-peers": "创建默认节点", "confirm-delete": "确定要删除接口“{id}”吗?" }, "peer-view": { diff --git a/frontend/src/stores/interfaces.js b/frontend/src/stores/interfaces.js index efe75c2..c2e5a93 100644 --- a/frontend/src/stores/interfaces.js +++ b/frontend/src/stores/interfaces.js @@ -148,6 +148,18 @@ export const interfaceStore = defineStore('interfaces', { throw new Error(error) }) }, + async CreateDefaultPeers(id) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/${base64_url_encode(id)}/create-default-peers`) + .then(() => { + this.fetching = false + }) + .catch(error => { + this.fetching = false + console.log(error) + throw new Error(error) + }) + }, async SaveConfiguration(id) { this.fetching = true return apiWrapper.post(`${baseUrl}/${base64_url_encode(id)}/save-config`) diff --git a/internal/adapters/database.go b/internal/adapters/database.go index c428c13..de3cf50 100644 --- a/internal/adapters/database.go +++ b/internal/adapters/database.go @@ -251,7 +251,7 @@ func (r *SqlRepo) migrate() error { if existingSysStat.SchemaVersion == 1 { const schemaVersion = 2 // Preserve existing behavior for installations that had default-peer-creation enabled. - if r.cfg.Core.CreateDefaultPeer { + if r.cfg.DefaultPeerCreationEnabled() { err := r.db.Model(&domain.Interface{}). Where("type = ?", domain.InterfaceTypeServer). Update("create_default_peer", true).Error diff --git a/internal/app/api/v0/backend/interface_service.go b/internal/app/api/v0/backend/interface_service.go index fca1fe8..40c3c37 100644 --- a/internal/app/api/v0/backend/interface_service.go +++ b/internal/app/api/v0/backend/interface_service.go @@ -2,6 +2,7 @@ package backend import ( "context" + "fmt" "io" "github.com/h44z/wg-portal/internal/config" @@ -18,6 +19,7 @@ type InterfaceServiceInterfaceManager interface { DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error PrepareInterface(ctx context.Context) (*domain.Interface, error) ApplyPeerDefaults(ctx context.Context, in *domain.Interface) error + CreateDefaultPeers(ctx context.Context, id domain.InterfaceIdentifier) error } type InterfaceServiceConfigFileManager interface { @@ -89,3 +91,10 @@ func (i InterfaceService) PersistInterfaceConfig(ctx context.Context, id domain. func (i InterfaceService) ApplyPeerDefaults(ctx context.Context, in *domain.Interface) error { return i.interfaces.ApplyPeerDefaults(ctx, in) } + +func (i InterfaceService) CreateDefaultPeers(ctx context.Context, id domain.InterfaceIdentifier) error { + if !i.cfg.DefaultPeerCreationEnabled() { + return fmt.Errorf("default peer creation is not enabled") + } + return i.interfaces.CreateDefaultPeers(ctx, id) +} diff --git a/internal/app/api/v0/handlers/endpoint_config.go b/internal/app/api/v0/handlers/endpoint_config.go index db6cc73..62da202 100644 --- a/internal/app/api/v0/handlers/endpoint_config.go +++ b/internal/app/api/v0/handlers/endpoint_config.go @@ -145,7 +145,7 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc { MinPasswordLength: e.cfg.Auth.MinPasswordLength, AvailableBackends: controllerFn(), LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin, - CreateDefaultPeer: e.cfg.Core.CreateDefaultPeer, + CreateDefaultPeer: e.cfg.DefaultPeerCreationEnabled(), }) } } diff --git a/internal/app/api/v0/handlers/endpoint_interfaces.go b/internal/app/api/v0/handlers/endpoint_interfaces.go index 5478a3b..18069ea 100644 --- a/internal/app/api/v0/handlers/endpoint_interfaces.go +++ b/internal/app/api/v0/handlers/endpoint_interfaces.go @@ -33,6 +33,8 @@ type InterfaceService interface { PersistInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) error // ApplyPeerDefaults applies the peer defaults to all peers of the given interface. ApplyPeerDefaults(ctx context.Context, in *domain.Interface) error + // CreateDefaultPeers creates default peers for all existing users on the given interface. + CreateDefaultPeers(ctx context.Context, id domain.InterfaceIdentifier) error } type InterfaceEndpoint struct { @@ -73,6 +75,7 @@ func (e InterfaceEndpoint) RegisterRoutes(g *routegroup.Bundle) { apiGroup.HandleFunc("GET /config/{id}", e.handleConfigGet()) apiGroup.HandleFunc("POST /{id}/save-config", e.handleSaveConfigPost()) apiGroup.HandleFunc("POST /{id}/apply-peer-defaults", e.handleApplyPeerDefaultsPost()) + apiGroup.HandleFunc("POST /{id}/create-default-peers", e.handleCreateDefaultPeersPost()) apiGroup.HandleFunc("GET /peers/{id}", e.handlePeersGet()) } @@ -421,3 +424,34 @@ func (e InterfaceEndpoint) handleApplyPeerDefaultsPost() http.HandlerFunc { respond.Status(w, http.StatusNoContent) } } + +// handleCreateDefaultPeersPost returns a gorm Handler function. +// +// @ID interfaces_handleCreateDefaultPeersPost +// @Tags Interface +// @Summary Create default peers for all existing users on the given interface. +// @Produce json +// @Param id path string true "The interface identifier" +// @Success 204 "No content if creating the default peers was successful" +// @Failure 400 {object} model.Error +// @Failure 500 {object} model.Error +// @Router /interface/{id}/create-default-peers [post] +func (e InterfaceEndpoint) handleCreateDefaultPeersPost() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := Base64UrlDecode(request.Path(r, "id")) + if id == "" { + respond.JSON(w, http.StatusBadRequest, + model.Error{Code: http.StatusBadRequest, Message: "missing interface id"}) + return + } + + if err := e.interfaceService.CreateDefaultPeers(r.Context(), domain.InterfaceIdentifier(id)); err != nil { + respond.JSON(w, http.StatusInternalServerError, model.Error{ + Code: http.StatusInternalServerError, Message: err.Error(), + }) + return + } + + respond.Status(w, http.StatusNoContent) + } +} diff --git a/internal/app/wireguard/wireguard.go b/internal/app/wireguard/wireguard.go index c564240..a2a135e 100644 --- a/internal/app/wireguard/wireguard.go +++ b/internal/app/wireguard/wireguard.go @@ -36,6 +36,7 @@ type InterfaceAndPeerDatabaseRepo interface { GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) GetUsedIpsPerSubnet(ctx context.Context, subnets []domain.Cidr) (map[domain.Cidr][]domain.Cidr, error) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) + GetAllUsers(ctx context.Context) ([]domain.User, error) } type WgQuickController interface { @@ -59,7 +60,8 @@ type Manager struct { db InterfaceAndPeerDatabaseRepo wg *ControllerManager - userLockMap *sync.Map + userLockMap *sync.Map + interfaceLockMap *sync.Map } func NewWireGuardManager( @@ -69,11 +71,12 @@ func NewWireGuardManager( db InterfaceAndPeerDatabaseRepo, ) (*Manager, error) { m := &Manager{ - cfg: cfg, - bus: bus, - wg: wg, - db: db, - userLockMap: &sync.Map{}, + cfg: cfg, + bus: bus, + wg: wg, + db: db, + userLockMap: &sync.Map{}, + interfaceLockMap: &sync.Map{}, } m.connectToMessageBus() @@ -93,10 +96,11 @@ func (m Manager) connectToMessageBus() { _ = m.bus.Subscribe(app.TopicUserDisabled, m.handleUserDisabledEvent) _ = m.bus.Subscribe(app.TopicUserEnabled, m.handleUserEnabledEvent) _ = m.bus.Subscribe(app.TopicUserDeleted, m.handleUserDeletedEvent) + _ = m.bus.Subscribe(app.TopicInterfaceCreated, m.handleInterfaceCreatedEvent) } func (m Manager) handleUserCreationEvent(user domain.User) { - if !m.cfg.Core.CreateDefaultPeerOnCreation { + if !m.cfg.Core.CreateDefaultPeerOnUserCreation { return } @@ -117,7 +121,7 @@ func (m Manager) handleUserCreationEvent(user domain.User) { } func (m Manager) handleUserLoginEvent(userId domain.UserIdentifier) { - if !m.cfg.Core.CreateDefaultPeer { + if !m.cfg.Core.CreateDefaultPeerOnLogin { return } @@ -269,6 +273,31 @@ func (m Manager) handleUserDeletedEvent(user domain.User) { } } +// handleInterfaceCreatedEvent creates default peers for all existing users when a new interface is created. +// This ensures users that already exist (e.g. imported via a prior LDAP sync that had no interface available) +// also receive a default peer for the newly created interface. +func (m Manager) handleInterfaceCreatedEvent(iface domain.Interface) { + if !m.cfg.Core.CreateDefaultPeerOnUserCreation { + return + } + + _, loaded := m.interfaceLockMap.LoadOrStore(iface.Identifier, "create") + if loaded { + return // another goroutine is already handling this interface + } + defer m.interfaceLockMap.Delete(iface.Identifier) + + slog.Debug("handling new interface event", "interface", iface.Identifier) + + ctx := domain.SetUserInfo(context.Background(), domain.SystemAdminContextUserInfo()) + + err := m.CreateDefaultPeers(ctx, iface.Identifier) + if err != nil { + slog.Error("failed to create default peers on new interface", + "interface", iface.Identifier, "error", err) + } +} + func (m Manager) runExpiredPeersCheck(ctx context.Context) { ctx = domain.SetUserInfo(ctx, domain.SystemAdminContextUserInfo()) diff --git a/internal/app/wireguard/wireguard_interfaces.go b/internal/app/wireguard/wireguard_interfaces.go index 455c2fd..c4d1923 100644 --- a/internal/app/wireguard/wireguard_interfaces.go +++ b/internal/app/wireguard/wireguard_interfaces.go @@ -387,7 +387,7 @@ func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error SaveConfig: m.cfg.Advanced.ConfigStoragePath != "", DisplayName: string(id), Type: domain.InterfaceTypeServer, - CreateDefaultPeer: m.cfg.Core.CreateDefaultPeer, + CreateDefaultPeer: m.cfg.DefaultPeerCreationEnabled(), DriverType: "", Disabled: nil, DisabledReason: "", diff --git a/internal/app/wireguard/wireguard_interfaces_test.go b/internal/app/wireguard/wireguard_interfaces_test.go index 80e56e0..4d15fd0 100644 --- a/internal/app/wireguard/wireguard_interfaces_test.go +++ b/internal/app/wireguard/wireguard_interfaces_test.go @@ -94,13 +94,6 @@ func TestImportPeer_AddressMapping(t *testing.T) { } } -func (f *mockDB) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) { - return &domain.User{ - Identifier: id, - IsAdmin: false, - }, nil -} - func TestInterface_IsUserAllowed(t *testing.T) { cfg := &config.Config{ Auth: config.Auth{ diff --git a/internal/app/wireguard/wireguard_peers.go b/internal/app/wireguard/wireguard_peers.go index 4232942..60bb3da 100644 --- a/internal/app/wireguard/wireguard_peers.go +++ b/internal/app/wireguard/wireguard_peers.go @@ -15,6 +15,10 @@ import ( // CreateDefaultPeer creates a default peer for the given user on all server interfaces. func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdentifier) error { + if !m.cfg.DefaultPeerCreationEnabled() { + return nil + } + if err := domain.ValidateAdminAccessRights(ctx); err != nil { return err } @@ -24,39 +28,21 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdenti return fmt.Errorf("failed to fetch all interfaces: %w", err) } - userPeers, err := m.db.GetUserPeers(context.Background(), userId) + user, err := m.db.GetUser(ctx, userId) if err != nil { - return fmt.Errorf("failed to retrieve existing peers prior to default peer creation: %w", err) + return fmt.Errorf("failed to fetch user: %w", err) } var newPeers []domain.Peer for _, iface := range existingInterfaces { - if iface.Type != domain.InterfaceTypeServer { - continue // only create default peers for server interfaces - } - - if !iface.CreateDefaultPeer { - continue // only create default peers if the interface flag is set - } - - peerAlreadyCreated := slices.ContainsFunc(userPeers, func(peer domain.Peer) bool { - return peer.InterfaceIdentifier == iface.Identifier - }) - if peerAlreadyCreated { - continue // skip creation if a peer already exists for this interface - } - - peer, err := m.PreparePeer(ctx, iface.Identifier) + peer, err := m.prepareDefaultPeer(ctx, &iface, user) if err != nil { - return fmt.Errorf("failed to create default peer for interface %s: %w", iface.Identifier, err) + return fmt.Errorf("failed to prepare default peer: %w", err) } - peer.UserIdentifier = userId - peer.Notes = fmt.Sprintf("Default peer created for user %s", userId) - peer.AutomaticallyCreated = true - peer.GenerateDisplayName("Default") - - newPeers = append(newPeers, *peer) + if peer != nil { + newPeers = append(newPeers, *peer) + } } for i, peer := range newPeers { @@ -67,9 +53,61 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdenti } } - slog.InfoContext(ctx, "created default peers for user", - "user", userId, - "count", len(newPeers)) + slog.InfoContext(ctx, "created default peers for user", "user", userId, "count", len(newPeers)) + + return nil +} + +// CreateDefaultPeers creates default peers for all existing users on the given interface. +func (m Manager) CreateDefaultPeers(ctx context.Context, interfaceId domain.InterfaceIdentifier) error { + if !m.cfg.DefaultPeerCreationEnabled() { + return nil + } + + if err := domain.ValidateAdminAccessRights(ctx); err != nil { + return err + } + + iface, err := m.db.GetInterface(ctx, interfaceId) + if err != nil { + return fmt.Errorf("failed to fetch interface %s: %w", interfaceId, err) + } + + if !iface.CreateDefaultPeers() { + return nil + } + + users, err := m.db.GetAllUsers(ctx) + if err != nil { + return fmt.Errorf("failed to fetch all users: %w", err) + } + + var errs error + var peerCount int + for _, user := range users { + peer, err := m.prepareDefaultPeer(ctx, iface, &user) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to prepare default peer for user %s: %w", + user.Identifier, err)) + continue + } + if peer == nil { + continue + } + _, err = m.CreatePeer(ctx, peer) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to create default peer for user %s: %w", + user.Identifier, err)) + continue + } + peerCount++ + } + + if errs != nil { + return fmt.Errorf("failed to create default peers for interface %s: %w", interfaceId, errs) + } + + slog.InfoContext(ctx, "created default peers for interface", "interface", interfaceId, "count", peerCount) return nil } @@ -639,4 +677,39 @@ func (m Manager) checkInterfaceAccess(ctx context.Context, id domain.InterfaceId return nil } +func (m Manager) prepareDefaultPeer(ctx context.Context, iface *domain.Interface, user *domain.User) ( + *domain.Peer, + error, +) { + if !iface.CreateDefaultPeers() || !user.CreateDefaultPeers() { + return nil, nil + } + + userPeers, err := m.db.GetUserPeers(ctx, user.Identifier) + if err != nil { + return nil, fmt.Errorf("failed to retrieve existing peers prior to default peer creation: %w", err) + } + + peerAlreadyCreated := slices.ContainsFunc(userPeers, func(peer domain.Peer) bool { + // Ignore the AutomaticallyCreated flag on the peer. + // If a user already has a peer for a given interface, no default peer should be created. + return peer.InterfaceIdentifier == iface.Identifier + }) + if peerAlreadyCreated { + return nil, nil // skip creation if a peer already exists for this interface + } + + peer, err := m.PreparePeer(ctx, iface.Identifier) + if err != nil { + return nil, fmt.Errorf("failed to create default peer for interface %s: %w", iface.Identifier, err) + } + + peer.UserIdentifier = user.Identifier + peer.Notes = fmt.Sprintf("Default peer created for user %s", user.Identifier) + peer.AutomaticallyCreated = true + peer.GenerateDisplayName("Default") + + return peer, nil +} + // endregion helper-functions diff --git a/internal/app/wireguard/wireguard_peers_test.go b/internal/app/wireguard/wireguard_peers_test.go index 8ebb4b4..19dd1b2 100644 --- a/internal/app/wireguard/wireguard_peers_test.go +++ b/internal/app/wireguard/wireguard_peers_test.go @@ -61,6 +61,7 @@ type mockDB struct { savedPeers map[domain.PeerIdentifier]*domain.Peer iface *domain.Interface interfaces []domain.Interface + users []domain.User } func (f *mockDB) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) { @@ -141,6 +142,15 @@ func (f *mockDB) GetUsedIpsPerSubnet(ctx context.Context, subnets []domain.Cidr) ) { return map[domain.Cidr][]domain.Cidr{}, nil } +func (f *mockDB) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) { + return &domain.User{ + Identifier: id, + IsAdmin: false, + }, nil +} +func (f *mockDB) GetAllUsers(ctx context.Context) ([]domain.User, error) { + return f.users, nil +} // --- Test --- @@ -205,7 +215,7 @@ func TestCreatePeer_SetsIdentifier_FromPublicKey(t *testing.T) { func TestCreateDefaultPeer_RespectsInterfaceFlag(t *testing.T) { // Arrange cfg := &config.Config{} - cfg.Core.CreateDefaultPeer = true + cfg.Core.CreateDefaultPeerOnLogin = true bus := &mockBus{} ctrlMgr := &ControllerManager{ diff --git a/internal/config/config.go b/internal/config/config.go index a88b2d1..fa3f1fc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,14 +21,17 @@ type Config struct { AdminPassword string `yaml:"admin_password"` AdminApiToken string `yaml:"admin_api_token"` // if set, the API access is enabled automatically - EditableKeys bool `yaml:"editable_keys"` - CreateDefaultPeer bool `yaml:"create_default_peer"` - CreateDefaultPeerOnCreation bool `yaml:"create_default_peer_on_creation"` - ReEnablePeerAfterUserEnable bool `yaml:"re_enable_peer_after_user_enable"` - DeletePeerAfterUserDeleted bool `yaml:"delete_peer_after_user_deleted"` - SelfProvisioningAllowed bool `yaml:"self_provisioning_allowed"` - ImportExisting bool `yaml:"import_existing"` - RestoreState bool `yaml:"restore_state"` + EditableKeys bool `yaml:"editable_keys"` + CreateDefaultPeer bool `yaml:"create_default_peer"` // DEPRECATED: in favor of CreateDefaultPeerOnLogin + CreateDefaultPeerOnCreation bool `yaml:"create_default_peer_on_creation"` // DEPRECATED: in favor of CreateDefaultPeerOnUserCreation + CreateDefaultPeerOnLogin bool `yaml:"create_default_peer_on_login"` + CreateDefaultPeerOnUserCreation bool `yaml:"create_default_peer_on_user_creation"` + CreateDefaultPeerOnInterfaceCreation bool `yaml:"create_default_peer_on_interface_creation"` + ReEnablePeerAfterUserEnable bool `yaml:"re_enable_peer_after_user_enable"` + DeletePeerAfterUserDeleted bool `yaml:"delete_peer_after_user_deleted"` + SelfProvisioningAllowed bool `yaml:"self_provisioning_allowed"` + ImportExisting bool `yaml:"import_existing"` + RestoreState bool `yaml:"restore_state"` } `yaml:"core"` Advanced struct { @@ -78,7 +81,7 @@ func (c *Config) LogStartupValues() { slog.Debug("Config Features", "editableKeys", c.Core.EditableKeys, - "createDefaultPeerOnCreation", c.Core.CreateDefaultPeerOnCreation, + "createDefaultPeerOnCreation", c.Core.CreateDefaultPeerOnUserCreation, "reEnablePeerAfterUserEnable", c.Core.ReEnablePeerAfterUserEnable, "deletePeerAfterUserDeleted", c.Core.DeletePeerAfterUserDeleted, "selfProvisioningAllowed", c.Core.SelfProvisioningAllowed, @@ -112,6 +115,13 @@ func (c *Config) LogStartupValues() { } +// DefaultPeerCreationEnabled returns true if at least one default peer generation mechanism is enabled. +func (c *Config) DefaultPeerCreationEnabled() bool { + return c.Core.CreateDefaultPeerOnLogin || + c.Core.CreateDefaultPeerOnInterfaceCreation || + c.Core.CreateDefaultPeerOnUserCreation +} + // defaultConfig returns the default configuration func defaultConfig() *Config { cfg := &Config{} @@ -122,8 +132,13 @@ func defaultConfig() *Config { cfg.Core.AdminApiToken = getEnvStr("WG_PORTAL_CORE_ADMIN_API_TOKEN", "") // by default, the API access is disabled cfg.Core.ImportExisting = getEnvBool("WG_PORTAL_CORE_IMPORT_EXISTING", true) cfg.Core.RestoreState = getEnvBool("WG_PORTAL_CORE_RESTORE_STATE", true) - cfg.Core.CreateDefaultPeer = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER", false) - cfg.Core.CreateDefaultPeerOnCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION", false) + cfg.Core.CreateDefaultPeer = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER", false) // deprecated + cfg.Core.CreateDefaultPeerOnCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION", + false) // deprecated + cfg.Core.CreateDefaultPeerOnLogin = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER", false) + cfg.Core.CreateDefaultPeerOnUserCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_USER_CREATION", false) + cfg.Core.CreateDefaultPeerOnInterfaceCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_INTERFACE_CREATION", + false) cfg.Core.EditableKeys = getEnvBool("WG_PORTAL_CORE_EDITABLE_KEYS", true) cfg.Core.SelfProvisioningAllowed = getEnvBool("WG_PORTAL_CORE_SELF_PROVISIONING_ALLOWED", false) cfg.Core.ReEnablePeerAfterUserEnable = getEnvBool("WG_PORTAL_CORE_RE_ENABLE_PEER_AFTER_USER_ENABLE", true) @@ -246,6 +261,8 @@ func GetConfig() (*Config, error) { } } + handleDeprecatedConfigValues(cfg) + return cfg, nil } @@ -339,3 +356,18 @@ func getEnvDuration(name string, fallback time.Duration) time.Duration { return d } + +func handleDeprecatedConfigValues(cfg *Config) { + // deprecated, will be removed in 2.4 + if cfg.Core.CreateDefaultPeer { + slog.Warn("DEPRECATION WARNING: deprecated core config option: create_default_peer (WG_PORTAL_CORE_CREATE_DEFAULT_PEER)") + cfg.Core.CreateDefaultPeerOnLogin = true + } + + // deprecated, will be removed in 2.4 + if cfg.Core.CreateDefaultPeerOnCreation { + slog.Warn("DEPRECATION WARNING: deprecated core config option: create_default_peer_on_creation (WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION)") + cfg.Core.CreateDefaultPeerOnUserCreation = true + cfg.Core.CreateDefaultPeerOnInterfaceCreation = true + } +} diff --git a/internal/domain/interface.go b/internal/domain/interface.go index 2efd5bf..fe484ec 100644 --- a/internal/domain/interface.go +++ b/internal/domain/interface.go @@ -240,6 +240,18 @@ func (i *Interface) GetRoutingTable() int { } } +// CreateDefaultPeers determines whether default peers should be created for this interface. +func (i *Interface) CreateDefaultPeers() bool { + if !i.CreateDefaultPeer { + return false // only create default peers if the interface flag is set + } + if i.Type != InterfaceTypeServer { + return false // only create default peers for server interfaces + } + + return true +} + type PhysicalInterface struct { Identifier InterfaceIdentifier // device name, for example: wg0 KeyPair // private/public Key of the server interface diff --git a/internal/domain/interface_test.go b/internal/domain/interface_test.go index 9f0ee50..a363cde 100644 --- a/internal/domain/interface_test.go +++ b/internal/domain/interface_test.go @@ -139,3 +139,29 @@ func TestInterface_GetRoutingTableNonLocal(t *testing.T) { iface.RoutingTable = "abc" assert.Equal(t, 0, iface.GetRoutingTable()) } + +func TestInterface_CreateDefaultPeers(t *testing.T) { + iface := &Interface{} + assert.False(t, iface.CreateDefaultPeers()) + + iface.CreateDefaultPeer = true + assert.False(t, iface.CreateDefaultPeers()) // still wrong type + + iface2 := &Interface{Type: InterfaceTypeServer} + assert.False(t, iface2.CreateDefaultPeers()) // CreateDefaultPeer flag is false + + iface2.CreateDefaultPeer = true + assert.True(t, iface2.CreateDefaultPeers()) + + iface3 := &Interface{Type: InterfaceTypeClient} + assert.False(t, iface3.CreateDefaultPeers()) + + iface3.CreateDefaultPeer = true + assert.False(t, iface3.CreateDefaultPeers()) + + iface4 := &Interface{Type: InterfaceTypeAny} + assert.False(t, iface4.CreateDefaultPeers()) + + iface4.CreateDefaultPeer = true + assert.False(t, iface4.CreateDefaultPeers()) +} diff --git a/internal/domain/user.go b/internal/domain/user.go index 872e52b..39fc982 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -270,6 +270,18 @@ func (u *User) DisplayName() string { return displayName } +// CreateDefaultPeers determines whether default peers should be created for this user. +func (u *User) CreateDefaultPeers() bool { + if u.IsDisabled() { + return false + } + if u.IsLocked() { + return false + } + + return true +} + // region webauthn func (u *User) WebAuthnID() []byte { diff --git a/internal/domain/user_test.go b/internal/domain/user_test.go index 515a41c..7100c2d 100644 --- a/internal/domain/user_test.go +++ b/internal/domain/user_test.go @@ -145,3 +145,17 @@ func TestUser_HashPassword(t *testing.T) { user.Password = "" assert.NoError(t, user.HashPassword()) } + +func TestUser_CreateDefaultPeers(t *testing.T) { + user := &User{} + assert.True(t, user.CreateDefaultPeers()) + + user2 := &User{Disabled: &time.Time{}} + assert.False(t, user2.CreateDefaultPeers()) + + user3 := &User{Locked: &time.Time{}} + assert.False(t, user3.CreateDefaultPeers()) + + user4 := &User{Disabled: &time.Time{}, Locked: &time.Time{}} + assert.False(t, user4.CreateDefaultPeers()) +}