From 2cf760b9798e60194c01f0fd4a6255051e8666d8 Mon Sep 17 00:00:00 2001 From: Hein Date: Tue, 9 Dec 2025 10:31:08 +0200 Subject: [PATCH] Added a few auth shortcuts --- pkg/security/examples_funcspec.go | 160 ++++++++++++++++++++++++++++++ pkg/security/middleware.go | 109 ++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 pkg/security/examples_funcspec.go diff --git a/pkg/security/examples_funcspec.go b/pkg/security/examples_funcspec.go new file mode 100644 index 0000000..ff5186b --- /dev/null +++ b/pkg/security/examples_funcspec.go @@ -0,0 +1,160 @@ +package security + +// This file contains usage examples for integrating security with funcspec handlers +// These are example snippets - not executable code + +/* +Example 1: Wrap handlers with authentication (required) + + import ( + "github.com/bitechdev/ResolveSpec/pkg/funcspec" + "github.com/bitechdev/ResolveSpec/pkg/security" + "github.com/gorilla/mux" + ) + + // Setup + db := ... // your database connection + securityList := ... // your security list + handler := funcspec.NewHandler(db) + router := mux.NewRouter() + + // Wrap handler with required authentication (returns 401 if not authenticated) + ordersHandler := security.WithAuth( + handler.SqlQueryList("SELECT * FROM orders WHERE user_id = [rid_user]", false, false, false), + securityList, + ) + router.HandleFunc("/api/orders", ordersHandler).Methods("GET") + +Example 2: Wrap handlers with optional authentication + + // Wrap handler with optional authentication (falls back to guest if not authenticated) + productsHandler := security.WithOptionalAuth( + handler.SqlQueryList("SELECT * FROM products WHERE deleted = false", false, false, false), + securityList, + ) + router.HandleFunc("/api/products", productsHandler).Methods("GET") + + // The handler will show all products for guests, but could show personalized pricing + // or recommendations for authenticated users based on [rid_user] + +Example 3: Wrap handlers with both authentication and security context + + // Use the convenience function for both auth and security context + usersHandler := security.WithAuthAndSecurity( + handler.SqlQueryList("SELECT * FROM users WHERE active = true", false, false, false), + securityList, + ) + router.HandleFunc("/api/users", usersHandler).Methods("GET") + + // Or use WithOptionalAuthAndSecurity for optional auth + postsHandler := security.WithOptionalAuthAndSecurity( + handler.SqlQueryList("SELECT * FROM posts WHERE published = true", false, false, false), + securityList, + ) + router.HandleFunc("/api/posts", postsHandler).Methods("GET") + +Example 4: Wrap a single funcspec handler with security context only + + import ( + "github.com/bitechdev/ResolveSpec/pkg/funcspec" + "github.com/bitechdev/ResolveSpec/pkg/security" + "github.com/gorilla/mux" + ) + + // Setup + db := ... // your database connection + securityList := ... // your security list + handler := funcspec.NewHandler(db) + router := mux.NewRouter() + + // Wrap a specific handler with security context + usersHandler := security.WithSecurityContext( + handler.SqlQueryList("SELECT * FROM users WHERE active = true", false, false, false), + securityList, + ) + router.HandleFunc("/api/users", usersHandler).Methods("GET") + +Example 5: Wrap multiple handlers for different paths + + // Products list endpoint + productsHandler := security.WithSecurityContext( + handler.SqlQueryList("SELECT * FROM products WHERE deleted = false", false, true, true), + securityList, + ) + router.HandleFunc("/api/products", productsHandler).Methods("GET") + + // Single product endpoint + productHandler := security.WithSecurityContext( + handler.SqlQuery("SELECT * FROM products WHERE id = [id]", true), + securityList, + ) + router.HandleFunc("/api/products/{id}", productHandler).Methods("GET") + + // Orders endpoint with user filtering + ordersHandler := security.WithSecurityContext( + handler.SqlQueryList("SELECT * FROM orders WHERE user_id = [rid_user]", false, false, false), + securityList, + ) + router.HandleFunc("/api/orders", ordersHandler).Methods("GET") + +Example 6: Helper function to wrap multiple handlers + + // Create a helper function for your application + func secureHandler(h funcspec.HTTPFuncType, sl *SecurityList) funcspec.HTTPFuncType { + return security.WithSecurityContext(h, sl) + } + + // Use it to wrap handlers + router.HandleFunc("/api/users", secureHandler( + handler.SqlQueryList("SELECT * FROM users", false, false, false), + securityList, + )).Methods("GET") + + router.HandleFunc("/api/roles", secureHandler( + handler.SqlQueryList("SELECT * FROM roles", false, false, false), + securityList, + )).Methods("GET") + +Example 7: Access SecurityList and user context in hooks + + // In your funcspec hook, you can now access the SecurityList and user context + handler.Hooks().Register(funcspec.BeforeQueryList, func(ctx *funcspec.HookContext) error { + // Get SecurityList from context + if secList, ok := security.GetSecurityList(ctx.Context); ok { + // Use secList to apply security rules + // e.g., apply row-level security, column masking, etc. + _ = secList + } + + // Get user context + if userCtx, ok := security.GetUserContext(ctx.Context); ok { + // Access user information + logger.Info("User %s (ID: %d) accessing resource", userCtx.UserName, userCtx.UserID) + } + + return nil + }) + +Example 8: Mixing authentication and security patterns + + // Public endpoint - no auth required, but has security context + publicHandler := security.WithSecurityContext( + handler.SqlQueryList("SELECT * FROM public_data", false, false, false), + securityList, + ) + router.HandleFunc("/api/public", publicHandler).Methods("GET") + + // Optional auth - personalized for logged-in users, works for guests + personalizedHandler := security.WithOptionalAuth( + handler.SqlQueryList("SELECT * FROM products WHERE category = [category]", false, true, false), + securityList, + ) + router.HandleFunc("/api/products/category/{category}", personalizedHandler).Methods("GET") + + // Required auth - must be logged in + privateHandler := security.WithAuthAndSecurity( + handler.SqlQueryList("SELECT * FROM private_data WHERE user_id = [rid_user]", false, false, false), + securityList, + ) + router.HandleFunc("/api/private", privateHandler).Methods("GET") +*/ diff --git a/pkg/security/middleware.go b/pkg/security/middleware.go index 28baaef..c2f1fdb 100644 --- a/pkg/security/middleware.go +++ b/pkg/security/middleware.go @@ -193,6 +193,115 @@ func SetSecurityMiddleware(securityList *SecurityList) func(http.Handler) http.H } } +// WithAuth wraps an HTTPFuncType handler with required authentication +// This function performs authentication and returns 401 if authentication fails +// Use this for handlers that require authenticated users +// +// Usage: +// +// handler := funcspec.NewHandler(db) +// wrappedHandler := security.WithAuth(handler.SqlQueryList("SELECT * FROM orders WHERE user_id = [rid_user]", false, false, false), securityList) +// router.HandleFunc("/api/orders", wrappedHandler) +func WithAuth(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // Get the security provider + provider := securityList.Provider() + if provider == nil { + http.Error(w, "Security provider not configured", http.StatusInternalServerError) + return + } + + // Authenticate the request + authenticatedReq, ok := authenticateRequest(w, r, provider) + if !ok { + return // authenticateRequest already wrote the error response + } + + // Continue with authenticated context + handler(w, authenticatedReq) + } +} + +// WithOptionalAuth wraps an HTTPFuncType handler with optional authentication +// This function tries to authenticate but falls back to guest context if authentication fails +// Use this for handlers that should show personalized content for authenticated users but still work for guests +// +// Usage: +// +// handler := funcspec.NewHandler(db) +// wrappedHandler := security.WithOptionalAuth(handler.SqlQueryList("SELECT * FROM products", false, false, false), securityList) +// router.HandleFunc("/api/products", wrappedHandler) +func WithOptionalAuth(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // Get the security provider + provider := securityList.Provider() + if provider == nil { + http.Error(w, "Security provider not configured", http.StatusInternalServerError) + return + } + + // Try to authenticate + userCtx, err := provider.Authenticate(r) + if err != nil { + // Authentication failed - set guest context and continue + guestCtx := createGuestContext(r) + handler(w, setUserContext(r, guestCtx)) + return + } + + // Authentication succeeded - set user context + handler(w, setUserContext(r, userCtx)) + } +} + +// WithSecurityContext wraps an HTTPFuncType handler with security context +// This function allows you to add security context to specific handler functions +// without needing to apply middleware globally +// +// Usage: +// +// handler := funcspec.NewHandler(db) +// wrappedHandler := security.WithSecurityContext(handler.SqlQueryList("SELECT * FROM users", false, false, false), securityList) +// router.HandleFunc("/api/users", wrappedHandler) +func WithSecurityContext(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), SECURITY_CONTEXT_KEY, securityList) + handler(w, r.WithContext(ctx)) + } +} + +// WithAuthAndSecurity wraps an HTTPFuncType handler with both authentication and security context +// This is a convenience function that combines WithAuth and WithSecurityContext +// Use this when you need both authentication and security context for a handler +// +// Usage: +// +// handler := funcspec.NewHandler(db) +// wrappedHandler := security.WithAuthAndSecurity(handler.SqlQueryList("SELECT * FROM users", false, false, false), securityList) +// router.HandleFunc("/api/users", wrappedHandler) +func WithAuthAndSecurity(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request) { + return WithAuth(WithSecurityContext(handler, securityList), securityList) +} + +// WithOptionalAuthAndSecurity wraps an HTTPFuncType handler with optional authentication and security context +// This is a convenience function that combines WithOptionalAuth and WithSecurityContext +// Use this when you want optional authentication and security context for a handler +// +// Usage: +// +// handler := funcspec.NewHandler(db) +// wrappedHandler := security.WithOptionalAuthAndSecurity(handler.SqlQueryList("SELECT * FROM products", false, false, false), securityList) +// router.HandleFunc("/api/products", wrappedHandler) +func WithOptionalAuthAndSecurity(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request) { + return WithOptionalAuth(WithSecurityContext(handler, securityList), securityList) +} + +// GetSecurityList extracts the SecurityList from request context +func GetSecurityList(ctx context.Context) (*SecurityList, bool) { + securityList, ok := ctx.Value(SECURITY_CONTEXT_KEY).(*SecurityList) + return securityList, ok +} + // GetUserContext extracts the full user context from request context func GetUserContext(ctx context.Context) (*UserContext, bool) { userCtx, ok := ctx.Value(UserContextKey).(*UserContext)