added setting to turn off login/edits - LOGIN_AND_EDITS
This commit is contained in:
@@ -80,6 +80,7 @@ PATH = data/gobsidian.db
|
|||||||
REQUIRE_ADMIN_ACTIVATION = true
|
REQUIRE_ADMIN_ACTIVATION = true
|
||||||
REQUIRE_EMAIL_CONFIRMATION = false
|
REQUIRE_EMAIL_CONFIRMATION = false
|
||||||
MFA_ENABLED_BY_DEFAULT = false
|
MFA_ENABLED_BY_DEFAULT = false
|
||||||
|
LOGIN_AND_EDITS = true
|
||||||
|
|
||||||
[SECURITY]
|
[SECURITY]
|
||||||
PWD_FAILURES_THRESHOLD = 5
|
PWD_FAILURES_THRESHOLD = 5
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ type Config struct {
|
|||||||
RequireAdminActivation bool
|
RequireAdminActivation bool
|
||||||
RequireEmailConfirmation bool
|
RequireEmailConfirmation bool
|
||||||
MFAEnabledByDefault bool
|
MFAEnabledByDefault bool
|
||||||
|
LoginAndEdits bool
|
||||||
|
|
||||||
// Security settings (failed-login thresholds and auto-ban config)
|
// Security settings (failed-login thresholds and auto-ban config)
|
||||||
PwdFailuresThreshold int
|
PwdFailuresThreshold int
|
||||||
@@ -92,6 +93,7 @@ var defaultConfig = map[string]map[string]string{
|
|||||||
"REQUIRE_ADMIN_ACTIVATION": "true",
|
"REQUIRE_ADMIN_ACTIVATION": "true",
|
||||||
"REQUIRE_EMAIL_CONFIRMATION": "false",
|
"REQUIRE_EMAIL_CONFIRMATION": "false",
|
||||||
"MFA_ENABLED_BY_DEFAULT": "false",
|
"MFA_ENABLED_BY_DEFAULT": "false",
|
||||||
|
"LOGIN_AND_EDITS": "true",
|
||||||
},
|
},
|
||||||
"SECURITY": {
|
"SECURITY": {
|
||||||
"PWD_FAILURES_THRESHOLD": "5",
|
"PWD_FAILURES_THRESHOLD": "5",
|
||||||
@@ -219,6 +221,7 @@ func Load() (*Config, error) {
|
|||||||
config.RequireAdminActivation, _ = authSection.Key("REQUIRE_ADMIN_ACTIVATION").Bool()
|
config.RequireAdminActivation, _ = authSection.Key("REQUIRE_ADMIN_ACTIVATION").Bool()
|
||||||
config.RequireEmailConfirmation, _ = authSection.Key("REQUIRE_EMAIL_CONFIRMATION").Bool()
|
config.RequireEmailConfirmation, _ = authSection.Key("REQUIRE_EMAIL_CONFIRMATION").Bool()
|
||||||
config.MFAEnabledByDefault, _ = authSection.Key("MFA_ENABLED_BY_DEFAULT").Bool()
|
config.MFAEnabledByDefault, _ = authSection.Key("MFA_ENABLED_BY_DEFAULT").Bool()
|
||||||
|
config.LoginAndEdits, _ = authSection.Key("LOGIN_AND_EDITS").Bool()
|
||||||
|
|
||||||
// Load SECURITY section
|
// Load SECURITY section
|
||||||
secSection := cfg.Section("SECURITY")
|
secSection := cfg.Section("SECURITY")
|
||||||
@@ -395,6 +398,8 @@ func (c *Config) SaveSetting(section, key, value string) error {
|
|||||||
c.RequireEmailConfirmation = value == "true"
|
c.RequireEmailConfirmation = value == "true"
|
||||||
case "MFA_ENABLED_BY_DEFAULT":
|
case "MFA_ENABLED_BY_DEFAULT":
|
||||||
c.MFAEnabledByDefault = value == "true"
|
c.MFAEnabledByDefault = value == "true"
|
||||||
|
case "LOGIN_AND_EDITS":
|
||||||
|
c.LoginAndEdits = value == "true"
|
||||||
}
|
}
|
||||||
case "SECURITY":
|
case "SECURITY":
|
||||||
switch key {
|
switch key {
|
||||||
|
|||||||
@@ -972,6 +972,7 @@ func (h *Handlers) IndexHandler(c *gin.Context) {
|
|||||||
"breadcrumbs": utils.GenerateBreadcrumbs(""),
|
"breadcrumbs": utils.GenerateBreadcrumbs(""),
|
||||||
"allowed_image_extensions": h.config.AllowedImageExtensions,
|
"allowed_image_extensions": h.config.AllowedImageExtensions,
|
||||||
"allowed_file_extensions": h.config.AllowedFileExtensions,
|
"allowed_file_extensions": h.config.AllowedFileExtensions,
|
||||||
|
"LoginAndEdits": h.config.LoginAndEdits,
|
||||||
"Authenticated": isAuthenticated(c),
|
"Authenticated": isAuthenticated(c),
|
||||||
"IsAdmin": isAdmin(c),
|
"IsAdmin": isAdmin(c),
|
||||||
"ContentTemplate": "folder_content",
|
"ContentTemplate": "folder_content",
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ import (
|
|||||||
|
|
||||||
const csrfSessionKey = "csrf_token"
|
const csrfSessionKey = "csrf_token"
|
||||||
|
|
||||||
|
// RequireLoginAndEdits blocks access if LoginAndEdits setting is false.
|
||||||
|
func (s *Server) RequireLoginAndEdits() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if !s.config.LoginAndEdits {
|
||||||
|
c.Redirect(http.StatusFound, s.config.URLPrefix+"/?error=not_found")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) randomToken(n int) (string, error) {
|
func (s *Server) randomToken(n int) (string, error) {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
|||||||
@@ -105,15 +105,15 @@ func (s *Server) setupRoutes() {
|
|||||||
r.GET("/view_text/*path", h.ViewTextHandler)
|
r.GET("/view_text/*path", h.ViewTextHandler)
|
||||||
|
|
||||||
// Auth routes
|
// Auth routes
|
||||||
r.GET("/editor/login", h.LoginPage)
|
r.GET("/editor/login", s.RequireLoginAndEdits(), h.LoginPage)
|
||||||
r.POST("/editor/login", s.CSRFRequire(), h.LoginPost)
|
r.POST("/editor/login", s.RequireLoginAndEdits(), s.CSRFRequire(), h.LoginPost)
|
||||||
r.POST("/editor/logout", s.RequireAuth(), s.CSRFRequire(), h.LogoutPost)
|
r.POST("/editor/logout", s.RequireAuth(), s.CSRFRequire(), h.LogoutPost)
|
||||||
// MFA challenge routes (no auth yet, but CSRF)
|
// MFA challenge routes (no auth yet, but CSRF)
|
||||||
r.GET("/editor/mfa", s.CSRFRequire(), h.MFALoginPage)
|
r.GET("/editor/mfa", s.RequireLoginAndEdits(), s.CSRFRequire(), h.MFALoginPage)
|
||||||
r.POST("/editor/mfa", s.CSRFRequire(), h.MFALoginVerify)
|
r.POST("/editor/mfa", s.RequireLoginAndEdits(), s.CSRFRequire(), h.MFALoginVerify)
|
||||||
|
|
||||||
// New /editor group protected by auth + CSRF
|
// New /editor group protected by auth + CSRF + LoginAndEdits
|
||||||
editor := r.Group("/editor", s.RequireAuth(), s.CSRFRequire())
|
editor := r.Group("/editor", s.RequireLoginAndEdits(), s.RequireAuth(), s.CSRFRequire())
|
||||||
{
|
{
|
||||||
editor.GET("/create", h.CreateNotePageHandler)
|
editor.GET("/create", h.CreateNotePageHandler)
|
||||||
editor.POST("/create", h.CreateNoteHandler)
|
editor.POST("/create", h.CreateNoteHandler)
|
||||||
|
|||||||
@@ -308,10 +308,12 @@
|
|||||||
<i class="fas fa-right-from-bracket"></i>
|
<i class="fas fa-right-from-bracket"></i>
|
||||||
</button>
|
</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{if .LoginAndEdits}}
|
||||||
<a href="{{url "/editor/login"}}" class="text-gray-400 hover:text-white transition-colors" title="Login">
|
<a href="{{url "/editor/login"}}" class="text-gray-400 hover:text-white transition-colors" title="Login">
|
||||||
<i class="fas fa-right-to-bracket"></i>
|
<i class="fas fa-right-to-bracket"></i>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<button id="sidebar-toggle" class="toggle-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
|
<button id="sidebar-toggle" class="toggle-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
|
||||||
<i id="sidebar-toggle-icon" class="fas fa-chevron-left"></i>
|
<i id="sidebar-toggle-icon" class="fas fa-chevron-left"></i>
|
||||||
@@ -582,6 +584,12 @@
|
|||||||
const collapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
const collapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
||||||
applySidebarState(collapsed);
|
applySidebarState(collapsed);
|
||||||
|
|
||||||
|
// Check for error in query params
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (urlParams.get('error') === 'not_found') {
|
||||||
|
showNotification('The page does not exist', 'error', 5000);
|
||||||
|
}
|
||||||
|
|
||||||
// Wire toggle button
|
// Wire toggle button
|
||||||
const toggleBtn = document.getElementById('sidebar-toggle');
|
const toggleBtn = document.getElementById('sidebar-toggle');
|
||||||
if (toggleBtn) {
|
if (toggleBtn) {
|
||||||
|
|||||||
Reference in New Issue
Block a user