From d8b51bf812f3c0a3cb39ef4ea7340f11a78b82bc Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sat, 1 Mar 2025 10:52:20 -0300 Subject: [PATCH] feat: implement email sending functionality for peer invites --- api/views.py | 11 ++-- templates/wireguard/wireguard_peer_list.html | 53 ++++++++++++++++---- wgwadmlibrary/tools.py | 44 ++++++++++++++++ 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/api/views.py b/api/views.py index 0a4bd72..b382cf0 100644 --- a/api/views.py +++ b/api/views.py @@ -18,7 +18,8 @@ from django.views.decorators.http import require_http_methods from user_manager.models import UserAcl, AuthenticationToken from vpn_invite.models import InviteSettings, PeerInvite -from wgwadmlibrary.tools import user_allowed_peers, user_has_access_to_peer, get_peer_invite_data, create_peer_invite +from wgwadmlibrary.tools import user_allowed_peers, user_has_access_to_peer, get_peer_invite_data, create_peer_invite, \ + send_email from wireguard.models import WebadminSettings, Peer, PeerStatus, WireGuardInstance @@ -334,9 +335,11 @@ def api_peer_invite(request): data['invite_data'] = get_peer_invite_data(peer_invite, invite_settings) if request.GET.get('action') == 'email': - data['status'] = 'success' - data['message'] = 'Email sent' - return JsonResponse(data) + data['status'], data['message'] = send_email(request.GET.get('address'), data['invite_data']['email_subject'], data['invite_data']['email_body']) + if data['status'] == 'success': + return JsonResponse(data) + else: + return JsonResponse(data, status=400) else: if request.GET.get('action') == 'email': data['status'] = 'error' diff --git a/templates/wireguard/wireguard_peer_list.html b/templates/wireguard/wireguard_peer_list.html index a5c0097..cba7607 100644 --- a/templates/wireguard/wireguard_peer_list.html +++ b/templates/wireguard/wireguard_peer_list.html @@ -653,11 +653,18 @@ $("#invitePassword").html("Access Password: " + inviteData.password + " (Share this password via a separate secure channel)"); $("#inviteExpiration").text(new Date(inviteData.expiration).toLocaleString()); } else { - $("#inviteMessage").html("
" + response.message + "
"); + $("#inviteMessage").html("
" + (response.message || "Unknown error") + "
"); } }, error: function(xhr, status, error) { - $("#inviteMessage").html("
Error creating invite: " + error + "
"); + var message = "Error creating invite."; + try { + var resp = xhr.responseJSON; + message = resp && resp.message ? resp.message : xhr.statusText; + } catch(err) { + message = xhr.statusText; + } + $("#inviteMessage").html("
" + message + "
"); } }); }); @@ -718,7 +725,7 @@ }); // Handler for sending invite via Email - $("#sendInviteEmailButton").on("click", function(e) { + $("#sendInviteEmailButton").on("click", function(e, textStatus, xhr) { e.preventDefault(); var contact = $("#inviteContactInput").val().trim(); if(!isValidEmail(contact)) { @@ -732,15 +739,30 @@ data: { invite: inviteData.uuid, action: 'email', address: contact }, type: 'GET', dataType: 'json', - success: function(response) { + success: function(response, textStatus, xhr) { + var message = response.message; + if (!message) { + message = xhr.statusText; + } if(response.status === "success") { - $("#inviteMessage").html("
Email sent successfully.
"); + $("#inviteMessage").html("
" + message + "
"); } else { - $("#inviteMessage").html("
" + response.message + "
"); + $("#inviteMessage").html("
" + message + "
"); } }, error: function(xhr, status, error) { - $("#inviteMessage").html("
Error sending email: " + error + "
"); + var message = "Error sending email."; + try { + var resp = xhr.responseJSON; + if (resp && resp.message) { + message = resp.message; + } else { + message = xhr.statusText; + } + } catch(err) { + message = xhr.statusText; + } + $("#inviteMessage").html("
" + message + "
"); } }); } else { @@ -764,13 +786,24 @@ $("#inviteText").text(inviteData.text_body); $("#invitePassword").html("Access Password: " + inviteData.password + " (Share this password via a separate secure channel)"); $("#inviteExpiration").text(new Date(inviteData.expiration).toLocaleString()); - $("#inviteMessage").html("
Invite refreshed successfully.
"); + $("#inviteMessage").html("
" + (response.message || xhr.statusText) + "
"); } else { - $("#inviteMessage").html("
" + response.message + "
"); + $("#inviteMessage").html("
" + (response.message || "Error refreshing invite.") + "
"); } }, error: function(xhr, status, error) { - $("#inviteMessage").html("
Error refreshing invite: " + error + "
"); + var message = "Error refreshing invite."; + try { + var resp = xhr.responseJSON; + if (resp && resp.message) { + message = resp.message; + } else { + message = xhr.statusText; + } + } catch(err) { + message = xhr.statusText; + } + $("#inviteMessage").html("
" + message + "
"); } }); } else { diff --git a/wgwadmlibrary/tools.py b/wgwadmlibrary/tools.py index 24ebf1c..c8146b4 100644 --- a/wgwadmlibrary/tools.py +++ b/wgwadmlibrary/tools.py @@ -1,15 +1,20 @@ import ipaddress import random import re +import smtplib import subprocess from datetime import timedelta +from email.mime.text import MIMEText +from django.core.exceptions import ValidationError +from django.core.validators import validate_email from django.db.models import Max from django.utils import timezone from user_manager.models import UserAcl from vpn_invite.models import PeerInvite, InviteSettings from wireguard.models import Peer, WireGuardInstance +from wireguard_tools.models import EmailSettings def user_has_access_to_instance(user_acl: UserAcl, instance: WireGuardInstance): @@ -169,3 +174,42 @@ def create_peer_invite(peer: Peer, invite_settings: InviteSettings): peer=peer, invite_password=password[:32], invite_expiration=timezone.now() + timedelta(minutes=invite_settings.invite_expiration) ) return peer_invite + + +def send_email(destination, subject, body): + success = 'error' + message = '' + try: + validate_email(destination) + except ValidationError: + return 'error', 'Invalid email address.' + + email_settings = EmailSettings.objects.filter(name='email_settings', enabled=True).first() + if not email_settings: + message = 'Email settings not configured.' + return success, message + + try: + msg = MIMEText(body, 'plain') + msg['Subject'] = subject + msg['From'] = email_settings.smtp_from_address + msg['To'] = destination + + if email_settings.smtp_encryption.lower() == 'ssl': + server = smtplib.SMTP_SSL(email_settings.smtp_host, email_settings.smtp_port) + else: + server = smtplib.SMTP(email_settings.smtp_host, email_settings.smtp_port) + server.starttls() + + if email_settings.smtp_username and email_settings.smtp_password: + server.login(email_settings.smtp_username, email_settings.smtp_password) + + server.sendmail(email_settings.smtp_from_address, destination, msg.as_string()) + server.quit() + success = 'success' + message = 'Email sent successfully.' + except Exception as e: + success = 'error' + message = f'Error sending email: {str(e)}' + + return success, message