diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index c89c1d2..6b76124 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [master] push: - branches: [master, stable, legacy] + branches: [master] # Publish vX.X.X tags as releases. tags: ["v*.*.*"] @@ -64,12 +64,12 @@ jobs: # major and major.minor tags are not available for alpha or beta releases type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} # add v{{major}} tag, even for beta or release-canidate releases type=match,pattern=(v\d),group=1,enable=${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }} # add {{major}} tag, even for beta releases or release-canidate releases type=match,pattern=v(\d),group=1,enable=${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }} - # set latest tag for default branch - type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Docker image uses: docker/build-push-action@v6 diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index cbec92b..b148619 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -27,7 +27,14 @@ jobs: run: pip install mike mkdocs-material[imaging] mkdocs-minify-plugin mkdocs-swagger-ui-tag - name: Publish documentation - run: mike deploy --push --update-aliases ${{ github.ref_name }} latest + if: ${{ ! startsWith(github.ref, 'refs/tags/') }} + run: mike deploy --push ${{ github.ref_name }} env: GIT_COMMITTER_NAME: "github-actions[bot]" GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com" + - name: Publish latest documentation + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: mike deploy --push --update-aliases ${{ github.ref_name }} latest + env: + GIT_COMMITTER_NAME: "github-actions[bot]" + GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com" \ No newline at end of file diff --git a/docs/documentation/configuration/examples.md b/docs/documentation/configuration/examples.md index 83571dd..1409d2b 100644 --- a/docs/documentation/configuration/examples.md +++ b/docs/documentation/configuration/examples.md @@ -72,7 +72,8 @@ auth: auth: 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. - id: azure provider_name: azure display_name: Login withEntra ID @@ -80,6 +81,8 @@ auth: base_url: "https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0" client_id: "${AZURE_CLIENT_ID}" client_secret: "${AZURE_CLIENT_SECRET}" + allowed_domains: + - "outlook.com" extra_scopes: - profile - email diff --git a/docs/documentation/configuration/overview.md b/docs/documentation/configuration/overview.md index 27d4309..bc47ba0 100644 --- a/docs/documentation/configuration/overview.md +++ b/docs/documentation/configuration/overview.md @@ -368,6 +368,10 @@ Below are the properties for each OIDC provider entry inside `auth.oidc`: - **Default:** *(empty)* - **Description:** A list of additional OIDC scopes (e.g., `profile`, `email`). +#### `allowed_domains` +- **Default:** *(empty)* +- **Description:** A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups. + #### `field_map` - **Default:** *(empty)* - **Description:** Maps OIDC claims to WireGuard Portal user fields. @@ -437,6 +441,10 @@ Below are the properties for each OAuth provider entry inside `auth.oauth`: - **Default:** *(empty)* - **Description:** A list of OAuth scopes. +#### `allowed_domains` +- **Default:** *(empty)* +- **Description:** A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups. + #### `field_map` - **Default:** *(empty)* - **Description:** Maps OAuth attributes to WireGuard Portal fields. diff --git a/docs/documentation/getting-started/docker.md b/docs/documentation/getting-started/docker.md index da1b63a..091aaf7 100644 --- a/docs/documentation/getting-started/docker.md +++ b/docs/documentation/getting-started/docker.md @@ -110,31 +110,33 @@ WireGuard Portal supports managing WireGuard interfaces through three distinct d ## Image Versioning All images are hosted on Docker Hub at [https://hub.docker.com/r/wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal) or in the [GitHub Container Registry](https://github.com/h44z/wg-portal/pkgs/container/wg-portal). + +Version **2** is the current stable release. Version **1** has moved to legacy status and is no longer recommended. + There are three types of tags in the repository: #### Semantic versioned tags For example, `2.0.0-rc.1` or `v2.0.0-rc.1`. -These are official releases of WireGuard Portal. They correspond to the GitHub tags that we make, and you can see the release notes for them here: [https://github.com/h44z/wg-portal/releases](https://github.com/h44z/wg-portal/releases). +These are official releases of WireGuard Portal. For production deployments of WireGuard Portal, we strongly recommend using one of these versioned tags instead of the latest or canary tags. -Once these tags show up in this repository, they will never change. +There are different types of these tags: -For production deployments of WireGuard Portal, we strongly recommend using one of these tags, e.g. `wgportal/wg-portal:2.0.0`, instead of the latest or canary tags. + - Major version tags: `v2` or `2`. These tags always refer to the latest image for WireGuard Portal version **2**. + - Minor version tags: `v2.x` or `2.0`. These tags always refer to the latest image for WireGuard Portal version **2.x**. + - Specific version tags (patch version): `v2.0.0` or `2.0.0`. These tags denote a very specific release. They correspond to the GitHub tags that we make, and you can see the release notes for them here: [https://github.com/h44z/wg-portal/releases](https://github.com/h44z/wg-portal/releases). Once these tags for a specific version show up in the Docker repository, they will never change. -If you only want to stay at the same major or major+minor version, use either `v[MAJOR]` or `[MAJOR].[MINOR]` tags. For example `v2` or `2.0`. +#### The `latest` tag -Version **2** is the current stable release. Version **1** has moved to legacy status and is no longer recommended. +The lastest tag is the latest stable release of WireGuard Portal. For version **2**, this is the same as the `v2` tag. -#### latest +#### The `master` tag -This is the most recent build to master! It changes a lot and is very unstable. +This is the most recent build to the main branch! It changes a lot and is very unstable. -We recommend that you don't use it except for development purposes. +We recommend that you don't use it except for development purposes or to test the latest features. -#### Branch tags - -For each commit in the master and the stable branch, a corresponding Docker image is build. These images use the `master` or `stable` tags. ## Configuration diff --git a/docs/documentation/rest-api/swagger.yaml b/docs/documentation/rest-api/swagger.yaml index 46c00ae..fb6cd00 100644 --- a/docs/documentation/rest-api/swagger.yaml +++ b/docs/documentation/rest-api/swagger.yaml @@ -692,6 +692,7 @@ paths: tags: - Interfaces 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, ...). operationId: interfaces_handleUpdatePut parameters: - description: The interface identifier. @@ -739,6 +740,7 @@ paths: - Interfaces /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, ...). operationId: interfaces_handleCreatePost parameters: - description: The interface data. @@ -779,6 +781,34 @@ paths: summary: Create a new interface record. tags: - Interfaces + /interface/prepare: + get: + description: This endpoint returns a new interface with default values (fresh key pair, valid name, new IP address pool, ...). + operationId: interfaces_handlePrepareGet + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Interface' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/models.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/models.Error' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/models.Error' + security: + - BasicAuth: [] + summary: Prepare a new interface record. + tags: + - Interfaces /metrics/by-interface/{id}: get: operationId: metrics_handleMetricsForInterfaceGet @@ -967,7 +997,7 @@ paths: tags: - Peers put: - description: Only admins can update existing records. + description: Only admins can update existing records. The peer record must contain all required fields (e.g., public key, allowed IPs). operationId: peers_handleUpdatePut parameters: - description: The peer identifier. @@ -1078,7 +1108,7 @@ paths: - Peers /peer/new: post: - description: Only admins can create new records. + description: Only admins can create new records. The peer record must contain all required fields (e.g., public key, allowed IPs). operationId: peers_handleCreatePost parameters: - description: The peer data. @@ -1119,6 +1149,48 @@ paths: summary: Create a new peer record. tags: - Peers + /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. + operationId: peers_handlePrepareGet + parameters: + - description: The interface identifier. + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Peer' + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.Error' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/models.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/models.Error' + "404": + description: Not Found + schema: + $ref: '#/definitions/models.Error' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/models.Error' + security: + - BasicAuth: [] + summary: Prepare a new peer record for the given WireGuard interface. + tags: + - Peers /provisioning/data/peer-config: get: description: Normal users can only access their own record. Admins can access all records. diff --git a/frontend/index.html b/frontend/index.html index b667f8d..fd8033f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -24,7 +24,7 @@
- + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 53a4b7c..cffbb8a 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -140,6 +140,7 @@ const currentYear = ref(new Date().getFullYear()) - + + diff --git a/frontend/src/lang/translations/pt.json b/frontend/src/lang/translations/pt.json index a36440f..a895400 100644 --- a/frontend/src/lang/translations/pt.json +++ b/frontend/src/lang/translations/pt.json @@ -20,14 +20,14 @@ "delete": "Eliminar" }, "login": { - "headline": "Por favor, inicie sessão", + "headline": "Por favor, inicie a sessão", "username": { "label": "Nome de utilizador", - "placeholder": "Introduza o seu nome de utilizador" + "placeholder": "Por favor, insira o seu nome de utilizador" }, "password": { "label": "Palavra-passe", - "placeholder": "Introduza a sua palavra-passe" + "placeholder": "Por favor, insira a sua palavra-passe" }, "button": "Iniciar sessão" }, @@ -40,143 +40,513 @@ "settings": "Definições", "audit": "Registo de Auditoria", "login": "Iniciar Sessão", - "logout": "Terminar Sessão" + "logout": "Terminar Sessão", + "keygen": "Gerador de Chave" }, "home": { - "title": "Início", - "card": { - "interfaces": "Interfaces", - "users": "Utilizadores" + "headline": "WireGuard® Portal VPN", + "info-headline": "Mais Informações", + "abstract": "WireGuard® é uma VPN extremamente simples, mas rápida e moderna que utiliza criptografia de última geração. O seu objetivo é ser mais rápida, simples, leve e útil que o IPsec, enquanto evita grandes dores de cabeça. Pretende ser consideravelmente mais eficiente que o OpenVPN.", + "installation": { + "box-header": "Instalação do WireGuard", + "headline": "Instalação", + "content": "As instruções de instalação para o software cliente podem ser encontradas no site oficial do WireGuard.", + "button": "Abrir Instruções" + }, + "about-wg": { + "box-header": "Sobre o WireGuard", + "headline": "Sobre", + "content": "WireGuard® é uma VPN extremamente simples, mas rápida e moderna que utiliza criptografia de última geração.", + "button": "Mais" + }, + "about-portal": { + "box-header": "Sobre o WireGuard Portal", + "headline": "WireGuard Portal", + "content": "WireGuard Portal é um portal web de configuração simples para o WireGuard.", + "button": "Mais" + }, + "profiles": { + "headline": "Perfis VPN", + "abstract": "Pode aceder e baixar as suas configurações pessoais de VPN através do seu Perfil de Utilizador.", + "content": "Para encontrar todos os seus perfis configurados, clique no botão abaixo.", + "button": "Abrir meu perfil" + }, + "admin": { + "headline": "Área de Administração", + "abstract": "Na área de administração, pode gerir os peers do WireGuard, a interface do servidor e os utilizadores que têm permissão para aceder ao Portal WireGuard.", + "content": "", + "button-admin": "Abrir Administração do Servidor", + "button-user": "Abrir Administração de Utilizadores" } }, "interfaces": { - "title": "Interfaces", - "create": "Criar Interface", - "name": "Nome", - "address": "Endereço", - "listen-port": "Porta de Escuta", - "public-key": "Chave Pública", - "private-key": "Chave Privada", - "actions": "Ações", - "delete-dialog": { - "title": "Eliminar Interface", - "text": "Tem a certeza de que deseja eliminar a interface '{{name}}'?" + "headline": "Administração de Interfaces", + "headline-peers": "Peers VPN Atuais", + "headline-endpoints": "Endpoints Atuais", + "no-interface": { + "default-selection": "Nenhuma interface disponível", + "headline": "Nenhuma interface encontrada...", + "abstract": "Clique no botão + acima para criar uma nova interface WireGuard." }, - "form": { - "name": { - "label": "Nome", - "placeholder": "Introduza um nome exclusivo para a interface" - }, - "address": { - "label": "Endereço", - "placeholder": "Introduza um endereço válido (ex: 10.0.0.1/24)" - }, - "listen-port": { - "label": "Porta de Escuta", - "placeholder": "Introduza a porta onde o WireGuard irá escutar (ex: 51820)" - }, - "private-key": { - "label": "Chave Privada", - "placeholder": "Será gerada automaticamente se não for fornecida" - } - } + "no-peer": { + "headline": "Nenhum peer disponível", + "abstract": "Atualmente, não há peers disponíveis para a interface WireGuard selecionada." + }, + "table-heading": { + "name": "Nome", + "user": "Utilizador", + "ip": "IPs", + "endpoint": "Endpoint", + "status": "Status" + }, + "interface": { + "headline": "Status da interface para", + "mode": "modo", + "key": "Chave Pública", + "endpoint": "Endpoint Público", + "port": "Porta de Escuta", + "peers": "Peers Ativados", + "total-peers": "Total de Peers", + "endpoints": "Endpoints Ativados", + "total-endpoints": "Total de Endpoints", + "ip": "Endereço IP", + "default-allowed-ip": "IPs permitidos por padrão", + "dns": "Servidores DNS", + "mtu": "MTU", + "default-keep-alive": "Intervalo de Keepalive Padrão", + "button-show-config": "Mostrar configuração", + "button-download-config": "Baixar configuração", + "button-store-config": "Armazenar configuração para wg-quick", + "button-edit": "Editar interface" + }, + "button-add-interface": "Adicionar Interface", + "button-add-peer": "Adicionar Peer", + "button-add-peers": "Adicionar Vários Peers", + "button-show-peer": "Mostrar Peer", + "button-edit-peer": "Editar Peer", + "peer-disabled": "Peer desativado, razão:", + "peer-expiring": "Peer expira em", + "peer-connected": "Conectado", + "peer-not-connected": "Não Conectado", + "peer-handshake": "Último handshake:" }, "users": { - "title": "Utilizadores", - "create": "Criar Utilizador", - "name": "Nome", - "email": "Email", - "enabled": "Ativo", - "is-admin": "Administrador", - "actions": "Ações", - "edit": "Editar", - "delete-dialog": { - "title": "Eliminar Utilizador", - "text": "Tem a certeza de que deseja eliminar o utilizador '{{nome}}'?" + "headline": "Administração de Utilizadores", + "table-heading": { + "id": "ID", + "email": "E-Mail", + "firstname": "Primeiro Nome", + "lastname": "Último Nome", + "source": "Fonte", + "peers": "Peers", + "admin": "Administrador" }, - "form": { - "name": { - "label": "Nome", - "placeholder": "Introduza o nome do utilizador" + "no-user": { + "headline": "Nenhum utilizador disponível", + "abstract": "Atualmente, não há utilizadores registados no Portal WireGuard." + }, + "button-add-user": "Adicionar Utilizador", + "button-show-user": "Mostrar Utilizador", + "button-edit-user": "Editar Utilizador", + "user-disabled": "Utilizador desativado, razão:", + "user-locked": "Conta bloqueada, razão:", + "admin": "O utilizador tem privilégios de administrador", + "no-admin": "O utilizador não tem privilégios de administrador" + }, + "profile": { + "headline": "Os Meus Peers VPN", + "table-heading": { + "name": "Nome", + "ip": "IPs", + "stats": "Status", + "interface": "Interface do Servidor" + }, + "no-peer": { + "headline": "Nenhum peer disponível", + "abstract": "Atualmente, não há peers associados ao seu perfil de utilizador." + }, + "peer-connected": "Conectado", + "button-add-peer": "Adicionar Peer", + "button-show-peer": "Mostrar Peer", + "button-edit-peer": "Editar Peer" + }, + "settings": { + "headline": "Definições", + "abstract": "Aqui pode alterar suas Definições pessoais.", + "api": { + "headline": "Definições da API", + "abstract": "Aqui pode configurar as definições da API RESTful.", + "active-description": "A API está atualmente ativa para a sua conta de utilizador. Todos os pedidos para a API são autenticadas com Basic Auth. Use as seguintes credenciais para autenticação.", + "inactive-description": "A API está atualmente inativa. Pressione o botão abaixo para ativá-la.", + "user-label": "Nome de utilizador API:", + "user-placeholder": "O utilizador da API", + "token-label": "Senha da API:", + "token-placeholder": "O token da API", + "token-created-label": "Acesso API concedido em: ", + "button-disable-title": "Desativar API, invalidando o token atual.", + "button-disable-text": "Desativar API", + "button-enable-title": "Ativar API, gerando um novo token.", + "button-enable-text": "Ativar API", + "api-link": "Documentação da API" + } + }, + "audit": { + "headline": "Registo de Auditoria", + "abstract": "Aqui pode encontrar o registo de auditoria de todas as ações realizadas no WireGuard Portal.", + "no-entries": { + "headline": "Nenhuma entrada no registo", + "abstract": "Atualmente, não há entradas de registo de auditoria gravadas." + }, + "entries-headline": "Entradas do Registo", + "table-heading": { + "id": "#", + "time": "Hora", + "user": "Utilizador", + "severity": "Gravidade", + "origin": "Origem", + "message": "Mensagem" + } + }, + "keygen": { + "headline": "Gerador de Chaves WireGuard", + "abstract": "Gere novas chaves WireGuard. As chaves são geradas no seu browser e nunca são enviadas para o servidor.", + "headline-keypair": "Novo Par de Chaves", + "headline-preshared-key": "Nova Chave Pré-Partilhada", + "button-generate": "Gerar", + "private-key": { + "label": "Chave Privada", + "placeholder": "A chave privada" + }, + "public-key": { + "label": "Chave Pública", + "placeholder": "A chave pública" + }, + "preshared-key": { + "label": "Chave Pré-Partilhada", + "placeholder": "A chave pré-partilhada" + } + }, + "modals": { + "user-view": { + "headline": "Conta de Utilizador:", + "tab-user": "Informação", + "tab-peers": "Peers", + "headline-info": "Informação do Utilizador:", + "headline-notes": "Notas:", + "email": "E-Mail", + "firstname": "Primeiro Nome", + "lastname": "Último Nome", + "phone": "Número de Telefone", + "department": "Departamento", + "api-enabled": "Acesso API", + "disabled": "Conta Desativada", + "locked": "Conta Bloqueada", + "no-peers": "O utilizador não tem peers associados.", + "peers": { + "name": "Nome", + "interface": "Interface", + "ip": "IP's" + } + }, + "user-edit": { + "headline-edit": "Editar utilizador:", + "headline-new": "Novo utilizador", + "header-general": "Geral", + "header-personal": "Informação do Utilizador", + "header-notes": "Notas", + "header-state": "Estado", + "identifier": { + "label": "Identificador", + "placeholder": "O identificador único do utilizador" }, - "email": { - "label": "Email", - "placeholder": "Introduza o email do utilizador" + "source": { + "label": "Fonte", + "placeholder": "A fonte do utilizador" }, "password": { "label": "Palavra-passe", - "placeholder": "Deixe em branco para manter a atual" + "placeholder": "Uma palavra-passe super secreta", + "description": "Deixe este campo em branco para manter a palavra-passe atual." }, - "is-admin": { - "label": "Administrador" + "email": { + "label": "Email", + "placeholder": "O endereço de e-mail" }, - "enabled": { - "label": "Ativo" + "phone": { + "label": "Telefone", + "placeholder": "O número de telefone" + }, + "department": { + "label": "Departamento", + "placeholder": "O departamento" + }, + "firstname": { + "label": "Primeiro Nome", + "placeholder": "Primeiro Nome" + }, + "lastname": { + "label": "Último Nome", + "placeholder": "Último Nome" + }, + "notes": { + "label": "Notas", + "placeholder": "" + }, + "disabled": { + "label": "Desativado (sem conexão WireGuard e login possível)" + }, + "locked": { + "label": "Bloqueado (sem login possível, as conexões WireGuard ainda funcionam)" + }, + "admin": { + "label": "É Administrador" } - } - }, - "peers": { - "title": "Peers", - "create": "Criar Peer", - "public-key": "Chave Pública", - "preshared-key": "Chave Pré-partilhada", - "endpoint": "Endpoint", - "allowed-ips": "IPs Permitidos", - "latest-handshake": "Último Handshake", - "transfer-rx": "Recebido", - "transfer-tx": "Enviado", - "persistent-keepalive": "Keepalive Persistente", - "actions": "Ações", - "edit": "Editar", - "delete-dialog": { - "title": "Eliminar Peer", - "text": "Tem a certeza de que deseja eliminar este peer?" }, - "form": { + "interface-view": { + "headline": "Configuração para a Interface:" + }, + "interface-edit": { + "headline-edit": "Editar Interface:", + "headline-new": "Nova Interface", + "tab-interface": "Interface", + "tab-peerdef": "Padrões de Peer", + "header-general": "Geral", + "header-network": "Rede", + "header-crypto": "Criptografia", + "header-hooks": "Hooks da Interface", + "header-peer-hooks": "Hooks", + "header-state": "Estado", + "identifier": { + "label": "Identificador", + "placeholder": "O identificador único da interface" + }, + "mode": { + "label": "Modo da Interface", + "server": "Modo Servidor", + "client": "Modo Cliente", + "any": "Modo Desconhecido" + }, + "display-name": { + "label": "Nome de Exibição", + "placeholder": "O nome descritivo para a interface" + }, + "private-key": { + "label": "Chave Privada", + "placeholder": "A chave privada" + }, "public-key": { "label": "Chave Pública", - "placeholder": "Introduza a chave pública do peer" + "placeholder": "A chave pública" + }, + "ip": { + "label": "Endereços IP", + "placeholder": "Endereços IP (formato CIDR)" + }, + "listen-port": { + "label": "Porta de Escuta", + "placeholder": "A porta de escuta" + }, + "dns": { + "label": "Servidor DNS", + "placeholder": "Os servidores DNS que devem ser usados" + }, + "dns-search": { + "label": "Domínios de Pesquisa DNS", + "placeholder": "Prefixos de pesquisa DNS" + }, + "mtu": { + "label": "MTU", + "placeholder": "O MTU da interface (0 = manter o valor padrão)" + }, + "firewall-mark": { + "label": "Marca de Firewall", + "placeholder": "Marca de firewall aplicada ao tráfego de saída. (0 = automático)" + }, + "routing-table": { + "label": "Tabela de Roteamento", + "placeholder": "O ID da tabela de roteamento", + "description": "Casos especiais: off = não gerenciar rotas, 0 = automático" + }, + "pre-up": { + "label": "Pre-Up", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "post-up": { + "label": "Post-Up", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "pre-down": { + "label": "Pre-Down", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "post-down": { + "label": "Post-Down", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "disabled": { + "label": "Interface Desativada" + }, + "save-config": { + "label": "Guardar configuração wg-quick automaticamente" + }, + "defaults": { + "endpoint": { + "label": "Endereço do Endpoint", + "placeholder": "Endereço do Endpoint", + "description": "O endereço do endpoint ao qual os peers se irão conectar. (ex. wg.exemplo.com ou wg.exemplo.com:51820)" + }, + "networks": { + "label": "Redes IP", + "placeholder": "Endereços de Rede", + "description": "Os peers irão obter endereços IP a partir dessas sub-redes." + }, + "allowed-ip": { + "label": "Endereços IP Permitidos", + "placeholder": "Endereços IP Permitidos por padrão" + }, + "mtu": { + "label": "MTU", + "placeholder": "O MTU do cliente (0 = manter o valor padrão)" + }, + "keep-alive": { + "label": "Intervalo de Keep Alive", + "placeholder": "Keepalive persistente (0 = padrão)" + } + }, + "button-apply-defaults": "Aplicar Padrões de Peer" + }, + "peer-view": { + "headline-peer": "Peer:", + "headline-endpoint": "Endpoint:", + "section-info": "Informação do Peer", + "section-status": "Estado Atual", + "section-config": "Configuração", + "identifier": "Identificador", + "ip": "Endereços IP", + "user": "Utilizador Associado", + "notes": "Notas", + "expiry-status": "Expira em", + "disabled-status": "Desativado em", + "traffic": "Tráfego", + "connection-status": "Estatísticas de Conexão", + "upload": "Bytes Enviados (do Servidor para o Peer)", + "download": "Bytes Recebidos (do Peer para o Servidor)", + "pingable": "É Pingável", + "handshake": "Último Handshake", + "connected-since": "Conectado desde", + "endpoint": "Endpoint", + "button-download": "Baixar configuração", + "button-email": "Enviar configuração por E-Mail" + }, + "peer-edit": { + "headline-edit-peer": "Editar peer:", + "headline-edit-endpoint": "Editar endpoint:", + "headline-new-peer": "Criar peer", + "headline-new-endpoint": "Criar endpoint", + "header-general": "Geral", + "header-network": "Rede", + "header-crypto": "Criptografia", + "header-hooks": "Hooks (Executados no Peer)", + "header-state": "Estado", + "display-name": { + "label": "Nome de Exibição", + "placeholder": "O nome descritivo para o peer" + }, + "linked-user": { + "label": "Utilizador Associado", + "placeholder": "A conta de utilizador que possui este peer" + }, + "private-key": { + "label": "Chave Privada", + "placeholder": "A chave privada", + "help": "A chave privada é armazenada de forma segura no servidor. Se o utilizador já tiver uma cópia, pode omitir este campo. O servidor ainda funciona exclusivamente com a chave pública do peer." + }, + "public-key": { + "label": "Chave Pública", + "placeholder": "A chave pública" }, "preshared-key": { - "label": "Chave Pré-partilhada", - "placeholder": "Opcional: Chave partilhada adicional para maior segurança" + "label": "Chave Pré-Partilhada", + "placeholder": "Chave pré-partilhada opcional" + }, + "endpoint-public-key": { + "label": "Chave Pública do Endpoint", + "placeholder": "A chave pública do endpoint remoto" }, "endpoint": { - "label": "Endpoint", - "placeholder": "Endereço público do peer (ex: 1.2.3.4:51820)" + "label": "Endereço do Endpoint", + "placeholder": "O endereço do endpoint remoto" }, - "allowed-ips": { - "label": "IPs Permitidos", - "placeholder": "Lista de IPs (ex: 10.0.0.2/32, 192.168.1.0/24)" + "ip": { + "label": "Endereços IP", + "placeholder": "Endereços IP (formato CIDR)" }, - "persistent-keepalive": { - "label": "Keepalive Persistente", - "placeholder": "Ex: 25 (em segundos)" + "allowed-ip": { + "label": "Endereços IP Permitidos", + "placeholder": "Endereços IP permitidos" + }, + "extra-allowed-ip": { + "label": "Endereços IP adicionais permitidos", + "placeholder": "IPs adicionais permitidos (lado do servidor)", + "description": "Esses IPs serão adicionados à interface WireGuard remota como IPs permitidos." + }, + "dns": { + "label": "Servidor DNS", + "placeholder": "Os servidores DNS que devem ser utilizados" + }, + "dns-search": { + "label": "Domínios de Pesquisa DNS", + "placeholder": "Prefixos de pesquisa DNS" + }, + "keep-alive": { + "label": "Intervalo de Keep Alive", + "placeholder": "Keepalive persistente (0 = padrão)" + }, + "mtu": { + "label": "MTU", + "placeholder": "O MTU do cliente (0 = manter o padrão)" + }, + "pre-up": { + "label": "Pre-Up", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "post-up": { + "label": "Post-Up", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "pre-down": { + "label": "Pre-Down", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "post-down": { + "label": "Post-Down", + "placeholder": "Um ou vários comandos bash separados por ;" + }, + "disabled": { + "label": "Peer Desativado" + }, + "ignore-global": { + "label": "Ignorar definições globais" + }, + "expires-at": { + "label": "Data de expiração" + } + }, + "peer-multi-create": { + "headline-peer": "Criar múltiplos peers", + "headline-endpoint": "Criar múltiplos endpoints", + "identifiers": { + "label": "Identificadores de utilizador", + "placeholder": "Identificadores de utilizador", + "description": "Um identificador de utilizador (nome de utilizador) para o qual um peer deve ser criado." + }, + "prefix": { + "headline-peer": "Peer:", + "headline-endpoint": "Endpoint:", + "label": "Prefixo do nome exibido", + "placeholder": "O prefixo", + "description": "Um prefixo que será adicionado ao nome exibido do peer." } } - }, - "settings": { - "title": "Definições", - "password": { - "label": "Nova Palavra-passe", - "placeholder": "Deixe em branco para manter a atual" - }, - "save": "Guardar Alterações" - }, - "audit": { - "title": "Registo de Auditoria", - "username": "Utilizador", - "ip": "Endereço IP", - "method": "Método", - "path": "Caminho", - "status": "Estado", - "timestamp": "Data/Hora" - }, - "errors": { - "required": "Este campo é obrigatório", - "invalid-email": "Endereço de email inválido", - "invalid-address": "Endereço inválido", - "invalid-endpoint": "Endpoint inválido", - "invalid-allowed-ips": "Formato de IPs Permitidos inválido" } } diff --git a/frontend/src/stores/profile.js b/frontend/src/stores/profile.js index 243622a..ba3798c 100644 --- a/frontend/src/stores/profile.js +++ b/frontend/src/stores/profile.js @@ -129,7 +129,7 @@ export const profileStore = defineStore('profile', { return apiWrapper.post(`${baseUrl}/${base64_url_encode(currentUser)}/api/enable`) .then(this.setUser) .catch(error => { - this.setPeers([]) + this.fetching = false console.log("Failed to activate API for ", currentUser, ": ", error) notify({ title: "Backend Connection Failure", @@ -143,7 +143,7 @@ export const profileStore = defineStore('profile', { return apiWrapper.post(`${baseUrl}/${base64_url_encode(currentUser)}/api/disable`) .then(this.setUser) .catch(error => { - this.setPeers([]) + this.fetching = false console.log("Failed to deactivate API for ", currentUser, ": ", error) notify({ title: "Backend Connection Failure", diff --git a/go.mod b/go.mod index cb79fe6..6e91eb1 100644 --- a/go.mod +++ b/go.mod @@ -16,19 +16,19 @@ require ( github.com/stretchr/testify v1.10.0 github.com/swaggo/swag v1.16.4 github.com/vardius/message-bus v1.1.5 - github.com/vishvananda/netlink v1.3.0 + github.com/vishvananda/netlink v1.3.1 github.com/xhit/go-simple-mail/v2 v2.16.0 github.com/yeqown/go-qrcode/v2 v2.2.5 github.com/yeqown/go-qrcode/writer/compressed v1.0.1 - golang.org/x/crypto v0.37.0 - golang.org/x/oauth2 v0.29.0 - golang.org/x/sys v0.32.0 + golang.org/x/crypto v0.38.0 + golang.org/x/oauth2 v0.30.0 + golang.org/x/sys v0.33.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.11 gorm.io/driver/sqlserver v1.5.4 - gorm.io/gorm v1.25.12 + gorm.io/gorm v1.26.1 ) require ( @@ -62,7 +62,6 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -82,8 +81,8 @@ require ( github.com/yeqown/reedsolomon v1.0.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/net v0.39.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.32.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect google.golang.org/protobuf v1.36.6 // indirect diff --git a/go.sum b/go.sum index 09db5c2..f13a87c 100644 --- a/go.sum +++ b/go.sum @@ -44,24 +44,16 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= -github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= -github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU= -github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY= github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= @@ -83,8 +75,6 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= -github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -98,12 +88,10 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -117,8 +105,6 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.3 h1:PO1wNKj/bTAwxSJnO1Z4Ai8j4magtqg2SLNjEDzcXQo= -github.com/jackc/pgx/v5 v5.7.3/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -179,16 +165,10 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus-community/pro-bing v0.6.1 h1:EQukUOma9YFZRPe4DGSscxUf9LH07rpqwisNWjSZrgU= -github.com/prometheus-community/pro-bing v0.6.1/go.mod h1:jNCOI3D7pmTCeaoF41cNS6uaxeFY/Gmc3ffwbuJVzAQ= github.com/prometheus-community/pro-bing v0.7.0 h1:KFYFbxC2f2Fp6c+TyxbCOEarf7rbnzr9Gw8eIb0RfZA= github.com/prometheus-community/pro-bing v0.7.0/go.mod h1:Moob9dvlY50Bfq6i88xIwfyw7xLFHH69LUgx9n5zqCE= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= @@ -218,9 +198,8 @@ github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 h1:q0hKh5a5FRkhuTb5 github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/vardius/message-bus v1.1.5 h1:YSAC2WB4HRlwc4neFPTmT88kzzoiQ+9WRRbej/E/LZc= github.com/vardius/message-bus v1.1.5/go.mod h1:6xladCV2lMkUAE4bzzS85qKOiB5miV7aBVRafiTJGqw= -github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= -github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA= @@ -237,25 +216,16 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -268,28 +238,18 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -303,16 +263,11 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.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.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -320,13 +275,9 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -337,18 +288,12 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 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.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.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.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -356,8 +301,6 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -378,35 +321,26 @@ gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= -modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= +gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA= -modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= -modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= +modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc= +modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= -modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= -modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/libc v1.63.0 h1:wKzb61wOGCzgahQBORb1b0dZonh8Ufzl/7r4Yf1D5YA= modernc.org/libc v1.63.0/go.mod h1:wDzH1mgz1wUIEwottFt++POjGRO9sgyQKrpXaz3x89E= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= -modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ= -modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= diff --git a/internal/app/api/core/assets/doc/v1_swagger.json b/internal/app/api/core/assets/doc/v1_swagger.json index 56f3ffb..c479a70 100644 --- a/internal/app/api/core/assets/doc/v1_swagger.json +++ b/internal/app/api/core/assets/doc/v1_swagger.json @@ -118,6 +118,7 @@ "BasicAuth": [] } ], + "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": [ "application/json" ], @@ -250,6 +251,7 @@ "BasicAuth": [] } ], + "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": [ "application/json" ], @@ -309,6 +311,50 @@ } } }, + "/interface/prepare": { + "get": { + "security": [ + { + "BasicAuth": [] + } + ], + "description": "This endpoint returns a new interface with default values (fresh key pair, valid name, new IP address pool, ...).", + "produces": [ + "application/json" + ], + "tags": [ + "Interfaces" + ], + "summary": "Prepare a new interface record.", + "operationId": "interfaces_handlePrepareGet", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Interface" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/models.Error" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/models.Error" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/models.Error" + } + } + } + } + }, "/metrics/by-interface/{id}": { "get": { "security": [ @@ -547,7 +593,7 @@ "BasicAuth": [] } ], - "description": "Only admins can update existing records.", + "description": "Only admins can update existing records. The peer record must contain all required fields (e.g., public key, allowed IPs).", "produces": [ "application/json" ], @@ -779,7 +825,7 @@ "BasicAuth": [] } ], - "description": "Only admins can create new records.", + "description": "Only admins can create new records. The peer record must contain all required fields (e.g., public key, allowed IPs).", "produces": [ "application/json" ], @@ -839,6 +885,71 @@ } } }, + "/peer/prepare/{id}": { + "get": { + "security": [ + { + "BasicAuth": [] + } + ], + "description": "This endpoint is used to prepare a new peer record. The returned data contains a fresh key pair and valid ip address.", + "produces": [ + "application/json" + ], + "tags": [ + "Peers" + ], + "summary": "Prepare a new peer record for the given WireGuard interface.", + "operationId": "peers_handlePrepareGet", + "parameters": [ + { + "type": "string", + "description": "The interface identifier.", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Peer" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Error" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/models.Error" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/models.Error" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/models.Error" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/models.Error" + } + } + } + } + }, "/provisioning/data/peer-config": { "get": { "security": [ diff --git a/internal/app/api/core/assets/doc/v1_swagger.yaml b/internal/app/api/core/assets/doc/v1_swagger.yaml index 01b8d19..295bd79 100644 --- a/internal/app/api/core/assets/doc/v1_swagger.yaml +++ b/internal/app/api/core/assets/doc/v1_swagger.yaml @@ -748,6 +748,8 @@ paths: tags: - Interfaces 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, ...). operationId: interfaces_handleUpdatePut parameters: - description: The interface identifier. @@ -795,6 +797,8 @@ paths: - Interfaces /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, ...). operationId: interfaces_handleCreatePost parameters: - description: The interface data. @@ -835,6 +839,35 @@ paths: summary: Create a new interface record. tags: - Interfaces + /interface/prepare: + get: + description: This endpoint returns a new interface with default values (fresh + key pair, valid name, new IP address pool, ...). + operationId: interfaces_handlePrepareGet + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Interface' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/models.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/models.Error' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/models.Error' + security: + - BasicAuth: [] + summary: Prepare a new interface record. + tags: + - Interfaces /metrics/by-interface/{id}: get: operationId: metrics_handleMetricsForInterfaceGet @@ -1024,7 +1057,8 @@ paths: tags: - Peers put: - description: Only admins can update existing records. + description: Only admins can update existing records. The peer record must contain + all required fields (e.g., public key, allowed IPs). operationId: peers_handleUpdatePut parameters: - description: The peer identifier. @@ -1136,7 +1170,8 @@ paths: - Peers /peer/new: post: - description: Only admins can create new records. + description: Only admins can create new records. The peer record must contain + all required fields (e.g., public key, allowed IPs). operationId: peers_handleCreatePost parameters: - description: The peer data. @@ -1177,6 +1212,49 @@ paths: summary: Create a new peer record. tags: - Peers + /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. + operationId: peers_handlePrepareGet + parameters: + - description: The interface identifier. + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Peer' + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.Error' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/models.Error' + "403": + description: Forbidden + schema: + $ref: '#/definitions/models.Error' + "404": + description: Not Found + schema: + $ref: '#/definitions/models.Error' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/models.Error' + security: + - BasicAuth: [] + summary: Prepare a new peer record for the given WireGuard interface. + tags: + - Peers /provisioning/data/peer-config: get: description: Normal users can only access their own record. Admins can access diff --git a/internal/app/api/v1/backend/interface_service.go b/internal/app/api/v1/backend/interface_service.go index fcf6871..35f0b84 100644 --- a/internal/app/api/v1/backend/interface_service.go +++ b/internal/app/api/v1/backend/interface_service.go @@ -11,6 +11,7 @@ import ( type InterfaceServiceInterfaceManagerRepo interface { GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interface, [][]domain.Peer, error) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) + PrepareInterface(ctx context.Context) (*domain.Interface, error) CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, []domain.Peer, error) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error @@ -60,6 +61,19 @@ func (s InterfaceService) GetById(ctx context.Context, id domain.InterfaceIdenti return interfaceData, interfacePeers, nil } +func (s InterfaceService) Prepare(ctx context.Context) (*domain.Interface, error) { + if err := domain.ValidateAdminAccessRights(ctx); err != nil { + return nil, err + } + + interfaceData, err := s.interfaces.PrepareInterface(ctx) + if err != nil { + return nil, err + } + + return interfaceData, nil +} + func (s InterfaceService) Create(ctx context.Context, iface *domain.Interface) (*domain.Interface, error) { if err := domain.ValidateAdminAccessRights(ctx); err != nil { return nil, err diff --git a/internal/app/api/v1/backend/peer_service.go b/internal/app/api/v1/backend/peer_service.go index 5210c45..fbfe186 100644 --- a/internal/app/api/v1/backend/peer_service.go +++ b/internal/app/api/v1/backend/peer_service.go @@ -13,6 +13,7 @@ type PeerServicePeerManagerRepo interface { GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) + PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error @@ -95,6 +96,19 @@ func (s PeerService) GetById(ctx context.Context, id domain.PeerIdentifier) (*do return peer, nil } +func (s PeerService) Prepare(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) { + if err := domain.ValidateAdminAccessRights(ctx); err != nil { + return nil, err + } + + peer, err := s.peers.PreparePeer(ctx, id) + if err != nil { + return nil, err + } + + return peer, nil +} + func (s PeerService) Create(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) { if err := domain.ValidateAdminAccessRights(ctx); err != nil { return nil, err diff --git a/internal/app/api/v1/handlers/endpoint_interface.go b/internal/app/api/v1/handlers/endpoint_interface.go index a0c9d5f..1ea7241 100644 --- a/internal/app/api/v1/handlers/endpoint_interface.go +++ b/internal/app/api/v1/handlers/endpoint_interface.go @@ -15,6 +15,7 @@ import ( type InterfaceEndpointInterfaceService interface { GetAll(context.Context) ([]domain.Interface, [][]domain.Peer, error) GetById(context.Context, domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) + Prepare(context.Context) (*domain.Interface, error) Create(context.Context, *domain.Interface) (*domain.Interface, error) Update(context.Context, domain.InterfaceIdentifier, *domain.Interface) (*domain.Interface, []domain.Peer, error) Delete(context.Context, domain.InterfaceIdentifier) error @@ -49,6 +50,7 @@ func (e InterfaceEndpoint) RegisterRoutes(g *routegroup.Bundle) { apiGroup.HandleFunc("GET /all", e.handleAllGet()) apiGroup.HandleFunc("GET /by-id/{id}", e.handleByIdGet()) + apiGroup.HandleFunc("GET /prepare", e.handlePrepareGet()) apiGroup.HandleFunc("POST /new", e.handleCreatePost()) apiGroup.HandleFunc("PUT /by-id/{id}", e.handleUpdatePut()) apiGroup.HandleFunc("DELETE /by-id/{id}", e.handleDelete()) @@ -112,11 +114,38 @@ func (e InterfaceEndpoint) handleByIdGet() http.HandlerFunc { } } +// handlePrepareGet returns a gorm handler function. +// +// @ID interfaces_handlePrepareGet +// @Tags Interfaces +// @Summary Prepare a new interface record. +// @Description This endpoint returns a new interface with default values (fresh key pair, valid name, new IP address pool, ...). +// @Produce json +// @Success 200 {object} models.Interface +// @Failure 401 {object} models.Error +// @Failure 403 {object} models.Error +// @Failure 500 {object} models.Error +// @Router /interface/prepare [get] +// @Security BasicAuth +func (e InterfaceEndpoint) handlePrepareGet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + iface, err := e.interfaces.Prepare(r.Context()) + if err != nil { + status, model := ParseServiceError(err) + respond.JSON(w, status, model) + return + } + + respond.JSON(w, http.StatusOK, models.NewInterface(iface, nil)) + } +} + // handleCreatePost returns a gorm handler function. // // @ID interfaces_handleCreatePost // @Tags Interfaces // @Summary Create a new interface record. +// @Description This endpoint creates a new interface with the provided data. All required fields must be filled (e.g. name, private key, public key, ...). // @Param request body models.Interface true "The interface data." // @Produce json // @Success 200 {object} models.Interface @@ -155,6 +184,7 @@ func (e InterfaceEndpoint) handleCreatePost() http.HandlerFunc { // @ID interfaces_handleUpdatePut // @Tags Interfaces // @Summary Update an interface record. +// @Description This endpoint updates an existing interface with the provided data. All required fields must be filled (e.g. name, private key, public key, ...). // @Param id path string true "The interface identifier." // @Param request body models.Interface true "The interface data." // @Produce json diff --git a/internal/app/api/v1/handlers/endpoint_peer.go b/internal/app/api/v1/handlers/endpoint_peer.go index ad79d13..393143e 100644 --- a/internal/app/api/v1/handlers/endpoint_peer.go +++ b/internal/app/api/v1/handlers/endpoint_peer.go @@ -16,6 +16,7 @@ type PeerService interface { GetForInterface(context.Context, domain.InterfaceIdentifier) ([]domain.Peer, error) GetForUser(context.Context, domain.UserIdentifier) ([]domain.Peer, error) GetById(context.Context, domain.PeerIdentifier) (*domain.Peer, error) + Prepare(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) Create(context.Context, *domain.Peer) (*domain.Peer, error) Update(context.Context, domain.PeerIdentifier, *domain.Peer) (*domain.Peer, error) Delete(context.Context, domain.PeerIdentifier) error @@ -51,6 +52,7 @@ func (e PeerEndpoint) RegisterRoutes(g *routegroup.Bundle) { apiGroup.HandleFunc("GET /by-user/{id}", e.handleAllForUserGet()) 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("POST /new", e.handleCreatePost()) 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()) @@ -156,12 +158,48 @@ func (e PeerEndpoint) handleByIdGet() http.HandlerFunc { } } +// handlePrepareGet returns a gorm handler function. +// +// @ID peers_handlePrepareGet +// @Tags Peers +// @Summary Prepare a new peer record for the given WireGuard interface. +// @Description This endpoint is used to prepare a new peer record. The returned data contains a fresh key pair and valid ip address. +// @Param id path string true "The interface identifier." +// @Produce json +// @Success 200 {object} models.Peer +// @Failure 400 {object} models.Error +// @Failure 401 {object} models.Error +// @Failure 403 {object} models.Error +// @Failure 404 {object} models.Error +// @Failure 500 {object} models.Error +// @Router /peer/prepare/{id} [get] +// @Security BasicAuth +func (e PeerEndpoint) handlePrepareGet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := request.Path(r, "id") + if id == "" { + respond.JSON(w, http.StatusBadRequest, + models.Error{Code: http.StatusBadRequest, Message: "missing interface id"}) + return + } + + peer, err := e.peers.Prepare(r.Context(), domain.InterfaceIdentifier(id)) + if err != nil { + status, model := ParseServiceError(err) + respond.JSON(w, status, model) + return + } + + respond.JSON(w, http.StatusOK, models.NewPeer(peer)) + } +} + // handleCreatePost returns a gorm handler function. // // @ID peers_handleCreatePost // @Tags Peers // @Summary Create a new peer record. -// @Description Only admins can create new records. +// @Description Only admins can create new records. The peer record must contain all required fields (e.g., public key, allowed IPs). // @Param request body models.Peer true "The peer data." // @Produce json // @Success 200 {object} models.Peer @@ -200,7 +238,7 @@ func (e PeerEndpoint) handleCreatePost() http.HandlerFunc { // @ID peers_handleUpdatePut // @Tags Peers // @Summary Update a peer record. -// @Description Only admins can update existing records. +// @Description Only admins can update existing records. The peer record must contain all required fields (e.g., public key, allowed IPs). // @Param id path string true "The peer identifier." // @Param request body models.Peer true "The peer data." // @Produce json diff --git a/internal/app/auth/auth.go b/internal/app/auth/auth.go index bbda138..380fe81 100644 --- a/internal/app/auth/auth.go +++ b/internal/app/auth/auth.go @@ -63,6 +63,8 @@ type AuthenticatorOauth interface { ParseUserInfo(raw map[string]any) (*domain.AuthenticatorUserInfo, error) // RegistrationEnabled returns whether registration is enabled for the OAuth authenticator. RegistrationEnabled() bool + // GetAllowedDomains returns the list of whitelisted domains + GetAllowedDomains() []string } // AuthenticatorLdap is the interface for all LDAP authenticators. @@ -392,6 +394,23 @@ func (a *Authenticator) randString(nByte int) (string, error) { return base64.RawURLEncoding.EncodeToString(b), nil } +func isDomainAllowed(email string, allowedDomains []string) bool { + if len(allowedDomains) == 0 { + return true + } + parts := strings.Split(email, "@") + if len(parts) != 2 { + return false + } + domain := strings.ToLower(parts[1]) + for _, allowed := range allowedDomains { + if domain == strings.ToLower(allowed) { + return true + } + } + return false +} + // OauthLoginStep2 finishes the oauth authentication flow by exchanging the code for an access token and // fetching the user information. func (a *Authenticator) OauthLoginStep2(ctx context.Context, providerId, nonce, code string) (*domain.User, error) { @@ -431,6 +450,10 @@ func (a *Authenticator) OauthLoginStep2(ctx context.Context, providerId, nonce, return nil, fmt.Errorf("unable to process user information: %w", err) } + if !isDomainAllowed(userInfo.Email, oauthProvider.GetAllowedDomains()) { + return nil, fmt.Errorf("user is not in allowed domains: %w", err) + } + if user.IsLocked() || user.IsDisabled() { a.bus.Publish(app.TopicAuditLoginFailed, domain.AuditEventWrapper[audit.AuthEvent]{ Ctx: ctx, diff --git a/internal/app/auth/auth_oauth.go b/internal/app/auth/auth_oauth.go index 7e730bc..56d53c5 100644 --- a/internal/app/auth/auth_oauth.go +++ b/internal/app/auth/auth_oauth.go @@ -27,6 +27,7 @@ type PlainOauthAuthenticator struct { userAdminMapping *config.OauthAdminMapping registrationEnabled bool userInfoLogging bool + allowedDomains []string } func newPlainOauthAuthenticator( @@ -56,6 +57,7 @@ func newPlainOauthAuthenticator( provider.userAdminMapping = &cfg.AdminMapping provider.registrationEnabled = cfg.RegistrationEnabled provider.userInfoLogging = cfg.LogUserInfo + provider.allowedDomains = cfg.AllowedDomains return provider, nil } @@ -65,6 +67,10 @@ func (p PlainOauthAuthenticator) GetName() string { return p.name } +func (p PlainOauthAuthenticator) GetAllowedDomains() []string { + return p.allowedDomains +} + // RegistrationEnabled returns whether registration is enabled for the OAuth authenticator. func (p PlainOauthAuthenticator) RegistrationEnabled() bool { return p.registrationEnabled diff --git a/internal/app/auth/auth_oidc.go b/internal/app/auth/auth_oidc.go index d832768..0a4ecb0 100644 --- a/internal/app/auth/auth_oidc.go +++ b/internal/app/auth/auth_oidc.go @@ -24,6 +24,7 @@ type OidcAuthenticator struct { userAdminMapping *config.OauthAdminMapping registrationEnabled bool userInfoLogging bool + allowedDomains []string } func newOidcAuthenticator( @@ -57,6 +58,7 @@ func newOidcAuthenticator( provider.userAdminMapping = &cfg.AdminMapping provider.registrationEnabled = cfg.RegistrationEnabled provider.userInfoLogging = cfg.LogUserInfo + provider.allowedDomains = cfg.AllowedDomains return provider, nil } @@ -66,6 +68,10 @@ func (o OidcAuthenticator) GetName() string { return o.name } +func (o OidcAuthenticator) GetAllowedDomains() []string { + return o.allowedDomains +} + // RegistrationEnabled returns whether registration is enabled for this authenticator. func (o OidcAuthenticator) RegistrationEnabled() bool { return o.registrationEnabled diff --git a/internal/config/auth.go b/internal/config/auth.go index 7e70dae..3132fb7 100644 --- a/internal/config/auth.go +++ b/internal/config/auth.go @@ -188,6 +188,9 @@ type OpenIDConnectProvider struct { // ExtraScopes specifies optional requested permissions. ExtraScopes []string `yaml:"extra_scopes"` + // AllowedDomains defines the list of allowed domains + AllowedDomains []string `yaml:"allowed_domains"` + // FieldMap is used to map the names of the user-info endpoint fields to wg-portal fields FieldMap OauthFields `yaml:"field_map"` @@ -226,6 +229,9 @@ type OAuthProvider struct { // Scope specifies optional requested permissions. Scopes []string `yaml:"scopes"` + // AllowedDomains defines the list of allowed domains + AllowedDomains []string `yaml:"allowed_domains"` + // FieldMap is used to map the names of the user-info endpoint fields to wg-portal fields FieldMap OauthFields `yaml:"field_map"`