refactor template handling

This commit is contained in:
Christoph Haas 2023-06-21 00:03:44 +02:00
parent 4c7f2b24bc
commit 7a0a3b1e9e
14 changed files with 129 additions and 25 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/h44z/wg-portal/internal/app/api/core" "github.com/h44z/wg-portal/internal/app/api/core"
handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers" handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers"
"github.com/h44z/wg-portal/internal/app/auth" "github.com/h44z/wg-portal/internal/app/auth"
"github.com/h44z/wg-portal/internal/app/filetemplate"
"github.com/h44z/wg-portal/internal/app/users" "github.com/h44z/wg-portal/internal/app/users"
"github.com/h44z/wg-portal/internal/app/wireguard" "github.com/h44z/wg-portal/internal/app/wireguard"
"os" "os"
@ -63,7 +64,12 @@ func main() {
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard) statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard)
internal.AssertNoError(err) internal.AssertNoError(err)
backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager, statisticsCollector) templateManager, err := filetemplate.NewTemplateManager(cfg, database, database)
internal.AssertNoError(err)
backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager,
statisticsCollector, templateManager)
internal.AssertNoError(err) internal.AssertNoError(err)
err = backend.Startup(ctx) err = backend.Startup(ctx)
internal.AssertNoError(err) internal.AssertNoError(err)

View File

@ -201,7 +201,7 @@ func (r *SqlRepo) migrate() error {
func (r *SqlRepo) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) { func (r *SqlRepo) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) {
var in domain.Interface var in domain.Interface
err := r.db.WithContext(ctx).First(&in, id).Error err := r.db.WithContext(ctx).Preload("Addresses").First(&in, id).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
return nil, domain.ErrNotFound return nil, domain.ErrNotFound
@ -356,6 +356,17 @@ func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIden
// region peers // region peers
func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
var peer domain.Peer
err := r.db.WithContext(ctx).Where("identifier = ?", id).Find(&peer).Error
if err != nil {
return nil, err
}
return &peer, nil
}
func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) { func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) {
var peers []domain.Peer var peers []domain.Peer

View File

@ -5,6 +5,7 @@ import (
"github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app"
"github.com/h44z/wg-portal/internal/app/api/v0/model" "github.com/h44z/wg-portal/internal/app/api/v0/model"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
"io"
"net/http" "net/http"
) )
@ -129,17 +130,23 @@ func (e interfaceEndpoint) handleConfigGet() gin.HandlerFunc {
return return
} }
c.JSON(http.StatusOK, `[Interface] config, err := e.app.GetInterfaceConfig(c.Request.Context(), domain.InterfaceIdentifier(id))
Address = 10.0.0.1/32, fd12:3456:789a::1/128 if err != nil {
ListenPort = 51820 c.JSON(http.StatusInternalServerError, model.Error{
PrivateKey = <Private Key> Code: http.StatusInternalServerError, Message: err.Error(),
SaveConfig = true })
return
}
[Peer] configString, err := io.ReadAll(config)
PublicKey = <Client public key> if err != nil {
PresharedKey = <Pre-Shared Key> c.JSON(http.StatusInternalServerError, model.Error{
AllowedIPs = 10.0.0.2/32,fd12:3456:789a::2/128 Code: http.StatusInternalServerError, Message: err.Error(),
PersistentKeepalive = 25`) })
return
}
c.JSON(http.StatusOK, string(configString))
} }
} }

View File

@ -19,9 +19,10 @@ type App struct {
UserManager UserManager
WireGuardManager WireGuardManager
StatisticsCollector StatisticsCollector
TemplateManager
} }
func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator, users UserManager, wireGuard WireGuardManager, stats StatisticsCollector) (*App, error) { func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator, users UserManager, wireGuard WireGuardManager, stats StatisticsCollector, templates TemplateManager) (*App, error) {
a := &App{ a := &App{
Config: cfg, Config: cfg,
@ -31,6 +32,7 @@ func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator,
UserManager: users, UserManager: users,
WireGuardManager: wireGuard, WireGuardManager: wireGuard,
StatisticsCollector: stats, StatisticsCollector: stats,
TemplateManager: templates,
} }
startupContext, cancel := context.WithTimeout(context.Background(), 30*time.Second) startupContext, cancel := context.WithTimeout(context.Background(), 30*time.Second)

View File

@ -0,0 +1,52 @@
package filetemplate
import (
"context"
"fmt"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
"io"
)
type Manager struct {
cfg *config.Config
tplHandler *TemplateHandler
users UserDatabaseRepo
wg WireguardDatabaseRepo
}
func NewTemplateManager(cfg *config.Config, users UserDatabaseRepo, wg WireguardDatabaseRepo) (*Manager, error) {
tplHandler, err := newTemplateHandler()
if err != nil {
return nil, fmt.Errorf("failed to initialize template handler: %w", err)
}
m := &Manager{
cfg: cfg,
tplHandler: tplHandler,
users: users,
wg: wg,
}
return m, nil
}
func (m Manager) GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) {
iface, peers, err := m.wg.GetInterfaceAndPeers(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to fetch interface %s: %w", id, err)
}
return m.tplHandler.GetInterfaceConfig(iface, peers)
}
func (m Manager) GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) {
peer, err := m.wg.GetPeer(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to fetch peer %s: %w", id, err)
}
return m.tplHandler.GetPeerConfig(peer)
}

View File

@ -0,0 +1,16 @@
package filetemplate
import (
"context"
"github.com/h44z/wg-portal/internal/domain"
)
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

