Compare commits

..

19 Commits

Author SHA1 Message Date
Potorochin Max
364f7b3a5b Update russian translation (#574)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
Signed-off-by: ornaras <ornaras.us@gmail.com>
2025-11-21 13:50:47 +01:00
Christoph Haas
907bb0599a fix race condition during ldap initialization (#571)
Some checks failed
Docker / Build and Push (push) Has been cancelled
Docker / release (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
2025-11-20 18:28:20 +01:00
Christoph
d759fc7dc7 allow to log raw LDAP user data (#571)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
2025-11-19 16:00:11 +01:00
Christoph
67192170fc doc: fix incorrect config examples
Some checks failed
Docker / Build and Push (push) Has been cancelled
Docker / release (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
2025-11-18 23:23:49 +01:00
Isak Wertwein
8f25bef050 feat: config by environment variables (#570)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
* feat: config by environment variables without config file

Signed-off-by: Isak Wertwein <isak.wertwein@gmail.com>

* string slice by environment variable

Signed-off-by: Isak Wertwein <isak.wertwein@gmail.com>

---------

Signed-off-by: Isak Wertwein <isak.wertwein@gmail.com>
2025-11-16 18:33:25 +01:00
Christoph Haas
8bc4990441 chore: update frontend and backend dependencies
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
2025-11-13 20:01:47 +01:00
Christoph Haas
80dc7f290a correct enum for User-Source in api doc (#562) 2025-11-13 20:00:37 +01:00
dependabot[bot]
de91506bfa chore(deps): bump the patch group across 1 directory with 2 updates (#566)
Bumps the patch group with 2 updates in the / directory: [gorm.io/driver/sqlserver](https://github.com/go-gorm/sqlserver) and [gorm.io/gorm](https://github.com/go-gorm/gorm).


Updates `gorm.io/driver/sqlserver` from 1.6.1 to 1.6.3
- [Commits](https://github.com/go-gorm/sqlserver/compare/v1.6.1...v1.6.3)

Updates `gorm.io/gorm` from 1.31.0 to 1.31.1
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.31.0...v1.31.1)

---
updated-dependencies:
- dependency-name: gorm.io/driver/sqlserver
  dependency-version: 1.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: gorm.io/gorm
  dependency-version: 1.31.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 19:35:29 +01:00
dependabot[bot]
380d71ba07 chore(deps): bump softprops/action-gh-release in the actions group (#567)
Bumps the actions group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release).


Updates `softprops/action-gh-release` from 2.4.1 to 2.4.2
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](6da8fa9354...5be0e66d93)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 19:34:54 +01:00
Osvaldo-Net
3d4a190949 Mejoras en la traducción al español y añade traducción para la herramienta de calculadora y cambio de contraseña. (#568)
* Mejorar traducción al español

* Update es.json

* Mejorar/Añadir traducciones al español
2025-11-13 17:48:33 +01:00
David Gonzalez
df450cf384 fix(helm): Append prerelease pattern for Helm kubeVersion check (#560)
Some checks failed
Chart / lint-test (push) Has been cancelled
Chart / publish (push) Has been cancelled
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
2025-11-09 20:53:31 +01:00
Dmytro Bondar
9fbebc82f6 Update and pin all actions versions (#564)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
Signed-off-by: Dmytro Bondar <git@bonddim.dev>
2025-11-07 23:17:00 +01:00
Christoph Haas
7c557d3e66 add german translation for ip calculator (#503) 2025-11-07 23:13:24 +01:00
Christoph Haas
bda99464f1 fix path parameter handling in REST api (#563) 2025-11-07 23:12:36 +01:00
Tomáš Lukča
d66a4b71b8 add IPCalculator View (#557)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
* create IPCalculator View + add cidr_tools package

* fixed translation and comma separated ip as placeholder
2025-11-03 17:40:42 +01:00
dependabot[bot]
da76327569 chore(deps): bump golang.org/x/oauth2 from 0.31.0 to 0.32.0 (#544)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.31.0 to 0.32.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.31.0...v0.32.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 21:14:22 +02:00
dependabot[bot]
c154cb3977 chore(deps): bump golang.org/x/sys from 0.36.0 to 0.37.0 (#545)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.36.0 to 0.37.0.
- [Commits](https://github.com/golang/sys/compare/v0.36.0...v0.37.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 21:12:15 +02:00
dependabot[bot]
7bca35728d chore(deps): bump golang.org/x/crypto from 0.42.0 to 0.43.0 (#546)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.42.0 to 0.43.0.
- [Commits](https://github.com/golang/crypto/compare/v0.42.0...v0.43.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 21:12:04 +02:00
h44z
3d923b328e password change UI (#543) (#548) 2025-10-15 21:11:40 +02:00
36 changed files with 1531 additions and 665 deletions

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -35,16 +35,16 @@ jobs:
# ct lint requires Python 3.x to run following packages: # ct lint requires Python 3.x to run following packages:
# - yamale (https://github.com/23andMe/Yamale) # - yamale (https://github.com/23andMe/Yamale)
# - yamllint (https://github.com/adrienverge/yamllint) # - yamllint (https://github.com/adrienverge/yamllint)
- uses: actions/setup-python@v6 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: '3.x' python-version: '3.x'
- uses: helm/chart-testing-action@v2 - uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
- name: Run chart-testing (lint) - name: Run chart-testing (lint)
run: ct lint --config ct.yaml run: ct lint --config ct.yaml
- uses: nolar/setup-k3d-k3s@v1 - uses: nolar/setup-k3d-k3s@293b8e5822a20bc0d5bcdd4826f1a665e72aba96 # v1.0.9
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -60,9 +60,9 @@ jobs:
permissions: permissions:
packages: write packages: write
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: docker/login-action@v3 - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v5 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Get Version - name: Get Version
shell: bash shell: bash
@@ -32,14 +32,14 @@ jobs:
- name: Login to Docker Hub - name: Login to Docker Hub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v3 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v3 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -47,7 +47,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
with: with:
images: | images: |
wgportal/wg-portal wgportal/wg-portal
@@ -68,7 +68,7 @@ jobs:
type=semver,pattern=v{{major}} type=semver,pattern=v{{major}}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v6 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
@@ -80,7 +80,7 @@ jobs:
BUILD_VERSION=${{ env.BUILD_VERSION }} BUILD_VERSION=${{ env.BUILD_VERSION }}
- name: Export binaries from images - name: Export binaries from images
uses: docker/build-push-action@v6 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
@@ -96,7 +96,7 @@ jobs:
done done
- name: Upload binaries - name: Upload binaries
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: binaries name: binaries
path: binaries/wg-portal_linux* path: binaries/wg-portal_linux*
@@ -110,12 +110,12 @@ jobs:
contents: write contents: write
steps: steps:
- name: Download binaries - name: Download binaries
uses: actions/download-artifact@v5 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
name: binaries name: binaries
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with: with:
files: 'wg-portal_linux*' files: 'wg-portal_linux*'
generate_release_notes: true generate_release_notes: true

View File

@@ -15,11 +15,11 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-python@v6 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with: with:
python-version: 3.x python-version: 3.x

View File

@@ -2,7 +2,7 @@ apiVersion: v2
name: wg-portal name: wg-portal
description: WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication description: WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
# Version is set to ensure compatibility with the chart's Ingress resource. # Version is set to ensure compatibility with the chart's Ingress resource.
kubeVersion: ">=1.19.0" kubeVersion: ">=1.19.0-0"
type: application type: application
home: https://wgportal.org home: https://wgportal.org
icon: https://wgportal.org/latest/assets/images/logo.svg icon: https://wgportal.org/latest/assets/images/logo.svg
@@ -16,7 +16,7 @@ annotations:
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.7.1 version: 0.7.2
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to

View File

@@ -1,6 +1,6 @@
# wg-portal # wg-portal
![Version: 0.7.1](https://img.shields.io/badge/Version-0.7.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v2](https://img.shields.io/badge/AppVersion-v2-informational?style=flat-square) ![Version: 0.7.2](https://img.shields.io/badge/Version-0.7.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v2](https://img.shields.io/badge/AppVersion-v2-informational?style=flat-square)
WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
@@ -12,7 +12,7 @@ WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
## Requirements ## Requirements
Kubernetes: `>=1.19.0` Kubernetes: `>=1.19.0-0`
## Installing the Chart ## Installing the Chart

View File

@@ -67,8 +67,7 @@ auth:
auth: auth:
ldap: ldap:
# a sample LDAP provider with user sync enabled # a sample LDAP provider with user sync enabled
- id: ldap - provider_name: ldap
provider_name: Active Directory
url: ldap://srv-ad1.company.local:389 url: ldap://srv-ad1.company.local:389
bind_user: ldap_wireguard@company.local bind_user: ldap_wireguard@company.local
bind_pass: super-s3cr3t-ldap bind_pass: super-s3cr3t-ldap
@@ -99,8 +98,7 @@ auth:
oidc: oidc:
# A sample Entra ID provider with environment variable substitution. # A sample Entra ID provider with environment variable substitution.
# Only users with an @outlook.com email address are allowed to register or login. # Only users with an @outlook.com email address are allowed to register or login.
- id: azure - provider_name: azure
provider_name: azure
display_name: Login with</br>Entra ID display_name: Login with</br>Entra ID
registration_enabled: true registration_enabled: true
base_url: "https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0" base_url: "https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0"
@@ -113,8 +111,7 @@ auth:
- email - email
# a sample provider where users with the attribute `wg_admin` set to `true` are considered as admins # a sample provider where users with the attribute `wg_admin` set to `true` are considered as admins
- id: oidc-with-admin-attribute - provider_name: google
provider_name: google
display_name: Login with</br>Google display_name: Login with</br>Google
base_url: https://accounts.google.com base_url: https://accounts.google.com
client_id: the-client-id-1234.apps.googleusercontent.com client_id: the-client-id-1234.apps.googleusercontent.com
@@ -136,8 +133,7 @@ auth:
log_user_info: true log_user_info: true
# a sample provider where users in the group `the-admin-group` are considered as admins # a sample provider where users in the group `the-admin-group` are considered as admins
- id: oidc-with-admin-group - provider_name: google2
provider_name: google2
display_name: Login with</br>Google2 display_name: Login with</br>Google2
base_url: https://accounts.google.com base_url: https://accounts.google.com
client_id: another-client-id-1234.apps.googleusercontent.com client_id: another-client-id-1234.apps.googleusercontent.com
@@ -168,8 +164,7 @@ auth:
oauth: oauth:
# a sample provider where users with the attribute `this-attribute-must-be-true` set to `true` or `True` # a sample provider where users with the attribute `this-attribute-must-be-true` set to `true` or `True`
# are considered as admins # are considered as admins
- id: google_plain_oauth-with-admin-attribute - provider_name: google3
provider_name: google3
display_name: Login with</br>Google3 display_name: Login with</br>Google3
client_id: another-client-id-1234.apps.googleusercontent.com client_id: another-client-id-1234.apps.googleusercontent.com
client_secret: A_CLIENT_SECRET client_secret: A_CLIENT_SECRET
@@ -191,8 +186,7 @@ auth:
# a sample provider where either users with the attribute `this-attribute-must-be-true` set to `true` or # a sample provider where either users with the attribute `this-attribute-must-be-true` set to `true` or
# users in the group `admin-group-name` are considered as admins # users in the group `admin-group-name` are considered as admins
- id: google_plain_oauth_with_groups - provider_name: google4
provider_name: google4
display_name: Login with</br>Google4 display_name: Login with</br>Google4
client_id: another-client-id-1234.apps.googleusercontent.com client_id: another-client-id-1234.apps.googleusercontent.com
client_secret: A_CLIENT_SECRET client_secret: A_CLIENT_SECRET

View File

@@ -127,51 +127,63 @@ More advanced options are found in the subsequent `Advanced` section.
### `admin_user` ### `admin_user`
- **Default:** `admin@wgportal.local` - **Default:** `admin@wgportal.local`
- **Environment Variable:** `WG_PORTAL_CORE_ADMIN_USER`
- **Description:** The administrator user. This user will be created as a default admin if it does not yet exist. - **Description:** The administrator user. This user will be created as a default admin if it does not yet exist.
### `admin_password` ### `admin_password`
- **Default:** `wgportal-default` - **Default:** `wgportal-default`
- **Environment Variable:** `WG_PORTAL_CORE_ADMIN_PASSWORD`
- **Description:** The administrator password. The default password should be changed immediately! - **Description:** The administrator password. The default password should be changed immediately!
- **Important:** The password should be strong and secure. The minimum password length is specified in [auth.min_password_length](#min_password_length). By default, it is 16 characters. - **Important:** The password should be strong and secure. The minimum password length is specified in [auth.min_password_length](#min_password_length). By default, it is 16 characters.
### `disable_admin_user` ### `disable_admin_user`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_CORE_DISABLE_ADMIN_USER`
- **Description:** If `true`, no admin user is created. This is useful if you plan to manage users exclusively through external authentication providers such as LDAP or OAuth. - **Description:** If `true`, no admin user is created. This is useful if you plan to manage users exclusively through external authentication providers such as LDAP or OAuth.
### `admin_api_token` ### `admin_api_token`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_CORE_ADMIN_API_TOKEN`
- **Description:** An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user. - **Description:** An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user.
### `editable_keys` ### `editable_keys`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_EDITABLE_KEYS`
- **Description:** Allow editing of WireGuard key-pairs directly in the UI. - **Description:** Allow editing of WireGuard key-pairs directly in the UI.
### `create_default_peer` ### `create_default_peer`
- **Default:** `false` - **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. - **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for **all** server interfaces.
### `create_default_peer_on_creation` ### `create_default_peer_on_creation`
- **Default:** `false` - **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. - **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.
### `re_enable_peer_after_user_enable` ### `re_enable_peer_after_user_enable`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_RE_ENABLE_PEER_AFTER_USER_ENABLE`
- **Description:** Re-enable all peers that were previously disabled if the associated user is re-enabled. - **Description:** Re-enable all peers that were previously disabled if the associated user is re-enabled.
### `delete_peer_after_user_deleted` ### `delete_peer_after_user_deleted`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_CORE_DELETE_PEER_AFTER_USER_DELETED`
- **Description:** If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled. - **Description:** If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled.
### `self_provisioning_allowed` ### `self_provisioning_allowed`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_CORE_SELF_PROVISIONING_ALLOWED`
- **Description:** Allow registered (non-admin) users to self-provision peers from their profile page. - **Description:** Allow registered (non-admin) users to self-provision peers from their profile page.
### `import_existing` ### `import_existing`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_IMPORT_EXISTING`
- **Description:** On startup, import existing WireGuard interfaces and peers into WireGuard Portal. - **Description:** On startup, import existing WireGuard interfaces and peers into WireGuard Portal.
### `restore_state` ### `restore_state`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_CORE_RESTORE_STATE`
- **Description:** Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started. - **Description:** Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started.
--- ---
@@ -188,11 +200,14 @@ The current MikroTik backend is in **BETA** and may not support all features.
### `local_resolvconf_prefix` ### `local_resolvconf_prefix`
- **Default:** `tun.` - **Default:** `tun.`
- **Environment Variable:** `WG_PORTAL_BACKEND_LOCAL_RESOLVCONF_PREFIX`
- **Description:** Interface name prefix for WireGuard interfaces on the local system which is used to configure DNS servers with *resolvconf*. - **Description:** Interface name prefix for WireGuard interfaces on the local system which is used to configure DNS servers with *resolvconf*.
It depends on the *resolvconf* implementation you are using, most use a prefix of `tun.`, but some have an empty prefix (e.g., systemd). It depends on the *resolvconf* implementation you are using, most use a prefix of `tun.`, but some have an empty prefix (e.g., systemd).
### `ignored_local_interfaces` ### `ignored_local_interfaces`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_BACKEND_IGNORED_LOCAL_INTERFACES`
(comma-separated values)
- **Description:** A list of interface names to exclude when enumerating local interfaces. - **Description:** A list of interface names to exclude when enumerating local interfaces.
This is useful if you want to prevent certain interfaces from being imported from the local system. This is useful if you want to prevent certain interfaces from being imported from the local system.
@@ -256,54 +271,67 @@ Additional or more specialized configuration options for logging and interface c
### `log_level` ### `log_level`
- **Default:** `info` - **Default:** `info`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LOG_LEVEL`
- **Description:** The log level used by the application. Valid options are: `trace`, `debug`, `info`, `warn`, `error`. - **Description:** The log level used by the application. Valid options are: `trace`, `debug`, `info`, `warn`, `error`.
### `log_pretty` ### `log_pretty`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LOG_PRETTY`
- **Description:** If `true`, log messages are colorized and formatted for readability (pretty-print). - **Description:** If `true`, log messages are colorized and formatted for readability (pretty-print).
### `log_json` ### `log_json`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LOG_JSON`
- **Description:** If `true`, log messages are structured in JSON format. - **Description:** If `true`, log messages are structured in JSON format.
### `start_listen_port` ### `start_listen_port`
- **Default:** `51820` - **Default:** `51820`
- **Environment Variable:** `WG_PORTAL_ADVANCED_START_LISTEN_PORT`
- **Description:** The first port to use when automatically creating new WireGuard interfaces. - **Description:** The first port to use when automatically creating new WireGuard interfaces.
### `start_cidr_v4` ### `start_cidr_v4`
- **Default:** `10.11.12.0/24` - **Default:** `10.11.12.0/24`
- **Environment Variable:** `WG_PORTAL_ADVANCED_START_CIDR_V4`
- **Description:** The initial IPv4 subnet to use when automatically creating new WireGuard interfaces. - **Description:** The initial IPv4 subnet to use when automatically creating new WireGuard interfaces.
### `start_cidr_v6` ### `start_cidr_v6`
- **Default:** `fdfd:d3ad:c0de:1234::0/64` - **Default:** `fdfd:d3ad:c0de:1234::0/64`
- **Environment Variable:** `WG_PORTAL_ADVANCED_START_CIDR_V6`
- **Description:** The initial IPv6 subnet to use when automatically creating new WireGuard interfaces. - **Description:** The initial IPv6 subnet to use when automatically creating new WireGuard interfaces.
### `use_ip_v6` ### `use_ip_v6`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_ADVANCED_USE_IP_V6`
- **Description:** Enable or disable IPv6 support. - **Description:** Enable or disable IPv6 support.
### `config_storage_path` ### `config_storage_path`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_ADVANCED_CONFIG_STORAGE_PATH`
- **Description:** Path to a directory where `wg-quick` style configuration files will be stored (if you need local filesystem configs). - **Description:** Path to a directory where `wg-quick` style configuration files will be stored (if you need local filesystem configs).
### `expiry_check_interval` ### `expiry_check_interval`
- **Default:** `15m` - **Default:** `15m`
- **Environment Variable:** `WG_PORTAL_ADVANCED_EXPIRY_CHECK_INTERVAL`
- **Description:** Interval after which existing peers are checked if they are expired. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - **Description:** Interval after which existing peers are checked if they are expired. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
### `rule_prio_offset` ### `rule_prio_offset`
- **Default:** `20000` - **Default:** `20000`
- **Environment Variable:** `WG_PORTAL_ADVANCED_RULE_PRIO_OFFSET`
- **Description:** Offset for IP route rule priorities when configuring routing. - **Description:** Offset for IP route rule priorities when configuring routing.
### `route_table_offset` ### `route_table_offset`
- **Default:** `20000` - **Default:** `20000`
- **Environment Variable:** `WG_PORTAL_ADVANCED_ROUTE_TABLE_OFFSET`
- **Description:** Offset for IP route table IDs when configuring routing. - **Description:** Offset for IP route table IDs when configuring routing.
### `api_admin_only` ### `api_admin_only`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_ADVANCED_API_ADMIN_ONLY`
- **Description:** If `true`, the public REST API is accessible only to admin users. The API docs live at [`/api/v1/doc.html`](../rest-api/api-doc.md). - **Description:** If `true`, the public REST API is accessible only to admin users. The API docs live at [`/api/v1/doc.html`](../rest-api/api-doc.md).
### `limit_additional_user_peers` ### `limit_additional_user_peers`
- **Default:** `0` - **Default:** `0`
- **Environment Variable:** `WG_PORTAL_ADVANCED_LIMIT_ADDITIONAL_USER_PEERS`
- **Description:** Limit additional peers a normal user can create. `0` means unlimited. - **Description:** Limit additional peers a normal user can create. `0` means unlimited.
--- ---
@@ -317,18 +345,22 @@ If sensitive values (like private keys) should be stored in an encrypted format,
### `debug` ### `debug`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_DATABASE_DEBUG`
- **Description:** If `true`, logs all database statements (verbose). - **Description:** If `true`, logs all database statements (verbose).
### `slow_query_threshold` ### `slow_query_threshold`
- **Default:** "0" - **Default:** "0"
- **Environment Variable:** `WG_PORTAL_DATABASE_SLOW_QUERY_THRESHOLD`
- **Description:** A time threshold (e.g., `100ms`) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses `s`, `ms` for seconds, milliseconds, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). The value must be a string. - **Description:** A time threshold (e.g., `100ms`) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses `s`, `ms` for seconds, milliseconds, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). The value must be a string.
### `type` ### `type`
- **Default:** `sqlite` - **Default:** `sqlite`
- **Environment Variable:** `WG_PORTAL_DATABASE_TYPE`
- **Description:** The database type. Valid options: `sqlite`, `mssql`, `mysql`, `postgres`. - **Description:** The database type. Valid options: `sqlite`, `mssql`, `mysql`, `postgres`.
### `dsn` ### `dsn`
- **Default:** `data/sqlite.db` - **Default:** `data/sqlite.db`
- **Environment Variable:** `WG_PORTAL_DATABASE_DSN`
- **Description:** The Data Source Name (DSN) for connecting to the database. - **Description:** The Data Source Name (DSN) for connecting to the database.
For example: For example:
```text ```text
@@ -337,6 +369,7 @@ If sensitive values (like private keys) should be stored in an encrypted format,
### `encryption_passphrase` ### `encryption_passphrase`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_DATABASE_ENCRYPTION_PASSPHRASE`
- **Description:** Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set. - **Description:** Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set.
**Important:** Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward. **Important:** Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward.
New or updated records will be encrypted; existing data remains in plaintext until its next modified. New or updated records will be encrypted; existing data remains in plaintext until its next modified.
@@ -349,38 +382,47 @@ Controls how WireGuard Portal collects and reports usage statistics, including p
### `use_ping_checks` ### `use_ping_checks`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_USE_PING_CHECKS`
- **Description:** Enable periodic ping checks to verify that peers remain responsive. - **Description:** Enable periodic ping checks to verify that peers remain responsive.
### `ping_check_workers` ### `ping_check_workers`
- **Default:** `10` - **Default:** `10`
- **Environment Variable:** `WG_PORTAL_STATISTICS_PING_CHECK_WORKERS`
- **Description:** Number of parallel worker processes for ping checks. - **Description:** Number of parallel worker processes for ping checks.
### `ping_unprivileged` ### `ping_unprivileged`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_STATISTICS_PING_UNPRIVILEGED`
- **Description:** If `false`, ping checks run without root privileges. This is currently considered BETA. - **Description:** If `false`, ping checks run without root privileges. This is currently considered BETA.
### `ping_check_interval` ### `ping_check_interval`
- **Default:** `1m` - **Default:** `1m`
- **Environment Variable:** `WG_PORTAL_STATISTICS_PING_CHECK_INTERVAL`
- **Description:** Interval between consecutive ping checks for all peers. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - **Description:** Interval between consecutive ping checks for all peers. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
### `data_collection_interval` ### `data_collection_interval`
- **Default:** `1m` - **Default:** `1m`
- **Environment Variable:** `WG_PORTAL_STATISTICS_DATA_COLLECTION_INTERVAL`
- **Description:** Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). - **Description:** Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
### `collect_interface_data` ### `collect_interface_data`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_COLLECT_INTERFACE_DATA`
- **Description:** If `true`, collects interface-level data (bytes in/out) for monitoring and statistics. - **Description:** If `true`, collects interface-level data (bytes in/out) for monitoring and statistics.
### `collect_peer_data` ### `collect_peer_data`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_COLLECT_PEER_DATA`
- **Description:** If `true`, collects peer-level data (bytes, last handshake, endpoint, etc.). - **Description:** If `true`, collects peer-level data (bytes, last handshake, endpoint, etc.).
### `collect_audit_data` ### `collect_audit_data`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_STATISTICS_COLLECT_AUDIT_DATA`
- **Description:** If `true`, logs certain portal events (such as user logins) to the database. - **Description:** If `true`, logs certain portal events (such as user logins) to the database.
### `listening_address` ### `listening_address`
- **Default:** `:8787` - **Default:** `:8787`
- **Environment Variable:** `WG_PORTAL_STATISTICS_LISTENING_ADDRESS`
- **Description:** Address and port for the integrated Prometheus metric server (e.g., `:8787` or `127.0.0.1:8787`). - **Description:** Address and port for the integrated Prometheus metric server (e.g., `:8787` or `127.0.0.1:8787`).
--- ---
@@ -393,45 +435,55 @@ To send emails to all peers that have a valid email-address as user-identifier,
### `host` ### `host`
- **Default:** `127.0.0.1` - **Default:** `127.0.0.1`
- **Environment Variable:** `WG_PORTAL_MAIL_HOST`
- **Description:** Hostname or IP of the SMTP server. - **Description:** Hostname or IP of the SMTP server.
### `port` ### `port`
- **Default:** `25` - **Default:** `25`
- **Environment Variable:** `WG_PORTAL_MAIL_PORT`
- **Description:** Port number for the SMTP server. - **Description:** Port number for the SMTP server.
### `encryption` ### `encryption`
- **Default:** `none` - **Default:** `none`
- **Environment Variable:** `WG_PORTAL_MAIL_ENCRYPTION`
- **Description:** SMTP encryption type. Valid values: `none`, `tls`, `starttls`. - **Description:** SMTP encryption type. Valid values: `none`, `tls`, `starttls`.
### `cert_validation` ### `cert_validation`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_MAIL_CERT_VALIDATION`
- **Description:** If `true`, validate the SMTP server certificate (relevant if `encryption` = `tls`). - **Description:** If `true`, validate the SMTP server certificate (relevant if `encryption` = `tls`).
### `username` ### `username`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_MAIL_USERNAME`
- **Description:** Optional SMTP username for authentication. - **Description:** Optional SMTP username for authentication.
### `password` ### `password`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_MAIL_PASSWORD`
- **Description:** Optional SMTP password for authentication. - **Description:** Optional SMTP password for authentication.
### `auth_type` ### `auth_type`
- **Default:** `plain` - **Default:** `plain`
- **Environment Variable:** `WG_PORTAL_MAIL_AUTH_TYPE`
- **Description:** SMTP authentication type. Valid values: `plain`, `login`, `crammd5`. - **Description:** SMTP authentication type. Valid values: `plain`, `login`, `crammd5`.
### `from` ### `from`
- **Default:** `Wireguard Portal <noreply@wireguard.local>` - **Default:** `Wireguard Portal <noreply@wireguard.local>`
- **Environment Variable:** `WG_PORTAL_MAIL_FROM`
- **Description:** The default "From" address when sending emails. - **Description:** The default "From" address when sending emails.
### `link_only` ### `link_only`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_MAIL_LINK_ONLY`
- **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration. - **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.
### `allow_peer_email` ### `allow_peer_email`
- **Default:** `false` - **Default:** `false`
- **Description:** If `true`, and a peer has no valid user record linked, but the user-identifier of the peer is a valid email address, emails will be sent to that email address. - **Environment Variable:** `WG_PORTAL_MAIL_ALLOW_PEER_EMAIL`
If false, and the peer has no valid user record linked, emails will not be sent. - **Description:** If `true`, and a peer has no valid user record linked, but the user-identifier of the peer is a valid email address, emails will be sent to that email address.
If a peer has linked a valid user, the email address is always taken from the user record. If false, and the peer has no valid user record linked, emails will not be sent.
If a peer has linked a valid user, the email address is always taken from the user record.
--- ---
@@ -444,12 +496,14 @@ Some core authentication options are shared across all providers, while others a
### `min_password_length` ### `min_password_length`
- **Default:** `16` - **Default:** `16`
- **Environment Variable:** `WG_PORTAL_AUTH_MIN_PASSWORD_LENGTH`
- **Description:** Minimum password length for local authentication. This is not enforced for LDAP authentication. - **Description:** Minimum password length for local authentication. This is not enforced for LDAP authentication.
The default admin password strength is also enforced by this setting. The default admin password strength is also enforced by this setting.
- **Important:** The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters. - **Important:** The password should be strong and secure. It is recommended to use a password with at least 16 characters, including uppercase and lowercase letters, numbers, and special characters.
### `hide_login_form` ### `hide_login_form`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_AUTH_HIDE_LOGIN_FORM`
- **Description:** If `true`, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method. - **Description:** If `true`, the login form is hidden and only the OIDC, OAuth, LDAP, or WebAuthn providers are shown. This is useful if you want to enforce a specific authentication method.
If no social login providers are configured, the login form is always shown, regardless of this setting. If no social login providers are configured, the login form is always shown, regardless of this setting.
- **Important:** You can still access the login form by adding the `?all` query parameter to the login URL (e.g. https://wg.portal/#/login?all). - **Important:** You can still access the login form by adding the `?all` query parameter to the login URL (e.g. https://wg.portal/#/login?all).
@@ -691,6 +745,10 @@ Below are the properties for each LDAP provider entry inside `auth.ldap`:
(&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*)) (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))
``` ```
#### `sync_log_user_info`
- **Default:** `false`
- **Description:** If `true`, logs LDAP user data at the trace level during synchronization.
#### `disable_missing` #### `disable_missing`
- **Default:** `false` - **Default:** `false`
- **Description:** If `true`, any user **not** found in LDAP (during sync) is disabled in WireGuard Portal. - **Description:** If `true`, any user **not** found in LDAP (during sync) is disabled in WireGuard Portal.
@@ -715,6 +773,7 @@ The `webauthn` section contains configuration options for WebAuthn authenticatio
#### `enabled` #### `enabled`
- **Default:** `true` - **Default:** `true`
- **Environment Variable:** `WG_PORTAL_AUTH_WEBAUTHN_ENABLED`
- **Description:** If `true`, Passkey authentication is enabled. If `false`, WebAuthn is disabled. - **Description:** If `true`, Passkey authentication is enabled. If `false`, WebAuthn is disabled.
Users are encouraged to use Passkeys for secure authentication instead of passwords. Users are encouraged to use Passkeys for secure authentication instead of passwords.
If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure. If a passkey is registered, the password login is still available as a fallback. Ensure that the password is strong and secure.
@@ -727,48 +786,59 @@ Without a valid `external_url`, the login process may fail due to CSRF protectio
### `listening_address` ### `listening_address`
- **Default:** `:8888` - **Default:** `:8888`
- **Environment Variable:** `WG_PORTAL_WEB_LISTENING_ADDRESS`
- **Description:** The listening address and port for the web server (e.g., `:8888` to bind on all interfaces or `127.0.0.1:8888` to bind only on the loopback interface). - **Description:** The listening address and port for the web server (e.g., `:8888` to bind on all interfaces or `127.0.0.1:8888` to bind only on the loopback interface).
Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces. Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.
### `external_url` ### `external_url`
- **Default:** `http://localhost:8888` - **Default:** `http://localhost:8888`
- **Environment Variable:** `WG_PORTAL_WEB_EXTERNAL_URL`
- **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects. - **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects.
**Important:** If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server. **Important:** If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.
### `site_company_name` ### `site_company_name`
- **Default:** `WireGuard Portal` - **Default:** `WireGuard Portal`
- **Environment Variable:** `WG_PORTAL_WEB_SITE_COMPANY_NAME`
- **Description:** The company name that is shown at the bottom of the web frontend. - **Description:** The company name that is shown at the bottom of the web frontend.
### `site_title` ### `site_title`
- **Default:** `WireGuard Portal` - **Default:** `WireGuard Portal`
- **Environment Variable:** `WG_PORTAL_WEB_SITE_TITLE`
- **Description:** The title that is shown in the web frontend. - **Description:** The title that is shown in the web frontend.
### `session_identifier` ### `session_identifier`
- **Default:** `wgPortalSession` - **Default:** `wgPortalSession`
- **Environment Variable:** `WG_PORTAL_WEB_SESSION_IDENTIFIER`
- **Description:** The session identifier for the web frontend. - **Description:** The session identifier for the web frontend.
### `session_secret` ### `session_secret`
- **Default:** `very_secret` - **Default:** `very_secret`
- **Environment Variable:** `WG_PORTAL_WEB_SESSION_SECRET`
- **Description:** The session secret for the web frontend. - **Description:** The session secret for the web frontend.
### `csrf_secret` ### `csrf_secret`
- **Default:** `extremely_secret` - **Default:** `extremely_secret`
- **Environment Variable:** `WG_PORTAL_WEB_CSRF_SECRET`
- **Description:** The CSRF secret. - **Description:** The CSRF secret.
### `request_logging` ### `request_logging`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_WEB_REQUEST_LOGGING`
- **Description:** Log all HTTP requests. - **Description:** Log all HTTP requests.
### `expose_host_info` ### `expose_host_info`
- **Default:** `false` - **Default:** `false`
- **Environment Variable:** `WG_PORTAL_WEB_EXPOSE_HOST_INFO`
- **Description:** Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information. - **Description:** Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.
### `cert_file` ### `cert_file`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEB_CERT_FILE`
- **Description:** (Optional) Path to the TLS certificate file. - **Description:** (Optional) Path to the TLS certificate file.
### `key_file` ### `key_file`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEB_KEY_FILE`
- **Description:** (Optional) Path to the TLS certificate key file. - **Description:** (Optional) Path to the TLS certificate key file.
--- ---
@@ -780,12 +850,15 @@ Further details can be found in the [usage documentation](../usage/webhooks.md).
### `url` ### `url`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEBHOOK_URL`
- **Description:** The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled. - **Description:** The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.
### `authentication` ### `authentication`
- **Default:** *(empty)* - **Default:** *(empty)*
- **Environment Variable:** `WG_PORTAL_WEBHOOK_AUTHENTICATION`
- **Description:** The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: `Bearer <token>`. - **Description:** The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: `Bearer <token>`.
### `timeout` ### `timeout`
- **Default:** `10s` - **Default:** `10s`
- **Environment Variable:** `WG_PORTAL_WEBHOOK_TIMEOUT`
- **Description:** The timeout for the webhook request. If the request takes longer than this, it is aborted. - **Description:** The timeout for the webhook request. If the request takes longer than this, it is aborted.

View File

@@ -512,6 +512,8 @@ definitions:
description: The source of the user. This field is optional. description: The source of the user. This field is optional.
enum: enum:
- db - db
- ldap
- oauth
example: db example: db
type: string type: string
required: required:

View File

@@ -8,29 +8,30 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@fontsource/nunito-sans": "^5.2.5", "@fontsource/nunito-sans": "^5.2.7",
"@fortawesome/fontawesome-free": "^6.7.2", "@fortawesome/fontawesome-free": "^7.1.0",
"@kyvg/vue3-notification": "^3.4.1", "@kyvg/vue3-notification": "^3.4.2",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@simplewebauthn/browser": "^13.1.0", "@simplewebauthn/browser": "^13.2.2",
"@vojtechlanka/vue-tags-input": "^3.1.1", "@vojtechlanka/vue-tags-input": "^3.1.1",
"bootstrap": "^5.3.7", "bootstrap": "^5.3.8",
"bootswatch": "^5.3.7", "bootswatch": "^5.3.8",
"flag-icons": "^7.3.2", "cidr-tools": "^11.0.3",
"ip-address": "^10.0.1", "flag-icons": "^7.5.0",
"is-cidr": "^5.1.1", "ip-address": "^10.1.0",
"is-cidr": "^6.0.1",
"is-ip": "^5.0.1", "is-ip": "^5.0.1",
"pinia": "^3.0.2", "pinia": "^3.0.4",
"prismjs": "^1.30.0", "prismjs": "^1.30.0",
"vue": "^3.5.13", "vue": "^3.5.24",
"vue-i18n": "^11.1.3", "vue-i18n": "^11.1.12",
"vue-prism-component": "github:h44z/vue-prism-component", "vue-prism-component": "github:h44z/vue-prism-component",
"vue-router": "^4.5.0" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^6.0.1",
"sass-embedded": "^1.86.3", "sass-embedded": "^1.93.3",
"vite": "^6.3.6" "vite": "^7.2.2"
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
@@ -43,21 +44,21 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.4" "@babel/types": "^7.28.5"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -67,13 +68,13 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.28.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -538,9 +539,9 @@
} }
}, },
"node_modules/@fortawesome/fontawesome-free": { "node_modules/@fortawesome/fontawesome-free": {
"version": "6.7.2", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz",
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==", "integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -597,9 +598,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@kyvg/vue3-notification": { "node_modules/@kyvg/vue3-notification": {
"version": "3.4.1", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/@kyvg/vue3-notification/-/vue3-notification-3.4.1.tgz", "resolved": "https://registry.npmjs.org/@kyvg/vue3-notification/-/vue3-notification-3.4.2.tgz",
"integrity": "sha512-WhTWCbF36JHLJR5UdKmJF7KXGOGVy4tLeaJuKTHZhwttZWnbF9w1/c2d32tvCSwY9CdeX/n9uoaKWLMKK3vOyg==", "integrity": "sha512-CZ2zOdXsbGCtWbdqMgbusKtZTkMT+dYpw9bmAitsdSNHT0knh4njD8X95JIyTMWvNVjhDkFedbkNiZLcPqttwQ==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "vue": "^3.0.0"
@@ -926,6 +927,13 @@
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.29",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz",
"integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.4", "version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
@@ -1235,9 +1243,9 @@
] ]
}, },
"node_modules/@simplewebauthn/browser": { "node_modules/@simplewebauthn/browser": {
"version": "13.2.0", "version": "13.2.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.0.tgz", "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.2.tgz",
"integrity": "sha512-N3fuA1AAnTo5gCStYoIoiasPccC+xPLx2YU88Dv0GeAmPQTWHETlZQq5xZ0DgUq1H9loXMWQH5qqUjcI7BHJ1A==", "integrity": "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
@@ -1248,16 +1256,19 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "5.2.4", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "1.0.0-beta.29"
},
"engines": { "engines": {
"node": "^18.0.0 || >=20.0.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"vite": "^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
"vue": "^3.2.25" "vue": "^3.2.25"
} }
}, },
@@ -1276,53 +1287,53 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
"integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.4", "@babel/parser": "^7.28.5",
"@vue/shared": "3.5.22", "@vue/shared": "3.5.24",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
"integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.22", "@vue/compiler-core": "3.5.24",
"@vue/shared": "3.5.22" "@vue/shared": "3.5.24"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
"integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", "integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.4", "@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.22", "@vue/compiler-core": "3.5.24",
"@vue/compiler-dom": "3.5.22", "@vue/compiler-dom": "3.5.24",
"@vue/compiler-ssr": "3.5.22", "@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.22", "@vue/shared": "3.5.24",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.19", "magic-string": "^0.30.21",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
"integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", "integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.22", "@vue/compiler-dom": "3.5.24",
"@vue/shared": "3.5.22" "@vue/shared": "3.5.24"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@@ -1359,53 +1370,53 @@
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
"integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/shared": "3.5.22" "@vue/shared": "3.5.24"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
"integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.22", "@vue/reactivity": "3.5.24",
"@vue/shared": "3.5.22" "@vue/shared": "3.5.24"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
"integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.22", "@vue/reactivity": "3.5.24",
"@vue/runtime-core": "3.5.22", "@vue/runtime-core": "3.5.24",
"@vue/shared": "3.5.22", "@vue/shared": "3.5.24",
"csstype": "^3.1.3" "csstype": "^3.1.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
"integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.5.22", "@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.22" "@vue/shared": "3.5.24"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.5.22" "vue": "3.5.24"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/birpc": { "node_modules/birpc": {
@@ -1481,15 +1492,27 @@
} }
}, },
"node_modules/cidr-regex": { "node_modules/cidr-regex": {
"version": "4.1.3", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-4.1.3.tgz", "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-5.0.1.tgz",
"integrity": "sha512-86M1y3ZeQvpZkZejQCcS+IaSWjlDUC+ORP0peScQ4uEUFCZ8bEQVz7NlJHqysoUb6w3zCjx4Mq/8/2RHhMwHYw==", "integrity": "sha512-2Apfc6qH9uwF3QHmlYBA8ExB9VHq+1/Doj9sEMY55TVBcpQ3y/+gmMpcNIBBtfb5k54Vphmta+1IxjMqPlWWAA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"ip-regex": "^5.0.0" "ip-regex": "5.0.0"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=20"
}
},
"node_modules/cidr-tools": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/cidr-tools/-/cidr-tools-11.0.3.tgz",
"integrity": "sha512-7p0rp7B2P+nZfBkJlrQzUMDyUHeYK2h/XCJY80VUl1v5oxwLxQjZMy39BXVOXugwAX67l0oJ/QQ6OhANgUtUbw==",
"license": "BSD-2-Clause",
"dependencies": {
"ip-bigint": "^8.2.1"
},
"engines": {
"node": ">=18"
} }
}, },
"node_modules/clone-regexp": { "node_modules/clone-regexp": {
@@ -1698,14 +1721,23 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "10.0.1", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/ip-bigint": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/ip-bigint/-/ip-bigint-8.2.2.tgz",
"integrity": "sha512-wPoOpHigOtoY29UCFA0L82cJVFcT7M+TsrgipUVpFw7HV9LpLEuNXCymt3623jzHPlIZzFaCyaVf9VACssFYew==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=18"
}
},
"node_modules/ip-regex": { "node_modules/ip-regex": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz",
@@ -1719,15 +1751,15 @@
} }
}, },
"node_modules/is-cidr": { "node_modules/is-cidr": {
"version": "5.1.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-5.1.1.tgz", "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-6.0.1.tgz",
"integrity": "sha512-AwzRMjtJNTPOgm7xuYZ71715z99t+4yRnSnSzgK5err5+heYi4zMuvmpUadaJ28+KCXCQo8CjUrKQZRWSPmqTQ==", "integrity": "sha512-JIJlvXodfsoWFAvvjB7Elqu8qQcys2SZjkIJCLdk4XherUqZ6+zH7WIpXkp4B3ZxMH0Fz7zIsZwyvs6JfM0csw==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"cidr-regex": "^4.1.1" "cidr-regex": "5.0.1"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=20"
} }
}, },
"node_modules/is-extglob": { "node_modules/is-extglob": {
@@ -1807,9 +1839,9 @@
} }
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.19", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
@@ -1889,19 +1921,19 @@
} }
}, },
"node_modules/pinia": { "node_modules/pinia": {
"version": "3.0.3", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^7.7.2" "@vue/devtools-api": "^7.7.7"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/posva" "url": "https://github.com/sponsors/posva"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=4.4.4", "typescript": ">=4.5.0",
"vue": "^2.7.0 || ^3.5.11" "vue": "^3.5.11"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {
@@ -2020,9 +2052,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz",
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", "integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@@ -2042,9 +2074,9 @@
} }
}, },
"node_modules/sass-embedded": { "node_modules/sass-embedded": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.3.tgz",
"integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==", "integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -2065,30 +2097,30 @@
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"sass-embedded-all-unknown": "1.93.2", "sass-embedded-all-unknown": "1.93.3",
"sass-embedded-android-arm": "1.93.2", "sass-embedded-android-arm": "1.93.3",
"sass-embedded-android-arm64": "1.93.2", "sass-embedded-android-arm64": "1.93.3",
"sass-embedded-android-riscv64": "1.93.2", "sass-embedded-android-riscv64": "1.93.3",
"sass-embedded-android-x64": "1.93.2", "sass-embedded-android-x64": "1.93.3",
"sass-embedded-darwin-arm64": "1.93.2", "sass-embedded-darwin-arm64": "1.93.3",
"sass-embedded-darwin-x64": "1.93.2", "sass-embedded-darwin-x64": "1.93.3",
"sass-embedded-linux-arm": "1.93.2", "sass-embedded-linux-arm": "1.93.3",
"sass-embedded-linux-arm64": "1.93.2", "sass-embedded-linux-arm64": "1.93.3",
"sass-embedded-linux-musl-arm": "1.93.2", "sass-embedded-linux-musl-arm": "1.93.3",
"sass-embedded-linux-musl-arm64": "1.93.2", "sass-embedded-linux-musl-arm64": "1.93.3",
"sass-embedded-linux-musl-riscv64": "1.93.2", "sass-embedded-linux-musl-riscv64": "1.93.3",
"sass-embedded-linux-musl-x64": "1.93.2", "sass-embedded-linux-musl-x64": "1.93.3",
"sass-embedded-linux-riscv64": "1.93.2", "sass-embedded-linux-riscv64": "1.93.3",
"sass-embedded-linux-x64": "1.93.2", "sass-embedded-linux-x64": "1.93.3",
"sass-embedded-unknown-all": "1.93.2", "sass-embedded-unknown-all": "1.93.3",
"sass-embedded-win32-arm64": "1.93.2", "sass-embedded-win32-arm64": "1.93.3",
"sass-embedded-win32-x64": "1.93.2" "sass-embedded-win32-x64": "1.93.3"
} }
}, },
"node_modules/sass-embedded-all-unknown": { "node_modules/sass-embedded-all-unknown": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.3.tgz",
"integrity": "sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==", "integrity": "sha512-3okGgnE41eg+CPLtAPletu6nQ4N0ij7AeW+Sl5Km4j29XcmqZQeFwYjHe1AlKTEgLi/UAONk1O8i8/lupeKMbw==",
"cpu": [ "cpu": [
"!arm", "!arm",
"!arm64", "!arm64",
@@ -2099,13 +2131,13 @@
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"sass": "1.93.2" "sass": "1.93.3"
} }
}, },
"node_modules/sass-embedded-android-arm": { "node_modules/sass-embedded-android-arm": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.3.tgz",
"integrity": "sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==", "integrity": "sha512-8xOw9bywfOD6Wv24BgCmgjkk6tMrsOTTHcb28KDxeJtFtoxiUyMbxo0vChpPAfp2Hyg2tFFKS60s0s4JYk+Raw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2120,9 +2152,9 @@
} }
}, },
"node_modules/sass-embedded-android-arm64": { "node_modules/sass-embedded-android-arm64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.3.tgz",
"integrity": "sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==", "integrity": "sha512-uqUl3Kt1IqdGVAcAdbmC+NwuUJy8tM+2ZnB7/zrt6WxWVShVCRdFnWR9LT8HJr7eJN7AU8kSXxaVX/gedanPsg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2137,9 +2169,9 @@
} }
}, },
"node_modules/sass-embedded-android-riscv64": { "node_modules/sass-embedded-android-riscv64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.3.tgz",
"integrity": "sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==", "integrity": "sha512-2jNJDmo+3qLocjWqYbXiBDnfgwrUeZgZFHJIwAefU7Fn66Ot7rsXl+XPwlokaCbTpj7eMFIqsRAZ/uDueXNCJg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2154,9 +2186,9 @@
} }
}, },
"node_modules/sass-embedded-android-x64": { "node_modules/sass-embedded-android-x64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.3.tgz",
"integrity": "sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==", "integrity": "sha512-y0RoAU6ZenQFcjM9PjQd3cRqRTjqwSbtWLL/p68y2oFyh0QGN0+LQ826fc0ZvU/AbqCsAizkqjzOn6cRZJxTTQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2171,9 +2203,9 @@
} }
}, },
"node_modules/sass-embedded-darwin-arm64": { "node_modules/sass-embedded-darwin-arm64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.3.tgz",
"integrity": "sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==", "integrity": "sha512-7zb/hpdMOdKteK17BOyyypemglVURd1Hdz6QGsggy60aUFfptTLQftLRg8r/xh1RbQAUKWFbYTNaM47J9yPxYg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2188,9 +2220,9 @@
} }
}, },
"node_modules/sass-embedded-darwin-x64": { "node_modules/sass-embedded-darwin-x64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.3.tgz",
"integrity": "sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==", "integrity": "sha512-Ek1Vp8ZDQEe327Lz0b7h3hjvWH3u9XjJiQzveq74RPpJQ2q6d9LfWpjiRRohM4qK6o4XOHw1X10OMWPXJtdtWg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2205,9 +2237,9 @@
} }
}, },
"node_modules/sass-embedded-linux-arm": { "node_modules/sass-embedded-linux-arm": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.3.tgz",
"integrity": "sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==", "integrity": "sha512-yeiv2y+dp8B4wNpd3+JsHYD0mvpXSfov7IGyQ1tMIR40qv+ROkRqYiqQvAOXf76Qwh4Y9OaYZtLpnsPjfeq6mA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2222,9 +2254,9 @@
} }
}, },
"node_modules/sass-embedded-linux-arm64": { "node_modules/sass-embedded-linux-arm64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.3.tgz",
"integrity": "sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==", "integrity": "sha512-RBrHWgfd8Dd8w4fbmdRVXRrhh8oBAPyeWDTKAWw8ZEmuXfVl4ytjDuyxaVilh6rR1xTRTNpbaA/YWApBlLrrNw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2239,9 +2271,9 @@
} }
}, },
"node_modules/sass-embedded-linux-musl-arm": { "node_modules/sass-embedded-linux-musl-arm": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.3.tgz",
"integrity": "sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==", "integrity": "sha512-fU0fwAwbp7sBE3h5DVU5UPzvaLg7a4yONfFWkkcCp6ZrOiPuGRHXXYriWQ0TUnWy4wE+svsVuWhwWgvlb/tkKg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2256,9 +2288,9 @@
} }
}, },
"node_modules/sass-embedded-linux-musl-arm64": { "node_modules/sass-embedded-linux-musl-arm64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.3.tgz",
"integrity": "sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==", "integrity": "sha512-PS829l+eUng+9W4PFclXGb4uA2+965NHV3/Sa5U7qTywjeeUUYTZg70dJHSqvhrBEfCc2XJABeW3adLJbyQYkw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2273,9 +2305,9 @@
} }
}, },
"node_modules/sass-embedded-linux-musl-riscv64": { "node_modules/sass-embedded-linux-musl-riscv64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.3.tgz",
"integrity": "sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==", "integrity": "sha512-cK1oBY+FWQquaIGEeQ5H74KTO8cWsSWwXb/WaildOO9U6wmUypTgUYKQ0o5o/29nZbWWlM1PHuwVYTSnT23Jjg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2290,9 +2322,9 @@
} }
}, },
"node_modules/sass-embedded-linux-musl-x64": { "node_modules/sass-embedded-linux-musl-x64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.3.tgz",
"integrity": "sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==", "integrity": "sha512-A7wkrsHu2/I4Zpa0NMuPGkWDVV7QGGytxGyUq3opSXgAexHo/vBPlGoDXoRlSdex0cV+aTMRPjoGIfdmNlHwyg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2307,9 +2339,9 @@
} }
}, },
"node_modules/sass-embedded-linux-riscv64": { "node_modules/sass-embedded-linux-riscv64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.3.tgz",
"integrity": "sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==", "integrity": "sha512-vWkW1+HTF5qcaHa6hO80gx/QfB6GGjJUP0xLbnAoY4pwEnw5ulGv6RM8qYr8IDhWfVt/KH+lhJ2ZFxnJareisQ==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2324,9 +2356,9 @@
} }
}, },
"node_modules/sass-embedded-linux-x64": { "node_modules/sass-embedded-linux-x64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.3.tgz",
"integrity": "sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==", "integrity": "sha512-k6uFxs+e5jSuk1Y0niCwuq42F9ZC5UEP7P+RIOurIm8w/5QFa0+YqeW+BPWEW5M1FqVOsNZH3qGn4ahqvAEjPA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2341,9 +2373,9 @@
} }
}, },
"node_modules/sass-embedded-unknown-all": { "node_modules/sass-embedded-unknown-all": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.3.tgz",
"integrity": "sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==", "integrity": "sha512-o5wj2rLpXH0C+GJKt/VpWp6AnMsCCbfFmnMAttcrsa+U3yrs/guhZ3x55KAqqUsE8F47e3frbsDL+1OuQM5DAA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@@ -2354,13 +2386,13 @@
"!win32" "!win32"
], ],
"dependencies": { "dependencies": {
"sass": "1.93.2" "sass": "1.93.3"
} }
}, },
"node_modules/sass-embedded-win32-arm64": { "node_modules/sass-embedded-win32-arm64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.3.tgz",
"integrity": "sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==", "integrity": "sha512-0dOfT9moy9YmBolodwYYXtLwNr4jL4HQC9rBfv6mVrD7ud8ue2kDbn+GVzj1hEJxvEexVSmDCf7MHUTLcGs9xQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2375,9 +2407,9 @@
} }
}, },
"node_modules/sass-embedded-win32-x64": { "node_modules/sass-embedded-win32-x64": {
"version": "1.93.2", "version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz", "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.3.tgz",
"integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==", "integrity": "sha512-wHFVfxiS9hU/sNk7KReD+lJWRp3R0SLQEX4zfOnRP2zlvI2X4IQR5aZr9GNcuMP6TmNpX0nQPZTegS8+h9RrEg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2576,25 +2608,25 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.3.6", "version": "7.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.5.0",
"picomatch": "^4.0.2", "picomatch": "^4.0.3",
"postcss": "^8.5.3", "postcss": "^8.5.6",
"rollup": "^4.34.9", "rollup": "^4.43.0",
"tinyglobby": "^0.2.13" "tinyglobby": "^0.2.15"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
}, },
"engines": { "engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0" "node": "^20.19.0 || >=22.12.0"
}, },
"funding": { "funding": {
"url": "https://github.com/vitejs/vite?sponsor=1" "url": "https://github.com/vitejs/vite?sponsor=1"
@@ -2603,14 +2635,14 @@
"fsevents": "~2.3.3" "fsevents": "~2.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0", "jiti": ">=1.21.0",
"less": "*", "less": "^4.0.0",
"lightningcss": "^1.21.0", "lightningcss": "^1.21.0",
"sass": "*", "sass": "^1.70.0",
"sass-embedded": "*", "sass-embedded": "^1.70.0",
"stylus": "*", "stylus": ">=0.54.8",
"sugarss": "*", "sugarss": "^5.0.0",
"terser": "^5.16.0", "terser": "^5.16.0",
"tsx": "^4.8.1", "tsx": "^4.8.1",
"yaml": "^2.4.2" "yaml": "^2.4.2"
@@ -2684,17 +2716,17 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.5.22", "version": "3.5.24",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", "integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.22", "@vue/compiler-dom": "3.5.24",
"@vue/compiler-sfc": "3.5.22", "@vue/compiler-sfc": "3.5.24",
"@vue/runtime-dom": "3.5.22", "@vue/runtime-dom": "3.5.24",
"@vue/server-renderer": "3.5.22", "@vue/server-renderer": "3.5.24",
"@vue/shared": "3.5.22" "@vue/shared": "3.5.24"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@@ -2737,9 +2769,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.5.1", "version": "4.6.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz",
"integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.6.4" "@vue/devtools-api": "^6.6.4"
@@ -2748,7 +2780,7 @@
"url": "https://github.com/sponsors/posva" "url": "https://github.com/sponsors/posva"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.2.0" "vue": "^3.5.0"
} }
}, },
"node_modules/vue-router/node_modules/@vue/devtools-api": { "node_modules/vue-router/node_modules/@vue/devtools-api": {

View File

@@ -8,28 +8,29 @@
"preview": "vite preview --port 5050" "preview": "vite preview --port 5050"
}, },
"dependencies": { "dependencies": {
"@fontsource/nunito-sans": "^5.2.5", "@fontsource/nunito-sans": "^5.2.7",
"@fortawesome/fontawesome-free": "^6.7.2", "@fortawesome/fontawesome-free": "^7.1.0",
"@kyvg/vue3-notification": "^3.4.1", "@kyvg/vue3-notification": "^3.4.2",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@simplewebauthn/browser": "^13.1.0", "@simplewebauthn/browser": "^13.2.2",
"@vojtechlanka/vue-tags-input": "^3.1.1", "@vojtechlanka/vue-tags-input": "^3.1.1",
"bootstrap": "^5.3.7", "bootstrap": "^5.3.8",
"bootswatch": "^5.3.7", "bootswatch": "^5.3.8",
"flag-icons": "^7.3.2", "cidr-tools": "^11.0.3",
"ip-address": "^10.0.1", "flag-icons": "^7.5.0",
"is-cidr": "^5.1.1", "ip-address": "^10.1.0",
"is-cidr": "^6.0.1",
"is-ip": "^5.0.1", "is-ip": "^5.0.1",
"pinia": "^3.0.2", "pinia": "^3.0.4",
"prismjs": "^1.30.0", "prismjs": "^1.30.0",
"vue": "^3.5.13", "vue": "^3.5.24",
"vue-i18n": "^11.1.3", "vue-i18n": "^11.1.12",
"vue-prism-component": "github:h44z/vue-prism-component", "vue-prism-component": "github:h44z/vue-prism-component",
"vue-router": "^4.5.0" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^6.0.1",
"sass-embedded": "^1.86.3", "sass-embedded": "^1.93.3",
"vite": "^6.3.6" "vite": "^7.2.2"
} }
} }

