package http import ( "context" "fmt" "net/http" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config" httpmw "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/http/middleware" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/admin" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/gateway" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/healthcheck" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/plugin" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/site" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/tenant" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/handler/user" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/interface/http/middleware" ) // Server represents the HTTP server type Server struct { server *http.Server logger *zap.Logger jwtMiddleware *httpmw.JWTMiddleware apikeyMiddleware *httpmw.APIKeyMiddleware rateLimitMiddlewares *httpmw.RateLimitMiddlewares // CWE-770: Registration and auth endpoints rate limiting securityHeadersMiddleware *httpmw.SecurityHeadersMiddleware requestSizeLimitMw *httpmw.RequestSizeLimitMiddleware config *config.Config healthHandler *healthcheck.Handler registerHandler *gateway.RegisterHandler loginHandler *gateway.LoginHandler refreshTokenHandler *gateway.RefreshTokenHandler helloHandler *gateway.HelloHandler meHandler *gateway.MeHandler createTenantHandler *tenant.CreateHandler getTenantHandler *tenant.GetHandler createUserHandler *user.CreateHandler getUserHandler *user.GetHandler createSiteHandler *site.CreateHandler getSiteHandler *site.GetHandler listSitesHandler *site.ListHandler deleteSiteHandler *site.DeleteHandler rotateSiteAPIKeyHandler *site.RotateAPIKeyHandler verifySiteHandler *site.VerifySiteHandler pluginStatusHandler *plugin.StatusHandler pluginVerifyHandler *plugin.PluginVerifyHandler pluginVersionHandler *plugin.VersionHandler syncPagesHandler *plugin.SyncPagesHandler searchPagesHandler *plugin.SearchPagesHandler deletePagesHandler *plugin.DeletePagesHandler syncStatusHandler *plugin.SyncStatusHandler unlockAccountHandler *admin.UnlockAccountHandler accountStatusHandler *admin.AccountStatusHandler } // ProvideServer creates a new HTTP server func ProvideServer( cfg *config.Config, logger *zap.Logger, jwtMiddleware *httpmw.JWTMiddleware, apikeyMiddleware *httpmw.APIKeyMiddleware, rateLimitMiddlewares *httpmw.RateLimitMiddlewares, securityHeadersMiddleware *httpmw.SecurityHeadersMiddleware, requestSizeLimitMw *httpmw.RequestSizeLimitMiddleware, healthHandler *healthcheck.Handler, registerHandler *gateway.RegisterHandler, loginHandler *gateway.LoginHandler, refreshTokenHandler *gateway.RefreshTokenHandler, helloHandler *gateway.HelloHandler, meHandler *gateway.MeHandler, createTenantHandler *tenant.CreateHandler, getTenantHandler *tenant.GetHandler, createUserHandler *user.CreateHandler, getUserHandler *user.GetHandler, createSiteHandler *site.CreateHandler, getSiteHandler *site.GetHandler, listSitesHandler *site.ListHandler, deleteSiteHandler *site.DeleteHandler, rotateSiteAPIKeyHandler *site.RotateAPIKeyHandler, verifySiteHandler *site.VerifySiteHandler, pluginStatusHandler *plugin.StatusHandler, pluginVerifyHandler *plugin.PluginVerifyHandler, pluginVersionHandler *plugin.VersionHandler, syncPagesHandler *plugin.SyncPagesHandler, searchPagesHandler *plugin.SearchPagesHandler, deletePagesHandler *plugin.DeletePagesHandler, syncStatusHandler *plugin.SyncStatusHandler, unlockAccountHandler *admin.UnlockAccountHandler, accountStatusHandler *admin.AccountStatusHandler, ) *Server { mux := http.NewServeMux() s := &Server{ logger: logger, jwtMiddleware: jwtMiddleware, apikeyMiddleware: apikeyMiddleware, rateLimitMiddlewares: rateLimitMiddlewares, securityHeadersMiddleware: securityHeadersMiddleware, requestSizeLimitMw: requestSizeLimitMw, config: cfg, healthHandler: healthHandler, registerHandler: registerHandler, loginHandler: loginHandler, refreshTokenHandler: refreshTokenHandler, helloHandler: helloHandler, meHandler: meHandler, createTenantHandler: createTenantHandler, getTenantHandler: getTenantHandler, createUserHandler: createUserHandler, getUserHandler: getUserHandler, createSiteHandler: createSiteHandler, getSiteHandler: getSiteHandler, listSitesHandler: listSitesHandler, deleteSiteHandler: deleteSiteHandler, rotateSiteAPIKeyHandler: rotateSiteAPIKeyHandler, verifySiteHandler: verifySiteHandler, pluginStatusHandler: pluginStatusHandler, pluginVerifyHandler: pluginVerifyHandler, pluginVersionHandler: pluginVersionHandler, syncPagesHandler: syncPagesHandler, searchPagesHandler: searchPagesHandler, deletePagesHandler: deletePagesHandler, syncStatusHandler: syncStatusHandler, unlockAccountHandler: unlockAccountHandler, accountStatusHandler: accountStatusHandler, } // Register routes s.registerRoutes(mux) // Create HTTP server // CWE-770: Configure timeouts to prevent resource exhaustion s.server = &http.Server{ Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port), Handler: s.applyMiddleware(mux), ReadTimeout: cfg.HTTP.ReadTimeout, WriteTimeout: cfg.HTTP.WriteTimeout, IdleTimeout: cfg.HTTP.IdleTimeout, } logger.Info("✓ HTTP server configured", zap.String("address", s.server.Addr), zap.Duration("read_timeout", cfg.HTTP.ReadTimeout), zap.Duration("write_timeout", cfg.HTTP.WriteTimeout), zap.Int64("max_body_size", cfg.HTTP.MaxRequestBodySize)) return s } // registerRoutes registers all HTTP routes func (s *Server) registerRoutes(mux *http.ServeMux) { // ===== PUBLIC ROUTES (No authentication, no tenant) ===== // Health check mux.HandleFunc("GET /health", s.healthHandler.Handle) // Version endpoint - public API for checking backend version mux.HandleFunc("GET /api/v1/version", s.pluginVersionHandler.Handle) // Public gateway routes (registration, login, etc.) // CWE-770: Apply request size limits and rate limiting // Apply small size limit (1MB) for registration/login endpoints if s.config.RateLimit.RegistrationEnabled { mux.HandleFunc("POST /api/v1/register", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyRegistrationRateLimit(s.registerHandler.Handle)), ).ServeHTTP) } else { mux.HandleFunc("POST /api/v1/register", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.registerHandler.Handle), ).ServeHTTP) } mux.HandleFunc("POST /api/v1/login", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.loginHandler.Handle), ).ServeHTTP) mux.HandleFunc("POST /api/v1/refresh", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.refreshTokenHandler.Handle), ).ServeHTTP) // ===== AUTHENTICATED ROUTES (JWT only, no tenant context) ===== // Gateway routes // CWE-770: Apply small size limit (1MB) and generic rate limiting for hello endpoint if s.config.RateLimit.GenericEnabled { mux.HandleFunc("POST /api/v1/hello", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthOnlyWithGenericRateLimit(s.helloHandler.Handle)), ).ServeHTTP) } else { mux.HandleFunc("POST /api/v1/hello", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthOnly(s.helloHandler.Handle)), ).ServeHTTP) } // CWE-770: Apply generic rate limiting to /me endpoint to prevent profile enumeration and DoS if s.config.RateLimit.GenericEnabled { mux.HandleFunc("GET /api/v1/me", s.applyAuthOnlyWithGenericRateLimit(s.meHandler.Handle)) } else { mux.HandleFunc("GET /api/v1/me", s.applyAuthOnly(s.meHandler.Handle)) } // Tenant management routes - these operate at system/admin level // CWE-770: Apply small size limit (1MB) and generic rate limiting for tenant creation if s.config.RateLimit.GenericEnabled { mux.HandleFunc("POST /api/v1/tenants", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthOnlyWithGenericRateLimit(s.createTenantHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/tenants/{id}", s.applyAuthOnlyWithGenericRateLimit(s.getTenantHandler.HandleByID)) mux.HandleFunc("GET /api/v1/tenants/slug/{slug}", s.applyAuthOnlyWithGenericRateLimit(s.getTenantHandler.HandleBySlug)) } else { mux.HandleFunc("POST /api/v1/tenants", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthOnly(s.createTenantHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/tenants/{id}", s.applyAuthOnly(s.getTenantHandler.HandleByID)) mux.HandleFunc("GET /api/v1/tenants/slug/{slug}", s.applyAuthOnly(s.getTenantHandler.HandleBySlug)) } // ===== TENANT-SCOPED ROUTES (JWT + Tenant context) ===== // User routes - these operate within a tenant context // CWE-770: Apply small size limit (1MB) and generic rate limiting for user creation if s.config.RateLimit.GenericEnabled { mux.HandleFunc("POST /api/v1/users", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.createUserHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/users/{id}", s.applyAuthAndTenantWithGenericRateLimit(s.getUserHandler.Handle)) } else { mux.HandleFunc("POST /api/v1/users", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenant(s.createUserHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/users/{id}", s.applyAuthAndTenant(s.getUserHandler.Handle)) } // Site management routes - JWT authenticated, tenant-scoped // CWE-770: Apply small size limit (1MB) and generic rate limiting for site management if s.config.RateLimit.GenericEnabled { mux.HandleFunc("POST /api/v1/sites", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.createSiteHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/sites", s.applyAuthAndTenantWithGenericRateLimit(s.listSitesHandler.Handle)) mux.HandleFunc("GET /api/v1/sites/{id}", s.applyAuthAndTenantWithGenericRateLimit(s.getSiteHandler.Handle)) mux.HandleFunc("DELETE /api/v1/sites/{id}", s.applyAuthAndTenantWithGenericRateLimit(s.deleteSiteHandler.Handle)) mux.HandleFunc("POST /api/v1/sites/{id}/rotate-api-key", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.rotateSiteAPIKeyHandler.Handle)), ).ServeHTTP) mux.HandleFunc("POST /api/v1/sites/{id}/verify", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenantWithGenericRateLimit(s.verifySiteHandler.Handle)), ).ServeHTTP) } else { mux.HandleFunc("POST /api/v1/sites", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenant(s.createSiteHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/sites", s.applyAuthAndTenant(s.listSitesHandler.Handle)) mux.HandleFunc("GET /api/v1/sites/{id}", s.applyAuthAndTenant(s.getSiteHandler.Handle)) mux.HandleFunc("DELETE /api/v1/sites/{id}", s.applyAuthAndTenant(s.deleteSiteHandler.Handle)) mux.HandleFunc("POST /api/v1/sites/{id}/rotate-api-key", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenant(s.rotateSiteAPIKeyHandler.Handle)), ).ServeHTTP) mux.HandleFunc("POST /api/v1/sites/{id}/verify", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthAndTenant(s.verifySiteHandler.Handle)), ).ServeHTTP) } // ===== ADMIN ROUTES (JWT authenticated) ===== // CWE-307: Admin endpoints for account lockout management // CWE-770: Apply small size limit (1MB) and generic rate limiting for admin endpoints if s.config.RateLimit.GenericEnabled { mux.HandleFunc("POST /api/v1/admin/unlock-account", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthOnlyWithGenericRateLimit(s.unlockAccountHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/admin/account-status", s.applyAuthOnlyWithGenericRateLimit(s.accountStatusHandler.Handle)) } else { mux.HandleFunc("POST /api/v1/admin/unlock-account", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAuthOnly(s.unlockAccountHandler.Handle)), ).ServeHTTP) mux.HandleFunc("GET /api/v1/admin/account-status", s.applyAuthOnly(s.accountStatusHandler.Handle)) } // ===== WORDPRESS PLUGIN API ROUTES (API Key authentication) ===== // CWE-770: Apply lenient site-based rate limiting to protect core business endpoints // Default: 1000 requests/hour per site (very lenient for high-volume legitimate traffic) if s.config.RateLimit.PluginAPIEnabled { // Plugin status/verification - with rate limiting mux.HandleFunc("GET /api/v1/plugin/status", s.applyAPIKeyAuthWithPluginRateLimit(s.pluginStatusHandler.Handle)) // Plugin domain verification endpoint mux.HandleFunc("POST /api/v1/plugin/verify", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.pluginVerifyHandler.Handle)), ).ServeHTTP) // Page sync and search routes // CWE-770: Apply larger size limit (50MB) for page sync (bulk operations) + rate limiting mux.HandleFunc("POST /api/v1/plugin/pages/sync", s.requestSizeLimitMw.LimitLarge()( http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.syncPagesHandler.Handle)), ).ServeHTTP) // Apply medium limit (5MB) for search and delete operations + rate limiting mux.HandleFunc("POST /api/v1/plugin/pages/search", s.requestSizeLimitMw.LimitMedium()( http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.searchPagesHandler.Handle)), ).ServeHTTP) mux.HandleFunc("DELETE /api/v1/plugin/pages", s.requestSizeLimitMw.LimitMedium()( http.HandlerFunc(s.applyAPIKeyAuthWithPluginRateLimit(s.deletePagesHandler.Handle)), ).ServeHTTP) mux.HandleFunc("DELETE /api/v1/plugin/pages/all", s.applyAPIKeyAuthWithPluginRateLimit(s.deletePagesHandler.HandleDeleteAll)) mux.HandleFunc("GET /api/v1/plugin/pages/status", s.applyAPIKeyAuthWithPluginRateLimit(s.syncStatusHandler.Handle)) mux.HandleFunc("GET /api/v1/plugin/pages/{page_id}", s.applyAPIKeyAuthWithPluginRateLimit(s.syncStatusHandler.HandleGetPageDetails)) } else { // Plugin endpoints without rate limiting (not recommended for production) mux.HandleFunc("GET /api/v1/plugin/status", s.applyAPIKeyAuth(s.pluginStatusHandler.Handle)) // Plugin domain verification endpoint mux.HandleFunc("POST /api/v1/plugin/verify", s.requestSizeLimitMw.LimitSmall()( http.HandlerFunc(s.applyAPIKeyAuth(s.pluginVerifyHandler.Handle)), ).ServeHTTP) mux.HandleFunc("POST /api/v1/plugin/pages/sync", s.requestSizeLimitMw.LimitLarge()( http.HandlerFunc(s.applyAPIKeyAuth(s.syncPagesHandler.Handle)), ).ServeHTTP) mux.HandleFunc("POST /api/v1/plugin/pages/search", s.requestSizeLimitMw.LimitMedium()( http.HandlerFunc(s.applyAPIKeyAuth(s.searchPagesHandler.Handle)), ).ServeHTTP) mux.HandleFunc("DELETE /api/v1/plugin/pages", s.requestSizeLimitMw.LimitMedium()( http.HandlerFunc(s.applyAPIKeyAuth(s.deletePagesHandler.Handle)), ).ServeHTTP) mux.HandleFunc("DELETE /api/v1/plugin/pages/all", s.applyAPIKeyAuth(s.deletePagesHandler.HandleDeleteAll)) mux.HandleFunc("GET /api/v1/plugin/pages/status", s.applyAPIKeyAuth(s.syncStatusHandler.Handle)) mux.HandleFunc("GET /api/v1/plugin/pages/{page_id}", s.applyAPIKeyAuth(s.syncStatusHandler.HandleGetPageDetails)) } } // applyMiddleware applies global middleware to all routes func (s *Server) applyMiddleware(handler http.Handler) http.Handler { // Apply middleware in order (innermost to outermost) // 1. Logger middleware (logging) // 2. Security headers middleware (CWE-693: Protection Mechanism Failure) handler = middleware.LoggerMiddleware(s.logger)(handler) handler = s.securityHeadersMiddleware.Handler(handler) return handler } // applyAuthOnly applies only JWT authentication middleware (no tenant) func (s *Server) applyAuthOnly(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: JWT validation -> Auth check -> Handler s.jwtMiddleware.Handler( s.jwtMiddleware.RequireAuth( http.HandlerFunc(handler), ), ).ServeHTTP(w, r) } } // applyAuthOnlyWithGenericRateLimit applies JWT authentication + generic rate limiting (CWE-770) // Used for authenticated CRUD endpoints (tenant/user/site management, admin, /me, /hello) // Applies user-based rate limiting (extracted from JWT context) func (s *Server) applyAuthOnlyWithGenericRateLimit(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: JWT validation -> Auth check -> Generic rate limit (user-based) -> Handler s.jwtMiddleware.Handler( s.jwtMiddleware.RequireAuth( s.rateLimitMiddlewares.Generic.HandlerWithUserKey( http.HandlerFunc(handler), ), ), ).ServeHTTP(w, r) } } // applyAuthAndTenant applies JWT authentication + tenant middleware func (s *Server) applyAuthAndTenant(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: JWT validation -> Auth check -> Tenant -> Handler s.jwtMiddleware.Handler( s.jwtMiddleware.RequireAuth( middleware.TenantMiddleware()( http.HandlerFunc(handler), ), ), ).ServeHTTP(w, r) } } // applyAuthAndTenantWithGenericRateLimit applies JWT authentication + tenant + generic rate limiting (CWE-770) // Used for tenant-scoped CRUD endpoints (user/site management) // Applies user-based rate limiting (extracted from JWT context) func (s *Server) applyAuthAndTenantWithGenericRateLimit(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: JWT validation -> Auth check -> Tenant -> Generic rate limit (user-based) -> Handler s.jwtMiddleware.Handler( s.jwtMiddleware.RequireAuth( middleware.TenantMiddleware()( s.rateLimitMiddlewares.Generic.HandlerWithUserKey( http.HandlerFunc(handler), ), ), ), ).ServeHTTP(w, r) } } // applyAPIKeyAuth applies API key authentication middleware (for WordPress plugin) func (s *Server) applyAPIKeyAuth(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: API key validation -> Require API key -> Handler s.apikeyMiddleware.Handler( s.apikeyMiddleware.RequireAPIKey( http.HandlerFunc(handler), ), ).ServeHTTP(w, r) } } // applyAPIKeyAuthWithPluginRateLimit applies API key authentication + plugin API rate limiting (CWE-770) // Used for WordPress Plugin API endpoints (core business endpoints) // Applies site-based rate limiting (extracted from API key context) func (s *Server) applyAPIKeyAuthWithPluginRateLimit(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: API key validation -> Require API key -> Plugin rate limit (site-based) -> Handler s.apikeyMiddleware.Handler( s.apikeyMiddleware.RequireAPIKey( s.rateLimitMiddlewares.PluginAPI.HandlerWithSiteKey( http.HandlerFunc(handler), ), ), ).ServeHTTP(w, r) } } // applyRegistrationRateLimit applies rate limiting middleware for registration (CWE-770) func (s *Server) applyRegistrationRateLimit(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Chain: Rate limit check -> Handler s.rateLimitMiddlewares.Registration.Handler( http.HandlerFunc(handler), ).ServeHTTP(w, r) } } // Start starts the HTTP server func (s *Server) Start() error { s.logger.Info("") s.logger.Info("🚀 MaplePress Backend is ready!") s.logger.Info("", zap.String("address", s.server.Addr), zap.String("url", fmt.Sprintf("http://localhost:%s", s.server.Addr[len(s.server.Addr)-4:]))) s.logger.Info("") if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { return fmt.Errorf("failed to start server: %w", err) } return nil } // Shutdown gracefully shuts down the HTTP server func (s *Server) Shutdown(ctx context.Context) error { s.logger.Info("shutting down HTTP server") if err := s.server.Shutdown(ctx); err != nil { return fmt.Errorf("failed to shutdown server: %w", err) } s.logger.Info("HTTP server shut down successfully") return nil }