Skip to content

Commit

Permalink
Merge pull request #63 from nwesterhausen/improvements
Browse files Browse the repository at this point in the history
Fixes an issue where SMTP settings would not save if entered in the web UI.

Adds a configuration option to disable configuration via the web UI (or API). It is configured by default to allow configuration editing in the web UI (which matches current behavior). But by setting the showConfiguration in the app section to false (and restarting the server) will completely disable any editing of configuration in the web UI. This includes:

    Editing any configuration values
    Adding, updating, or removing domains
    Reading SMTP configuration
    Reading the admin email for alerts

This should cover all instances of info that you wouldn't want leaking; of course, all the domains on the dashboard are visible with the data included there. This is more to lock the configuration and not allow any visitor to change settings or read sensitive settings.
  • Loading branch information
nwesterhausen authored Apr 11, 2024
2 parents 3c26e6c + ce02a82 commit c1cc5cb
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 42 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Workspace settings
{
"files.exclude": {
"**/*_templ.go": true,
"node_modules/": true,
}
}
4 changes: 2 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ func main() {
app.Use(middleware.Logger())

// set up our routes
handlers.SetupRoutes(app)
handlers.SetupRoutes(app, config.Config.App.ShowConfiguration)
handlers.SetupConfigRoutes(app, config)
handlers.SetupDomainRoutes(app, domains)
handlers.SetupDomainRoutes(app, domains, config.Config.App.ShowConfiguration)

// if the mailer was configured, add the routes
if _mailer != nil {
Expand Down
3 changes: 3 additions & 0 deletions configuration/app.configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type AppConfiguration struct {
Port int `yaml:"port" json:"port" default:"3124"`
// Allow automtic WHOIS refresh
AutomateWHOISRefresh bool `yaml:"automateWHOISRefresh" json:"automateWHOISRefresh" default:"true"`
// Show the configuration in the web interface. This is a security risk and should be disabled in production
ShowConfiguration bool `yaml:"showConfiguration" json:"showConfiguration" default:"false"`
}

type AlertsConfiguration struct {
Expand Down Expand Up @@ -95,6 +97,7 @@ func DefaultConfiguration(filepath string) Configuration {
App: AppConfiguration{
Port: 3124,
AutomateWHOISRefresh: true,
ShowConfiguration: true,
},
Scheduler: SchedulerConfiguration{
WhoisCacheStaleInterval: 190,
Expand Down
8 changes: 6 additions & 2 deletions handlers/base.handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (
"github.com/nwesterhausen/domain-monitor/views/layout"
)

func HandlerShowBase(c echo.Context) error {
return View(c, layout.Base())
type BaseHandler struct {
IncludeConfiguration bool
}

func (bh *BaseHandler) HandlerShowBase(c echo.Context) error {
return View(c, layout.Base(bh.IncludeConfiguration))
}
45 changes: 28 additions & 17 deletions handlers/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,41 @@ import (
"github.com/nwesterhausen/domain-monitor/service"
)

func SetupRoutes(app *echo.Echo) {
app.GET("/", HandlerShowBase)
func SetupRoutes(app *echo.Echo, includeConfiguration bool) {
bh := &BaseHandler{IncludeConfiguration: includeConfiguration}
app.GET("/", bh.HandlerShowBase)

app.GET("/dashboard", HandlerRenderDashboard)
app.GET("/configuration", HandlerRenderConfiguration)
if includeConfiguration {
app.GET("/configuration", HandlerRenderConfiguration)
}
}

func SetupDomainRoutes(app *echo.Echo, domains configuration.DomainConfiguration) {
func SetupDomainRoutes(app *echo.Echo, domains configuration.DomainConfiguration, configurationEnabled bool) {
domainHtmx := app.Group("/domain")
domainApi := app.Group("/api/domain")

ds := service.NewDomainService(domains)
dhapi := NewApiDomainHandler(ds)
dh := NewDomainHandler(ds)

domainApi.POST("/create", dhapi.HandleDomainCreate)
domainApi.GET("", dhapi.HandleDomainList)
domainApi.GET("/:fqdn", dhapi.HandleDomainShow)
domainApi.PUT("/:fqdn", dhapi.HandleDomainUpdate)
domainApi.DELETE("/:fqdn", dhapi.HandleDomainDelete)
if configurationEnabled {
domainApi.POST("/create", dhapi.HandleDomainCreate)
domainApi.PUT("/:fqdn", dhapi.HandleDomainUpdate)
domainApi.DELETE("/:fqdn", dhapi.HandleDomainDelete)
}

domainHtmx.GET("/:fqdn/card", dh.GetCard)
domainHtmx.GET("/cards", dh.GetCards)
domainHtmx.GET("/tbody", dh.GetListTbody)
domainHtmx.GET("/edit/:fqdn", dh.GetEditDomain)
domainHtmx.POST("/update", dh.PostUpdateDomain)
domainHtmx.POST("/new", dh.PostNewDomain)
domainHtmx.DELETE("/:fqdn", dh.DeleteDomain)
if configurationEnabled {
domainHtmx.POST("/update", dh.PostUpdateDomain)
domainHtmx.POST("/new", dh.PostNewDomain)
domainHtmx.DELETE("/:fqdn", dh.DeleteDomain)
}
}

func SetupConfigRoutes(app *echo.Echo, config configuration.Configuration) {
Expand All @@ -45,13 +52,17 @@ func SetupConfigRoutes(app *echo.Echo, config configuration.Configuration) {
ch := NewConfigurationHandler(cs)

configApi.GET("/:section/:key", ch.GetSectionKey)
configApi.POST("/:section/:key", ch.SetSectionKey)

configGroup.GET("/app", ch.RenderAppConfiguration)
configGroup.GET("/domain", ch.RenderDomainConfiguration)
configGroup.GET("/smtp", ch.RenderSmtpConfiguration)
configGroup.GET("/scheduler", ch.RenderSchedulerConfiguration)
configGroup.GET("/alerts", ch.RenderAlertsConfiguration)
if config.Config.App.ShowConfiguration {
configApi.POST("/:section/:key", ch.SetSectionKey)
}

if config.Config.App.ShowConfiguration {
configGroup.GET("/app", ch.RenderAppConfiguration)
configGroup.GET("/domain", ch.RenderDomainConfiguration)
configGroup.GET("/smtp", ch.RenderSmtpConfiguration)
configGroup.GET("/scheduler", ch.RenderSchedulerConfiguration)
configGroup.GET("/alerts", ch.RenderAlertsConfiguration)
}
}

func SetupMailerRoutes(app *echo.Echo, ms *service.MailerService, alertRecipient string) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.1.0",
"version": "1.2.0",
"repository": {
"type": "git",
"url": "https://github.com/nwesterhausen/domain-monitor"
Expand Down
21 changes: 20 additions & 1 deletion service/configuration.service.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ func (s *ConfigurationService) GetConfigurationValue(section string, key string)
return s.GetAppConfiguration().Port, nil
case "automateWHOISRefresh":
return s.GetAppConfiguration().AutomateWHOISRefresh, nil
case "showConfiguration":
return s.GetAppConfiguration().ShowConfiguration, nil
default:
return nil, &ErrInvalidConfigurationKey{
Key: key,
Expand All @@ -94,6 +96,10 @@ func (s *ConfigurationService) GetConfigurationValue(section string, key string)
case "alerts":
switch key {
case "admin":
if !s.GetAppConfiguration().ShowConfiguration {
log.Println("🚨 Configuration editing is disabled in config.yaml (Admin email is not accessible via GET)")
return nil, errors.New("configuration editing is disabled")
}
return s.GetAlertsConfiguration().Admin, nil
case "sendAlerts":
return s.GetAlertsConfiguration().SendAlerts, nil
Expand All @@ -115,6 +121,11 @@ func (s *ConfigurationService) GetConfigurationValue(section string, key string)
}
}
case "smtp":
if !s.GetAppConfiguration().ShowConfiguration {
log.Println("🚨 Configuration editing is disabled in config.yaml (SMTP settings are not accessible via GET)")
return nil, errors.New("configuration editing is disabled")
}

switch key {
case "host":
return s.GetSMTPConfiguration().Host, nil
Expand Down Expand Up @@ -157,10 +168,16 @@ func (s *ConfigurationService) GetConfigurationValue(section string, key string)

// Set each specific configuration value
func (s *ConfigurationService) SetConfigurationValue(section string, key string, value interface{}) error {
if !s.GetAppConfiguration().ShowConfiguration {
log.Println("🚨 Configuration editing is disabled in config.yaml")
return errors.New("configuration editing is disabled")
}


stringVal, ok := value.(string)
if !ok {
log.Println("Value is not expected type (string)")
return errors.New("Value is not expected type (string)")
return errors.New("value is not expected type (string)")
}
intVal, intErr := strconv.Atoi(stringVal)
// The toggles just send "on" or "" as the string for the value
Expand All @@ -180,6 +197,8 @@ func (s *ConfigurationService) SetConfigurationValue(section string, key string,
s.store.Config.App.Port = intVal
case "automateWHOISRefresh":
s.store.Config.App.AutomateWHOISRefresh = boolVal
case "showConfiguration":
s.store.Config.App.ShowConfiguration = boolVal
default:
return &ErrInvalidConfigurationKey{
Key: key,
Expand Down
41 changes: 26 additions & 15 deletions views/configuration/configuration.templ
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@ templ Configuration() {
<div>
<h1 class="text-xl text-secondary">Configuration</h1>
<p class="p-2">
Changes here are applied to the two config files located in a config dir where you run this server. If you want to
modify those files directly, changes will be realized upon restarting the server. Committing changes using this
configuration page will cause the server to restart so whatever changes you make take immediate effect.
Changes here are applied to the two config files located in a config dir where you run this server (<code>domain.yaml</code> and <code>config.yaml</code>).
After making changes, you will need to restart the server for them to take effect.
</p>
</div>
<div role="tablist" class="tabs tabs-boxed">
<a role="tab" hx-target="#tabContent" hx-get="/config/domain" class="transition-color tab config-tab tab-active" _="on click remove .tab-active from .config-tab then add .tab-active to me">Domains</a>
<a role="tab" hx-target="#tabContent" hx-get="/config/app" class="transition-color tab config-tab" _="on click remove .tab-active from .config-tab then add .tab-active to me">Application</a>
<a role="tab" hx-target="#tabContent" hx-get="/config/app" class="transition-color tab config-tab tab-active" _="on click remove .tab-active from .config-tab then add .tab-active to me">Application</a>
<a role="tab" hx-target="#tabContent" hx-get="/config/domain" class="transition-color tab config-tab " _="on click remove .tab-active from .config-tab then add .tab-active to me">Domains</a>
<a role="tab" hx-target="#tabContent" hx-get="/config/alerts" class="transition-color tab config-tab" _="on click remove .tab-active from .config-tab then add .tab-active to me">Alerts</a>
<a role="tab" hx-target="#tabContent" hx-get="/config/smtp" class="transition-color tab config-tab" _="on click remove .tab-active from .config-tab then add .tab-active to me">SMTP</a>
<a role="tab" hx-target="#tabContent" hx-get="/config/scheduler" class="transition-color tab config-tab" _="on click remove .tab-active from .config-tab then add .tab-active to me">Scheduler</a>
</div>
<div id="tabContent" class="p-2 mt-3" hx-get="/config/domain" hx-trigger="load"></div>
<div id="tabContent" class="p-2 mt-3" hx-get="/config/app" hx-trigger="load"></div>
</div>
}

Expand Down Expand Up @@ -73,7 +72,14 @@ templ AppTab(conf configuration.AppConfiguration) {
/>
</label>
</div>
<button class="btn btn-xs btn-outline btn-success max-w-md ms-8">Save</button>
<div class="form-control max-w-md">
<label class="label cursor-pointer">
<span class="label-text">Allow Configuration from Web GUI</span>
<input type="checkbox" name="value" class="toggle toggle-success" checked?={conf.ShowConfiguration}
hx-post="/api/config/app/showConfiguration" hx-trigger="click throttle:10ms" hx-inclue="this"
/>
</label>
</div>
</div>
</div>
}
Expand All @@ -95,7 +101,7 @@ templ AlertsTab(conf configuration.AlertsConfiguration) {
<div class="label">
<span class="label-text">Admin Email</span>
</div>
<input type="text" placeholder="smtp.example.com" class="input input-bordered w-full max-w-lg" value={conf.Admin} name="value" />
<input type="text" placeholder="admin@example.com" class="input input-bordered w-full max-w-lg" value={conf.Admin} name="value" />
<div class="label">
<span class="label-text-alt">The email that any alerts should be sent to</span>
</div>
Expand Down Expand Up @@ -167,7 +173,8 @@ templ SmtpTab(conf configuration.SMTPConfiguration) {
<div class="label">
<span class="label-text">SMTP Host</span>
</div>
<input type="text" placeholder="smtp.example.com" class="input input-bordered w-full max-w-lg" value={conf.Host} />
<input type="text" placeholder="smtp.example.com" class="input input-bordered w-full max-w-lg" value={conf.Host} name="value"
hx-post="/api/config/smtp/host" hx-trigger="input throttle:500ms" hx-inclue="this"/>
<div class="label">
<span class="label-text-alt">The SMTP hostname (or IP address)</span>
</div>
Expand All @@ -176,7 +183,8 @@ templ SmtpTab(conf configuration.SMTPConfiguration) {
<div class="label">
<span class="label-text">SMTP Port</span>
</div>
<input type="text" placeholder="25" class="input input-bordered w-full max-w-lg" value={strconv.Itoa(conf.Port)} />
<input type="text" placeholder="25" class="input input-bordered w-full max-w-lg" value={strconv.Itoa(conf.Port)} name="value"
hx-post="/api/config/smtp/host" hx-trigger="input throttle:500ms" hx-inclue="this" />
<div class="label">
<span class="label-text-alt">The SMTP port to connect to</span>
</div>
Expand All @@ -192,7 +200,8 @@ templ SmtpTab(conf configuration.SMTPConfiguration) {
<div class="label">
<span class="label-text">SMTP Username</span>
</div>
<input type="text" placeholder="smtpuser" class="input input-bordered w-full max-w-lg" value={conf.AuthUser} />
<input type="text" placeholder="smtpuser" class="input input-bordered w-full max-w-lg" value={conf.AuthUser} name="value"
hx-post="/api/config/smtp/authUser" hx-trigger="input throttle:500ms" hx-inclue="this" />
<div class="label">
<span class="label-text-alt">Username if required to login to SMTP server</span>
</div>
Expand All @@ -201,7 +210,8 @@ templ SmtpTab(conf configuration.SMTPConfiguration) {
<div class="label">
<span class="label-text">SMTP Password</span>
</div>
<input type="password" placeholder="" class="input input-bordered w-full max-w-lg" value={conf.AuthPass} />
<input type="password" placeholder="" class="input input-bordered w-full max-w-lg" value={conf.AuthPass} name="value"
hx-post="/api/config/smtp/authPass" hx-trigger="input throttle:500ms" hx-inclue="this" />
<div class="label">
<span class="label-text-alt">Password if required to login to SMTP server</span>
</div>
Expand All @@ -210,7 +220,8 @@ templ SmtpTab(conf configuration.SMTPConfiguration) {
<div class="label">
<span class="label-text">From Name</span>
</div>
<input type="text" placeholder="Domain Monitor" class="input input-bordered w-full max-w-lg" value={conf.FromName} />
<input type="text" placeholder="Domain Monitor" class="input input-bordered w-full max-w-lg" value={conf.FromName} name="value"
hx-post="/api/config/smtp/fromName" hx-trigger="input throttle:500ms" hx-inclue="this" />
<div class="label">
<span class="label-text-alt">Name to use in the from field for email messages</span>
</div>
Expand All @@ -219,12 +230,12 @@ templ SmtpTab(conf configuration.SMTPConfiguration) {
<div class="label">
<span class="label-text">From Address</span>
</div>
<input type="text" placeholder="[email protected]" class="input input-bordered w-full max-w-lg" value={conf.FromAddress} />
<input type="text" placeholder="[email protected]" class="input input-bordered w-full max-w-lg" value={conf.FromAddress} name="value"
hx-post="/api/config/smtp/fromAddress" hx-trigger="input throttle:500ms" hx-inclue="this" />
<div class="label">
<span class="label-text-alt">Email address to use in the from field for messages</span>
</div>
</label>
<button class="btn btn-xs btn-outline btn-success max-w-md ms-8">Save</button>
</div>
</div>
}
Expand Down
11 changes: 9 additions & 2 deletions views/layout/base.layout.templ
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package layout

import "github.com/nwesterhausen/domain-monitor/views/modal"

templ Base() {
templ Base(includeConfig bool) {
<!DOCTYPE html>
<html lang="en" data-theme="fantasy">
<head>
Expand All @@ -22,7 +22,7 @@ templ Base() {
</head>
<body>
<div class="container-fluid">
@Navigation()
@Navigation(includeConfig)
<div class="container-fluid" id="content">
<div hx-get="/dashboard" hx-trigger="load">
<p class="htmx-indicator">
Expand All @@ -36,3 +36,10 @@ templ Base() {
</body>
</html>
}

templ BaseWithConfig() {
@Base(true)
}
templ BaseWithoutConfig() {
@Base(false)
}
10 changes: 8 additions & 2 deletions views/layout/navigation.layout.templ
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package layout

templ Navigation() {
templ Navigation(withConfiguration bool) {
<div class="navbar bg-base-100">
<div class="navbar-start">
<span class="text-xl">Domain Monitor</span>
Expand All @@ -13,10 +13,16 @@ templ Navigation() {
Loading <span class="loading loading-dots loading-xs"></span>
</div>
</div>
if withConfiguration {
@ConfigurationButton()
}
</div>
}

templ ConfigurationButton() {
<div class="navbar-end">
<ul class="menu menu-horizontal px-1">
<li><a hx-get="/configuration" hx-indicator="#loading-indication" hx-target="#content">Configuration</a></li>
</ul>
</div>
</div>
}

0 comments on commit c1cc5cb

Please sign in to comment.