From e923b0a2a3a6aef4e97bcc714396f198bbec52ef Mon Sep 17 00:00:00 2001 From: Hein Date: Mon, 16 Feb 2026 10:38:06 +0200 Subject: [PATCH] feat(routes): add authentication middleware support for routes --- pkg/resolvespec/resolvespec.go | 134 +++++++++++++++++++++++++++---- pkg/restheadspec/restheadspec.go | 71 +++++++++++----- 2 files changed, 171 insertions(+), 34 deletions(-) diff --git a/pkg/resolvespec/resolvespec.go b/pkg/resolvespec/resolvespec.go index bf78e08..a67b0c7 100644 --- a/pkg/resolvespec/resolvespec.go +++ b/pkg/resolvespec/resolvespec.go @@ -216,9 +216,30 @@ type BunRouterHandler interface { Handle(method, path string, handler bunrouter.HandlerFunc) } +// wrapBunRouterHandler wraps a bunrouter handler with auth middleware if provided +func wrapBunRouterHandler(handler bunrouter.HandlerFunc, authMiddleware MiddlewareFunc) bunrouter.HandlerFunc { + if authMiddleware == nil { + return handler + } + + return func(w http.ResponseWriter, req bunrouter.Request) error { + // Create an http.Handler that calls the bunrouter handler + httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _ = handler(w, req) + }) + + // Wrap with auth middleware and execute + wrappedHandler := authMiddleware(httpHandler) + wrappedHandler.ServeHTTP(w, req.Request) + + return nil + } +} + // SetupBunRouterRoutes sets up bunrouter routes for the ResolveSpec API // Accepts bunrouter.Router or bunrouter.Group -func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { +// authMiddleware is optional - if provided, routes will be protected with the middleware +func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler, authMiddleware MiddlewareFunc) { // CORS config corsConfig := common.DefaultCORSConfig() @@ -256,7 +277,7 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { currentEntity := entity // POST route without ID - r.Handle("POST", entityPath, func(w http.ResponseWriter, req bunrouter.Request) error { + postEntityHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewHTTPRequest(req.Request) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -267,10 +288,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("POST", entityPath, wrapBunRouterHandler(postEntityHandler, authMiddleware)) // POST route with ID - r.Handle("POST", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + postEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewHTTPRequest(req.Request) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -282,10 +304,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("POST", entityWithIDPath, wrapBunRouterHandler(postEntityWithIDHandler, authMiddleware)) // GET route without ID - r.Handle("GET", entityPath, func(w http.ResponseWriter, req bunrouter.Request) error { + getEntityHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewHTTPRequest(req.Request) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -296,10 +319,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.HandleGet(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("GET", entityPath, wrapBunRouterHandler(getEntityHandler, authMiddleware)) // GET route with ID - r.Handle("GET", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + getEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewHTTPRequest(req.Request) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -311,9 +335,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.HandleGet(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("GET", entityWithIDPath, wrapBunRouterHandler(getEntityWithIDHandler, authMiddleware)) // OPTIONS route without ID (returns metadata) + // Don't apply auth middleware to OPTIONS - CORS preflight must not require auth r.Handle("OPTIONS", entityPath, func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewHTTPRequest(req.Request) @@ -330,6 +356,7 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { }) // OPTIONS route with ID (returns metadata) + // Don't apply auth middleware to OPTIONS - CORS preflight must not require auth r.Handle("OPTIONS", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewHTTPRequest(req.Request) @@ -355,8 +382,8 @@ func ExampleWithBunRouter(bunDB *bun.DB) { // Create bunrouter bunRouter := bunrouter.New() - // Setup ResolveSpec routes with bunrouter - SetupBunRouterRoutes(bunRouter, handler) + // Setup ResolveSpec routes with bunrouter without authentication + SetupBunRouterRoutes(bunRouter, handler, nil) // Start server // http.ListenAndServe(":8080", bunRouter) @@ -377,8 +404,8 @@ func ExampleBunRouterWithBunDB(bunDB *bun.DB) { // Create bunrouter bunRouter := bunrouter.New() - // Setup ResolveSpec routes - SetupBunRouterRoutes(bunRouter, handler) + // Setup ResolveSpec routes without authentication + SetupBunRouterRoutes(bunRouter, handler, nil) // This gives you the full uptrace stack: bunrouter + Bun ORM // http.ListenAndServe(":8080", bunRouter) @@ -396,8 +423,87 @@ func ExampleBunRouterWithGroup(bunDB *bun.DB) { apiGroup := bunRouter.NewGroup("/api") // Setup ResolveSpec routes on the group - routes will be under /api - SetupBunRouterRoutes(apiGroup, handler) + SetupBunRouterRoutes(apiGroup, handler, nil) // Start server // http.ListenAndServe(":8080", bunRouter) } + +// ExampleWithGORMAndAuth shows how to use ResolveSpec with GORM and authentication +func ExampleWithGORMAndAuth(db *gorm.DB) { + // Create handler using GORM + _ = NewHandlerWithGORM(db) + + // Create auth middleware + // import "github.com/bitechdev/ResolveSpec/pkg/security" + // secList := security.NewSecurityList(myProvider) + // authMiddleware := func(h http.Handler) http.Handler { + // return security.NewAuthHandler(secList, h) + // } + + // Setup router with authentication + _ = mux.NewRouter() + // SetupMuxRoutes(muxRouter, handler, authMiddleware) + + // Register models + // handler.RegisterModel("public", "users", &User{}) + + // Start server + // http.ListenAndServe(":8080", muxRouter) +} + +// ExampleWithBunAndAuth shows how to use ResolveSpec with Bun and authentication +func ExampleWithBunAndAuth(bunDB *bun.DB) { + // Create Bun adapter + dbAdapter := database.NewBunAdapter(bunDB) + + // Create model registry + registry := modelregistry.NewModelRegistry() + // registry.RegisterModel("public.users", &User{}) + + // Create handler + _ = NewHandler(dbAdapter, registry) + + // Create auth middleware + // import "github.com/bitechdev/ResolveSpec/pkg/security" + // secList := security.NewSecurityList(myProvider) + // authMiddleware := func(h http.Handler) http.Handler { + // return security.NewAuthHandler(secList, h) + // } + + // Setup routes with authentication + _ = mux.NewRouter() + // SetupMuxRoutes(muxRouter, handler, authMiddleware) + + // Start server + // http.ListenAndServe(":8080", muxRouter) +} + +// ExampleBunRouterWithBunDBAndAuth shows the full uptrace stack with authentication +func ExampleBunRouterWithBunDBAndAuth(bunDB *bun.DB) { + // Create Bun database adapter + dbAdapter := database.NewBunAdapter(bunDB) + + // Create model registry + registry := modelregistry.NewModelRegistry() + // registry.RegisterModel("public.users", &User{}) + + // Create handler with Bun + _ = NewHandler(dbAdapter, registry) + + // Create auth middleware + // import "github.com/bitechdev/ResolveSpec/pkg/security" + // secList := security.NewSecurityList(myProvider) + // authMiddleware := func(h http.Handler) http.Handler { + // return security.NewAuthHandler(secList, h) + // } + + // Create bunrouter + _ = bunrouter.New() + + // Setup ResolveSpec routes with authentication + // SetupBunRouterRoutes(bunRouter, handler, authMiddleware) + + // This gives you the full uptrace stack: bunrouter + Bun ORM with authentication + // http.ListenAndServe(":8080", bunRouter) +} diff --git a/pkg/restheadspec/restheadspec.go b/pkg/restheadspec/restheadspec.go index 5a743bb..99582fd 100644 --- a/pkg/restheadspec/restheadspec.go +++ b/pkg/restheadspec/restheadspec.go @@ -280,9 +280,30 @@ type BunRouterHandler interface { Handle(method, path string, handler bunrouter.HandlerFunc) } +// wrapBunRouterHandler wraps a bunrouter handler with auth middleware if provided +func wrapBunRouterHandler(handler bunrouter.HandlerFunc, authMiddleware MiddlewareFunc) bunrouter.HandlerFunc { + if authMiddleware == nil { + return handler + } + + return func(w http.ResponseWriter, req bunrouter.Request) error { + // Create an http.Handler that calls the bunrouter handler + httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _ = handler(w, req) + }) + + // Wrap with auth middleware and execute + wrappedHandler := authMiddleware(httpHandler) + wrappedHandler.ServeHTTP(w, req.Request) + + return nil + } +} + // SetupBunRouterRoutes sets up bunrouter routes for the RestHeadSpec API // Accepts bunrouter.Router or bunrouter.Group -func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { +// authMiddleware is optional - if provided, routes will be protected with the middleware +func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler, authMiddleware MiddlewareFunc) { // CORS config corsConfig := common.DefaultCORSConfig() @@ -313,7 +334,7 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { currentEntity := entity // GET and POST for /{schema}/{entity} - r.Handle("GET", entityPath, func(w http.ResponseWriter, req bunrouter.Request) error { + getEntityHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -324,9 +345,10 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("GET", entityPath, wrapBunRouterHandler(getEntityHandler, authMiddleware)) - r.Handle("POST", entityPath, func(w http.ResponseWriter, req bunrouter.Request) error { + postEntityHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -337,10 +359,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("POST", entityPath, wrapBunRouterHandler(postEntityHandler, authMiddleware)) // GET, POST, PUT, PATCH, DELETE for /{schema}/{entity}/:id - r.Handle("GET", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + getEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -352,9 +375,10 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("GET", entityWithIDPath, wrapBunRouterHandler(getEntityWithIDHandler, authMiddleware)) - r.Handle("POST", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + postEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -366,9 +390,10 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("POST", entityWithIDPath, wrapBunRouterHandler(postEntityWithIDHandler, authMiddleware)) - r.Handle("PUT", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + putEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -380,9 +405,10 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("PUT", entityWithIDPath, wrapBunRouterHandler(putEntityWithIDHandler, authMiddleware)) - r.Handle("PATCH", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + patchEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -394,9 +420,10 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("PATCH", entityWithIDPath, wrapBunRouterHandler(patchEntityWithIDHandler, authMiddleware)) - r.Handle("DELETE", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { + deleteEntityWithIDHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -408,10 +435,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.Handle(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("DELETE", entityWithIDPath, wrapBunRouterHandler(deleteEntityWithIDHandler, authMiddleware)) // Metadata endpoint - r.Handle("GET", metadataPath, func(w http.ResponseWriter, req bunrouter.Request) error { + metadataHandler := func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) common.SetCORSHeaders(respAdapter, reqAdapter, corsConfig) @@ -422,9 +450,11 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { handler.HandleGet(respAdapter, reqAdapter, params) return nil - }) + } + r.Handle("GET", metadataPath, wrapBunRouterHandler(metadataHandler, authMiddleware)) // OPTIONS route without ID (returns metadata) + // Don't apply auth middleware to OPTIONS - CORS preflight must not require auth r.Handle("OPTIONS", entityPath, func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) @@ -441,6 +471,7 @@ func SetupBunRouterRoutes(r BunRouterHandler, handler *Handler) { }) // OPTIONS route with ID (returns metadata) + // Don't apply auth middleware to OPTIONS - CORS preflight must not require auth r.Handle("OPTIONS", entityWithIDPath, func(w http.ResponseWriter, req bunrouter.Request) error { respAdapter := router.NewHTTPResponseWriter(w) reqAdapter := router.NewBunRouterRequest(req) @@ -466,8 +497,8 @@ func ExampleBunRouterWithBunDB(bunDB *bun.DB) { // Create bunrouter bunRouter := bunrouter.New() - // Setup routes - SetupBunRouterRoutes(bunRouter, handler) + // Setup routes without authentication + SetupBunRouterRoutes(bunRouter, handler, nil) // Start server if err := http.ListenAndServe(":8080", bunRouter); err != nil { @@ -487,7 +518,7 @@ func ExampleBunRouterWithGroup(bunDB *bun.DB) { apiGroup := bunRouter.NewGroup("/api") // Setup RestHeadSpec routes on the group - routes will be under /api - SetupBunRouterRoutes(apiGroup, handler) + SetupBunRouterRoutes(apiGroup, handler, nil) // Start server if err := http.ListenAndServe(":8080", bunRouter); err != nil {