View File

@@ -133,6 +133,9 @@ const userDisplayName = computed(() => {
<li class="nav-item"> <li class="nav-item">
<RouterLink :to="{ name: 'key-generator' }" class="nav-link">{{ $t('menu.keygen') }}</RouterLink> <RouterLink :to="{ name: 'key-generator' }" class="nav-link">{{ $t('menu.keygen') }}</RouterLink>
</li> </li>
<li class="nav-item">
<RouterLink :to="{ name: 'ip-calculator' }" class="nav-link">{{ $t('menu.calculator') }}</RouterLink>
</li>
</ul> </ul>
<div class="navbar-nav d-flex justify-content-end"> <div class="navbar-nav d-flex justify-content-end">

View File

@@ -42,7 +42,8 @@
"audit": "Event Protokoll", "audit": "Event Protokoll",
"login": "Anmelden", "login": "Anmelden",
"logout": "Abmelden", "logout": "Abmelden",
"keygen": "Schlüsselgenerator" "keygen": "Schlüsselgenerator",
"calculator": "IP-Rechner"
}, },
"home": { "home": {
"headline": "WireGuard® VPN Portal", "headline": "WireGuard® VPN Portal",
@@ -221,6 +222,16 @@
"button-delete-text": "Passkey löschen. Sie können sich anschließend nicht mehr mit diesem Passkey anmelden.", "button-delete-text": "Passkey löschen. Sie können sich anschließend nicht mehr mit diesem Passkey anmelden.",
"button-register-title": "Passkey registrieren", "button-register-title": "Passkey registrieren",
"button-register-text": "Einen neuen Passkey registrieren, um Ihr Konto zu sichern." "button-register-text": "Einen neuen Passkey registrieren, um Ihr Konto zu sichern."
},
"password": {
"headline": "Passwort-Einstellungen",
"abstract": "Hier können Sie Ihr Passwort ändern.",
"current-label": "Aktuelles Passwort",
"new-label": "Neues Passwort",
"new-confirm-label": "Neues Passwort bestätigen",
"change-button-text": "Passwort ändern",
"invalid-confirm-label": "Passwörter stimmen nicht überein",
"weak-label": "Passwort ist zu schwach"
} }
}, },
"audit": { "audit": {
@@ -259,6 +270,26 @@
"placeholder": "Der geteilte Schlüssel" "placeholder": "Der geteilte Schlüssel"
} }
}, },
"calculator": {
"headline": "WireGuard IP-Rechner",
"abstract": "Erzeuge erlaubte IPs für WireGuard. Die IP-Subnetze werden lokal in Ihrem Browser generiert und niemals an den Server gesendet.",
"headline-allowed-ip": "Neue erlaubte IPs",
"button-exclude-private": "Private IP-Bereiche ausschließen",
"allowed-ip": {
"label": "Erlaubte IPs",
"placeholder": "0.0.0.0/0, ::/0",
"empty": "Wert darf nicht leer sein"
},
"dissallowed-ip": {
"label": "Nicht erlaubte IPs",
"placeholder": "10.0.0.0/8, 192.168.0.0/16",
"invalid": "Ungültige Adresse: {addr}"
},
"new-allowed-ip": {
"label": "Erlaubte IPs",
"placeholder": ""
}
},
"modals": { "modals": {
"user-view": { "user-view": {
"headline": "Benutzerkonto:", "headline": "Benutzerkonto:",

View File

@@ -42,7 +42,8 @@
"audit": "Audit Log", "audit": "Audit Log",
"login": "Login", "login": "Login",
"logout": "Logout", "logout": "Logout",
"keygen": "Key Generator" "keygen": "Key Generator",
"calculator": "IP Calculator"
}, },
"home": { "home": {
"headline": "WireGuard® VPN Portal", "headline": "WireGuard® VPN Portal",
@@ -221,6 +222,16 @@
"button-delete-text": "Delete the passkey. You will not be able to log in with this passkey anymore.", "button-delete-text": "Delete the passkey. You will not be able to log in with this passkey anymore.",
"button-register-title": "Register Passkey", "button-register-title": "Register Passkey",
"button-register-text": "Register a new Passkey to secure your account." "button-register-text": "Register a new Passkey to secure your account."
},
"password": {
"headline": "Password Settings",
"abstract": "Here you can change your password.",
"current-label": "Current Password",
"new-label": "New Password",
"new-confirm-label": "Confirm New Password",
"change-button-text": "Change Password",
"invalid-confirm-label": "Passwords do not match",
"weak-label": "Password is too weak"
} }
}, },
"audit": { "audit": {
@@ -259,6 +270,26 @@
"placeholder": "The pre-shared key" "placeholder": "The pre-shared key"
} }
}, },
"calculator": {
"headline": "WireGuard IP Calculator",
"abstract": "Generate a WireGuard Allowed IPs. The IP subnets are generated in your local browser and are never sent to the server.",
"headline-allowed-ip": "New Allowed IPs",
"button-exclude-private": "Exclude Private IP Ranges",
"allowed-ip": {
"label": "Allowed IPs",
"placeholder": "0.0.0.0/0, ::/0",
"empty": "Value cannot be empty"
},
"dissallowed-ip": {
"label": "Disallowed IPs",
"placeholder": "10.0.0.0/8, 192.168.0.0/16",
"invalid": "Invalid address: {addr}"
},
"new-allowed-ip": {
"label": "Allowed IPs",
"placeholder": ""
}
},
"modals": { "modals": {
"user-view": { "user-view": {
"headline": "User Account:", "headline": "User Account:",

View File

@@ -2,6 +2,26 @@
"languages": { "languages": {
"es": "Español" "es": "Español"
}, },
"calculator": {
"abstract": "Genera direcciones IP permitidas de WireGuard. Las subredes IP se generan en tu navegador local y nunca se envían al servidor.",
"allowed-ip": {
"empty": "El valor no puede estar vacío",
"label": "IPs permitidas",
"placeholder": "0.0.0.0/0, ::/0"
},
"button-exclude-private": "Excluir rangos de IP privadas",
"dissallowed-ip": {
"invalid": "Dirección inválida: {addr}",
"label": "IPs no permitidas",
"placeholder": "10.0.0.0/8, 192.168.0.0/16"
},
"headline": "Calculadora de IPs de WireGuard",
"headline-allowed-ip": "Nuevas IPs permitidas",
"new-allowed-ip": {
"label": "IPs permitidas",
"placeholder": ""
}
},
"general": { "general": {
"pagination": { "pagination": {
"size": "Numero de elementos", "size": "Numero de elementos",
@@ -33,6 +53,7 @@
"button-webauthn": "Usar clave de acceso" "button-webauthn": "Usar clave de acceso"
}, },
"menu": { "menu": {
"calculator": "Calculadora IP",
"home": "Inicio", "home": "Inicio",
"interfaces": "Interfaces", "interfaces": "Interfaces",
"users": "Usuarios", "users": "Usuarios",
@@ -69,7 +90,7 @@
"profiles": { "profiles": {
"headline": "Perfiles VPN", "headline": "Perfiles VPN",
"abstract": "Puedes acceder y descargar tus configuraciones personales de VPN desde tu perfil de usuario.", "abstract": "Puedes acceder y descargar tus configuraciones personales de VPN desde tu perfil de usuario.",
"content": "para ver todos tus perfiles configurados, haz clic en el botón de abajo.", "content": "Para ver todos tus perfiles configurados, haz clic en el botón de abajo.",
"button": "Abrir mi perfil" "button": "Abrir mi perfil"
}, },
"admin": { "admin": {
@@ -96,7 +117,7 @@
"table-heading": { "table-heading": {
"name": "Nombre", "name": "Nombre",
"user": "Usuario", "user": "Usuario",
"ip": "IP's", "ip": "IPs",
"endpoint": "Endpoint", "endpoint": "Endpoint",
"status": "Estado" "status": "Estado"
}, },
@@ -114,6 +135,7 @@
"total-endpoints": "Endpoints totales", "total-endpoints": "Endpoints totales",
"ip": "Dirección IP", "ip": "Dirección IP",
"default-allowed-ip": "IPs permitidas por defecto", "default-allowed-ip": "IPs permitidas por defecto",
"default-dns": "Servidores DNS por defecto",
"dns": "Servidores DNS", "dns": "Servidores DNS",
"mtu": "MTU", "mtu": "MTU",
"default-keep-alive": "Intervalo Keepalive por defecto", "default-keep-alive": "Intervalo Keepalive por defecto",
@@ -160,7 +182,7 @@
"headline": "Mis peers VPN", "headline": "Mis peers VPN",
"table-heading": { "table-heading": {
"name": "Nombre", "name": "Nombre",
"ip": "IP's", "ip": "IPs",
"stats": "Estado", "stats": "Estado",
"interface": "Interfaz del servidor" "interface": "Interfaz del servidor"
}, },
@@ -220,6 +242,16 @@
"button-delete-text": "Eliminar la llave de acceso. Ya no podrás iniciar sesión con ella.", "button-delete-text": "Eliminar la llave de acceso. Ya no podrás iniciar sesión con ella.",
"button-register-title": "Registrar llave de acceso", "button-register-title": "Registrar llave de acceso",
"button-register-text": "Registrar una nueva llave de acceso para proteger tu cuenta." "button-register-text": "Registrar una nueva llave de acceso para proteger tu cuenta."
},
"password": {
"headline": "Configuración de contraseña",
"abstract": "Aquí puedes cambiar tu contraseña.",
"current-label": "Contraseña actual",
"new-label": "Nueva contraseña",
"new-confirm-label": "Confirmar nueva contraseña",
"change-button-text": "Cambiar contraseña",
"invalid-confirm-label": "Las contraseñas no coinciden",
"weak-label": "La contraseña es demasiado débil"
} }
}, },
"audit": { "audit": {
@@ -269,7 +301,7 @@
"firstname": "Nombre", "firstname": "Nombre",
"lastname": "Apellido", "lastname": "Apellido",
"phone": "Número de Teléfono", "phone": "Número de Teléfono",
"depeertment": "Departamento", "department": "Departamento",
"api-enabled": "Acceso API", "api-enabled": "Acceso API",
"disabled": "Cuenta Deshabilitada", "disabled": "Cuenta Deshabilitada",
"locked": "Cuenta Bloqueada", "locked": "Cuenta Bloqueada",
@@ -277,7 +309,7 @@
"peers": { "peers": {
"name": "Nombre", "name": "Nombre",
"interface": "Interfaz", "interface": "Interfaz",
"ip": "IP's" "ip": "IPs"
} }
}, },
"user-edit": { "user-edit": {
@@ -309,7 +341,7 @@
"label": "Teléfono", "label": "Teléfono",
"placeholder": "El número de teléfono" "placeholder": "El número de teléfono"
}, },
"depeertment": { "department": {
"label": "Departamento", "label": "Departamento",
"placeholder": "El departamento" "placeholder": "El departamento"
}, },
@@ -338,6 +370,16 @@
"interface-view": { "interface-view": {
"headline": "Configuración de la interfaz:" "headline": "Configuración de la interfaz:"
}, },
"password": {
"abstract": "Aquí puedes cambiar tu contraseña.",
"change-button-text": "Cambiar contraseña",
"current-label": "Contraseña actual",
"headline": "Configuración de contraseña",
"invalid-confirm-label": "Las contraseñas no coinciden",
"new-confirm-label": "Confirmar nueva contraseña",
"new-label": "Nueva contraseña",
"weak-label": "La contraseña es demasiado débil"
},
"interface-edit": { "interface-edit": {
"headline-edit": "Editar interfaz:", "headline-edit": "Editar interfaz:",
"headline-new": "Nueva interfaz", "headline-new": "Nueva interfaz",
@@ -461,6 +503,8 @@
"section-config": "Configuración", "section-config": "Configuración",
"identifier": "Identificador", "identifier": "Identificador",
"ip": "Direcciones IP", "ip": "Direcciones IP",
"allowed-ip": "Direcciones IP permitidas",
"extra-allowed-ip": "Direcciones IP permitidas del lado del servidor",
"user": "Usuario Asociado", "user": "Usuario Asociado",
"notes": "Notas", "notes": "Notas",
"expiry-status": "Expira en", "expiry-status": "Expira en",
@@ -469,10 +513,10 @@
"connection-status": "Estadísticas de Conexión", "connection-status": "Estadísticas de Conexión",
"upload": "Bytes Subidos (del Servidor al peer)", "upload": "Bytes Subidos (del Servidor al peer)",
"download": "Bytes Descargados (del peer al Servidor)", "download": "Bytes Descargados (del peer al Servidor)",
"pingable": "Es Alcanzable (Ping)", "pingable": "Alcanzable (ping)",
"handshake": "Último Handshake", "handshake": "Último handshake",
"connected-since": "Conectado desde", "connected-since": "Conectado desde",
"endpoint": "Endpoint", "endpoint": "Dirección del host remoto",
"button-download": "Descargar configuración", "button-download": "Descargar configuración",
"button-email": "Enviar configuración por Correo Electrónico", "button-email": "Enviar configuración por Correo Electrónico",
"style-label": "Estilo de Configuración" "style-label": "Estilo de Configuración"
@@ -488,7 +532,7 @@
"header-hooks": "Hooks (Ejecutados en el peer)", "header-hooks": "Hooks (Ejecutados en el peer)",
"header-state": "Estado", "header-state": "Estado",
"display-name": { "display-name": {
"label": "Nombre para Mostrar", "label": "Nombre para mostrar",
"placeholder": "El nombre descriptivo para el peer" "placeholder": "El nombre descriptivo para el peer"
}, },
"linked-user": { "linked-user": {
@@ -501,7 +545,7 @@
"help": "La clave privada se almacena de forma segura en el servidor. Si el usuario ya posee una copia, puedes omitir este campo. El servidor sigue funcionando exclusivamente con la clave pública del peer." "help": "La clave privada se almacena de forma segura en el servidor. Si el usuario ya posee una copia, puedes omitir este campo. El servidor sigue funcionando exclusivamente con la clave pública del peer."
}, },
"public-key": { "public-key": {
"label": "Cave Pública", "label": "Clave Pública",
"placeholder": "La Clave pública" "placeholder": "La Clave pública"
}, },
"preshared-key": { "preshared-key": {
@@ -512,6 +556,10 @@
"label": "Dirección del endpoint", "label": "Dirección del endpoint",
"placeholder": "La dirección del endpoint remoto" "placeholder": "La dirección del endpoint remoto"
}, },
"endpoint-public-key": {
"label": "Clave pública del punto del endpoint",
"placeholder": "La clave pública del endpoint remoto"
},
"ip": { "ip": {
"label": "Direcciones IP", "label": "Direcciones IP",
"placeholder": "Direcciones IP (formato CIDR)" "placeholder": "Direcciones IP (formato CIDR)"
@@ -576,11 +624,11 @@
"description": "Un identificador de usuario (el nombre de usuario) para el cual debe crearse un peer." "description": "Un identificador de usuario (el nombre de usuario) para el cual debe crearse un peer."
}, },
"prefix": { "prefix": {
"headline-peer": "peer:", "headline-peer": "Peer:",
"headline-endpoint": "Endpoint:", "headline-endpoint": "Endpoint:",
"label": "Prefijo del Nombre peera Mostrar", "label": "Prefijo del nombre del peer a mostrar",
"placeholder": "El prefijo", "placeholder": "Prefijo",
"description": "Un prefijo que se agregará al nombre mostrado de los peers." "description": "Un prefijo que se agregará al nombre visible de los peers."
} }
} }
} }

View File

@@ -29,7 +29,8 @@
"label": "Пароль", "label": "Пароль",
"placeholder": "Пожалуйста, введите ваш пароль" "placeholder": "Пожалуйста, введите ваш пароль"
}, },
"button": "Войти" "button": "Войти",
"button-webauthn": "Использовать Passkey"
}, },
"menu": { "menu": {
"home": "Главная", "home": "Главная",
@@ -37,8 +38,12 @@
"users": "Пользователи", "users": "Пользователи",
"lang": "Сменить язык", "lang": "Сменить язык",
"profile": "Мой профиль", "profile": "Мой профиль",
"settings": "Настройки",
"audit": "Журнал аудита",
"login": "Вход", "login": "Вход",
"logout": "Выход" "logout": "Выход",
"keygen": "Генератор ключей",
"calculator": "Калькулятор IP-адресов"
}, },
"home": { "home": {
"headline": "Портал VPN WireGuard®", "headline": "Портал VPN WireGuard®",
@@ -100,6 +105,8 @@
"interface": { "interface": {
"headline": "Статус интерфейса для", "headline": "Статус интерфейса для",
"backend": "бэкэнд", "backend": "бэкэнд",
"unknown-backend": "Неизвестно",
"wrong-backend": "Неверный бэкэнд, вместо него используется локальный сервер WireGuard!",
"key": "Публичный ключ", "key": "Публичный ключ",
"endpoint": "Публичная конечная точка", "endpoint": "Публичная конечная точка",
"port": "Порт прослушивания", "port": "Порт прослушивания",
@@ -112,6 +119,7 @@
"dns": "DNS-серверы", "dns": "DNS-серверы",
"mtu": "MTU", "mtu": "MTU",
"default-keep-alive": "Интервал поддержания активности по умолчанию", "default-keep-alive": "Интервал поддержания активности по умолчанию",
"default-dns": "DNS-сервера по-умолчанию",
"button-show-config": "Показать конфигурацию", "button-show-config": "Показать конфигурацию",
"button-download-config": "Скачать конфигурацию", "button-download-config": "Скачать конфигурацию",
"button-store-config": "Сохранить конфигурацию для wg-quick", "button-store-config": "Сохранить конфигурацию для wg-quick",
@@ -168,6 +176,121 @@
"button-show-peer": "Показать пира", "button-show-peer": "Показать пира",
"button-edit-peer": "Редактировать пира" "button-edit-peer": "Редактировать пира"
}, },
"settings": {
"headline": "Настройки",
"abstract": "Здесь вы можете изменить персональные настройки.",
"api": {
"headline": "Настройки API",
"abstract": "Здесь можете настроить RESTful API.",
"active-description": "В данный момент API активен для вашей учетной записи. Все запросы API проверяются с помощью Basic Auth. Для проверки подлинности используйте следующие учетные данные.",
"inactive-description": "В данный момент API неактивен. Нажмите кнопку ниже, чтобы активировать его.",
"user-label": "Имя пользователя API:",
"user-placeholder": "Имя пользователя API",
"token-label": "API-пароль:",
"token-placeholder": "API-токен",
"token-created-label": "Доступ к API предоставлен с: ",
"button-disable-title": "Отключение API приведет к аннулированию текущего токена.",
"button-disable-text": "Отключить API",
"button-enable-title": "Включение API приведет к созданию нового токена.",
"button-enable-text": "Включить API",
"api-link": "Документация API"
},
"webauthn": {
"headline": "Настройки Passkey",
"abstract": "Passkey - это современный способ аутентификации пользователей без использования паролей. Он надежно хранятся в вашем браузере и могут быть использованы для входа в WireGuard Portal.",
"active-description": "В данный момент для вашей учетной записи пользователя активен по крайней мере один Passkey.",
"inactive-description": "В настоящее время для вашей учетной записи пользователя не зарегистрировано ни одного Passkey. Нажмите кнопку ниже, чтобы зарегистрировать новый Passkey.",
"table": {
"name": "Название",
"created": "Создано",
"actions": ""
},
"credentials-list": "Зарегистрированные Passkeys",
"modal-delete": {
"headline": "Удалить Passkey",
"abstract": "Вы уверены, что хотите удалить этот Passkey? Вы больше не сможете войти в систему с помощью этого Passkey.",
"created": "Создано:",
"button-delete": "Удалить",
"button-cancel": "Отмена"
},
"button-rename-title": "Переименновать",
"button-rename-text": "Переименновать Passkey.",
"button-save-title": "Сохранить",
"button-save-text": "Сохранить новое название Passkey.",
"button-cancel-title": "Отмена",
"button-cancel-text": "Отмена переименования Passkey.",
"button-delete-title": "Удалить",
"button-delete-text": "Удалить Passkey. Вы больше не сможете войти в систему с помощью этого Passkey.",
"button-register-title": "Зарегистрировать Passkey",
"button-register-text": "Зарегистрировать Passkey, чтобы защитить свою учетную запись."
},
"password": {
"headline": "Настройки пароля",
"abstract": "Здесь можете изменить свой пароль.",
"current-label": "Текущий пароль",
"new-label": "Новый пароль",
"new-confirm-label": "Повторно новый пароль",
"change-button-text": "Изменить пароль",
"invalid-confirm-label": "Пароли не совпадают",
"weak-label": "Пароль слишком простой"
}
},
"audit": {
"headline": "Журнал аудита",
"abstract": "Здесь вы можете ознакомиться с журналом аудита всех действий, выполненных на WireGuard Portal.",
"no-entries": {
"headline": "Нет доступных записей в журнале",
"abstract": "В данный момент, журнал аудита пуст."
},
"entries-headline": "Записи журнала",
"table-heading": {
"id": "#",
"time": "Время",
"user": "Пользователь",
"severity": "Серьезность",
"origin": "Источник",
"message": "Сообщение"
}
},
"keygen": {
"headline": "Генератор WireGuard-ключей",
"abstract": "Генерация WireGuard-ключей. Ключи генерируются в вашем локальном браузере и никогда не отправляются на сервер.",
"headline-keypair": "Новая пара ключей",
"headline-preshared-key": "Новый общий ключ",
"button-generate": "Генерировать",
"private-key": {
"label": "Приватный ключ",
"placeholder": "Приватный ключ"
},
"public-key": {
"label": "Публичный ключ",
"placeholder": "Публичный ключ"
},
"preshared-key": {
"label": "Общий ключ",
"placeholder": "Общий ключ"
}
},
"calculator": {
"headline": "Калькулятор IP-адресов",
"abstract": "Генерация разрешенных IP-адресов. IP-подсети генерируются в вашем локальном браузере и никогда не отправляются на сервер.",
"headline-allowed-ip": "Новые разрешенные IP-адреса",
"button-exclude-private": "Исключить частные диапазоны IP-адресов",
"allowed-ip": {
"label": "Разрешенные IP-адреса",
"placeholder": "0.0.0.0/0, ::/0",
"empty": "Поле ввода не должно быть пустым"
},
"dissallowed-ip": {
"label": "Запрещенные IP-адреса",
"placeholder": "10.0.0.0/8, 192.168.0.0/16",
"invalid": "Некорректный адрес: {addr}"
},
"new-allowed-ip": {
"label": "Разрешенные IP-адреса",
"placeholder": ""
}
},
"modals": { "modals": {
"user-view": { "user-view": {
"headline": "Учетная запись пользователя:", "headline": "Учетная запись пользователя:",
@@ -180,6 +303,7 @@
"lastname": "Фамилия", "lastname": "Фамилия",
"phone": "Номер телефона", "phone": "Номер телефона",
"department": "Отдел", "department": "Отдел",
"api-enabled": "API",
"disabled": "Учетная запись отключена", "disabled": "Учетная запись отключена",
"locked": "Учетная запись заблокирована", "locked": "Учетная запись заблокирована",
"no-peers": "У пользователя нет связанных пиров.", "no-peers": "У пользователя нет связанных пиров.",
@@ -207,7 +331,8 @@
"password": { "password": {
"label": "Пароль", "label": "Пароль",
"placeholder": "Надежный пароль", "placeholder": "Надежный пароль",
"description": "Оставьте это поле пустым, чтобы сохранить текущий пароль." "description": "Оставьте это поле пустым, чтобы сохранить текущий пароль.",
"too-weak": "Пароль слишком простой. Используйте более сложный пароль."
}, },
"email": { "email": {
"label": "Электронная почта", "label": "Электронная почта",
@@ -267,6 +392,11 @@
"client": "Режим клиента", "client": "Режим клиента",
"any": "Неизвестный режим" "any": "Неизвестный режим"
}, },
"backend": {
"label": "Бэкэнд интерфейса",
"invalid-label": "Оригинальный бэкэнд больше недоступн, вместо нее используется локальная WireGuard-бэкэнд!",
"local": "Локальный WireGuard-бэкэнд"
},
"display-name": { "display-name": {
"label": "Отображаемое имя", "label": "Отображаемое имя",
"placeholder": "Описательное имя для интерфейса" "placeholder": "Описательное имя для интерфейса"
@@ -364,6 +494,8 @@
"section-config": "Конфигурация", "section-config": "Конфигурация",
"identifier": "Идентификатор", "identifier": "Идентификатор",
"ip": "IP-адреса", "ip": "IP-адреса",
"allowed-ip": "Разрешённые IP-адреса",
"extra-allowed-ip": "Разрешённые IP-адреса на стороне сервера",
"user": "Связанный пользователь", "user": "Связанный пользователь",
"notes": "Заметки", "notes": "Заметки",
"expiry-status": "Истекает в", "expiry-status": "Истекает в",
@@ -376,8 +508,10 @@
"handshake": "Последнее рукопожатие", "handshake": "Последнее рукопожатие",
"connected-since": "Подключен с", "connected-since": "Подключен с",
"endpoint": "Конечная точка", "endpoint": "Конечная точка",
"endpoint-key": "Публичный ключ конечной точки",
"button-download": "Скачать конфигурацию", "button-download": "Скачать конфигурацию",
"button-email": "Отправить конфигурацию по электронной почте" "button-email": "Отправить конфигурацию по электронной почте",
"style-label": "Вид конфигурации"
}, },
"peer-edit": { "peer-edit": {
"headline-edit-peer": "Редактировать пира:", "headline-edit-peer": "Редактировать пира:",
@@ -399,7 +533,8 @@
}, },
"private-key": { "private-key": {
"label": "Приватный ключ", "label": "Приватный ключ",
"placeholder": "Приватный ключ" "placeholder": "Приватный ключ",
"help": "Закрытый ключ надежно хранится на сервере. Если у пользователя уже есть копия, вы можете не указывать это поле. Сервер работает исключительно с открытым ключом клиента."
}, },
"public-key": { "public-key": {
"label": "Публичный ключ", "label": "Публичный ключ",
@@ -431,61 +566,61 @@
"description": "Эти IP-адреса будут добавлены в удаленный интерфейс WireGuard как разрешенные IP-адреса." "description": "Эти IP-адреса будут добавлены в удаленный интерфейс WireGuard как разрешенные IP-адреса."
}, },
"dns": { "dns": {
"label": "DNS Server", "label": "DNS-сервер",
"placeholder": "The DNS servers that should be used" "placeholder": "Используемые DNS-серверы"
}, },
"dns-search": { "dns-search": {
"label": "DNS Search Domains", "label": "Поисковые домены DNS",
"placeholder": "DNS search prefixes" "placeholder": "Префиксы поиска DNS"
}, },
"keep-alive": { "keep-alive": {
"label": "Keep Alive Interval", "label": "Интервал поддержания активности",
"placeholder": "Persistent Keepalive (0 = default)" "placeholder": "Постоянное поддержание активности (0 = значение по умолчанию)"
}, },
"mtu": { "mtu": {
"label": "MTU", "label": "MTU",
"placeholder": "The client MTU (0 = keep default)" "placeholder": "MTU клиента (0 = использовать значение по умолчанию)"
}, },
"pre-up": { "pre-up": {
"label": "Pre-Up", "label": "Pre-Up",
"placeholder": "One or multiple bash commands separated by ;" "placeholder": "Одна или несколько команд bash, разделенных ;"
}, },
"post-up": { "post-up": {
"label": "Post-Up", "label": "Post-Up",
"placeholder": "One or multiple bash commands separated by ;" "placeholder": "Одна или несколько команд bash, разделенных ;"
}, },
"pre-down": { "pre-down": {
"label": "Pre-Down", "label": "Pre-Down",
"placeholder": "One or multiple bash commands separated by ;" "placeholder": "Одна или несколько команд bash, разделенных ;"
}, },
"post-down": { "post-down": {
"label": "Post-Down", "label": "Post-Down",
"placeholder": "One or multiple bash commands separated by ;" "placeholder": "Одна или несколько команд bash, разделенных ;"
}, },
"disabled": { "disabled": {
"label": "Peer Disabled" "label": "Узел отключен"
}, },
"ignore-global": { "ignore-global": {
"label": "Ignore global settings" "label": "Игнорировать глобальные настройки"
}, },
"expires-at": { "expires-at": {
"label": "Expiry date" "label": "Дата истечения срока действия"
} }
}, },
"peer-multi-create": { "peer-multi-create": {
"headline-peer": "Create multiple peers", "headline-peer": "Создать несколько узлов",
"headline-endpoint": "Create multiple endpoints", "headline-endpoint": "Создать несколько конечных точек",
"identifiers": { "identifiers": {
"label": "User Identifiers", "label": "Идентификаторы пользователей",
"placeholder": "User Identifiers", "placeholder": "Идентификаторы пользователей",
"description": "A user identifier (the username) for which a peer should be created." "description": "Идентификатор пользователя (имя пользователя), для которого узел будет создан."
}, },
"prefix": { "prefix": {
"headline-peer": "Peer:", "headline-peer": "Узел:",
"headline-endpoint": "Endpoint:", "headline-endpoint": "Конечная точка:",
"label": "Display Name Prefix", "label": "Префикс отображаемого имени",
"placeholder": "The prefix", "placeholder": "Префикс",
"description": "A prefix that is added to the peers display name." "description": "Префикс будет добавлен к отображаемому имени узла."
} }
} }
} }

View File

@@ -72,6 +72,14 @@ const router = createRouter({
// this generates a separate chunk (About.[hash].js) for this route // this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import('../views/KeyGeneraterView.vue') component: () => import('../views/KeyGeneraterView.vue')
},
{
path: '/ip-calculator',
name: 'ip-calculator',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/IPCalculatorView.vue')
} }
], ],
linkActiveClass: "active", linkActiveClass: "active",
@@ -122,7 +130,7 @@ router.beforeEach(async (to) => {
} }
// redirect to login page if not logged in and trying to access a restricted page // redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/', '/login', '/key-generator'] const publicPages = ['/', '/login', '/key-generator', '/ip-calculator']
const authRequired = !publicPages.includes(to.path) const authRequired = !publicPages.includes(to.path)
if (authRequired && !auth.IsAuthenticated) { if (authRequired && !auth.IsAuthenticated) {

View File

@@ -151,6 +151,17 @@ export const profileStore = defineStore('profile', {
}) })
}) })
}, },
async changePassword(formData) {
this.fetching = true
let currentUser = authStore().user.Identifier
return apiWrapper.post(`${baseUrl}/${base64_url_encode(currentUser)}/change-password`, formData)
.then(this.fetching = false)
.catch(error => {
this.fetching = false;
console.log("Failed to change password for ", currentUser, ": ", error);
throw new Error(error);
});
},
async LoadPeers() { async LoadPeers() {
this.fetching = true this.fetching = true
let currentUser = authStore().user.Identifier let currentUser = authStore().user.Identifier

View File

@@ -0,0 +1,139 @@
<script setup>
import {ref, watch, computed} from "vue";
import isCidr from "is-cidr";
import {isIP} from "is-ip";
import {excludeCidr} from "cidr-tools";
import {useI18n} from 'vue-i18n';
const allowedIp = ref("")
const dissallowedIp = ref("")
const privateIP = ref("10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16")
const {t} = useI18n()
const errorAllowed = ref("")
const errorDissallowed = ref("")
/**
* Validate a comma-separated list of IP and/or CIDR addresses.
* @function validateIpAndCidrList
* @param {string} value - Comma-separated string (e.g. "10.0.0.0/8, 192.168.0.1")
* @returns {true|string} Returns true if all values are valid, otherwise an error message.
*/
function validateIpAndCidrList(value) {
const list = value.split(",").map(v => v.trim()).filter(Boolean);
if (list.length === 0) {
return t('calculator.allowed-ip.empty');
}
for (const addr of list) {
if (!isIP(addr) && !isCidr(addr)) {
return t('calculator.dissallowed-ip.invalid', {addr});
}
}
return true;
}
/**
* Watcher that validates allowed IPs input in real-time.
* Updates `errorAllowed` whenever `allowedIp` changes.
*/
watch(allowedIp, (newValue) => {
const result = validateIpAndCidrList(newValue);
errorAllowed.value = result === true ? "" : result;
});
/**
* Watcher that validates disallowed IPs input in real-time.
* Updates `errorDissallowed` whenever `dissallowedIp` changes.
*/
watch(dissallowedIp, (newValue) => {
if (!allowedIp.value || allowedIp.value.trim() === "") {
allowedIp.value = "0.0.0.0/0";
}
const result = validateIpAndCidrList(newValue);
errorDissallowed.value = result === true ? "" : result;
});
/**
* Dynamically computes the resulting "Allowed IPs" list
* by excluding the disallowed ranges from the allowed ranges.
* @constant
* @type {ComputedRef<string>}
* @returns {string} A comma-separated string of resulting CIDR blocks.
*/
const newAllowedIp = computed(() => {
if (errorAllowed.value || errorDissallowed.value) return "";
try {
const allowedList = allowedIp.value.split(",").map(v => v.trim()).filter(Boolean);
const disallowedList = dissallowedIp.value.split(",").map(v => v.trim()).filter(Boolean);
const result = excludeCidr(allowedList, disallowedList);
return result.join(", ");
} catch (e) {
console.error("Allowed IPs calculation error:", e);
return "";
}
});
/**
* Append private IP ranges to disallowed IPs.
* If any already exist, they are preserved and new ones are appended only if not present.
* @function addPrivateIPs
*/
function addPrivateIPs() {
const privateList = privateIP.value.split(",").map(v => v.trim());
const currentList = dissallowedIp.value.split(",").map(v => v.trim()).filter(Boolean);
const combined = Array.from(new Set([...currentList, ...privateList]));
dissallowedIp.value = combined.join(", ");
}
</script>
<template>
<div class="page-header">
<h1>{{ $t('calculator.headline') }}</h1>
</div>
<p class="lead">{{ $t('calculator.abstract') }}</p>
<div class="mt-4 row">
<div class="col-12 col-lg-5">
<fieldset>
<div class="form-group">
<label class="form-label mt-4">{{ $t('calculator.allowed-ip.label') }}</label>
<input class="form-control" v-model="allowedIp" :placeholder="$t('calculator.allowed-ip.placeholder')" :class="{ 'is-invalid': errorAllowed }">
<div v-if="errorAllowed" class="text-danger mt-1">{{ errorAllowed }}</div>
</div>
<div class="form-group">
<label class="form-label mt-4">{{ $t('calculator.dissallowed-ip.label') }}</label>
<input class="form-control" v-model="dissallowedIp" :placeholder="$t('calculator.dissallowed-ip.placeholder')" :class="{ 'is-invalid': errorDissallowed }">
<div v-if="errorDissallowed" class="text-danger mt-1">{{ errorDissallowed }}</div>
</div>
</fieldset>
<fieldset>
<hr class="mt-4">
<button class="btn btn-primary mb-4" type="button" @click="addPrivateIPs">{{ $t('calculator.button-exclude-private') }}</button>
</fieldset>
</div>
<div class="col-12 col-lg-2 mt-sm-4">
</div>
<div class="col-12 col-lg-5">
<h1>{{ $t('calculator.headline-allowed-ip') }}</h1>
<fieldset>
<div class="form-group">
<textarea class="form-control" :value="newAllowedIp" rows="6" :placeholder="$t('calculator.new-allowed-ip.placeholder')" readonly></textarea>
</div>
</fieldset>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,8 +1,9 @@
<script setup> <script setup>
import {onMounted, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import { profileStore } from "@/stores/profile"; import { profileStore } from "@/stores/profile";
import { settingsStore } from "@/stores/settings"; import { settingsStore } from "@/stores/settings";
import { authStore } from "../stores/auth"; import { authStore } from "../stores/auth";
import {notify} from "@kyvg/vue3-notification";
const profile = profileStore() const profile = profileStore()
const settings = settingsStore() const settings = settingsStore()
@@ -34,6 +35,45 @@ async function saveRename(credential) {
console.error("Failed to rename credential:", error); console.error("Failed to rename credential:", error);
} }
} }
const pwFormData = ref({
OldPassword: '',
Password: '',
PasswordRepeat: '',
})
const passwordWeak = computed(() => {
return pwFormData.value.Password && pwFormData.value.Password.length > 0 && pwFormData.value.Password.length < settings.Setting('MinPasswordLength')
})
const passwordChangeAllowed = computed(() => {
return pwFormData.value.Password && pwFormData.value.Password.length >= settings.Setting('MinPasswordLength') &&
pwFormData.value.Password === pwFormData.value.PasswordRepeat &&
pwFormData.value.OldPassword && pwFormData.value.OldPassword.length > 0 && pwFormData.value.OldPassword !== pwFormData.value.Password;
})
const updatePassword = async () => {
try {
await profile.changePassword(pwFormData.value);
pwFormData.value.OldPassword = '';
pwFormData.value.Password = '';
pwFormData.value.PasswordRepeat = '';
notify({
title: "Password changed!",
text: "Your password has been changed successfully.",
type: 'success',
});
} catch (e) {
notify({
title: "Failed to update password!",
text: e.toString(),
type: 'error',
})
}
}
</script> </script>
<template> <template>
@@ -43,52 +83,45 @@ async function saveRename(credential) {
<p class="lead">{{ $t('settings.abstract') }}</p> <p class="lead">{{ $t('settings.abstract') }}</p>
<div v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')"> <div class="card border-secondary p-5 mt-5" v-if="profile.user.Source === 'db'">
<div class="card border-secondary p-5" v-if="profile.user.ApiToken"> <h2 class="display-7">{{ $t('settings.password.headline') }}</h2>
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2> <p class="lead">{{ $t('settings.password.abstract') }}</p>
<p class="lead">{{ $t('settings.api.abstract') }}</p> <hr class="my-4">
<hr class="my-4">
<p>{{ $t('settings.api.active-description') }}</p> <div class="row">
<div class="row"> <div class="col-6">
<div class="col-6"> <div class="form-group">
<div class="form-group"> <label class="form-label mt-4" for="oldpw">{{ $t('settings.password.current-label') }}</label>
<label class="form-label mt-4">{{ $t('settings.api.user-label') }}</label> <input id="oldpw" v-model="pwFormData.OldPassword" class="form-control" :class="{ 'is-invalid': pwFormData.Password && !pwFormData.OldPassword }" type="password">
<input v-model="profile.user.Identifier" class="form-control" :placeholder="$t('settings.api.user-placeholder')" type="text" readonly>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label class="form-label mt-4">{{ $t('settings.api.token-label') }}</label>
<input v-model="profile.user.ApiToken" class="form-control" :placeholder="$t('settings.api.token-placeholder')" type="text" readonly>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="col-6">
<div class="col-12"> </div>
<div class="form-group"> </div>
<p class="form-label mt-4">{{ $t('settings.api.token-created-label') }} {{profile.user.ApiTokenCreated}}</p> <div class="row">
</div> <div class="col-6">
<div class="form-group has-success">
<label class="form-label mt-4" for="newpw">{{ $t('settings.password.new-label') }}</label>
<input id="newpw" v-model="pwFormData.Password" class="form-control" :class="{ 'is-invalid': passwordWeak, 'is-valid': pwFormData.Password !== '' && !passwordWeak }" type="password">
<div class="invalid-feedback" v-if="passwordWeak">{{ $t('settings.password.weak-label') }}</div>
</div> </div>
</div> </div>
<div class="row mt-5"> <div class="col-6">
<div class="col-6"> <div class="form-group">
<button class="btn btn-primary" :title="$t('settings.api.button-disable-title')" @click.prevent="profile.disableApi()" :disabled="profile.isFetching"> <label class="form-label mt-4" for="confirmnewpw">{{ $t('settings.password.new-confirm-label') }}</label>
<i class="fa-solid fa-minus-circle"></i> {{ $t('settings.api.button-disable-text') }} <input id="confirmnewpw" v-model="pwFormData.PasswordRepeat" class="form-control" :class="{ 'is-invalid': pwFormData.PasswordRepeat !== ''&& pwFormData.Password !== pwFormData.PasswordRepeat, 'is-valid': pwFormData.PasswordRepeat !== '' && pwFormData.Password === pwFormData.PasswordRepeat && !passwordWeak }" type="password">
</button> <div class="invalid-feedback" v-if="pwFormData.PasswordRepeat !== ''&& pwFormData.Password !== pwFormData.PasswordRepeat">{{ $t('settings.password.invalid-confirm-label') }}</div>
</div>
<div class="col-6">
<a href="/api/v1/doc.html" target="_blank" :alt="$t('settings.api.api-link')">{{ $t('settings.api.api-link') }}</a>
</div> </div>
</div> </div>
</div> </div>
<div class="card border-secondary p-5" v-else> <div class="row mt-5">
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2> <div class="col-6">
<p class="lead">{{ $t('settings.api.abstract') }}</p> <button class="btn btn-primary" :title="$t('settings.api.button-disable-title')" @click.prevent="updatePassword" :disabled="profile.isFetching || !passwordChangeAllowed">
<hr class="my-4"> <i class="fa-solid fa-floppy-disk"></i> {{ $t('settings.password.change-button-text') }}
<p>{{ $t('settings.api.inactive-description') }}</p> </button>
<button class="btn btn-primary" :title="$t('settings.api.button-enable-title')" @click.prevent="profile.enableApi()" :disabled="profile.isFetching"> </div>
<i class="fa-solid fa-plus-circle"></i> {{ $t('settings.api.button-enable-text') }} <div class="col-6">
</button> </div>
</div> </div>
</div> </div>
@@ -173,4 +206,53 @@ async function saveRename(credential) {
</div> </div>
</div> </div>
<div class="mt-5" v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')">
<div class="card border-secondary p-5" v-if="profile.user.ApiToken">
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
<p class="lead">{{ $t('settings.api.abstract') }}</p>
<hr class="my-4">
<p>{{ $t('settings.api.active-description') }}</p>
<div class="row">
<div class="col-6">
<div class="form-group">
<label class="form-label mt-4">{{ $t('settings.api.user-label') }}</label>
<input v-model="profile.user.Identifier" class="form-control" :placeholder="$t('settings.api.user-placeholder')" type="text" readonly>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label class="form-label mt-4">{{ $t('settings.api.token-label') }}</label>
<input v-model="profile.user.ApiToken" class="form-control" :placeholder="$t('settings.api.token-placeholder')" type="text" readonly>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="form-group">
<p class="form-label mt-4">{{ $t('settings.api.token-created-label') }} {{profile.user.ApiTokenCreated}}</p>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-6">
<button class="btn btn-primary" :title="$t('settings.api.button-disable-title')" @click.prevent="profile.disableApi()" :disabled="profile.isFetching">
<i class="fa-solid fa-minus-circle"></i> {{ $t('settings.api.button-disable-text') }}
</button>
</div>
<div class="col-6">
<a href="/api/v1/doc.html" target="_blank" :alt="$t('settings.api.api-link')">{{ $t('settings.api.api-link') }}</a>
</div>
</div>
</div>
<div class="card border-secondary p-5" v-else>
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
<p class="lead">{{ $t('settings.api.abstract') }}</p>
<hr class="my-4">
<p>{{ $t('settings.api.inactive-description') }}</p>
<button class="btn btn-primary" :title="$t('settings.api.button-enable-title')" @click.prevent="profile.enableApi()" :disabled="profile.isFetching">
<i class="fa-solid fa-plus-circle"></i> {{ $t('settings.api.button-enable-text') }}
</button>
</div>
</div>
</template> </template>

28
go.mod
View File

@@ -8,9 +8,9 @@ require (
github.com/coreos/go-oidc/v3 v3.16.0 github.com/coreos/go-oidc/v3 v3.16.0
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/go-ldap/ldap/v3 v3.4.12 github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-pkgz/routegroup v1.5.3 github.com/go-pkgz/routegroup v1.6.0
github.com/go-playground/validator/v10 v10.28.0 github.com/go-playground/validator/v10 v10.28.0
github.com/go-webauthn/webauthn v0.14.0 github.com/go-webauthn/webauthn v0.15.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/prometheus-community/pro-bing v0.7.0 github.com/prometheus-community/pro-bing v0.7.0
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
@@ -21,15 +21,15 @@ require (
github.com/xhit/go-simple-mail/v2 v2.16.0 github.com/xhit/go-simple-mail/v2 v2.16.0
github.com/yeqown/go-qrcode/v2 v2.2.5 github.com/yeqown/go-qrcode/v2 v2.2.5
github.com/yeqown/go-qrcode/writer/compressed v1.0.1 github.com/yeqown/go-qrcode/writer/compressed v1.0.1
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.44.0
golang.org/x/oauth2 v0.31.0 golang.org/x/oauth2 v0.33.0
golang.org/x/sys v0.36.0 golang.org/x/sys v0.38.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.6.0 gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0 gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlserver v1.6.1 gorm.io/driver/sqlserver v1.6.3
gorm.io/gorm v1.31.0 gorm.io/gorm v1.31.1
) )
require ( require (
@@ -59,7 +59,8 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-test/deep v1.1.1 // indirect github.com/go-test/deep v1.1.1 // indirect
github.com/go-webauthn/x v0.1.25 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/go-webauthn/x v0.1.26 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
@@ -77,7 +78,6 @@ require (
github.com/mdlayher/netlink v1.8.0 // indirect github.com/mdlayher/netlink v1.8.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/microsoft/go-mssqldb v1.9.3 // indirect github.com/microsoft/go-mssqldb v1.9.3 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -92,11 +92,11 @@ require (
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect
golang.org/x/mod v0.28.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/net v0.46.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.37.0 // indirect golang.org/x/tools v0.38.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.10 // indirect
modernc.org/libc v1.66.10 // indirect modernc.org/libc v1.66.10 // indirect

56
go.sum
View File

@@ -85,8 +85,8 @@ github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
github.com/go-pkgz/routegroup v1.5.3 h1:IvH1KLcQkMap9jucQGBlef3IBloxSAe8USUFvxShFqs= github.com/go-pkgz/routegroup v1.6.0 h1:44XHZgF6JIIldRlv+zjg6SygULASmjifnfIQjwCT0e4=
github.com/go-pkgz/routegroup v1.5.3/go.mod h1:Pmu04fhgWhRtBMIJ8HXppnnzOPjnL/IEPBIdO2zmeqg= github.com/go-pkgz/routegroup v1.6.0/go.mod h1:Pmu04fhgWhRtBMIJ8HXppnnzOPjnL/IEPBIdO2zmeqg=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -99,10 +99,12 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx2QnhL0= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88= github.com/go-webauthn/webauthn v0.15.0 h1:LR1vPv62E0/6+sTenX35QrCmpMCzLeVAcnXeH4MrbJY=
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY= github.com/go-webauthn/webauthn v0.15.0/go.mod h1:hcAOhVChPRG7oqG7Xj6XKN1mb+8eXTGP/B7zBLzkX5A=
github.com/go-webauthn/x v0.1.26 h1:eNzreFKnwNLDFoywGh9FA8YOMebBWTUNlNSdolQRebs=
github.com/go-webauthn/x v0.1.26/go.mod h1:jmf/phPV6oIsF6hmdVre+ovHkxjDOmNH0t6fekWUxvg=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
@@ -177,8 +179,6 @@ github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5
github.com/microsoft/go-mssqldb v1.9.3/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA= github.com/microsoft/go-mssqldb v1.9.3/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -262,8 +262,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -272,8 +272,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -291,10 +291,10 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -302,8 +302,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -324,8 +324,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -354,16 +354,16 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
@@ -385,11 +385,11 @@ gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlserver v1.6.1 h1:XWISFsu2I2pqd1KJhhTZNJMx1jNQ+zVL/Q8ovDcUjtY= gorm.io/driver/sqlserver v1.6.3 h1:UR+nWCuphPnq7UxnL57PSrlYjuvs+sf1N59GgFX7uAI=
gorm.io/driver/sqlserver v1.6.1/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U= gorm.io/driver/sqlserver v1.6.3/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=

View File

@@ -1550,6 +1550,38 @@
} }
} }
}, },
"/user/{id}/change-password": {
"post": {
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Change the password for the given user.",
"operationId": "users_handleChangePasswordPost",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.User"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
}
},
"/user/{id}/interfaces": { "/user/{id}/interfaces": {
"get": { "get": {
"produces": [ "produces": [
@@ -2159,6 +2191,10 @@
} }
] ]
}, },
"UserDisplayName": {
"description": "the owner display name",
"type": "string"
},
"UserIdentifier": { "UserIdentifier": {
"description": "the owner", "description": "the owner",
"type": "string" "type": "string"

View File

@@ -322,6 +322,9 @@ definitions:
allOf: allOf:
- $ref: '#/definitions/model.ConfigOption-string' - $ref: '#/definitions/model.ConfigOption-string'
description: the routing table description: the routing table
UserDisplayName:
description: the owner display name
type: string
UserIdentifier: UserIdentifier:
description: the owner description: the owner
type: string type: string
@@ -1442,6 +1445,27 @@ paths:
summary: Enable the REST API for the given user. summary: Enable the REST API for the given user.
tags: tags:
- Users - Users
/user/{id}/change-password:
post:
operationId: users_handleChangePasswordPost
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.User'
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Change the password for the given user.
tags:
- Users
/user/{id}/interfaces: /user/{id}/interfaces:
get: get:
operationId: users_handleInterfacesGet operationId: users_handleInterfacesGet

View File

@@ -17,11 +17,6 @@
"paths": { "paths": {
"/interface/all": { "/interface/all": {
"get": { "get": {
"security": [
{
"BasicAuth": []
}
],
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -52,16 +47,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/interface/by-id/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/interface/by-id/{id}": {
"get": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -110,14 +105,14 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
},
"put": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
},
"put": {
"description": "This endpoint updates an existing interface with the provided data. All required fields must be filled (e.g. name, private key, public key, ...).", "description": "This endpoint updates an existing interface with the provided data. All required fields must be filled (e.g. name, private key, public key, ...).",
"produces": [ "produces": [
"application/json" "application/json"
@@ -182,14 +177,14 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
},
"delete": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
},
"delete": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -241,16 +236,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/interface/new": {
"post": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/interface/new": {
"post": {
"description": "This endpoint creates a new interface with the provided data. All required fields must be filled (e.g. name, private key, public key, ...).", "description": "This endpoint creates a new interface with the provided data. All required fields must be filled (e.g. name, private key, public key, ...).",
"produces": [ "produces": [
"application/json" "application/json"
@@ -308,16 +303,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/interface/prepare": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/interface/prepare": {
"get": {
"description": "This endpoint returns a new interface with default values (fresh key pair, valid name, new IP address pool, ...).", "description": "This endpoint returns a new interface with default values (fresh key pair, valid name, new IP address pool, ...).",
"produces": [ "produces": [
"application/json" "application/json"
@@ -352,16 +347,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/metrics/by-interface/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/metrics/by-interface/{id}": {
"get": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -410,16 +405,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/metrics/by-peer/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/metrics/by-peer/{id}": {
"get": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -468,16 +463,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/metrics/by-user/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/metrics/by-user/{id}": {
"get": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -526,16 +521,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/peer/by-id/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/peer/by-id/{id}": {
"get": {
"description": "Normal users can only access their own records. Admins can access all records.", "description": "Normal users can only access their own records. Admins can access all records.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -585,14 +580,14 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
},
"put": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
},
"put": {
"description": "Only admins can update existing records. The peer record must contain all required fields (e.g., public key, allowed IPs).", "description": "Only admins can update existing records. The peer record must contain all required fields (e.g., public key, allowed IPs).",
"produces": [ "produces": [
"application/json" "application/json"
@@ -657,14 +652,14 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
},
"delete": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
},
"delete": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -716,16 +711,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/peer/by-interface/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/peer/by-interface/{id}": {
"get": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -765,16 +760,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/peer/by-user/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/peer/by-user/{id}": {
"get": {
"description": "Normal users can only access their own records. Admins can access all records.", "description": "Normal users can only access their own records. Admins can access all records.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -815,16 +810,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/peer/new": {
"post": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/peer/new": {
"post": {
"description": "Only admins can create new records. The peer record must contain all required fields (e.g., public key, allowed IPs).", "description": "Only admins can create new records. The peer record must contain all required fields (e.g., public key, allowed IPs).",
"produces": [ "produces": [
"application/json" "application/json"
@@ -882,16 +877,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/peer/prepare/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/peer/prepare/{id}": {
"get": {
"description": "This endpoint is used to prepare a new peer record. The returned data contains a fresh key pair and valid ip address.", "description": "This endpoint is used to prepare a new peer record. The returned data contains a fresh key pair and valid ip address.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -947,16 +942,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/provisioning/data/peer-config": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/provisioning/data/peer-config": {
"get": {
"description": "Normal users can only access their own record. Admins can access all records.", "description": "Normal users can only access their own record. Admins can access all records.",
"produces": [ "produces": [
"text/plain", "text/plain",
@@ -1013,16 +1008,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/provisioning/data/peer-qr": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/provisioning/data/peer-qr": {
"get": {
"description": "Normal users can only access their own record. Admins can access all records.", "description": "Normal users can only access their own record. Admins can access all records.",
"produces": [ "produces": [
"image/png", "image/png",
@@ -1079,16 +1074,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/provisioning/data/user-info": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/provisioning/data/user-info": {
"get": {
"description": "Normal users can only access their own record. Admins can access all records.", "description": "Normal users can only access their own record. Admins can access all records.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -1149,16 +1144,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/provisioning/new-peer": {
"post": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/provisioning/new-peer": {
"post": {
"description": "Normal users can only create new peers if self provisioning is allowed. Admins can always add new peers.", "description": "Normal users can only create new peers if self provisioning is allowed. Admins can always add new peers.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -1216,16 +1211,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/user/all": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/user/all": {
"get": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -1256,16 +1251,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/user/by-id/{id}": {
"get": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/user/by-id/{id}": {
"get": {
"description": "Normal users can only access their own record. Admins can access all records.", "description": "Normal users can only access their own record. Admins can access all records.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -1315,14 +1310,14 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
},
"put": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
},
"put": {
"description": "Only admins can update existing records.", "description": "Only admins can update existing records.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -1387,14 +1382,14 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
},
"delete": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
},
"delete": {
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -1446,16 +1441,16 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
}
},
"/user/new": {
"post": {
"security": [ "security": [
{ {
"BasicAuth": [] "BasicAuth": []
} }
], ]
}
},
"/user/new": {
"post": {
"description": "Only admins can create new records.", "description": "Only admins can create new records.",
"produces": [ "produces": [
"application/json" "application/json"
@@ -1513,7 +1508,12 @@
"$ref": "#/definitions/models.Error" "$ref": "#/definitions/models.Error"
} }
} }
} },
"security": [
{
"BasicAuth": []
}
]
} }
} }
}, },
@@ -2216,7 +2216,9 @@
"description": "The source of the user. This field is optional.", "description": "The source of the user. This field is optional.",
"type": "string", "type": "string",
"enum": [ "enum": [
"db" "db",
"ldap",
"oauth"
], ],
"example": "db" "example": "db"
} }

View File

@@ -561,6 +561,8 @@ definitions:
description: The source of the user. This field is optional. description: The source of the user. This field is optional.
enum: enum:
- db - db
- ldap
- oauth
example: db example: db
type: string type: string
required: required:

View File

@@ -2,6 +2,8 @@ package backend
import ( import (
"context" "context"
"fmt"
"strings"
"github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
@@ -70,6 +72,44 @@ func (u UserService) DeactivateApi(ctx context.Context, id domain.UserIdentifier
return u.users.DeactivateApi(ctx, id) return u.users.DeactivateApi(ctx, id)
} }
func (u UserService) ChangePassword(ctx context.Context, id domain.UserIdentifier, oldPassword, newPassword string) (*domain.User, error) {
oldPassword = strings.TrimSpace(oldPassword)
newPassword = strings.TrimSpace(newPassword)
if newPassword == "" {
return nil, fmt.Errorf("new password must not be empty")
}
// ensure that the new password is different from the old one
if oldPassword == newPassword {
return nil, fmt.Errorf("new password must be different from the old one")
}
user, err := u.users.GetUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
// ensure that the user uses the database backend; otherwise we can't change the password
if user.Source != domain.UserSourceDatabase {
return nil, fmt.Errorf("user source %s does not support password changes", user.Source)
}
// validate old password
if user.CheckPassword(oldPassword) != nil {
return nil, fmt.Errorf("current password is invalid")
}
user.Password = domain.PrivateString(newPassword)
// ensure that the new password is strong enough
if err := user.HasWeakPassword(u.cfg.Auth.MinPasswordLength); err != nil {
return nil, err
}
return u.users.UpdateUser(ctx, user)
}
func (u UserService) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) { func (u UserService) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) {
return u.wg.GetUserPeers(ctx, id) return u.wg.GetUserPeers(ctx, id)
} }

View File

@@ -28,6 +28,8 @@ type UserService interface {
ActivateApi(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) ActivateApi(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
// DeactivateApi disables the API for the user with the given id. // DeactivateApi disables the API for the user with the given id.
DeactivateApi(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) DeactivateApi(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
// ChangePassword changes the password for the user with the given id.
ChangePassword(ctx context.Context, id domain.UserIdentifier, oldPassword, newPassword string) (*domain.User, error)
// GetUserPeers returns all peers for the given user. // GetUserPeers returns all peers for the given user.
GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error)
// GetUserPeerStats returns all peer stats for the given user. // GetUserPeerStats returns all peer stats for the given user.
@@ -75,6 +77,7 @@ func (e UserEndpoint) RegisterRoutes(g *routegroup.Bundle) {
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("GET /{id}/interfaces", e.handleInterfacesGet()) apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("GET /{id}/interfaces", e.handleInterfacesGet())
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/api/enable", e.handleApiEnablePost()) apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/api/enable", e.handleApiEnablePost())
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/api/disable", e.handleApiDisablePost()) apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/api/disable", e.handleApiDisablePost())
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/change-password", e.handleChangePasswordPost())
} }
// handleAllGet returns a gorm Handler function. // handleAllGet returns a gorm Handler function.
@@ -391,3 +394,68 @@ func (e UserEndpoint) handleApiDisablePost() http.HandlerFunc {
respond.JSON(w, http.StatusOK, model.NewUser(user, false)) respond.JSON(w, http.StatusOK, model.NewUser(user, false))
} }
} }
// handleChangePasswordPost returns a gorm Handler function.
//
// @ID users_handleChangePasswordPost
// @Tags Users
// @Summary Change the password for the given user.
// @Produce json
// @Success 200 {object} model.User
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /user/{id}/change-password [post]
func (e UserEndpoint) handleChangePasswordPost() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userId := Base64UrlDecode(request.Path(r, "id"))
if userId == "" {
respond.JSON(w, http.StatusBadRequest,
model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"})
return
}
var passwordData struct {
OldPassword string `json:"OldPassword"`
Password string `json:"Password"`
PasswordRepeat string `json:"PasswordRepeat"`
}
if err := request.BodyJson(r, &passwordData); err != nil {
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
return
}
if passwordData.OldPassword == "" {
respond.JSON(w, http.StatusBadRequest,
model.Error{Code: http.StatusBadRequest, Message: "old password missing"})
return
}
if passwordData.Password == "" {
respond.JSON(w, http.StatusBadRequest,
model.Error{Code: http.StatusBadRequest, Message: "new password missing"})
return
}
if passwordData.OldPassword == passwordData.Password {
respond.JSON(w, http.StatusBadRequest,
model.Error{Code: http.StatusBadRequest, Message: "password did not change"})
return
}
if passwordData.Password != passwordData.PasswordRepeat {
respond.JSON(w, http.StatusBadRequest,
model.Error{Code: http.StatusBadRequest, Message: "password mismatch"})
return
}
user, err := e.userService.ChangePassword(r.Context(), domain.UserIdentifier(userId),
passwordData.OldPassword, passwordData.Password)
if err != nil {
respond.JSON(w, http.StatusInternalServerError,
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
respond.JSON(w, http.StatusOK, model.NewUser(user, false))
}
}

View File

@@ -48,12 +48,12 @@ func (e InterfaceEndpoint) RegisterRoutes(g *routegroup.Bundle) {
apiGroup.Use(e.authenticator.LoggedIn(ScopeAdmin)) apiGroup.Use(e.authenticator.LoggedIn(ScopeAdmin))
apiGroup.HandleFunc("GET /all", e.handleAllGet()) apiGroup.HandleFunc("GET /all", e.handleAllGet())
apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet()) apiGroup.HandleFunc("GET /by-id/{id...}", e.handleByIdGet())
apiGroup.HandleFunc("GET /prepare", e.handlePrepareGet()) apiGroup.HandleFunc("GET /prepare", e.handlePrepareGet())
apiGroup.HandleFunc("POST /new", e.handleCreatePost()) apiGroup.HandleFunc("POST /new", e.handleCreatePost())
apiGroup.HandleFunc("PUT /by-id/{id}", e.handleUpdatePut()) apiGroup.HandleFunc("PUT /by-id/{id...}", e.handleUpdatePut())
apiGroup.HandleFunc("DELETE /by-id/{id}", e.handleDelete()) apiGroup.HandleFunc("DELETE /by-id/{id...}", e.handleDelete())
} }
// handleAllGet returns a gorm Handler function. // handleAllGet returns a gorm Handler function.

View File

@@ -44,10 +44,10 @@ func (e MetricsEndpoint) RegisterRoutes(g *routegroup.Bundle) {
apiGroup := g.Mount("/metrics") apiGroup := g.Mount("/metrics")
apiGroup.Use(e.authenticator.LoggedIn()) apiGroup.Use(e.authenticator.LoggedIn())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id}", apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id...}",
e.handleMetricsForInterfaceGet()) e.handleMetricsForInterfaceGet())
apiGroup.HandleFunc("GET /by-user/{id}", e.handleMetricsForUserGet()) apiGroup.HandleFunc("GET /by-user/{id...}", e.handleMetricsForUserGet())
apiGroup.HandleFunc("GET /by-peer/{id}", e.handleMetricsForPeerGet()) apiGroup.HandleFunc("GET /by-peer/{id...}", e.handleMetricsForPeerGet())
} }
// handleMetricsForInterfaceGet returns a gorm Handler function. // handleMetricsForInterfaceGet returns a gorm Handler function.

View File

@@ -47,15 +47,15 @@ func (e PeerEndpoint) RegisterRoutes(g *routegroup.Bundle) {
apiGroup := g.Mount("/peer") apiGroup := g.Mount("/peer")
apiGroup.Use(e.authenticator.LoggedIn()) apiGroup.Use(e.authenticator.LoggedIn())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id}", apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /by-interface/{id...}",
e.handleAllForInterfaceGet()) e.handleAllForInterfaceGet())
apiGroup.HandleFunc("GET /by-user/{id}", e.handleAllForUserGet()) apiGroup.HandleFunc("GET /by-user/{id...}", e.handleAllForUserGet())
apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet()) apiGroup.HandleFunc("GET /by-id/{id...}", e.handleByIdGet())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /prepare/{id}", e.handlePrepareGet()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /prepare/{id...}", e.handlePrepareGet())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /new", e.handleCreatePost()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /new", e.handleCreatePost())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id}", e.handleUpdatePut()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id...}", e.handleUpdatePut())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id}", e.handleDelete()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id...}", e.handleDelete())
} }
// handleAllForInterfaceGet returns a gorm Handler function. // handleAllForInterfaceGet returns a gorm Handler function.

