mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-30 08:14:25 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf760b979 | ||
|
|
0a9c107095 | ||
|
|
4e2fe33b77 |
@@ -58,6 +58,9 @@ func (h *Handler) SqlQueryList(sqlquery string, pNoCount, pBlankparms, pAllowFil
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Create local copy to avoid modifying the captured parameter across requests
|
||||||
|
sqlquery := sqlquery
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 900*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 900*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -393,6 +396,9 @@ func (h *Handler) SqlQuery(sqlquery string, pBlankparms bool) HTTPFuncType {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Create local copy to avoid modifying the captured parameter across requests
|
||||||
|
sqlquery := sqlquery
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 600*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 600*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -758,8 +764,10 @@ func (h *Handler) replaceMetaVariables(sqlquery string, r *http.Request, userCtx
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(sqlquery, "[rid_session]") {
|
if strings.Contains(sqlquery, "[rid_session]") {
|
||||||
sessionID, _ := strconv.ParseInt(userCtx.SessionID, 10, 64)
|
sqlquery = strings.ReplaceAll(sqlquery, "[rid_session]", fmt.Sprintf("%d", userCtx.SessionRID))
|
||||||
sqlquery = strings.ReplaceAll(sqlquery, "[rid_session]", fmt.Sprintf("%d", sessionID))
|
}
|
||||||
|
if strings.Contains(sqlquery, "[id_session]") {
|
||||||
|
sqlquery = strings.ReplaceAll(sqlquery, "[id_session]", userCtx.SessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(sqlquery, "[method]") {
|
if strings.Contains(sqlquery, "[method]") {
|
||||||
|
|||||||
160
pkg/security/examples_funcspec.go
Normal file
160
pkg/security/examples_funcspec.go
Normal file
@@ -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")
|
||||||
|
*/
|
||||||
@@ -11,6 +11,7 @@ type UserContext struct {
|
|||||||
UserName string `json:"user_name"`
|
UserName string `json:"user_name"`
|
||||||
UserLevel int `json:"user_level"`
|
UserLevel int `json:"user_level"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
|
SessionRID int64 `json:"session_rid"`
|
||||||
RemoteID string `json:"remote_id"`
|
RemoteID string `json:"remote_id"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package security
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// contextKey is a custom type for context keys to avoid collisions
|
// contextKey is a custom type for context keys to avoid collisions
|
||||||
@@ -14,6 +15,7 @@ const (
|
|||||||
UserNameKey contextKey = "user_name"
|
UserNameKey contextKey = "user_name"
|
||||||
UserLevelKey contextKey = "user_level"
|
UserLevelKey contextKey = "user_level"
|
||||||
SessionIDKey contextKey = "session_id"
|
SessionIDKey contextKey = "session_id"
|
||||||
|
SessionRIDKey contextKey = "session_rid"
|
||||||
RemoteIDKey contextKey = "remote_id"
|
RemoteIDKey contextKey = "remote_id"
|
||||||
UserRolesKey contextKey = "user_roles"
|
UserRolesKey contextKey = "user_roles"
|
||||||
UserEmailKey contextKey = "user_email"
|
UserEmailKey contextKey = "user_email"
|
||||||
@@ -58,6 +60,7 @@ func setUserContext(r *http.Request, userCtx *UserContext) *http.Request {
|
|||||||
ctx = context.WithValue(ctx, UserNameKey, userCtx.UserName)
|
ctx = context.WithValue(ctx, UserNameKey, userCtx.UserName)
|
||||||
ctx = context.WithValue(ctx, UserLevelKey, userCtx.UserLevel)
|
ctx = context.WithValue(ctx, UserLevelKey, userCtx.UserLevel)
|
||||||
ctx = context.WithValue(ctx, SessionIDKey, userCtx.SessionID)
|
ctx = context.WithValue(ctx, SessionIDKey, userCtx.SessionID)
|
||||||
|
ctx = context.WithValue(ctx, SessionRIDKey, userCtx.SessionRID)
|
||||||
ctx = context.WithValue(ctx, RemoteIDKey, userCtx.RemoteID)
|
ctx = context.WithValue(ctx, RemoteIDKey, userCtx.RemoteID)
|
||||||
ctx = context.WithValue(ctx, UserRolesKey, userCtx.Roles)
|
ctx = context.WithValue(ctx, UserRolesKey, userCtx.Roles)
|
||||||
|
|
||||||
@@ -190,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
|
// GetUserContext extracts the full user context from request context
|
||||||
func GetUserContext(ctx context.Context) (*UserContext, bool) {
|
func GetUserContext(ctx context.Context) (*UserContext, bool) {
|
||||||
userCtx, ok := ctx.Value(UserContextKey).(*UserContext)
|
userCtx, ok := ctx.Value(UserContextKey).(*UserContext)
|
||||||
@@ -220,6 +332,16 @@ func GetSessionID(ctx context.Context) (string, bool) {
|
|||||||
return sessionID, ok
|
return sessionID, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSessionID extracts the session ID from context
|
||||||
|
func GetSessionRID(ctx context.Context) (int64, bool) {
|
||||||
|
sessionRIDStr, ok := ctx.Value(SessionRIDKey).(string)
|
||||||
|
sessionRID, err := strconv.ParseInt(sessionRIDStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return sessionRID, ok
|
||||||
|
}
|
||||||
|
|
||||||
// GetRemoteID extracts the remote ID from context
|
// GetRemoteID extracts the remote ID from context
|
||||||
func GetRemoteID(ctx context.Context) (string, bool) {
|
func GetRemoteID(ctx context.Context) (string, bool) {
|
||||||
remoteID, ok := ctx.Value(RemoteIDKey).(string)
|
remoteID, ok := ctx.Value(RemoteIDKey).(string)
|
||||||
|
|||||||
Reference in New Issue
Block a user