Mikrotik integration (#467)
Some checks failed
Docker / Build and Push (push) Has been cancelled
github-pages / deploy (push) Has been cancelled
Docker / release (push) Has been cancelled

Allow MikroTik routes as WireGuard backends
This commit is contained in:
h44z
2025-08-10 14:42:02 +02:00
committed by GitHub
parent a86f83a219
commit 112f6bfb77
40 changed files with 3150 additions and 205 deletions

View File

@@ -21,17 +21,23 @@ import (
//go:embed frontend_config.js.gotpl
var frontendJs embed.FS
type ControllerManager interface {
GetControllerNames() []config.BackendBase
}
type ConfigEndpoint struct {
cfg *config.Config
authenticator Authenticator
controllerMgr ControllerManager
tpl *respond.TemplateRenderer
}
func NewConfigEndpoint(cfg *config.Config, authenticator Authenticator) ConfigEndpoint {
func NewConfigEndpoint(cfg *config.Config, authenticator Authenticator, ctrlMgr ControllerManager) ConfigEndpoint {
ep := ConfigEndpoint{
cfg: cfg,
authenticator: authenticator,
controllerMgr: ctrlMgr,
tpl: respond.NewTemplateRenderer(template.Must(template.ParseFS(frontendJs,
"frontend_config.js.gotpl"))),
}
@@ -96,13 +102,36 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
sessionUser := domain.GetUserInfo(r.Context())
controllerFn := func() []model.SettingsBackendNames {
controllers := e.controllerMgr.GetControllerNames()
names := make([]model.SettingsBackendNames, 0, len(controllers))
for _, controller := range controllers {
displayName := controller.GetDisplayName()
if displayName == "" {
displayName = controller.Id // fallback to ID if no display name is set
}
if controller.Id == config.LocalBackendName {
displayName = "modals.interface-edit.backend.local" // use a localized string for the local backend
}
names = append(names, model.SettingsBackendNames{
Id: controller.Id,
Name: displayName,
})
}
return names
}
hasSocialLogin := len(e.cfg.Auth.OAuth) > 0 || len(e.cfg.Auth.OpenIDConnect) > 0 || e.cfg.Auth.WebAuthn.Enabled
// For anonymous users, we return the settings object with minimal information
if sessionUser.Id == domain.CtxUnknownUserId || sessionUser.Id == "" {
respond.JSON(w, http.StatusOK, model.Settings{
WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled,
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled,
AvailableBackends: []model.SettingsBackendNames{}, // return an empty list instead of null
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
})
} else {
respond.JSON(w, http.StatusOK, model.Settings{
@@ -112,6 +141,7 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc {
ApiAdminOnly: e.cfg.Advanced.ApiAdminOnly,
WebAuthnEnabled: e.cfg.Auth.WebAuthn.Enabled,
MinPasswordLength: e.cfg.Auth.MinPasswordLength,
AvailableBackends: controllerFn(),
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
})
}

View File

@@ -6,11 +6,17 @@ type Error struct {
}
type Settings struct {
MailLinkOnly bool `json:"MailLinkOnly"`
PersistentConfigSupported bool `json:"PersistentConfigSupported"`
SelfProvisioning bool `json:"SelfProvisioning"`
ApiAdminOnly bool `json:"ApiAdminOnly"`
WebAuthnEnabled bool `json:"WebAuthnEnabled"`
MinPasswordLength int `json:"MinPasswordLength"`
LoginFormVisible bool `json:"LoginFormVisible"`
MailLinkOnly bool `json:"MailLinkOnly"`
PersistentConfigSupported bool `json:"PersistentConfigSupported"`
SelfProvisioning bool `json:"SelfProvisioning"`
ApiAdminOnly bool `json:"ApiAdminOnly"`
WebAuthnEnabled bool `json:"WebAuthnEnabled"`
MinPasswordLength int `json:"MinPasswordLength"`
AvailableBackends []SettingsBackendNames `json:"AvailableBackends"`
LoginFormVisible bool `json:"LoginFormVisible"`
}
type SettingsBackendNames struct {
Id string `json:"Id"`
Name string `json:"Name"`
}

View File

@@ -4,6 +4,7 @@ import (
"time"
"github.com/h44z/wg-portal/internal"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
)
@@ -11,6 +12,7 @@ type Interface struct {
Identifier string `json:"Identifier" example:"wg0"` // device name, for example: wg0
DisplayName string `json:"DisplayName"` // a nice display name/ description for the interface
Mode string `json:"Mode" example:"server"` // the interface type, either 'server', 'client' or 'any'
Backend string `json:"Backend" example:"local"` // the backend used for this interface e.g., local, mikrotik, ...
PrivateKey string `json:"PrivateKey" example:"abcdef=="` // private Key of the server interface
PublicKey string `json:"PublicKey" example:"abcdef=="` // public Key of the server interface
Disabled bool `json:"Disabled"` // flag that specifies if the interface is enabled (up) or not (down)
@@ -57,6 +59,7 @@ func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
Identifier: string(src.Identifier),
DisplayName: src.DisplayName,
Mode: string(src.Type),
Backend: string(src.Backend),
PrivateKey: src.PrivateKey,
PublicKey: src.PublicKey,
Disabled: src.IsDisabled(),
@@ -92,6 +95,10 @@ func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
Filename: src.GetConfigFileName(),
}
if iface.Backend == "" {
iface.Backend = config.LocalBackendName // default to local backend
}
if len(peers) > 0 {
iface.TotalPeers = len(peers)
@@ -146,6 +153,7 @@ func NewDomainInterface(src *Interface) *domain.Interface {
SaveConfig: src.SaveConfig,
DisplayName: src.DisplayName,
Type: domain.InterfaceType(src.Mode),
Backend: domain.InterfaceBackend(src.Backend),
DriverType: "", // currently unused
Disabled: nil, // set below
DisabledReason: src.DisabledReason,