summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/server/main.go178
-rw-r--r--middleware/auth.go12
2 files changed, 96 insertions, 94 deletions
diff --git a/cmd/server/main.go b/cmd/server/main.go
index e82b4bb..4f0d30f 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -46,16 +46,16 @@ func main() {
os.Exit(1)
}
- // --- Create routers: one for CSRF-protected routes, one for non-protected ---
- csrfMux := http.NewServeMux()
- mainMux := http.NewServeMux()
+ // --- Create router and define routes ---
+ mux := http.NewServeMux()
- // --- Static file serving (non-protected) ---
+ // --- Static file serving ---
assetsPath := strings.TrimRight(config.Paths.Assets, "/") + "/"
assetsFS := http.FileServer(http.Dir("templates"))
- mainMux.Handle(assetsPath, http.StripPrefix(assetsPath, assetsFS))
+ mux.Handle(assetsPath, http.StripPrefix(assetsPath, assetsFS))
- // --- Rate Limiter ---
+ // --- Frontend selection and routing ---
+ frontendType := os.Getenv("FRONTEND_TYPE")
rateLimiter := middleware.NewRateLimiter()
// Helper to switch handler based on HTTP method
@@ -69,79 +69,66 @@ func main() {
})
}
- // --- Route configuration based on frontend type ---
- frontendType := os.Getenv("FRONTEND_TYPE")
if frontendType == "php" {
- // --- PHP Frontend Routes ---
+ // Create specific handlers for each PHP page
loginPageHandler := handlers.NewPhpProxyHandler("login.php")
registerPageHandler := handlers.NewPhpProxyHandler("register.php")
accountPageHandler := handlers.NewPhpProxyHandler("account.php")
accountPasswordPageHandler := handlers.NewPhpProxyHandler("account_password.php")
- // POST actions go to csrfMux, GET pages go to mainMux
- csrfMux.HandleFunc(config.Paths.Login, handlers.LoginHandler)
- mainMux.Handle(config.Paths.Login, loginPageHandler)
+ // For /login, GET goes to PHP, POST goes to Go's login logic
+ loginHandler := methodSwitch(loginPageHandler, http.HandlerFunc(handlers.LoginHandler))
+ mux.Handle(config.Paths.Login, loginHandler)
if config.Paths.RegisterEnabled {
+ // For /register, GET goes to PHP, POST goes to Go's register logic
registerPostHandler := middleware.RateLimitMiddleware(rateLimiter)(http.HandlerFunc(handlers.RegisterHandler))
- csrfMux.Handle(config.Paths.Register, registerPostHandler)
- mainMux.Handle(config.Paths.Register, registerPageHandler)
+ registerHandler := methodSwitch(registerPageHandler, registerPostHandler)
+ mux.Handle(config.Paths.Register, registerHandler)
}
+ // For account pages, GET goes to PHP, POST (for password change) goes to Go
accountPasswordPostHandler := http.HandlerFunc(handlers.ChangePasswordHandler)
- csrfMux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(accountPasswordPostHandler))
- mainMux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(accountPasswordPageHandler)) // GET is the same page
-
- mainMux.Handle(config.Paths.Account, middleware.SessionAuth(accountPageHandler))
- csrfMux.Handle(config.Paths.Admin, middleware.SessionAuth(middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))))
- mainMux.Handle(config.Paths.Admin, middleware.SessionAuth(middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))))
+ accountPasswordHandler := methodSwitch(accountPasswordPageHandler, accountPasswordPostHandler)
+ mux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(accountPasswordHandler))
+ mux.Handle(config.Paths.Account, middleware.SessionAuth(accountPageHandler)) // This page is GET only
+ // Admin page still uses Go templates
+ mux.Handle(config.Paths.Admin, middleware.SessionAuth(middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))))
} else {
- // --- Go Template Frontend Routes ---
- // POST actions go to csrfMux, GET pages go to mainMux
- csrfMux.HandleFunc(config.Paths.Login, handlers.LoginHandler)
- mainMux.HandleFunc(config.Paths.Login, handlers.LoginHandler) // Handles GET
-
+ // --- Public Auth Routes (Go templates) ---
+ mux.HandleFunc(config.Paths.Login, handlers.LoginHandler)
if config.Paths.RegisterEnabled {
- registerHandler := middleware.RateLimitMiddleware(rateLimiter)(http.HandlerFunc(handlers.RegisterHandler))
- csrfMux.Handle(config.Paths.Register, registerHandler)
- mainMux.Handle(config.Paths.Register, registerHandler) // Handles GET
+ mux.Handle(config.Paths.Register, middleware.RateLimitMiddleware(rateLimiter)(http.HandlerFunc(handlers.RegisterHandler)))
}
-
- // Protected pages
- accountHandler := http.HandlerFunc(handlers.AccountPageHandler)
- adminHandler := middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))
- passwordHandler := http.HandlerFunc(handlers.ChangePasswordHandler)
-
- mainMux.Handle(config.Paths.Account, middleware.SessionAuth(accountHandler))
- csrfMux.Handle(config.Paths.Account, middleware.SessionAuth(accountHandler))
-
- mainMux.Handle(config.Paths.Admin, middleware.SessionAuth(adminHandler))
- csrfMux.Handle(config.Paths.Admin, middleware.SessionAuth(adminHandler))
-
- mainMux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(passwordHandler))
- csrfMux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(passwordHandler))
+ // --- Auth HTML pages (Protected by SessionAuth, Go templates) ---
+ mux.Handle(config.Paths.Account, middleware.SessionAuth(http.HandlerFunc(handlers.AccountPageHandler)))
+ mux.Handle(config.Paths.Admin, middleware.SessionAuth(middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))))
+ mux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(http.HandlerFunc(handlers.ChangePasswordHandler)))
}
- // --- Common Routes (non-CSRF) ---
- mainMux.HandleFunc(config.Paths.Logout, handlers.LogoutHandler)
- mainMux.HandleFunc("/auth/refresh", handlers.RefreshTokenHandler)
- mainMux.HandleFunc("/api/auth/token", handlers.TokenHandler)
+ // --- Common Routes ---
+ mux.HandleFunc(config.Paths.Logout, handlers.LogoutHandler)
+ mux.HandleFunc("/auth/refresh", handlers.RefreshTokenHandler)
+ mux.HandleFunc("/api/auth/token", handlers.TokenHandler)
- // --- Protected API Endpoints (for Admin UI, protected by Bearer/Session, CSRF applied) ---
+ // --- Protected API Endpoints (for Admin UI, protected by SessionAuth) ---
adminCreateUserAPI := http.HandlerFunc(handlers.CreateUserHandler)
getUsersHandler := http.HandlerFunc(handlers.GetUsersHandler)
- adminUsersAPI := methodSwitch(getUsersHandler, adminCreateUserAPI) // GET and POST
+ adminUsersAPI := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodPost {
+ adminCreateUserAPI.ServeHTTP(w, r)
+ } else {
+ getUsersHandler.ServeHTTP(w, r)
+ }
+ })
adminUpdateRoleAPI := http.HandlerFunc(handlers.UpdateUserRoleHandler)
adminDeleteUserAPI := http.HandlerFunc(handlers.DeleteUserHandler)
adminSetStatusAPI := http.HandlerFunc(handlers.SetUserActiveStatusHandler)
- // Apply CSRF protection to state-changing admin API endpoints
- csrfMux.Handle(config.Paths.AdminUsersAPI, middleware.BearerAuth(middleware.AdminMiddleware(adminUsersAPI)))
- mainMux.Handle(config.Paths.AdminUsersAPI, middleware.BearerAuth(middleware.AdminMiddleware(getUsersHandler))) // GET is safe
-
- csrfMux.Handle(config.Paths.AdminUsersAPI+"/", middleware.BearerAuth(middleware.AdminMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ mux.Handle(config.Paths.AdminUsersAPI, middleware.BearerAuth(middleware.AdminMiddleware(adminUsersAPI)))
+ mux.Handle(config.Paths.AdminUsersAPI+"/", middleware.BearerAuth(middleware.AdminMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case strings.HasSuffix(path, "/role") && r.Method == http.MethodPost:
@@ -155,67 +142,72 @@ func main() {
}
}))))
- // --- Main Application Proxy (not CSRF protected) ---
+ // --- Main Application Proxy ---
proxyHandler := handlers.NewProxy(targetURL)
- // --- CSRF Middleware Configuration ---
- csrfSecret := os.Getenv("CSRF_SECRET_KEY")
- if csrfSecret == "" {
- logging.AppLog.Error("CSRF_SECRET_KEY environment variable not set")
- os.Exit(1)
- }
- csrfOptions := []csrf.Option{
- csrf.Secure(os.Getenv("ENV") == "production"),
- csrf.Path("/"),
- csrf.HttpOnly(true),
- }
- trustedOrigins := os.Getenv("CSRF_TRUSTED_ORIGINS")
- if trustedOrigins != "" {
- origins := strings.Split(trustedOrigins, ",")
- csrfOptions = append(csrfOptions, csrf.TrustedOrigins(origins))
- }
- csrfMiddleware := csrf.Protect([]byte(csrfSecret), csrfOptions...)
- csrfProtectedHandler := csrfMiddleware(csrfMux)
-
- // --- Final Handler: Delegates to CSRF mux, main mux, or proxy ---
finalHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Check CSRF-protected routes first
- _, csrfPattern := csrfMux.Handler(r)
- if csrfPattern != "" {
- csrfProtectedHandler.ServeHTTP(w, r)
- return
- }
-
- // Check non-CSRF routes next
- _, mainPattern := mainMux.Handler(r)
- if mainPattern != "" {
- mainMux.ServeHTTP(w, r)
+ _, pattern := mux.Handler(r)
+ if pattern != "" {
+ mux.ServeHTTP(w, r)
return
}
- // Fallback to the proxy
path := r.URL.Path
+
+ // API Protection with Bearer Token
if config.Paths.ProtectAPI && strings.HasPrefix(path, config.Paths.APIPath) {
middleware.BearerAuth(proxyHandler).ServeHTTP(w, r)
- } else if config.Paths.ProtectFrontend {
+ return
+ }
+
+ // Frontend Protection with Session Cookie
+ if config.Paths.ProtectFrontend {
middleware.SessionAuth(proxyHandler).ServeHTTP(w, r)
- } else {
- proxyHandler.ServeHTTP(w, r)
+ return
}
+
+ // No protection
+ proxyHandler.ServeHTTP(w, r)
})
- // --- Start Server ---
+ // Get server port
port := os.Getenv("LISTEN_PORT")
if port == "" {
port = "8080"
}
listenAddr := ":" + port
+
+ // --- CSRF Protection ---
+ csrfSecret := os.Getenv("CSRF_SECRET_KEY")
+ if csrfSecret == "" {
+ logging.AppLog.Error("CSRF_SECRET_KEY environment variable not set")
+ os.Exit(1)
+ }
+ // In production, you should set csrf.Secure(true).
+ // You can use an environment variable to control this setting.
+ csrfOptions := []csrf.Option{
+ csrf.Secure(true), // Set to true in production
+ csrf.Path("/"),
+ }
+
+ trustedOrigins := os.Getenv("CSRF_TRUSTED_ORIGINS")
+ if trustedOrigins != "" {
+ origins := strings.Split(trustedOrigins, ",")
+ csrfOptions = append(csrfOptions, csrf.TrustedOrigins(origins))
+ }
+
+ csrfMiddleware := csrf.Protect(
+ []byte(csrfSecret),
+ csrfOptions...,
+ )
+
+ // --- Start Server ---
logging.AppLog.Info("Server starting on " + listenAddr)
// Start periodic background tasks
go startBackgroundTasks(pool, rateLimiter)
- if err := http.ListenAndServe(listenAddr, finalHandler); err != nil {
+ if err := http.ListenAndServe(listenAddr, middleware.HandleOptions(csrfMiddleware(finalHandler))); err != nil {
logging.AppLog.Error("Server failed to start", "error", err)
os.Exit(1)
}
@@ -319,6 +311,4 @@ func main() {
})
- }
-
-
+ } \ No newline at end of file
diff --git a/middleware/auth.go b/middleware/auth.go
index e4f096b..5025f32 100644
--- a/middleware/auth.go
+++ b/middleware/auth.go
@@ -154,4 +154,16 @@ func AdminMiddleware(next http.Handler) http.Handler {
logging.SecurityLog.Info("ADMIN ACCESS GRANTED", "user", user.Username, "path", r.URL.Path, "ip", ip)
next.ServeHTTP(w, r)
})
+}
+
+// HandleOptions is a middleware that handles pre-flight OPTIONS requests.
+// This should be placed before the CSRF middleware.
+func HandleOptions(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodOptions {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ next.ServeHTTP(w, r)
+ })
} \ No newline at end of file