View File

@@ -47,10 +47,10 @@ func (e UserEndpoint) RegisterRoutes(g *routegroup.Bundle) {
apiGroup.Use(e.authenticator.LoggedIn()) apiGroup.Use(e.authenticator.LoggedIn())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /all", e.handleAllGet()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("GET /all", e.handleAllGet())
apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet()) apiGroup.HandleFunc("GET /by-id/{id...}", e.handleByIdGet())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /new", e.handleCreatePost()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /new", e.handleCreatePost())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id}", e.handleUpdatePut()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("PUT /by-id/{id...}", e.handleUpdatePut())
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id}", e.handleDelete()) apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("DELETE /by-id/{id...}", e.handleDelete())
} }
// handleAllGet returns a gorm Handler function. // handleAllGet returns a gorm Handler function.

View File

@@ -13,7 +13,7 @@ type User struct {
// The email address of the user. This field is optional. // The email address of the user. This field is optional.
Email string `json:"Email" binding:"omitempty,email" example:"test@test.com"` Email string `json:"Email" binding:"omitempty,email" example:"test@test.com"`
// The source of the user. This field is optional. // The source of the user. This field is optional.
Source string `json:"Source" binding:"oneof=db" example:"db"` Source string `json:"Source" binding:"oneof=db ldap oauth" example:"db"`
// The name of the authentication provider. This field is read-only. // The name of the authentication provider. This field is read-only.
ProviderName string `json:"ProviderName,omitempty" readonly:"true" example:""` ProviderName string `json:"ProviderName,omitempty" readonly:"true" example:""`
// If this field is set, the user is an admin. // If this field is set, the user is an admin.

View File

@@ -20,18 +20,7 @@ type LdapAuthenticator struct {
} }
func newLdapAuthenticator(_ context.Context, cfg *config.LdapProvider) (*LdapAuthenticator, error) { func newLdapAuthenticator(_ context.Context, cfg *config.LdapProvider) (*LdapAuthenticator, error) {
var provider = &LdapAuthenticator{} return &LdapAuthenticator{cfg: cfg}, nil
provider.cfg = cfg
dn, err := ldap.ParseDN(cfg.AdminGroupDN)
if err != nil {
return nil, fmt.Errorf("failed to parse admin group DN: %w", err)
}
provider.cfg.FieldMap = provider.getLdapFieldMapping(cfg.FieldMap)
provider.cfg.ParsedAdminGroupDN = dn
return provider, nil
} }
// GetName returns the name of the LDAP authenticator. // GetName returns the name of the LDAP authenticator.
@@ -154,40 +143,3 @@ func (l LdapAuthenticator) ParseUserInfo(raw map[string]any) (*domain.Authentica
return userInfo, nil return userInfo, nil
} }
func (l LdapAuthenticator) getLdapFieldMapping(f config.LdapFields) config.LdapFields {
defaultMap := config.LdapFields{
BaseFields: config.BaseFields{
UserIdentifier: "mail",
Email: "mail",
Firstname: "givenName",
Lastname: "sn",
Phone: "telephoneNumber",
Department: "department",
},
GroupMembership: "memberOf",
}
if f.UserIdentifier != "" {
defaultMap.UserIdentifier = f.UserIdentifier
}
if f.Email != "" {
defaultMap.Email = f.Email
}
if f.Firstname != "" {
defaultMap.Firstname = f.Firstname
}
if f.Lastname != "" {
defaultMap.Lastname = f.Lastname
}
if f.Phone != "" {
defaultMap.Phone = f.Phone
}
if f.Department != "" {
defaultMap.Department = f.Department
}
if f.GroupMembership != "" {
defaultMap.GroupMembership = f.GroupMembership
}
return defaultMap
}

