V2 alpha - initial version (#172)

Initial alpha codebase for version 2 of WireGuard Portal.
This version is considered unstable and incomplete (for example, no public REST API)! 
Use with care!


Fixes/Implements the following issues:
 - OAuth support #154, #1 
 - New Web UI with internationalisation support #98, #107, #89, #62
 - Postgres Support #49 
 - Improved Email handling #47, #119 
 - DNS Search Domain support #46 
 - Bugfixes #94, #48 

---------

Co-authored-by: Fabian Wechselberger <wechselbergerf@hotmail.com>
This commit is contained in:
h44z
2023-08-04 13:34:18 +02:00
committed by GitHub
parent b3a5f2ac60
commit 8b820a5adf
788 changed files with 46139 additions and 11281 deletions

View File

@@ -0,0 +1,127 @@
package mail
import (
"context"
"fmt"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
"github.com/sirupsen/logrus"
"io"
)
type Manager struct {
cfg *config.Config
tplHandler *TemplateHandler
mailer Mailer
configFiles ConfigFileManager
users UserDatabaseRepo
wg WireguardDatabaseRepo
}
func NewMailManager(cfg *config.Config, mailer Mailer, configFiles ConfigFileManager, users UserDatabaseRepo, wg WireguardDatabaseRepo) (*Manager, error) {
tplHandler, err := newTemplateHandler(cfg.Web.ExternalUrl)
if err != nil {
return nil, fmt.Errorf("failed to initialize template handler: %w", err)
}
m := &Manager{
cfg: cfg,
tplHandler: tplHandler,
mailer: mailer,
configFiles: configFiles,
users: users,
wg: wg,
}
return m, nil
}
func (m Manager) SendPeerEmail(ctx context.Context, linkOnly bool, peers ...domain.PeerIdentifier) error {
for _, peerId := range peers {
peer, err := m.wg.GetPeer(ctx, peerId)
if err != nil {
return fmt.Errorf("failed to fetch peer %s: %w", peerId, err)
}
if peer.UserIdentifier == "" {
logrus.Debugf("skipping peer email for %s, no user linked", peerId)
continue
}
user, err := m.users.GetUser(ctx, peer.UserIdentifier)
if err != nil {
logrus.Debugf("skipping peer email for %s, unable to fetch user: %v", peerId, err)
continue
}
if user.Email == "" {
logrus.Debugf("skipping peer email for %s, user has no mail address", peerId)
continue
}
err = m.sendPeerEmail(ctx, linkOnly, user, peer)
if err != nil {
return fmt.Errorf("failed to send peer email for %s: %w", peerId, err)
}
}
return nil
}
func (m Manager) sendPeerEmail(ctx context.Context, linkOnly bool, user *domain.User, peer *domain.Peer) error {
qrName := "WireGuardQRCode.png"
configName := peer.GetConfigFileName()
var (
txtMail, htmlMail io.Reader
err error
mailOptions domain.MailOptions
)
if linkOnly {
txtMail, htmlMail, err = m.tplHandler.GetConfigMail(user, "deep link TBD")
if err != nil {
return fmt.Errorf("failed to get mail body: %w", err)
}
} else {
peerConfig, err := m.configFiles.GetPeerConfig(ctx, peer.Identifier)
if err != nil {
return fmt.Errorf("failed to fetch peer config for %s: %w", peer.Identifier, err)
}
peerConfigQr, err := m.configFiles.GetPeerConfigQrCode(ctx, peer.Identifier)
if err != nil {
return fmt.Errorf("failed to fetch peer config QR code for %s: %w", peer.Identifier, err)
}
txtMail, htmlMail, err = m.tplHandler.GetConfigMailWithAttachment(user, configName, qrName)
if err != nil {
return fmt.Errorf("failed to get full mail body: %w", err)
}
mailOptions.Attachments = append(mailOptions.Attachments, domain.MailAttachment{
Name: configName,
ContentType: "text/plain",
Data: peerConfig,
Embedded: false,
})
mailOptions.Attachments = append(mailOptions.Attachments, domain.MailAttachment{
Name: qrName,
ContentType: "image/png",
Data: peerConfigQr,
Embedded: true,
})
}
txtMailStr, _ := io.ReadAll(txtMail)
htmlMailStr, _ := io.ReadAll(htmlMail)
mailOptions.HtmlBody = string(htmlMailStr)
err = m.mailer.Send(ctx, "WireGuard VPN Configuration", string(txtMailStr), []string{user.Email}, &mailOptions)
if err != nil {
return fmt.Errorf("failed to send mail: %w", err)
}
return nil
}

View File

@@ -0,0 +1,27 @@
package mail
import (
"context"
"github.com/h44z/wg-portal/internal/domain"
"io"
)
type Mailer interface {
Send(ctx context.Context, subject, body string, to []string, options *domain.MailOptions) error
}
type ConfigFileManager interface {
GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error)
GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
GetPeerConfigQrCode(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
}
type UserDatabaseRepo interface {
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
}
type WireguardDatabaseRepo interface {
GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error)
}

