From d42fae8cf3c4e3881fc1b052468f8a983188a9c3 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 1 Mar 2025 12:04:46 -0300 Subject: [PATCH] feat: add public VPN invite view and template --- templates/vpn_invite/public_vpn_invite.html | 168 ++++++++++++++++++++ vpn_invite/models.py | 27 +++- vpn_invite_public/views.py | 32 +++- wireguard_tools/views.py | 44 +++-- wireguard_webadmin/urls.py | 22 ++- 5 files changed, 264 insertions(+), 29 deletions(-) create mode 100644 templates/vpn_invite/public_vpn_invite.html diff --git a/templates/vpn_invite/public_vpn_invite.html b/templates/vpn_invite/public_vpn_invite.html new file mode 100644 index 0000000..c54b2d4 --- /dev/null +++ b/templates/vpn_invite/public_vpn_invite.html @@ -0,0 +1,168 @@ + + + + + {% if authenticated %}VPN Invite{% else %}Authentication Required{% endif %} + + + +
+ {% if not authenticated %} +

Authentication Required

+ {% if error %} +
{{ error }}
+ {% endif %} +
+ {% csrf_token %} + + + +
+ {% else %} +

VPN Configuration

+
+ {{ invite_settings.download_instructions|safe }} +
+
+ {% if invite_settings.download_1_enabled and invite_settings.download_1_url %} + {{ invite_settings.download_1_label }} + {% endif %} + {% if invite_settings.download_2_enabled and invite_settings.download_2_url %} + {{ invite_settings.download_2_label }} + {% endif %} + {% if invite_settings.download_3_enabled and invite_settings.download_3_url %} + {{ invite_settings.download_3_label }} + {% endif %} + {% if invite_settings.download_4_enabled and invite_settings.download_4_url %} + {{ invite_settings.download_4_label }} + {% endif %} + {% if invite_settings.download_5_enabled and invite_settings.download_5_url %} + {{ invite_settings.download_5_label }} + {% endif %} +
+ +
+ +
+ {% endif %} +
+{% if authenticated %} + + +{% endif %} + + diff --git a/vpn_invite/models.py b/vpn_invite/models.py index a4f7dc4..cdc99ea 100644 --- a/vpn_invite/models.py +++ b/vpn_invite/models.py @@ -1,7 +1,24 @@ -from django.db import models -from wireguard.models import Peer import uuid +from django.db import models + +from wireguard.models import Peer + +DEFAULT_INVITE_MESSAGE = ''' +Hello, + +You're invited to join our secure WireGuard VPN network. Please click the link below to access your personalized VPN configuration: + +{invite_url} + +Note: This invitation link will expire in {expire_minutes} minutes. If you need a new link after expiration, please request another invite. +''' + +DEFAULT_HTML_MESSAGE = ''' +

Welcome to Your VPN Setup

+

Begin by downloading the WireGuard app for your device using one of the links below.

+

Once installed, you can either scan the QR code or download the configuration file to quickly import your settings and start using your secure VPN connection.

+''' class InviteSettings(models.Model): name = models.CharField(max_length=16, default='default_settings', unique=True) @@ -42,13 +59,13 @@ class InviteSettings(models.Model): invite_url = models.URLField(default='') - invite_text_body = models.TextField(default='Here is your WireGuard VPN invite link: {invite_url}\n\nThis link expires in {expire_minutes} minutes.') + invite_text_body = models.TextField(default=DEFAULT_INVITE_MESSAGE) invite_email_subject = models.CharField(max_length=64, default='WireGuard VPN Invite', blank=True, null=True) - invite_email_body = models.TextField(default='Here is your WireGuard VPN invite link: {invite_url}\n\nThis link expires in {expire_minutes} minutes.') + invite_email_body = models.TextField(default=DEFAULT_INVITE_MESSAGE) invite_email_enabled = models.BooleanField(default=True) - invite_whatsapp_body = models.TextField(default='Here is your WireGuard VPN invite link: {invite_url}\n\nThis link expires in {expire_minutes} minutes.') + invite_whatsapp_body = models.TextField(default=DEFAULT_INVITE_MESSAGE) invite_whatsapp_enabled = models.BooleanField(default=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False) diff --git a/vpn_invite_public/views.py b/vpn_invite_public/views.py index 91ea44a..019a203 100644 --- a/vpn_invite_public/views.py +++ b/vpn_invite_public/views.py @@ -1,3 +1,33 @@ +from django.http import Http404 from django.shortcuts import render +from django.utils import timezone -# Create your views here. +from vpn_invite.models import PeerInvite, InviteSettings + + +def view_public_vpn_invite(request): + PeerInvite.objects.filter(invite_expiration__lt=timezone.now()).delete() + try: + peer_invite = PeerInvite.objects.get(uuid=request.GET.get('token')) + invite_settings = InviteSettings.objects.get(name='default_settings') + except: + raise Http404 + + # Initialize context with default values + context = { + 'peer_invite': peer_invite, + 'invite_settings': invite_settings, + 'authenticated': False, + 'error': '' + } + + if request.method == 'POST': + password = request.POST.get('password', '') + # Check if the provided password matches the invite password + if password and password == peer_invite.invite_password: + context['authenticated'] = True + context['password'] = password + else: + context['error'] = "Invalid password. Please try again." + + return render(request, 'vpn_invite/public_vpn_invite.html', context=context) diff --git a/wireguard_tools/views.py b/wireguard_tools/views.py index 46911af..ca07a62 100644 --- a/wireguard_tools/views.py +++ b/wireguard_tools/views.py @@ -1,21 +1,23 @@ import os import re -import qrcode import subprocess +from io import BytesIO + +import qrcode +from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.http import HttpResponse from django.shortcuts import redirect, get_object_or_404, render, Http404 +from django.utils import timezone from dns.views import export_dns_configuration +from firewall.models import RedirectRule from firewall.tools import generate_firewall_header, generate_firewall_footer, generate_port_forward_firewall, \ export_user_firewall, generate_redirect_dns_rules from user_manager.models import UserAcl -from wireguard.models import WireGuardInstance, Peer, PeerAllowedIP -from firewall.models import RedirectRule -from django.contrib.auth.decorators import login_required -from django.contrib import messages -from io import BytesIO +from vpn_invite.models import PeerInvite from wgwadmlibrary.tools import user_has_access_to_peer - +from wireguard.models import WireGuardInstance, Peer, PeerAllowedIP def clean_command_field(command_field): @@ -159,15 +161,27 @@ def export_wireguard_configs(request): return redirect('/status/') -@login_required def download_config_or_qrcode(request): - if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists(): - return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) - peer = get_object_or_404(Peer, uuid=request.GET.get('uuid')) - user_acl = get_object_or_404(UserAcl, user=request.user) - - if not user_has_access_to_peer(user_acl, peer): - raise Http404 + # This view is used for private and public use. If the user is not authenticated properly, it will return a 404 instead of 403 to avoid leaking any further information. + if request.GET.get('token') and request.GET.get('password'): + PeerInvite.objects.filter(invite_expiration__lt=timezone.now()).delete() + try: + peer_invite = get_object_or_404(PeerInvite, uuid=request.GET.get('token'), invite_password=request.GET.get('password')) + peer = peer_invite.peer + except: + raise Http404 + else: + if not request.user.is_authenticated: + raise Http404 + + if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists(): + return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) + peer = get_object_or_404(Peer, uuid=request.GET.get('uuid')) + user_acl = get_object_or_404(UserAcl, user=request.user) + + if not user_has_access_to_peer(user_acl, peer): + raise Http404 + format_type = request.GET.get('format', 'conf') config_content = generate_peer_config(peer.uuid) diff --git a/wireguard_webadmin/urls.py b/wireguard_webadmin/urls.py index e30d28d..36f8c4e 100644 --- a/wireguard_webadmin/urls.py +++ b/wireguard_webadmin/urls.py @@ -17,17 +17,21 @@ Including another URLconf from django.contrib import admin from django.urls import path -from wireguard.views import view_wireguard_status, view_wireguard_manage_instance, view_apply_db_patches -from wireguard_peer.views import view_wireguard_peer_list, view_wireguard_peer_manage, view_manage_ip_address, view_wireguard_peer_sort -from console.views import view_console -from user_manager.views import view_user_list, view_manage_user, view_peer_group_list, view_peer_group_manage from accounts.views import view_create_first_user, view_login, view_logout -from wireguard_tools.views import export_wireguard_configs, download_config_or_qrcode, restart_wireguard_interfaces -from api.views import wireguard_status, cron_check_updates, cron_update_peer_latest_handshake, routerfleet_get_user_token, routerfleet_authenticate_session, peer_info, api_peer_invite -from firewall.views import view_redirect_rule_list, manage_redirect_rule, view_firewall_rule_list, manage_firewall_rule, view_manage_firewall_settings, view_generate_iptables_script, view_reset_firewall, view_firewall_migration_required +from api.views import wireguard_status, cron_check_updates, cron_update_peer_latest_handshake, \ + routerfleet_get_user_token, routerfleet_authenticate_session, peer_info, api_peer_invite +from console.views import view_console from dns.views import view_static_host_list, view_manage_static_host, view_manage_dns_settings, view_apply_dns_config -from wgrrd.views import view_rrd_graph +from firewall.views import view_redirect_rule_list, manage_redirect_rule, view_firewall_rule_list, manage_firewall_rule, \ + view_manage_firewall_settings, view_generate_iptables_script, view_reset_firewall, view_firewall_migration_required +from user_manager.views import view_user_list, view_manage_user, view_peer_group_list, view_peer_group_manage from vpn_invite.views import view_vpn_invite_list, view_vpn_invite_settings, view_email_settings +from vpn_invite_public.views import view_public_vpn_invite +from wgrrd.views import view_rrd_graph +from wireguard.views import view_wireguard_status, view_wireguard_manage_instance, view_apply_db_patches +from wireguard_peer.views import view_wireguard_peer_list, view_wireguard_peer_manage, view_manage_ip_address, \ + view_wireguard_peer_sort +from wireguard_tools.views import export_wireguard_configs, download_config_or_qrcode, restart_wireguard_interfaces urlpatterns = [ path('admin/', admin.site.urls), @@ -72,4 +76,6 @@ urlpatterns = [ path('vpn_invite/', view_vpn_invite_list, name='vpn_invite_list'), path('vpn_invite/settings/', view_vpn_invite_settings, name='vpn_invite_settings'), path('vpn_invite/smtp_settings/', view_email_settings, name='email_settings'), + path('invite/', view_public_vpn_invite, name='public_vpn_invite'), + path('invite/download_config/', download_config_or_qrcode, name='download_config_or_qrcode'), ]