View File

@@ -551,6 +551,12 @@ func (m Manager) updateLdapUsers(
return fmt.Errorf("failed to convert LDAP data for %v: %w", rawUser["dn"], err) return fmt.Errorf("failed to convert LDAP data for %v: %w", rawUser["dn"], err)
} }
if provider.SyncLogUserInfo {
slog.Debug("ldap user data",
"raw-user", rawUser, "user", user.Identifier,
"is-admin", user.IsAdmin, "provider", provider.ProviderName)
}
existingUser, err := m.users.GetUser(ctx, user.Identifier) existingUser, err := m.users.GetUser(ctx, user.Identifier)
if err != nil && !errors.Is(err, domain.ErrNotFound) { if err != nil && !errors.Is(err, domain.ErrNotFound) {
return fmt.Errorf("find error for user id %s: %w", user.Identifier, err) return fmt.Errorf("find error for user id %s: %w", user.Identifier, err)

View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"fmt"
"log/slog" "log/slog"
"regexp" "regexp"
"time" "time"
@@ -125,6 +126,45 @@ type LdapFields struct {
GroupMembership string `yaml:"memberof"` GroupMembership string `yaml:"memberof"`
} }
// getMappingWithDefaults returns a full field mapping for the LDAP provider.
// If specific fields are not set, the default values are used.
func (f LdapFields) getMappingWithDefaults() LdapFields {
defaultMap := LdapFields{
BaseFields: BaseFields{
UserIdentifier: "mail",
Email: "mail",
Firstname: "givenName",
Lastname: "sn",
Phone: "telephoneNumber",
Department: "department",
},
GroupMembership: "memberOf",
}
if f.UserIdentifier != "" {
defaultMap.UserIdentifier = f.UserIdentifier
}
if f.Email != "" {
defaultMap.Email = f.Email
}
if f.Firstname != "" {
defaultMap.Firstname = f.Firstname
}
if f.Lastname != "" {
defaultMap.Lastname = f.Lastname
}
if f.Phone != "" {
defaultMap.Phone = f.Phone
}
if f.Department != "" {
defaultMap.Department = f.Department
}
if f.GroupMembership != "" {
defaultMap.GroupMembership = f.GroupMembership
}
return defaultMap
}
// LdapProvider contains the configuration for the LDAP connection. // LdapProvider contains the configuration for the LDAP connection.
type LdapProvider struct { type LdapProvider struct {
// ProviderName is an internal name that is used to distinguish LDAP servers. It must not contain spaces or special characters. // ProviderName is an internal name that is used to distinguish LDAP servers. It must not contain spaces or special characters.
@@ -168,6 +208,8 @@ type LdapProvider struct {
SyncFilter string `yaml:"sync_filter"` SyncFilter string `yaml:"sync_filter"`
// SyncInterval is the interval between consecutive LDAP user syncs. If it is 0, sync is disabled. // SyncInterval is the interval between consecutive LDAP user syncs. If it is 0, sync is disabled.
SyncInterval time.Duration `yaml:"sync_interval"` SyncInterval time.Duration `yaml:"sync_interval"`
// If SyncLogUserInfo is set to true, the user info retrieved from the LDAP provider during a sync-run will be logged in trace level.
SyncLogUserInfo bool `yaml:"sync_log_user_info"`
// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database. // If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
RegistrationEnabled bool `yaml:"registration_enabled"` RegistrationEnabled bool `yaml:"registration_enabled"`
@@ -176,6 +218,19 @@ type LdapProvider struct {
LogUserInfo bool `yaml:"log_user_info"` LogUserInfo bool `yaml:"log_user_info"`
} }
// Sanitize checks the LDAP configuration and sets default values for missing fields.
func (l *LdapProvider) Sanitize() error {
l.FieldMap = l.FieldMap.getMappingWithDefaults()
dn, err := ldap.ParseDN(l.AdminGroupDN)
if err != nil {
return fmt.Errorf("failed to parse admin group DN: %w", err)
}
l.ParsedAdminGroupDN = dn
return nil
}
// OpenIDConnectProvider contains the configuration for the OpenID Connect provider. // OpenIDConnectProvider contains the configuration for the OpenID Connect provider.
type OpenIDConnectProvider struct { type OpenIDConnectProvider struct {
// ProviderName is an internal name that is used to distinguish oauth endpoints. It must not contain spaces or special characters. // ProviderName is an internal name that is used to distinguish oauth endpoints. It must not contain spaces or special characters.

View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"strconv"
"strings"
"time" "time"
"github.com/a8m/envsubst" "github.com/a8m/envsubst"
@@ -114,82 +116,94 @@ func (c *Config) LogStartupValues() {
func defaultConfig() *Config { func defaultConfig() *Config {
cfg := &Config{} cfg := &Config{}
cfg.Core.AdminUserDisabled = false cfg.Core.AdminUserDisabled = getEnvBool("WG_PORTAL_CORE_DISABLE_ADMIN_USER", false)
cfg.Core.AdminUser = "admin@wgportal.local" cfg.Core.AdminUser = getEnvStr("WG_PORTAL_CORE_ADMIN_USER", "admin@wgportal.local")
cfg.Core.AdminPassword = "wgportal-default" cfg.Core.AdminPassword = getEnvStr("WG_PORTAL_CORE_ADMIN_PASSWORD", "wgportal-default")
cfg.Core.AdminApiToken = "" // by default, the API access is disabled cfg.Core.AdminApiToken = getEnvStr("WG_PORTAL_CORE_ADMIN_API_TOKEN", "") // by default, the API access is disabled
cfg.Core.ImportExisting = true cfg.Core.ImportExisting = getEnvBool("WG_PORTAL_CORE_IMPORT_EXISTING", true)
cfg.Core.RestoreState = true cfg.Core.RestoreState = getEnvBool("WG_PORTAL_CORE_RESTORE_STATE", true)
cfg.Core.CreateDefaultPeer = false cfg.Core.CreateDefaultPeer = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER", false)
cfg.Core.CreateDefaultPeerOnCreation = false cfg.Core.CreateDefaultPeerOnCreation = getEnvBool("WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION", false)
cfg.Core.EditableKeys = true cfg.Core.EditableKeys = getEnvBool("WG_PORTAL_CORE_EDITABLE_KEYS", true)
cfg.Core.SelfProvisioningAllowed = false cfg.Core.SelfProvisioningAllowed = getEnvBool("WG_PORTAL_CORE_SELF_PROVISIONING_ALLOWED", false)
cfg.Core.ReEnablePeerAfterUserEnable = true cfg.Core.ReEnablePeerAfterUserEnable = getEnvBool("WG_PORTAL_CORE_RE_ENABLE_PEER_AFTER_USER_ENABLE", true)
cfg.Core.DeletePeerAfterUserDeleted = false cfg.Core.DeletePeerAfterUserDeleted = getEnvBool("WG_PORTAL_CORE_DELETE_PEER_AFTER_USER_DELETED", false)
cfg.Database = DatabaseConfig{ cfg.Database = DatabaseConfig{
Type: "sqlite", Debug: getEnvBool("WG_PORTAL_DATABASE_DEBUG", false),
DSN: "data/sqlite.db", SlowQueryThreshold: getEnvDuration("WG_PORTAL_DATABASE_SLOW_QUERY_THRESHOLD", 0),
Type: SupportedDatabase(getEnvStr("WG_PORTAL_DATABASE_TYPE", "sqlite")),
DSN: getEnvStr("WG_PORTAL_DATABASE_DSN", "data/sqlite.db"),
EncryptionPassphrase: getEnvStr("WG_PORTAL_DATABASE_ENCRYPTION_PASSPHRASE", ""),
} }
cfg.Backend = Backend{ cfg.Backend = Backend{
Default: LocalBackendName, // local backend is the default (using wgcrtl) Default: LocalBackendName, // local backend is the default (using wgcrtl)
IgnoredLocalInterfaces: getEnvStrSlice("WG_PORTAL_BACKEND_IGNORED_LOCAL_INTERFACES", nil),
// Most resolconf implementations use "tun." as a prefix for interface names. // Most resolconf implementations use "tun." as a prefix for interface names.
// But systemd's implementation uses no prefix, for example. // But systemd's implementation uses no prefix, for example.
LocalResolvconfPrefix: "tun.", LocalResolvconfPrefix: getEnvStr("WG_PORTAL_BACKEND_LOCAL_RESOLVCONF_PREFIX", "tun."),
} }
cfg.Web = WebConfig{ cfg.Web = WebConfig{
RequestLogging: false, RequestLogging: getEnvBool("WG_PORTAL_WEB_REQUEST_LOGGING", false),
ExternalUrl: "http://localhost:8888", ExposeHostInfo: getEnvBool("WG_PORTAL_WEB_EXPOSE_HOST_INFO", false),
ListeningAddress: ":8888", ExternalUrl: getEnvStr("WG_PORTAL_WEB_EXTERNAL_URL", "http://localhost:8888"),
SessionIdentifier: "wgPortalSession", ListeningAddress: getEnvStr("WG_PORTAL_WEB_LISTENING_ADDRESS", ":8888"),
SessionSecret: "very_secret", SessionIdentifier: getEnvStr("WG_PORTAL_WEB_SESSION_IDENTIFIER", "wgPortalSession"),
CsrfSecret: "extremely_secret", SessionSecret: getEnvStr("WG_PORTAL_WEB_SESSION_SECRET", "very_secret"),
SiteTitle: "WireGuard Portal", CsrfSecret: getEnvStr("WG_PORTAL_WEB_CSRF_SECRET", "extremely_secret"),
SiteCompanyName: "WireGuard Portal", SiteTitle: getEnvStr("WG_PORTAL_WEB_SITE_TITLE", "WireGuard Portal"),
SiteCompanyName: getEnvStr("WG_PORTAL_WEB_SITE_COMPANY_NAME", "WireGuard Portal"),
CertFile: getEnvStr("WG_PORTAL_WEB_CERT_FILE", ""),
KeyFile: getEnvStr("WG_PORTAL_WEB_KEY_FILE", ""),
} }
cfg.Advanced.LogLevel = "info" cfg.Advanced.LogLevel = getEnvStr("WG_PORTAL_ADVANCED_LOG_LEVEL", "info")
cfg.Advanced.StartListenPort = 51820 cfg.Advanced.LogPretty = getEnvBool("WG_PORTAL_ADVANCED_LOG_PRETTY", false)
cfg.Advanced.StartCidrV4 = "10.11.12.0/24" cfg.Advanced.LogJson = getEnvBool("WG_PORTAL_ADVANCED_LOG_JSON", false)
cfg.Advanced.StartCidrV6 = "fdfd:d3ad:c0de:1234::0/64" cfg.Advanced.StartListenPort = getEnvInt("WG_PORTAL_ADVANCED_START_LISTEN_PORT", 51820)
cfg.Advanced.UseIpV6 = true cfg.Advanced.StartCidrV4 = getEnvStr("WG_PORTAL_ADVANCED_START_CIDR_V4", "10.11.12.0/24")
cfg.Advanced.ExpiryCheckInterval = 15 * time.Minute cfg.Advanced.StartCidrV6 = getEnvStr("WG_PORTAL_ADVANCED_START_CIDR_V6", "fdfd:d3ad:c0de:1234::0/64")
cfg.Advanced.RulePrioOffset = 20000 cfg.Advanced.UseIpV6 = getEnvBool("WG_PORTAL_ADVANCED_USE_IP_V6", true)
cfg.Advanced.RouteTableOffset = 20000 cfg.Advanced.ConfigStoragePath = getEnvStr("WG_PORTAL_ADVANCED_CONFIG_STORAGE_PATH", "")
cfg.Advanced.ApiAdminOnly = true cfg.Advanced.ExpiryCheckInterval = getEnvDuration("WG_PORTAL_ADVANCED_EXPIRY_CHECK_INTERVAL", 15*time.Minute)
cfg.Advanced.LimitAdditionalUserPeers = 0 cfg.Advanced.RulePrioOffset = getEnvInt("WG_PORTAL_ADVANCED_RULE_PRIO_OFFSET", 20000)
cfg.Advanced.RouteTableOffset = getEnvInt("WG_PORTAL_ADVANCED_ROUTE_TABLE_OFFSET", 20000)
cfg.Advanced.ApiAdminOnly = getEnvBool("WG_PORTAL_ADVANCED_API_ADMIN_ONLY", true)
cfg.Advanced.LimitAdditionalUserPeers = getEnvInt("WG_PORTAL_ADVANCED_LIMIT_ADDITIONAL_USER_PEERS", 0)
cfg.Statistics.UsePingChecks = true cfg.Statistics.UsePingChecks = getEnvBool("WG_PORTAL_STATISTICS_USE_PING_CHECKS", true)
cfg.Statistics.PingCheckWorkers = 10 cfg.Statistics.PingCheckWorkers = getEnvInt("WG_PORTAL_STATISTICS_PING_CHECK_WORKERS", 10)
cfg.Statistics.PingUnprivileged = false cfg.Statistics.PingUnprivileged = getEnvBool("WG_PORTAL_STATISTICS_PING_UNPRIVILEGED", false)
cfg.Statistics.PingCheckInterval = 1 * time.Minute cfg.Statistics.PingCheckInterval = getEnvDuration("WG_PORTAL_STATISTICS_PING_CHECK_INTERVAL", 1*time.Minute)
cfg.Statistics.DataCollectionInterval = 1 * time.Minute cfg.Statistics.DataCollectionInterval = getEnvDuration("WG_PORTAL_STATISTICS_DATA_COLLECTION_INTERVAL",
cfg.Statistics.CollectInterfaceData = true 1*time.Minute)
cfg.Statistics.CollectPeerData = true cfg.Statistics.CollectInterfaceData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_INTERFACE_DATA", true)
cfg.Statistics.CollectAuditData = true cfg.Statistics.CollectPeerData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_PEER_DATA", true)
cfg.Statistics.ListeningAddress = ":8787" cfg.Statistics.CollectAuditData = getEnvBool("WG_PORTAL_STATISTICS_COLLECT_AUDIT_DATA", true)
cfg.Statistics.ListeningAddress = getEnvStr("WG_PORTAL_STATISTICS_LISTENING_ADDRESS", ":8787")
cfg.Mail = MailConfig{ cfg.Mail = MailConfig{
Host: "127.0.0.1", Host: getEnvStr("WG_PORTAL_MAIL_HOST", "127.0.0.1"),
Port: 25, Port: getEnvInt("WG_PORTAL_MAIL_PORT", 25),
Encryption: MailEncryptionNone, Encryption: MailEncryption(getEnvStr("WG_PORTAL_MAIL_ENCRYPTION", string(MailEncryptionNone))),
CertValidation: true, CertValidation: getEnvBool("WG_PORTAL_MAIL_CERT_VALIDATION", true),
Username: "", Username: getEnvStr("WG_PORTAL_MAIL_USERNAME", ""),
Password: "", Password: getEnvStr("WG_PORTAL_MAIL_PASSWORD", ""),
AuthType: MailAuthPlain, AuthType: MailAuthType(getEnvStr("WG_PORTAL_MAIL_AUTH_TYPE", string(MailAuthPlain))),
From: "Wireguard Portal <noreply@wireguard.local>", From: getEnvStr("WG_PORTAL_MAIL_FROM", "Wireguard Portal <noreply@wireguard.local>"),
LinkOnly: false, LinkOnly: getEnvBool("WG_PORTAL_MAIL_LINK_ONLY", false),
AllowPeerEmail: getEnvBool("WG_PORTAL_MAIL_ALLOW_PEER_EMAIL", false),
} }
cfg.Webhook.Url = "" // no webhook by default cfg.Webhook.Url = getEnvStr("WG_PORTAL_WEBHOOK_URL", "") // no webhook by default
cfg.Webhook.Authentication = "" cfg.Webhook.Authentication = getEnvStr("WG_PORTAL_WEBHOOK_AUTHENTICATION", "")
cfg.Webhook.Timeout = 10 * time.Second cfg.Webhook.Timeout = getEnvDuration("WG_PORTAL_WEBHOOK_TIMEOUT", 10*time.Second)
cfg.Auth.WebAuthn.Enabled = true cfg.Auth.WebAuthn.Enabled = getEnvBool("WG_PORTAL_AUTH_WEBAUTHN_ENABLED", true)
cfg.Auth.MinPasswordLength = 16 cfg.Auth.MinPasswordLength = getEnvInt("WG_PORTAL_AUTH_MIN_PASSWORD_LENGTH", 16)
cfg.Auth.HideLoginForm = false cfg.Auth.HideLoginForm = getEnvBool("WG_PORTAL_AUTH_HIDE_LOGIN_FORM", false)
return cfg return cfg
} }
@@ -222,6 +236,11 @@ func GetConfig() (*Config, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
for i := range cfg.Auth.Ldap {
if err := cfg.Auth.Ldap[i].Sanitize(); err != nil {
return nil, fmt.Errorf("sanitizing of ldap config for %s failed: %w", cfg.Auth.Ldap[i].ProviderName, err)
}
}
return cfg, nil return cfg, nil
} }
@@ -244,3 +263,75 @@ func loadConfigFile(cfg any, filename string) error {
return nil return nil
} }
func getEnvStr(name, fallback string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
return fallback
}
func getEnvStrSlice(name string, fallback []string) []string {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
strParts := strings.Split(v, ",")
stringSlice := make([]string, 0, len(strParts))
for _, s := range strParts {
trimmed := strings.TrimSpace(s)
if trimmed != "" {
stringSlice = append(stringSlice, trimmed)
}
}
return stringSlice
}
func getEnvBool(name string, fallback bool) bool {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
b, err := strconv.ParseBool(v)
if err != nil {
slog.Warn("invalid bool env, using fallback", "env", name, "value", v, "fallback", fallback)
return fallback
}
return b
}
func getEnvInt(name string, fallback int) int {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
i, err := strconv.Atoi(v)
if err != nil {
slog.Warn("invalid int env, using fallback", "env", name, "value", v, "fallback", fallback)
return fallback
}
return i
}
func getEnvDuration(name string, fallback time.Duration) time.Duration {
v, ok := os.LookupEnv(name)
if !ok {
return fallback
}
d, err := time.ParseDuration(v)
if err != nil {
slog.Warn("invalid duration env, using fallback", "env", name, "value", v, "fallback", fallback)
return fallback
}
return d
}