package server import ( "context" "log/slog" "net" "net/http" "time" "git.coopcloud.tech/wiki-cafe/member-console/internal/auth" "git.coopcloud.tech/wiki-cafe/member-console/internal/embeds" "git.coopcloud.tech/wiki-cafe/member-console/internal/middleware" "github.com/rs/cors" ) // Config holds the configuration for the server. type Config struct { Port string Env string CSRFSecret string Logger *slog.Logger } // Start initializes and starts the HTTP server. func Start(ctx context.Context, cfg Config) error { // Create a new HTTP request router httpRequestRouter := http.NewServeMux() // Set up authentication authConfig, err := auth.Setup() if err != nil { cfg.Logger.Error("failed to set up authentication", slog.Any("error", err)) return err } // Register auth handlers authConfig.RegisterHandlers(httpRequestRouter) // Create CORS configuration with default options corsOptions := cors.Options{ // Define minimal defaults - GET method is required AllowedMethods: []string{"GET"}, } // Create empty CSRF configuration with default values var csrfConfig middleware.CSRFConfig // Get and validate CSRF secret from config csrfKey, err := middleware.ParseCSRFKey(cfg.CSRFSecret) if err != nil { cfg.Logger.Error("invalid csrf-secret", slog.String("error", err.Error()), slog.String("hint", "must be exactly 32 bytes and persist across restarts")) return err } csrfConfig.Secret = csrfKey // Only override specific settings when needed if cfg.Env == "development" { // In development, cookies often need to work without HTTPS csrfConfig.Cookie.Secure = false } // Create middleware stack stack := middleware.CreateStack( middleware.RequestID(), // Generate a unique request ID middleware.Logging(), // Log requests with structured logging middleware.Recovery(), // Catch all panics middleware.Timeout(32*time.Second), // Set request timeout middleware.MaxBodySize(1024*1024), // 1MB size limit middleware.SecureHeaders(), // Set secure headers middleware.CORS(corsOptions), // CORS configuration middleware.CSRF(csrfConfig), // CSRF protection middleware.Compress(), // Response compression authConfig.Middleware(), // OIDC authentication middleware ) // Create HTTP server server := http.Server{ Addr: ":" + cfg.Port, Handler: stack(httpRequestRouter), ReadTimeout: 4 * time.Second, WriteTimeout: 8 * time.Second, IdleTimeout: 16 * time.Second, MaxHeaderBytes: 1024 * 1024, // 1MB BaseContext: func(_ net.Listener) context.Context { return ctx }, // Pass base context to all requests } // Serve the templates and static files using embedded content // Serve templates from the embedded "templates" directory httpRequestRouter.Handle("/", http.FileServer(http.FS(embeds.Templates))) // Serve static files from the embedded "static" directory httpRequestRouter.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(embeds.Static)))) // Log server startup with structured logging cfg.Logger.Info("starting server", slog.String("port", cfg.Port), slog.String("environment", cfg.Env), slog.String("address", "http://localhost:"+cfg.Port)) // Start server and log any errors if err := server.ListenAndServe(); err != nil { cfg.Logger.Error("server error", slog.Any("error", err)) return err } return nil }