@ -1,4 +1,4 @@
package app package filetemplate
import ( import (
"bytes" "bytes"
@ -14,30 +14,34 @@ import (
//go:embed tpl_files/* //go:embed tpl_files/*
var TemplateFiles embed.FS var TemplateFiles embed.FS
type templateHandler struct { type TemplateHandler struct {
wireGuardTemplates *template.Template wireGuardTemplates *template.Template
mailHtmlTemplates *htmlTemplate.Template mailHtmlTemplates *htmlTemplate.Template
mailTextTemplates *template.Template mailTextTemplates *template.Template
} }
func newTemplateHandler() (*templateHandler, error) { func newTemplateHandler() (*TemplateHandler, error) {
templateCache, err := template.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.tpl") tplFuncs := template.FuncMap{
"CidrsToString": domain.CidrsToString,
}
templateCache, err := template.New("WireGuard").Funcs(tplFuncs).ParseFS(TemplateFiles, "tpl_files/*.tpl")
if err != nil { if err != nil {
return nil, err return nil, err
} }
mailHtmlTemplateCache, err := htmlTemplate.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.gohtml") mailHtmlTemplateCache, err := htmlTemplate.New("WireGuard").Funcs(tplFuncs).ParseFS(TemplateFiles, "tpl_files/*.gohtml")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse html template files: %w", err) return nil, fmt.Errorf("failed to parse html template files: %w", err)
} }
mailTxtTemplateCache, err := template.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.gotpl") mailTxtTemplateCache, err := template.New("WireGuard").Funcs(tplFuncs).ParseFS(TemplateFiles, "tpl_files/*.gotpl")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse text template files: %w", err) return nil, fmt.Errorf("failed to parse text template files: %w", err)
} }
handler := &templateHandler{ handler := &TemplateHandler{
wireGuardTemplates: templateCache, wireGuardTemplates: templateCache,
mailHtmlTemplates: mailHtmlTemplateCache, mailHtmlTemplates: mailHtmlTemplateCache,
mailTextTemplates: mailTxtTemplateCache, mailTextTemplates: mailTxtTemplateCache,
@ -46,7 +50,7 @@ func newTemplateHandler() (*templateHandler, error) {
return handler, nil return handler, nil
} }
func (c templateHandler) GetInterfaceConfig(cfg *domain.Interface, peers []*domain.Peer) (io.Reader, error) { func (c TemplateHandler) GetInterfaceConfig(cfg *domain.Interface, peers []domain.Peer) (io.Reader, error) {
var tplBuff bytes.Buffer var tplBuff bytes.Buffer
err := c.wireGuardTemplates.ExecuteTemplate(&tplBuff, "wg_interface.tpl", map[string]interface{}{ err := c.wireGuardTemplates.ExecuteTemplate(&tplBuff, "wg_interface.tpl", map[string]interface{}{
@ -63,7 +67,7 @@ func (c templateHandler) GetInterfaceConfig(cfg *domain.Interface, peers []*doma
return &tplBuff, nil return &tplBuff, nil
} }
func (c templateHandler) GetPeerConfig(peer *domain.Peer) (io.Reader, error) { func (c TemplateHandler) GetPeerConfig(peer *domain.Peer) (io.Reader, error) {
var tplBuff bytes.Buffer var tplBuff bytes.Buffer
err := c.wireGuardTemplates.ExecuteTemplate(&tplBuff, "wg_peer.tpl", map[string]interface{}{ err := c.wireGuardTemplates.ExecuteTemplate(&tplBuff, "wg_peer.tpl", map[string]interface{}{
@ -79,7 +83,7 @@ func (c templateHandler) GetPeerConfig(peer *domain.Peer) (io.Reader, error) {
return &tplBuff, nil return &tplBuff, nil
} }
func (c templateHandler) GetConfigMail(user *domain.User, peer *domain.Peer, link string) (io.Reader, io.Reader, error) { func (c TemplateHandler) GetConfigMail(user *domain.User, peer *domain.Peer, link string) (io.Reader, io.Reader, error) {
var tplBuff bytes.Buffer var tplBuff bytes.Buffer
var htmlTplBuff bytes.Buffer var htmlTplBuff bytes.Buffer
@ -104,7 +108,7 @@ func (c templateHandler) GetConfigMail(user *domain.User, peer *domain.Peer, lin
return &tplBuff, &htmlTplBuff, nil return &tplBuff, &htmlTplBuff, nil
} }
func (c templateHandler) GetConfigMailWithAttachment(user *domain.User, peer *domain.Peer) (io.Reader, io.Reader, error) { func (c TemplateHandler) GetConfigMailWithAttachment(user *domain.User, peer *domain.Peer) (io.Reader, io.Reader, error) {
var tplBuff bytes.Buffer var tplBuff bytes.Buffer
var htmlTplBuff bytes.Buffer var htmlTplBuff bytes.Buffer

View File

@ -12,7 +12,7 @@
# Core settings # Core settings
PrivateKey = {{ .Interface.KeyPair.PrivateKey }} PrivateKey = {{ .Interface.KeyPair.PrivateKey }}
Address = {{ .Interface.AddressStr }} Address = {{ CidrsToString .Interface.Addresses }}
# Misc. settings (optional) # Misc. settings (optional)
{{- if ne .Interface.ListenPort 0}} {{- if ne .Interface.ListenPort 0}}

View File

@ -3,6 +3,7 @@ package app
import ( import (
"context" "context"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
"io"
) )
type Authenticator interface { type Authenticator interface {
@ -41,3 +42,8 @@ type WireGuardManager interface {
type StatisticsCollector interface { type StatisticsCollector interface {
StartBackgroundJobs(ctx context.Context) StartBackgroundJobs(ctx context.Context)
} }
type TemplateManager interface {
GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error)
GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
}