View File

@@ -0,0 +1,93 @@
package mail
import (
"bytes"
"embed"
"fmt"
htmlTemplate "html/template"
"io"
"text/template"
"github.com/h44z/wg-portal/internal/domain"
)
//go:embed tpl_files/*
var TemplateFiles embed.FS
type TemplateHandler struct {
portalUrl string
htmlTemplates *htmlTemplate.Template
textTemplates *template.Template
}
func newTemplateHandler(portalUrl string) (*TemplateHandler, error) {
htmlTemplateCache, err := htmlTemplate.New("Html").ParseFS(TemplateFiles, "tpl_files/*.gohtml")
if err != nil {
return nil, fmt.Errorf("failed to parse html template files: %w", err)
}
txtTemplateCache, err := template.New("Txt").ParseFS(TemplateFiles, "tpl_files/*.gotpl")
if err != nil {
return nil, fmt.Errorf("failed to parse text template files: %w", err)
}
handler := &TemplateHandler{
portalUrl: portalUrl,
htmlTemplates: htmlTemplateCache,
textTemplates: txtTemplateCache,
}
return handler, nil
}
func (c TemplateHandler) GetConfigMail(user *domain.User, link string) (io.Reader, io.Reader, error) {
var tplBuff bytes.Buffer
var htmlTplBuff bytes.Buffer
err := c.textTemplates.ExecuteTemplate(&tplBuff, "mail_with_link.gotpl", map[string]interface{}{
"User": user,
"Link": link,
"PortalUrl": c.portalUrl,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to execute template mail_with_link.gotpl: %w", err)
}
err = c.htmlTemplates.ExecuteTemplate(&htmlTplBuff, "mail_with_link.gohtml", map[string]interface{}{
"User": user,
"Link": link,
"PortalUrl": c.portalUrl,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to execute template mail_with_link.gohtml: %w", err)
}
return &tplBuff, &htmlTplBuff, nil
}
func (c TemplateHandler) GetConfigMailWithAttachment(user *domain.User, cfgName, qrName string) (io.Reader, io.Reader, error) {
var tplBuff bytes.Buffer
var htmlTplBuff bytes.Buffer
err := c.textTemplates.ExecuteTemplate(&tplBuff, "mail_with_attachment.gotpl", map[string]interface{}{
"User": user,
"ConfigFileName": cfgName,
"QrcodePngName": qrName,
"PortalUrl": c.portalUrl,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to execute template mail_with_attachment.gotpl: %w", err)
}
err = c.htmlTemplates.ExecuteTemplate(&htmlTplBuff, "mail_with_attachment.gohtml", map[string]interface{}{
"User": user,
"ConfigFileName": cfgName,
"QrcodePngName": qrName,
"PortalUrl": c.portalUrl,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to execute template mail_with_attachment.gohtml: %w", err)
}
return &tplBuff, &htmlTplBuff, nil
}

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="format-detection" content="date=no" />
<meta name="format-detection" content="address=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="x-apple-disable-message-reformatting" />
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,700,700i" rel="stylesheet" />
<!--<![endif]-->
<title>Email Template</title>
<!--[if gte mso 9]>
<style type="text/css" media="all">
sup { font-size: 100% !important; }
</style>
<![endif]-->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style type="text/css" media="screen">
/* Linked Styles */
body { padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background: #ffffff; -webkit-text-size-adjust:none }
a { color: #000000; text-decoration:none }
p { padding:0 !important; margin:0 !important }
img { -ms-interpolation-mode: bicubic; /* Allow smoother rendering of resized image in Internet Explorer */ }
.mcnPreviewText { display: none !important; }
/* Mobile styles */
@media only screen and (max-device-width: 480px), only screen and (max-width: 480px) {
.mobile-shell { width: 100% !important; min-width: 100% !important; }
.bg { background-size: 100% auto !important; -webkit-background-size: 100% auto !important; }
.text-header,
.m-center { text-align: center !important; }
.center { margin: 0 auto !important; }
.container { padding: 20px 10px !important }
.td { width: 100% !important; min-width: 100% !important; }
.m-br-15 { height: 15px !important; }
.p30-15 { padding: 30px 15px !important; }
.m-td,
.m-hide { display: none !important; width: 0 !important; height: 0 !important; font-size: 0 !important; line-height: 0 !important; min-height: 0 !important; }
.m-block { display: block !important; }
.fluid-img img { width: 100% !important; max-width: 100% !important; height: auto !important; }
.column,
.column-top,
.column-empty,
.column-empty2,
.column-dir-top { float: left !important; width: 100% !important; display: block !important; }
.column-empty { padding-bottom: 10px !important; }
.column-empty2 { padding-bottom: 30px !important; }
.content-spacing { width: 15px !important; }
}
</style>
</head>
<body class="body" style="padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background:#000000; -webkit-text-size-adjust:none;">
<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#000000">
<tr>
<td align="center" valign="top">
<table width="650" border="0" cellspacing="0" cellpadding="0" class="mobile-shell">
<tr>
<td class="td container" style="width:650px; min-width:650px; font-size:0pt; line-height:0pt; margin:0; font-weight:normal; padding:55px 0px;">
<!-- Article / Image On The Left - Copy On The Right -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="padding-bottom: 10px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="tbrr p30-15" style="padding: 60px 30px; border-radius:26px 26px 0px 0px;" bgcolor="#ffffff">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<th class="column-top" width="210" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="fluid-img" style="font-size:0pt; line-height:0pt; text-align:left;"><img src="cid:{{$.QrcodePngName}}" width="210" height="210" border="0" alt="" /></td>
</tr>
</table>
</th>
<th class="column-empty2" width="30" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;"></th>
<th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
{{if $.User.Firstname}}
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello {{$.User.Firstname}} {{$.User.Lastname}}</td>
{{else}}
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello</td>
{{end}}
</tr>
<tr>
<td class="text pb20" style="color:#000000; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">You or your administrator probably requested this VPN configuration. Scan the Qrcode or open the attached configuration file ({{$.ConfigFileName}}) in the WireGuard VPN client to establish a secure VPN connection.</td>
</tr>
</table>
</th>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- END Article / Image On The Left - Copy On The Right -->
<!-- Two Columns / Articles -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="padding-bottom: 10px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#ffffff">
<tr>
<td>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="p30-15" style="padding: 50px 30px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h3 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:25px; line-height:32px; text-align:left; padding-bottom:20px;">About WireGuard</td>
</tr>
<tr>
<td class="text pb20" style="color:#000000; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.</td>
</tr>
<!-- Button -->
<tr>
<td align="left">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="blue-button text-button" style="background:#000000; color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:14px; line-height:18px; padding:12px 30px; text-align:center; border-radius:0px 22px 22px 22px; font-weight:bold;"><a href="https://www.wireguard.com/install" target="_blank" class="link-white" style="color:#ffffff; text-decoration:none;"><span class="link-white" style="color:#ffffff; text-decoration:none;">Download WireGuard VPN Client</span></a></td>
</tr>
</table>
</td>
</tr>
<!-- END Button -->
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- END Two Columns / Articles -->
<!-- Footer -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="p30-15 bbrr" style="padding: 50px 30px; border-radius:0px 0px 26px 26px;" bgcolor="#ffffff">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="text-footer1 pb10" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:16px; line-height:20px; text-align:center; padding-bottom:10px;">This mail was generated using WireGuard Portal.</td>
</tr>
<tr>
<td class="text-footer2" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:12px; line-height:26px; text-align:center;"><a href="{{$.PortalUrl}}" target="_blank" rel="noopener noreferrer" class="link" style="color:#000000; text-decoration:none;"><span class="link" style="color:#000000; text-decoration:none;">Visit WireGuard Portal</span></a></td>
</tr>
</table>
</td>
</tr>
</table>
<!-- END Footer -->
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,24 @@
{{if $.User.Firstname}}
Hello {{$.User.Firstname}} {{$.User.Lastname}},
{{else}}
Hello,
{{end}}
You or your administrator probably requested this VPN configuration.
Scan the attached Qrcode or open the attached configuration file ({{$.ConfigFileName}})
in the WireGuard VPN client to establish a secure VPN connection.
About WireGuard:
WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.
It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache.
It intends to be considerably more performant than OpenVPN.
You can download and install the WireGuard VPN client from:
https://www.wireguard.com/install/
This mail was generated using WireGuard Portal.
{{$.PortalUrl}}

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="format-detection" content="date=no" />
<meta name="format-detection" content="address=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="x-apple-disable-message-reformatting" />
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,700,700i" rel="stylesheet" />
<!--<![endif]-->
<title>Email Template</title>
<!--[if gte mso 9]>
<style type="text/css" media="all">
sup { font-size: 100% !important; }
</style>
<![endif]-->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style type="text/css" media="screen">
/* Linked Styles */
body { padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background: #ffffff; -webkit-text-size-adjust:none }
a { color: #000000; text-decoration:none }
p { padding:0 !important; margin:0 !important }
img { -ms-interpolation-mode: bicubic; /* Allow smoother rendering of resized image in Internet Explorer */ }
.mcnPreviewText { display: none !important; }
/* Mobile styles */
@media only screen and (max-device-width: 480px), only screen and (max-width: 480px) {
.mobile-shell { width: 100% !important; min-width: 100% !important; }
.bg { background-size: 100% auto !important; -webkit-background-size: 100% auto !important; }
.text-header,
.m-center { text-align: center !important; }
.center { margin: 0 auto !important; }
.container { padding: 20px 10px !important }
.td { width: 100% !important; min-width: 100% !important; }
.m-br-15 { height: 15px !important; }
.p30-15 { padding: 30px 15px !important; }
.m-td,
.m-hide { display: none !important; width: 0 !important; height: 0 !important; font-size: 0 !important; line-height: 0 !important; min-height: 0 !important; }
.m-block { display: block !important; }
.fluid-img img { width: 100% !important; max-width: 100% !important; height: auto !important; }
.column,
.column-top,
.column-empty,
.column-empty2,
.column-dir-top { float: left !important; width: 100% !important; display: block !important; }
.column-empty { padding-bottom: 10px !important; }
.column-empty2 { padding-bottom: 30px !important; }
.content-spacing { width: 15px !important; }
}
</style>
</head>
<body class="body" style="padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background:#000000; -webkit-text-size-adjust:none;">
<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#000000">
<tr>
<td align="center" valign="top">
<table width="650" border="0" cellspacing="0" cellpadding="0" class="mobile-shell">
<tr>
<td class="td container" style="width:650px; min-width:650px; font-size:0pt; line-height:0pt; margin:0; font-weight:normal; padding:55px 0px;">
<!-- Article / Image On The Left - Copy On The Right -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="padding-bottom: 10px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="tbrr p30-15" style="padding: 60px 30px; border-radius:26px 26px 0px 0px;" bgcolor="#ffffff">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<th class="column-top" width="210" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="fluid-img" style="font-size:0pt; line-height:0pt; text-align:left;"><img src="cid:{{$.QrcodePngName}}" width="210" height="210" border="0" alt="" /></td>
</tr>
</table>
</th>
<th class="column-empty2" width="30" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;"></th>
<th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
{{if $.User.Firstname}}
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello {{$.User.Firstname}} {{$.User.Lastname}}</td>
{{else}}
<td class="h4 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello</td>
{{end}}
</tr>
<tr>
<td class="text pb20" style="color:#000000; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">You or your administrator probably requested this VPN configuration. Scan the Qrcode or open the attached configuration file ({{$.Peer.GetConfigFileName}}) in the WireGuard VPN client to establish a secure VPN connection.</td>
</tr>
</table>
</th>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- END Article / Image On The Left - Copy On The Right -->
<!-- Two Columns / Articles -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="padding-bottom: 10px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#ffffff">
<tr>
<td>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="p30-15" style="padding: 50px 30px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h3 pb20" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:25px; line-height:32px; text-align:left; padding-bottom:20px;">About WireGuard</td>
</tr>
<tr>
<td class="text pb20" style="color:#000000; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.</td>
</tr>
<!-- Button -->
<tr>
<td align="left">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="blue-button text-button" style="background:#000000; color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:14px; line-height:18px; padding:12px 30px; text-align:center; border-radius:0px 22px 22px 22px; font-weight:bold;"><a href="https://www.wireguard.com/install" target="_blank" class="link-white" style="color:#ffffff; text-decoration:none;"><span class="link-white" style="color:#ffffff; text-decoration:none;">Download WireGuard VPN Client</span></a></td>
</tr>
</table>
</td>
</tr>
<!-- END Button -->
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- END Two Columns / Articles -->
<!-- Footer -->
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="p30-15 bbrr" style="padding: 50px 30px; border-radius:0px 0px 26px 26px;" bgcolor="#ffffff">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="text-footer1 pb10" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:16px; line-height:20px; text-align:center; padding-bottom:10px;">This mail was generated using WireGuard Portal.</td>
</tr>
<tr>
<td class="text-footer2" style="color:#000000; font-family:'Muli', Arial,sans-serif; font-size:12px; line-height:26px; text-align:center;"><a href="{{$.PortalUrl}}" target="_blank" rel="noopener noreferrer" class="link" style="color:#000000; text-decoration:none;"><span class="link" style="color:#000000; text-decoration:none;">Visit WireGuard Portal</span></a></td>
</tr>
</table>
</td>
</tr>
</table>
<!-- END Footer -->
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -0,0 +1,24 @@
{{if $.User.Firstname}}
Hello {{$.User.Firstname}} {{$.User.Lastname}},
{{else}}
Hello,
{{end}}
You or your administrator probably requested this VPN configuration.
Scan the attached Qrcode or open the attached configuration file ({{$.ConfigFileName}})
in the WireGuard VPN client to establish a secure VPN connection.
About WireGuard:
WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.
It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache.
It intends to be considerably more performant than OpenVPN.
You can download and install the WireGuard VPN client from:
https://www.wireguard.com/install/
This mail was generated using WireGuard Portal.
{{$.PortalUrl}}