summaryrefslogtreecommitdiff
path: root/router/router.go
diff options
context:
space:
mode:
Diffstat (limited to 'router/router.go')
-rw-r--r--router/router.go228
1 files changed, 228 insertions, 0 deletions
diff --git a/router/router.go b/router/router.go
new file mode 100644
index 0000000..78b422d
--- /dev/null
+++ b/router/router.go
@@ -0,0 +1,228 @@
+package router
+
+import (
+ "auth-proxy/config"
+ "auth-proxy/handlers"
+ "auth-proxy/logging"
+ "auth-proxy/middleware"
+ "auth-proxy/models"
+ "auth-proxy/types"
+ "auth-proxy/worker"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/gorilla/csrf"
+)
+
+// NewRouter creates and configures the main application router.
+func NewRouter() http.Handler {
+ targetURL := os.Getenv("TARGET_URL")
+
+ // --- Create routers for Web and API ---
+ webMux := http.NewServeMux()
+ apiMux := http.NewServeMux()
+
+ // --- Static file serving (Web) ---
+ assetsPath := strings.TrimRight(config.Paths.Assets, "/") + "/"
+ assetsFS := http.FileServer(http.Dir("templates"))
+ webMux.Handle(assetsPath, http.StripPrefix(assetsPath, assetsFS))
+
+ // --- Rate Limiter ---
+ rateLimiter := middleware.NewRateLimiter()
+
+ // Helper to switch handler based on HTTP method
+ methodSwitch := func(get, post http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodPost {
+ post.ServeHTTP(w, r)
+ } else {
+ get.ServeHTTP(w, r)
+ }
+ })
+ }
+
+ // --- Web UI Routes (CSRF-protected) ---
+ if os.Getenv("FRONTEND_TYPE") == "php" {
+ // PHP frontend routes
+ loginPageHandler := handlers.NewPhpProxyHandler("login.php")
+ registerPageHandler := handlers.NewPhpProxyHandler("register.php")
+ accountPageHandler := handlers.NewPhpProxyHandler("account.php")
+ accountPasswordPageHandler := handlers.NewPhpProxyHandler("account_password.php")
+
+ loginHandler := methodSwitch(loginPageHandler, http.HandlerFunc(handlers.LoginHandler))
+ webMux.Handle(config.Paths.Login, loginHandler)
+
+ if config.Paths.RegisterEnabled {
+ registerPostHandler := middleware.RateLimitMiddleware(rateLimiter)(http.HandlerFunc(handlers.RegisterHandler))
+ registerHandler := methodSwitch(registerPageHandler, registerPostHandler)
+ webMux.Handle(config.Paths.Register, registerHandler)
+ }
+
+ accountPasswordPostHandler := http.HandlerFunc(handlers.ChangePasswordHandler)
+ accountPasswordHandler := methodSwitch(accountPasswordPageHandler, accountPasswordPostHandler)
+ webMux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(accountPasswordHandler))
+ webMux.Handle(config.Paths.Account, middleware.SessionAuth(accountPageHandler))
+ webMux.Handle(config.Paths.Admin, middleware.SessionAuth(middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))))
+ } else {
+ // Go template frontend routes
+ webMux.HandleFunc(config.Paths.Login, handlers.LoginHandler)
+ if config.Paths.RegisterEnabled {
+ webMux.Handle(config.Paths.Register, middleware.RateLimitMiddleware(rateLimiter)(http.HandlerFunc(handlers.RegisterHandler)))
+ }
+ webMux.Handle(config.Paths.Account, middleware.SessionAuth(http.HandlerFunc(handlers.AccountPageHandler)))
+ webMux.Handle(config.Paths.Admin, middleware.SessionAuth(middleware.AdminMiddleware(http.HandlerFunc(handlers.AdminPageHandler))))
+ webMux.Handle(config.Paths.AccountPassword, middleware.SessionAuth(http.HandlerFunc(handlers.ChangePasswordHandler)))
+ }
+ webMux.HandleFunc(config.Paths.Logout, handlers.LogoutHandler)
+
+ // --- API Routes (No CSRF) ---
+ apiMux.HandleFunc("/api/auth/token", handlers.TokenHandler)
+ apiMux.HandleFunc("/auth/refresh", handlers.RefreshTokenHandler)
+
+ // Admin API endpoints
+ adminCreateUserAPI := http.HandlerFunc(handlers.CreateUserHandler)
+ getUsersHandler := http.HandlerFunc(handlers.GetUsersHandler)
+ 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)
+
+ apiMux.Handle(config.Paths.AdminUsersAPI, middleware.BearerAuth(middleware.AdminMiddleware(adminUsersAPI)))
+ apiMux.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:
+ adminUpdateRoleAPI.ServeHTTP(w, r)
+ case strings.HasSuffix(path, "/status") && r.Method == http.MethodPost:
+ adminSetStatusAPI.ServeHTTP(w, r)
+ case r.Method == http.MethodDelete:
+ adminDeleteUserAPI.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ }))))
+
+ // --- Main Application Proxy ---
+ proxyHandler := handlers.NewProxy(targetURL)
+
+ // --- CSRF Protection (for Web UI) ---
+ csrfSecret := os.Getenv("CSRF_SECRET_KEY")
+ csrfOptions := []csrf.Option{
+ csrf.Secure(os.Getenv("ENV") == "production"),
+ csrf.Path("/"),
+ }
+ sameSiteMode := csrf.SameSiteLaxMode
+ switch strings.ToLower(os.Getenv("CSRF_SAME_SITE")) {
+ case "strict":
+ sameSiteMode = csrf.SameSiteStrictMode
+ case "none":
+ sameSiteMode = csrf.SameSiteNoneMode
+ }
+ csrfOptions = append(csrfOptions, csrf.SameSite(sameSiteMode))
+ trustedOrigins := os.Getenv("CSRF_TRUSTED_ORIGINS")
+ if trustedOrigins != "" {
+ origins := strings.Split(trustedOrigins, ",")
+ csrfOptions = append(csrfOptions, csrf.TrustedOrigins(origins))
+ }
+ csrfMiddleware := csrf.Protect([]byte(csrfSecret), csrfOptions...)
+ csrfProtectedWebMux := middleware.HandleOptions(csrfMiddleware(webMux))
+
+ // --- Final Handler ---
+ finalHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Handle API paths
+ if strings.HasPrefix(r.URL.Path, config.Paths.APIPath) {
+ // Check if a specific handler exists in the API mux
+ _, pattern := apiMux.Handler(r)
+
+ // If a specific pattern is found, let the API mux handle it internally.
+ if pattern != "" {
+ apiMux.ServeHTTP(w, r)
+ return
+ }
+
+ // If no specific handler is found, it's a request to be proxied.
+ // Protect it with Bearer/Cookie auth if PROTECT_API is enabled.
+ if config.Paths.ProtectAPI {
+ middleware.BearerAuth(proxyHandler).ServeHTTP(w, r)
+ } else {
+ proxyHandler.ServeHTTP(w, r)
+ }
+ return
+ }
+
+ // Check if the path is a web UI path and apply CSRF protection
+ _, webPattern := webMux.Handler(r)
+ if webPattern != "" {
+ csrfProtectedWebMux.ServeHTTP(w, r)
+ return
+ }
+
+ // Fallback to proxy for all other paths
+ if config.Paths.ProtectFrontend {
+ middleware.SessionAuth(proxyHandler).ServeHTTP(w, r)
+ } else {
+ proxyHandler.ServeHTTP(w, r)
+ }
+ })
+
+ return finalHandler
+}
+
+// StartBackgroundTasks launches periodic jobs for cleanup.
+func StartBackgroundTasks(pool *types.WorkerPool, limiter *types.RateLimiter) {
+ // Ticker for expired token cleanup (every hour)
+ tokenTicker := time.NewTicker(1 * time.Hour)
+ defer tokenTicker.Stop()
+
+ // Ticker for rate limiter cleanup (every 24 hours)
+ rateLimiterTicker := time.NewTicker(24 * time.Hour)
+ defer rateLimiterTicker.Stop()
+
+ logging.AppLog.Info("Background task scheduler started")
+
+ // Run initial cleanup tasks at startup
+ submitTokenCleanup(pool)
+ submitRateLimiterCleanup(pool, limiter)
+
+ // Run tasks on their respective tickers
+ for {
+ select {
+ case <-tokenTicker.C:
+ submitTokenCleanup(pool)
+ case <-rateLimiterTicker.C:
+ submitRateLimiterCleanup(pool, limiter)
+ }
+ }
+}
+
+// submitTokenCleanup submits the expired token deletion task to the worker pool.
+func submitTokenCleanup(pool *types.WorkerPool) {
+ worker.Submit(pool, func() {
+ logging.AppLog.Info("Running background task: deleting expired tokens")
+ rowsAffected, err := models.DeleteExpiredTokens()
+ if err != nil {
+ logging.AppLog.Error("Failed to delete expired tokens", "error", err)
+ } else {
+ if rowsAffected > 0 {
+ logging.AppLog.Info("Finished deleting expired tokens", "deleted_count", rowsAffected)
+ }
+ }
+ })
+}
+
+// submitRateLimiterCleanup submits the rate limiter cleanup task to the worker pool.
+func submitRateLimiterCleanup(pool *types.WorkerPool, limiter *types.RateLimiter) {
+ worker.Submit(pool, func() {
+ logging.AppLog.Info("Running background task: cleaning up rate limiter")
+ middleware.Cleanup(limiter)
+ })
+}
+