mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-29 07:44:25 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c5c7689e9 | ||
|
|
08050c960d |
@@ -47,8 +47,8 @@ func main() {
|
||||
handler.RegisterModel("public", modelNames[i], model)
|
||||
}
|
||||
|
||||
// Setup routes using new SetupMuxRoutes function
|
||||
resolvespec.SetupMuxRoutes(r, handler)
|
||||
// Setup routes using new SetupMuxRoutes function (without authentication)
|
||||
resolvespec.SetupMuxRoutes(r, handler, nil)
|
||||
|
||||
// Start server
|
||||
logger.Info("Starting server on :8080")
|
||||
|
||||
97
pkg/common/handler_example.go
Normal file
97
pkg/common/handler_example.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package common
|
||||
|
||||
// Example showing how to use the common handler interfaces
|
||||
// This file demonstrates the handler interface hierarchy and usage patterns
|
||||
|
||||
// ProcessWithAnyHandler demonstrates using the base SpecHandler interface
|
||||
// which works with any handler type (resolvespec, restheadspec, or funcspec)
|
||||
func ProcessWithAnyHandler(handler SpecHandler) Database {
|
||||
// All handlers expose GetDatabase() through the SpecHandler interface
|
||||
return handler.GetDatabase()
|
||||
}
|
||||
|
||||
// ProcessCRUDRequest demonstrates using the CRUDHandler interface
|
||||
// which works with resolvespec.Handler and restheadspec.Handler
|
||||
func ProcessCRUDRequest(handler CRUDHandler, w ResponseWriter, r Request, params map[string]string) {
|
||||
// Both resolvespec and restheadspec handlers implement Handle()
|
||||
handler.Handle(w, r, params)
|
||||
}
|
||||
|
||||
// ProcessMetadataRequest demonstrates getting metadata from CRUD handlers
|
||||
func ProcessMetadataRequest(handler CRUDHandler, w ResponseWriter, r Request, params map[string]string) {
|
||||
// Both resolvespec and restheadspec handlers implement HandleGet()
|
||||
handler.HandleGet(w, r, params)
|
||||
}
|
||||
|
||||
// Example usage patterns (not executable, just for documentation):
|
||||
/*
|
||||
// Example 1: Using with resolvespec.Handler
|
||||
func ExampleResolveSpec() {
|
||||
db := // ... get database
|
||||
registry := // ... get registry
|
||||
|
||||
handler := resolvespec.NewHandler(db, registry)
|
||||
|
||||
// Can be used as SpecHandler
|
||||
var specHandler SpecHandler = handler
|
||||
database := specHandler.GetDatabase()
|
||||
|
||||
// Can be used as CRUDHandler
|
||||
var crudHandler CRUDHandler = handler
|
||||
crudHandler.Handle(w, r, params)
|
||||
crudHandler.HandleGet(w, r, params)
|
||||
}
|
||||
|
||||
// Example 2: Using with restheadspec.Handler
|
||||
func ExampleRestHeadSpec() {
|
||||
db := // ... get database
|
||||
registry := // ... get registry
|
||||
|
||||
handler := restheadspec.NewHandler(db, registry)
|
||||
|
||||
// Can be used as SpecHandler
|
||||
var specHandler SpecHandler = handler
|
||||
database := specHandler.GetDatabase()
|
||||
|
||||
// Can be used as CRUDHandler
|
||||
var crudHandler CRUDHandler = handler
|
||||
crudHandler.Handle(w, r, params)
|
||||
crudHandler.HandleGet(w, r, params)
|
||||
}
|
||||
|
||||
// Example 3: Using with funcspec.Handler
|
||||
func ExampleFuncSpec() {
|
||||
db := // ... get database
|
||||
|
||||
handler := funcspec.NewHandler(db)
|
||||
|
||||
// Can be used as SpecHandler
|
||||
var specHandler SpecHandler = handler
|
||||
database := specHandler.GetDatabase()
|
||||
|
||||
// Can be used as QueryHandler
|
||||
var queryHandler QueryHandler = handler
|
||||
// funcspec has different methods: SqlQueryList() and SqlQuery()
|
||||
// which return HTTP handler functions
|
||||
}
|
||||
|
||||
// Example 4: Polymorphic handler processing
|
||||
func ProcessHandlers(handlers []SpecHandler) {
|
||||
for _, handler := range handlers {
|
||||
// All handlers expose the database
|
||||
db := handler.GetDatabase()
|
||||
|
||||
// Type switch for specific handler types
|
||||
switch h := handler.(type) {
|
||||
case CRUDHandler:
|
||||
// This is resolvespec or restheadspec
|
||||
// Can call Handle() and HandleGet()
|
||||
_ = h
|
||||
case QueryHandler:
|
||||
// This is funcspec
|
||||
// Can call SqlQueryList() and SqlQuery()
|
||||
_ = h
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -246,3 +246,39 @@ type PrimaryKeyNameProvider interface {
|
||||
type SchemaProvider interface {
|
||||
SchemaName() string
|
||||
}
|
||||
|
||||
// SpecHandler interface represents common functionality across all spec handlers
|
||||
// This is the base interface implemented by:
|
||||
// - resolvespec.Handler: Handles CRUD operations via request body with explicit operation field
|
||||
// - restheadspec.Handler: Handles CRUD operations via HTTP methods (GET/POST/PUT/DELETE)
|
||||
// - funcspec.Handler: Handles custom SQL query execution with dynamic parameters
|
||||
//
|
||||
// The interface hierarchy is:
|
||||
//
|
||||
// SpecHandler (base)
|
||||
// ├── CRUDHandler (resolvespec, restheadspec)
|
||||
// └── QueryHandler (funcspec)
|
||||
type SpecHandler interface {
|
||||
// GetDatabase returns the underlying database connection
|
||||
GetDatabase() Database
|
||||
}
|
||||
|
||||
// CRUDHandler interface for handlers that support CRUD operations
|
||||
// This is implemented by resolvespec.Handler and restheadspec.Handler
|
||||
type CRUDHandler interface {
|
||||
SpecHandler
|
||||
|
||||
// Handle processes API requests through router-agnostic interface
|
||||
Handle(w ResponseWriter, r Request, params map[string]string)
|
||||
|
||||
// HandleGet processes GET requests for metadata
|
||||
HandleGet(w ResponseWriter, r Request, params map[string]string)
|
||||
}
|
||||
|
||||
// QueryHandler interface for handlers that execute SQL queries
|
||||
// This is implemented by funcspec.Handler
|
||||
// Note: funcspec uses standard http.ResponseWriter and *http.Request instead of common interfaces
|
||||
type QueryHandler interface {
|
||||
SpecHandler
|
||||
// Methods are defined in funcspec package due to different function signature requirements
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ func NewHandler(db common.Database) *Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// GetDatabase returns the underlying database connection
|
||||
// Implements common.SpecHandler interface
|
||||
func (h *Handler) GetDatabase() common.Database {
|
||||
return h.db
|
||||
}
|
||||
|
||||
// Hooks returns the hook registry for this handler
|
||||
// Use this to register custom hooks for operations
|
||||
func (h *Handler) Hooks() *HookRegistry {
|
||||
|
||||
@@ -23,6 +23,15 @@ func Init(dev bool) {
|
||||
|
||||
}
|
||||
|
||||
func UpdateLoggerPath(path string, dev bool) {
|
||||
defaultConfig := zap.NewProductionConfig()
|
||||
if dev {
|
||||
defaultConfig = zap.NewDevelopmentConfig()
|
||||
}
|
||||
defaultConfig.OutputPaths = []string{path}
|
||||
UpdateLogger(&defaultConfig)
|
||||
}
|
||||
|
||||
func UpdateLogger(config *zap.Config) {
|
||||
defaultConfig := zap.NewProductionConfig()
|
||||
defaultConfig.OutputPaths = []string{"resolvespec.log"}
|
||||
|
||||
@@ -34,6 +34,12 @@ func NewHandler(db common.Database, registry common.ModelRegistry) *Handler {
|
||||
return handler
|
||||
}
|
||||
|
||||
// GetDatabase returns the underlying database connection
|
||||
// Implements common.SpecHandler interface
|
||||
func (h *Handler) GetDatabase() common.Database {
|
||||
return h.db
|
||||
}
|
||||
|
||||
// handlePanic is a helper function to handle panics with stack traces
|
||||
func (h *Handler) handlePanic(w common.ResponseWriter, method string, err interface{}) {
|
||||
stack := debug.Stack()
|
||||
|
||||
@@ -37,28 +37,46 @@ func NewStandardBunRouter() *router.StandardBunRouterAdapter {
|
||||
return router.NewStandardBunRouterAdapter()
|
||||
}
|
||||
|
||||
// MiddlewareFunc is a function that wraps an http.Handler with additional functionality
|
||||
type MiddlewareFunc func(http.Handler) http.Handler
|
||||
|
||||
// SetupMuxRoutes sets up routes for the ResolveSpec API with Mux
|
||||
func SetupMuxRoutes(muxRouter *mux.Router, handler *Handler) {
|
||||
muxRouter.HandleFunc("/{schema}/{entity}", func(w http.ResponseWriter, r *http.Request) {
|
||||
// authMiddleware is optional - if provided, routes will be protected with the middleware
|
||||
// Example: SetupMuxRoutes(router, handler, func(h http.Handler) http.Handler { return security.NewAuthHandler(securityList, h) })
|
||||
func SetupMuxRoutes(muxRouter *mux.Router, handler *Handler, authMiddleware MiddlewareFunc) {
|
||||
// Create handler functions
|
||||
postEntityHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
reqAdapter := router.NewHTTPRequest(r)
|
||||
respAdapter := router.NewHTTPResponseWriter(w)
|
||||
handler.Handle(respAdapter, reqAdapter, vars)
|
||||
}).Methods("POST")
|
||||
})
|
||||
|
||||
muxRouter.HandleFunc("/{schema}/{entity}/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
postEntityWithIDHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
reqAdapter := router.NewHTTPRequest(r)
|
||||
respAdapter := router.NewHTTPResponseWriter(w)
|
||||
handler.Handle(respAdapter, reqAdapter, vars)
|
||||
}).Methods("POST")
|
||||
})
|
||||
|
||||
muxRouter.HandleFunc("/{schema}/{entity}", func(w http.ResponseWriter, r *http.Request) {
|
||||
getEntityHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
reqAdapter := router.NewHTTPRequest(r)
|
||||
respAdapter := router.NewHTTPResponseWriter(w)
|
||||
handler.HandleGet(respAdapter, reqAdapter, vars)
|
||||
}).Methods("GET")
|
||||
})
|
||||
|
||||
// Apply authentication middleware if provided
|
||||
if authMiddleware != nil {
|
||||
postEntityHandler = authMiddleware(postEntityHandler).(http.HandlerFunc)
|
||||
postEntityWithIDHandler = authMiddleware(postEntityWithIDHandler).(http.HandlerFunc)
|
||||
getEntityHandler = authMiddleware(getEntityHandler).(http.HandlerFunc)
|
||||
}
|
||||
|
||||
// Register routes
|
||||
muxRouter.Handle("/{schema}/{entity}", postEntityHandler).Methods("POST")
|
||||
muxRouter.Handle("/{schema}/{entity}/{id}", postEntityWithIDHandler).Methods("POST")
|
||||
muxRouter.Handle("/{schema}/{entity}", getEntityHandler).Methods("GET")
|
||||
}
|
||||
|
||||
// Example usage functions for documentation:
|
||||
@@ -68,12 +86,20 @@ func ExampleWithGORM(db *gorm.DB) {
|
||||
// Create handler using GORM
|
||||
handler := NewHandlerWithGORM(db)
|
||||
|
||||
// Setup router
|
||||
// Setup router without authentication
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler)
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
// Register models
|
||||
// handler.RegisterModel("public", "users", &User{})
|
||||
|
||||
// To add authentication, pass a middleware function:
|
||||
// import "github.com/bitechdev/ResolveSpec/pkg/security"
|
||||
// secList := security.NewSecurityList(myProvider)
|
||||
// authMiddleware := func(h http.Handler) http.Handler {
|
||||
// return security.NewAuthHandler(secList, h)
|
||||
// }
|
||||
// SetupMuxRoutes(muxRouter, handler, authMiddleware)
|
||||
}
|
||||
|
||||
// ExampleWithBun shows how to switch to Bun ORM
|
||||
@@ -88,9 +114,9 @@ func ExampleWithBun(bunDB *bun.DB) {
|
||||
// Create handler
|
||||
handler := NewHandler(dbAdapter, registry)
|
||||
|
||||
// Setup routes
|
||||
// Setup routes without authentication
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler)
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
}
|
||||
|
||||
// SetupBunRouterRoutes sets up bunrouter routes for the ResolveSpec API
|
||||
|
||||
@@ -38,6 +38,12 @@ func NewHandler(db common.Database, registry common.ModelRegistry) *Handler {
|
||||
return handler
|
||||
}
|
||||
|
||||
// GetDatabase returns the underlying database connection
|
||||
// Implements common.SpecHandler interface
|
||||
func (h *Handler) GetDatabase() common.Database {
|
||||
return h.db
|
||||
}
|
||||
|
||||
// Hooks returns the hook registry for this handler
|
||||
// Use this to register custom hooks for operations
|
||||
func (h *Handler) Hooks() *HookRegistry {
|
||||
|
||||
@@ -90,31 +90,51 @@ func NewStandardBunRouter() *router.StandardBunRouterAdapter {
|
||||
return router.NewStandardBunRouterAdapter()
|
||||
}
|
||||
|
||||
// MiddlewareFunc is a function that wraps an http.Handler with additional functionality
|
||||
type MiddlewareFunc func(http.Handler) http.Handler
|
||||
|
||||
// SetupMuxRoutes sets up routes for the RestHeadSpec API with Mux
|
||||
func SetupMuxRoutes(muxRouter *mux.Router, handler *Handler) {
|
||||
// GET, POST, PUT, PATCH, DELETE for /{schema}/{entity}
|
||||
muxRouter.HandleFunc("/{schema}/{entity}", func(w http.ResponseWriter, r *http.Request) {
|
||||
// authMiddleware is optional - if provided, routes will be protected with the middleware
|
||||
// Example: SetupMuxRoutes(router, handler, func(h http.Handler) http.Handler { return security.NewAuthHandler(securityList, h) })
|
||||
func SetupMuxRoutes(muxRouter *mux.Router, handler *Handler, authMiddleware MiddlewareFunc) {
|
||||
// Create handler functions
|
||||
entityHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
reqAdapter := router.NewHTTPRequest(r)
|
||||
respAdapter := router.NewHTTPResponseWriter(w)
|
||||
handler.Handle(respAdapter, reqAdapter, vars)
|
||||
}).Methods("GET", "POST")
|
||||
})
|
||||
|
||||
// GET, PUT, PATCH, DELETE for /{schema}/{entity}/{id}
|
||||
muxRouter.HandleFunc("/{schema}/{entity}/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
entityWithIDHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
reqAdapter := router.NewHTTPRequest(r)
|
||||
respAdapter := router.NewHTTPResponseWriter(w)
|
||||
handler.Handle(respAdapter, reqAdapter, vars)
|
||||
}).Methods("GET", "PUT", "PATCH", "DELETE", "POST")
|
||||
})
|
||||
|
||||
// GET for metadata (using HandleGet)
|
||||
muxRouter.HandleFunc("/{schema}/{entity}/metadata", func(w http.ResponseWriter, r *http.Request) {
|
||||
metadataHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
reqAdapter := router.NewHTTPRequest(r)
|
||||
respAdapter := router.NewHTTPResponseWriter(w)
|
||||
handler.HandleGet(respAdapter, reqAdapter, vars)
|
||||
}).Methods("GET")
|
||||
})
|
||||
|
||||
// Apply authentication middleware if provided
|
||||
if authMiddleware != nil {
|
||||
entityHandler = authMiddleware(entityHandler).(http.HandlerFunc)
|
||||
entityWithIDHandler = authMiddleware(entityWithIDHandler).(http.HandlerFunc)
|
||||
metadataHandler = authMiddleware(metadataHandler).(http.HandlerFunc)
|
||||
}
|
||||
|
||||
// Register routes
|
||||
// GET, POST for /{schema}/{entity}
|
||||
muxRouter.Handle("/{schema}/{entity}", entityHandler).Methods("GET", "POST")
|
||||
|
||||
// GET, PUT, PATCH, DELETE, POST for /{schema}/{entity}/{id}
|
||||
muxRouter.Handle("/{schema}/{entity}/{id}", entityWithIDHandler).Methods("GET", "PUT", "PATCH", "DELETE", "POST")
|
||||
|
||||
// GET for metadata (using HandleGet)
|
||||
muxRouter.Handle("/{schema}/{entity}/metadata", metadataHandler).Methods("GET")
|
||||
}
|
||||
|
||||
// Example usage functions for documentation:
|
||||
@@ -124,12 +144,20 @@ func ExampleWithGORM(db *gorm.DB) {
|
||||
// Create handler using GORM
|
||||
handler := NewHandlerWithGORM(db)
|
||||
|
||||
// Setup router
|
||||
// Setup router without authentication
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler)
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
|
||||
// Register models
|
||||
// handler.registry.RegisterModel("public.users", &User{})
|
||||
|
||||
// To add authentication, pass a middleware function:
|
||||
// import "github.com/bitechdev/ResolveSpec/pkg/security"
|
||||
// secList := security.NewSecurityList(myProvider)
|
||||
// authMiddleware := func(h http.Handler) http.Handler {
|
||||
// return security.NewAuthHandler(secList, h)
|
||||
// }
|
||||
// SetupMuxRoutes(muxRouter, handler, authMiddleware)
|
||||
}
|
||||
|
||||
// ExampleWithBun shows how to switch to Bun ORM
|
||||
@@ -144,9 +172,9 @@ func ExampleWithBun(bunDB *bun.DB) {
|
||||
// Create handler
|
||||
handler := NewHandler(dbAdapter, registry)
|
||||
|
||||
// Setup routes
|
||||
// Setup routes without authentication
|
||||
muxRouter := mux.NewRouter()
|
||||
SetupMuxRoutes(muxRouter, handler)
|
||||
SetupMuxRoutes(muxRouter, handler, nil)
|
||||
}
|
||||
|
||||
// SetupBunRouterRoutes sets up bunrouter routes for the RestHeadSpec API
|
||||
|
||||
@@ -91,7 +91,8 @@ security.UserContext{
|
||||
RemoteID: "remote_xyz", // Remote system ID
|
||||
Roles: []string{"admin"}, // User roles
|
||||
Email: "john@example.com", // User email
|
||||
Claims: map[string]any{}, // Additional metadata
|
||||
Claims: map[string]any{}, // Additional authentication claims
|
||||
Meta: map[string]any{}, // Additional metadata (JSON-serializable)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -621,6 +622,67 @@ func main() {
|
||||
|
||||
---
|
||||
|
||||
## Authentication Modes
|
||||
|
||||
```go
|
||||
// Required authentication (default)
|
||||
// Authentication must succeed or returns 401
|
||||
router.Use(security.NewAuthMiddleware(securityList))
|
||||
|
||||
// Skip authentication for specific routes
|
||||
// Always sets guest user context
|
||||
func PublicRoute(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := security.SkipAuth(r.Context())
|
||||
r = r.WithContext(ctx)
|
||||
// Guest context will be set
|
||||
}
|
||||
|
||||
// Optional authentication for specific routes
|
||||
// Tries to authenticate, falls back to guest if it fails
|
||||
func HomeRoute(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := security.OptionalAuth(r.Context())
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
userCtx, _ := security.GetUserContext(r.Context())
|
||||
if userCtx.UserID == 0 {
|
||||
// Guest user
|
||||
} else {
|
||||
// Authenticated user
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Comparison:**
|
||||
- **Required**: Auth must succeed or return 401 (default)
|
||||
- **SkipAuth**: Never tries to authenticate, always guest
|
||||
- **OptionalAuth**: Tries to authenticate, guest on failure
|
||||
|
||||
---
|
||||
|
||||
## Standalone Handlers
|
||||
|
||||
```go
|
||||
// NewAuthHandler - Required authentication (returns 401 on failure)
|
||||
authHandler := security.NewAuthHandler(securityList, myHandler)
|
||||
http.Handle("/api/protected", authHandler)
|
||||
|
||||
// NewOptionalAuthHandler - Optional authentication (guest on failure)
|
||||
optionalHandler := security.NewOptionalAuthHandler(securityList, myHandler)
|
||||
http.Handle("/home", optionalHandler)
|
||||
|
||||
// Example handler
|
||||
func myHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userCtx, _ := security.GetUserContext(r.Context())
|
||||
if userCtx.UserID == 0 {
|
||||
// Guest user
|
||||
} else {
|
||||
// Authenticated user
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Context Helpers
|
||||
|
||||
```go
|
||||
@@ -635,6 +697,7 @@ sessionID, ok := security.GetSessionID(ctx)
|
||||
remoteID, ok := security.GetRemoteID(ctx)
|
||||
roles, ok := security.GetUserRoles(ctx)
|
||||
email, ok := security.GetUserEmail(ctx)
|
||||
meta, ok := security.GetUserMeta(ctx)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -118,14 +118,15 @@ Enhanced user context with complete user information:
|
||||
|
||||
```go
|
||||
type UserContext struct {
|
||||
UserID int // User's unique ID
|
||||
UserName string // Username
|
||||
UserLevel int // User privilege level
|
||||
SessionID string // Current session ID
|
||||
RemoteID string // Remote system ID
|
||||
Roles []string // User roles
|
||||
Email string // User email
|
||||
Claims map[string]any // Additional metadata
|
||||
UserID int // User's unique ID
|
||||
UserName string // Username
|
||||
UserLevel int // User privilege level
|
||||
SessionID string // Current session ID
|
||||
RemoteID string // Remote system ID
|
||||
Roles []string // User roles
|
||||
Email string // User email
|
||||
Claims map[string]any // Additional authentication claims
|
||||
Meta map[string]any // Additional metadata (can hold any JSON-serializable values)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -629,6 +630,142 @@ func (p *MyProvider) GetRowSecurity(ctx context.Context, userID int, schema, tab
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware and Handler API
|
||||
|
||||
### NewAuthMiddleware
|
||||
Standard middleware that authenticates all requests:
|
||||
|
||||
```go
|
||||
router.Use(security.NewAuthMiddleware(securityList))
|
||||
```
|
||||
|
||||
Routes can skip authentication using the `SkipAuth` helper:
|
||||
|
||||
```go
|
||||
func PublicHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := security.SkipAuth(r.Context())
|
||||
// This route will bypass authentication
|
||||
// A guest user context will be set instead
|
||||
}
|
||||
|
||||
router.Handle("/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := security.SkipAuth(r.Context())
|
||||
PublicHandler(w, r.WithContext(ctx))
|
||||
}))
|
||||
```
|
||||
|
||||
When authentication is skipped, a guest user context is automatically set:
|
||||
- UserID: 0
|
||||
- UserName: "guest"
|
||||
- Roles: ["guest"]
|
||||
- RemoteID: Request's remote address
|
||||
|
||||
Routes can use optional authentication with the `OptionalAuth` helper:
|
||||
|
||||
```go
|
||||
func OptionalAuthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := security.OptionalAuth(r.Context())
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// This route will try to authenticate
|
||||
// If authentication succeeds, authenticated user context is set
|
||||
// If authentication fails, guest user context is set instead
|
||||
|
||||
userCtx, _ := security.GetUserContext(r.Context())
|
||||
if userCtx.UserID == 0 {
|
||||
// Guest user
|
||||
fmt.Fprintf(w, "Welcome, guest!")
|
||||
} else {
|
||||
// Authenticated user
|
||||
fmt.Fprintf(w, "Welcome back, %s!", userCtx.UserName)
|
||||
}
|
||||
}
|
||||
|
||||
router.Handle("/home", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := security.OptionalAuth(r.Context())
|
||||
OptionalAuthHandler(w, r.WithContext(ctx))
|
||||
}))
|
||||
```
|
||||
|
||||
**Authentication Modes Summary:**
|
||||
- **Required (default)**: Authentication must succeed or returns 401
|
||||
- **SkipAuth**: Bypasses authentication entirely, always sets guest context
|
||||
- **OptionalAuth**: Tries authentication, falls back to guest context if it fails
|
||||
|
||||
### NewAuthHandler
|
||||
|
||||
Standalone authentication handler (without middleware wrapping):
|
||||
|
||||
```go
|
||||
// Use when you need authentication logic without middleware
|
||||
authHandler := security.NewAuthHandler(securityList, myHandler)
|
||||
http.Handle("/api/protected", authHandler)
|
||||
```
|
||||
|
||||
### NewOptionalAuthHandler
|
||||
|
||||
Standalone optional authentication handler that tries to authenticate but falls back to guest:
|
||||
|
||||
```go
|
||||
// Use for routes that should work for both authenticated and guest users
|
||||
optionalHandler := security.NewOptionalAuthHandler(securityList, myHandler)
|
||||
http.Handle("/home", optionalHandler)
|
||||
|
||||
// Example handler that checks user context
|
||||
func myHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userCtx, _ := security.GetUserContext(r.Context())
|
||||
if userCtx.UserID == 0 {
|
||||
fmt.Fprintf(w, "Welcome, guest!")
|
||||
} else {
|
||||
fmt.Fprintf(w, "Welcome back, %s!", userCtx.UserName)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Helper Functions
|
||||
|
||||
Extract user information from context:
|
||||
|
||||
```go
|
||||
// Get full user context
|
||||
userCtx, ok := security.GetUserContext(ctx)
|
||||
|
||||
// Get specific fields
|
||||
userID, ok := security.GetUserID(ctx)
|
||||
userName, ok := security.GetUserName(ctx)
|
||||
userLevel, ok := security.GetUserLevel(ctx)
|
||||
sessionID, ok := security.GetSessionID(ctx)
|
||||
remoteID, ok := security.GetRemoteID(ctx)
|
||||
roles, ok := security.GetUserRoles(ctx)
|
||||
email, ok := security.GetUserEmail(ctx)
|
||||
meta, ok := security.GetUserMeta(ctx)
|
||||
```
|
||||
|
||||
### Metadata Support
|
||||
|
||||
The `Meta` field in `UserContext` can hold any JSON-serializable values:
|
||||
|
||||
```go
|
||||
// Set metadata during login
|
||||
loginReq := security.LoginRequest{
|
||||
Username: "user@example.com",
|
||||
Password: "password",
|
||||
Meta: map[string]any{
|
||||
"department": "engineering",
|
||||
"location": "US",
|
||||
"preferences": map[string]any{
|
||||
"theme": "dark",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Access metadata in handlers
|
||||
meta, ok := security.GetUserMeta(ctx)
|
||||
if ok {
|
||||
department := meta["department"].(string)
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Part of the ResolveSpec project.
|
||||
|
||||
@@ -54,6 +54,8 @@ func (a *HeaderAuthenticatorExample) Authenticate(r *http.Request) (*UserContext
|
||||
RemoteID: r.Header.Get("X-Remote-ID"),
|
||||
Email: r.Header.Get("X-User-Email"),
|
||||
Roles: parseRoles(r.Header.Get("X-User-Roles")),
|
||||
Claims: make(map[string]any),
|
||||
Meta: make(map[string]any),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -125,6 +127,8 @@ func (a *JWTAuthenticatorExample) Login(ctx context.Context, req LoginRequest) (
|
||||
Email: user.Email,
|
||||
UserLevel: user.UserLevel,
|
||||
Roles: parseRoles(user.Roles),
|
||||
Claims: req.Claims,
|
||||
Meta: req.Meta,
|
||||
},
|
||||
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
||||
}, nil
|
||||
@@ -242,6 +246,9 @@ func (a *DatabaseAuthenticatorExample) Login(ctx context.Context, req LoginReque
|
||||
Email: user.Email,
|
||||
UserLevel: user.UserLevel,
|
||||
Roles: parseRoles(user.Roles),
|
||||
SessionID: sessionToken,
|
||||
Claims: req.Claims,
|
||||
Meta: req.Meta,
|
||||
},
|
||||
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
||||
}, nil
|
||||
@@ -320,6 +327,8 @@ func (a *DatabaseAuthenticatorExample) Authenticate(r *http.Request) (*UserConte
|
||||
UserLevel: session.UserLevel,
|
||||
SessionID: sessionToken,
|
||||
Roles: parseRoles(session.Roles),
|
||||
Claims: make(map[string]any),
|
||||
Meta: make(map[string]any),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -370,9 +379,12 @@ func (a *DatabaseAuthenticatorExample) RefreshToken(ctx context.Context, refresh
|
||||
return &LoginResponse{
|
||||
Token: newSessionToken,
|
||||
User: &UserContext{
|
||||
UserID: session.UserID,
|
||||
UserName: session.Username,
|
||||
Email: session.Email,
|
||||
UserID: session.UserID,
|
||||
UserName: session.Username,
|
||||
Email: session.Email,
|
||||
SessionID: newSessionToken,
|
||||
Claims: make(map[string]any),
|
||||
Meta: make(map[string]any),
|
||||
},
|
||||
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
||||
}, nil
|
||||
|
||||
@@ -7,35 +7,37 @@ import (
|
||||
|
||||
// UserContext holds authenticated user information
|
||||
type UserContext struct {
|
||||
UserID int
|
||||
UserName string
|
||||
UserLevel int
|
||||
SessionID string
|
||||
RemoteID string
|
||||
Roles []string
|
||||
Email string
|
||||
Claims map[string]any
|
||||
UserID int `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
UserLevel int `json:"user_level"`
|
||||
SessionID string `json:"session_id"`
|
||||
RemoteID string `json:"remote_id"`
|
||||
Roles []string `json:"roles"`
|
||||
Email string `json:"email"`
|
||||
Claims map[string]any `json:"claims"`
|
||||
Meta map[string]any `json:"meta"` // Additional metadata that can hold any JSON-serializable values
|
||||
}
|
||||
|
||||
// LoginRequest contains credentials for login
|
||||
type LoginRequest struct {
|
||||
Username string
|
||||
Password string
|
||||
Claims map[string]any // Additional login data
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Claims map[string]any `json:"claims"` // Additional login data
|
||||
Meta map[string]any `json:"meta"` // Additional metadata to be set on user context
|
||||
}
|
||||
|
||||
// LoginResponse contains the result of a login attempt
|
||||
type LoginResponse struct {
|
||||
Token string
|
||||
RefreshToken string
|
||||
User *UserContext
|
||||
ExpiresIn int64 // Token expiration in seconds
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
User *UserContext `json:"user"`
|
||||
ExpiresIn int64 `json:"expires_in"` // Token expiration in seconds
|
||||
}
|
||||
|
||||
// LogoutRequest contains information for logout
|
||||
type LogoutRequest struct {
|
||||
Token string
|
||||
UserID int
|
||||
Token string `json:"token"`
|
||||
UserID int `json:"user_id"`
|
||||
}
|
||||
|
||||
// Authenticator handles user authentication operations
|
||||
|
||||
@@ -10,21 +10,145 @@ type contextKey string
|
||||
|
||||
const (
|
||||
// Context keys for user information
|
||||
UserIDKey contextKey = "user_id"
|
||||
UserNameKey contextKey = "user_name"
|
||||
UserLevelKey contextKey = "user_level"
|
||||
SessionIDKey contextKey = "session_id"
|
||||
RemoteIDKey contextKey = "remote_id"
|
||||
UserRolesKey contextKey = "user_roles"
|
||||
UserEmailKey contextKey = "user_email"
|
||||
UserContextKey contextKey = "user_context"
|
||||
UserIDKey contextKey = "user_id"
|
||||
UserNameKey contextKey = "user_name"
|
||||
UserLevelKey contextKey = "user_level"
|
||||
SessionIDKey contextKey = "session_id"
|
||||
RemoteIDKey contextKey = "remote_id"
|
||||
UserRolesKey contextKey = "user_roles"
|
||||
UserEmailKey contextKey = "user_email"
|
||||
UserContextKey contextKey = "user_context"
|
||||
UserMetaKey contextKey = "user_meta"
|
||||
SkipAuthKey contextKey = "skip_auth"
|
||||
OptionalAuthKey contextKey = "optional_auth"
|
||||
)
|
||||
|
||||
// SkipAuth returns a context with skip auth flag set to true
|
||||
// Use this to mark routes that should bypass authentication middleware
|
||||
func SkipAuth(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, SkipAuthKey, true)
|
||||
}
|
||||
|
||||
// OptionalAuth returns a context with optional auth flag set to true
|
||||
// Use this to mark routes that should try to authenticate, but fall back to guest if authentication fails
|
||||
func OptionalAuth(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, OptionalAuthKey, true)
|
||||
}
|
||||
|
||||
// createGuestContext creates a guest user context for unauthenticated requests
|
||||
func createGuestContext(r *http.Request) *UserContext {
|
||||
return &UserContext{
|
||||
UserID: 0,
|
||||
UserName: "guest",
|
||||
UserLevel: 0,
|
||||
SessionID: "",
|
||||
RemoteID: r.RemoteAddr,
|
||||
Roles: []string{"guest"},
|
||||
Email: "",
|
||||
Claims: map[string]any{},
|
||||
Meta: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
// setUserContext adds a user context to the request context
|
||||
func setUserContext(r *http.Request, userCtx *UserContext) *http.Request {
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserContextKey, userCtx)
|
||||
ctx = context.WithValue(ctx, UserIDKey, userCtx.UserID)
|
||||
ctx = context.WithValue(ctx, UserNameKey, userCtx.UserName)
|
||||
ctx = context.WithValue(ctx, UserLevelKey, userCtx.UserLevel)
|
||||
ctx = context.WithValue(ctx, SessionIDKey, userCtx.SessionID)
|
||||
ctx = context.WithValue(ctx, RemoteIDKey, userCtx.RemoteID)
|
||||
ctx = context.WithValue(ctx, UserRolesKey, userCtx.Roles)
|
||||
|
||||
if userCtx.Email != "" {
|
||||
ctx = context.WithValue(ctx, UserEmailKey, userCtx.Email)
|
||||
}
|
||||
if len(userCtx.Meta) > 0 {
|
||||
ctx = context.WithValue(ctx, UserMetaKey, userCtx.Meta)
|
||||
}
|
||||
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
||||
// authenticateRequest performs authentication and adds user context to the request
|
||||
// This is the shared authentication logic used by both handler and middleware
|
||||
func authenticateRequest(w http.ResponseWriter, r *http.Request, provider SecurityProvider) (*http.Request, bool) {
|
||||
// Call the provider's Authenticate method
|
||||
userCtx, err := provider.Authenticate(r)
|
||||
if err != nil {
|
||||
http.Error(w, "Authentication failed: "+err.Error(), http.StatusUnauthorized)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return setUserContext(r, userCtx), true
|
||||
}
|
||||
|
||||
// NewAuthHandler creates an authentication handler that can be used standalone
|
||||
// This handler performs authentication and returns 401 if authentication fails
|
||||
// Use this when you need authentication logic without middleware wrapping
|
||||
func NewAuthHandler(securityList *SecurityList, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(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
|
||||
next.ServeHTTP(w, authenticatedReq)
|
||||
})
|
||||
}
|
||||
|
||||
// NewOptionalAuthHandler creates an optional authentication handler that can be used standalone
|
||||
// This handler tries to authenticate but falls back to guest context if authentication fails
|
||||
// Use this for routes that should show personalized content for authenticated users but still work for guests
|
||||
func NewOptionalAuthHandler(securityList *SecurityList, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(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)
|
||||
next.ServeHTTP(w, setUserContext(r, guestCtx))
|
||||
return
|
||||
}
|
||||
|
||||
// Authentication succeeded - set user context
|
||||
next.ServeHTTP(w, setUserContext(r, userCtx))
|
||||
})
|
||||
}
|
||||
|
||||
// NewAuthMiddleware creates an authentication middleware with the given security list
|
||||
// This middleware extracts user authentication from the request and adds it to context
|
||||
// Routes can skip authentication by setting SkipAuthKey context value (use SkipAuth helper)
|
||||
// Routes can use optional authentication by setting OptionalAuthKey context value (use OptionalAuth helper)
|
||||
// When authentication is skipped or fails with optional auth, a guest user context is set instead
|
||||
func NewAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if this route should skip authentication
|
||||
if skip, ok := r.Context().Value(SkipAuthKey).(bool); ok && skip {
|
||||
// Set guest user context for skipped routes
|
||||
guestCtx := createGuestContext(r)
|
||||
next.ServeHTTP(w, setUserContext(r, guestCtx))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the security provider
|
||||
provider := securityList.Provider()
|
||||
if provider == nil {
|
||||
@@ -32,31 +156,25 @@ func NewAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handl
|
||||
return
|
||||
}
|
||||
|
||||
// Call the provider's Authenticate method
|
||||
// Check if this route has optional authentication
|
||||
optional, _ := r.Context().Value(OptionalAuthKey).(bool)
|
||||
|
||||
// Try to authenticate
|
||||
userCtx, err := provider.Authenticate(r)
|
||||
if err != nil {
|
||||
if optional {
|
||||
// Optional auth failed - set guest context and continue
|
||||
guestCtx := createGuestContext(r)
|
||||
next.ServeHTTP(w, setUserContext(r, guestCtx))
|
||||
return
|
||||
}
|
||||
// Required auth failed - return error
|
||||
http.Error(w, "Authentication failed: "+err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Add user information to context
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserContextKey, userCtx)
|
||||
ctx = context.WithValue(ctx, UserIDKey, userCtx.UserID)
|
||||
ctx = context.WithValue(ctx, UserNameKey, userCtx.UserName)
|
||||
ctx = context.WithValue(ctx, UserLevelKey, userCtx.UserLevel)
|
||||
ctx = context.WithValue(ctx, SessionIDKey, userCtx.SessionID)
|
||||
ctx = context.WithValue(ctx, RemoteIDKey, userCtx.RemoteID)
|
||||
|
||||
if len(userCtx.Roles) > 0 {
|
||||
ctx = context.WithValue(ctx, UserRolesKey, userCtx.Roles)
|
||||
}
|
||||
if userCtx.Email != "" {
|
||||
ctx = context.WithValue(ctx, UserEmailKey, userCtx.Email)
|
||||
}
|
||||
|
||||
// Continue with authenticated context
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// Authentication succeeded - set user context
|
||||
next.ServeHTTP(w, setUserContext(r, userCtx))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -119,3 +237,164 @@ func GetUserEmail(ctx context.Context) (string, bool) {
|
||||
email, ok := ctx.Value(UserEmailKey).(string)
|
||||
return email, ok
|
||||
}
|
||||
|
||||
// GetUserMeta extracts user metadata from context
|
||||
func GetUserMeta(ctx context.Context) (map[string]any, bool) {
|
||||
meta, ok := ctx.Value(UserMetaKey).(map[string]any)
|
||||
return meta, ok
|
||||
}
|
||||
|
||||
// // Handler adapters for resolvespec/restheadspec compatibility
|
||||
// // These functions allow using NewAuthHandler and NewOptionalAuthHandler with custom handler abstractions
|
||||
|
||||
// // SpecHandlerAdapter is an interface for handler adapters that need authentication
|
||||
// // Implement this interface to create adapters for custom handler types
|
||||
// type SpecHandlerAdapter interface {
|
||||
// // AdaptToHTTPHandler converts the custom handler to a standard http.Handler
|
||||
// AdaptToHTTPHandler() http.Handler
|
||||
// }
|
||||
|
||||
// // ResolveSpecHandlerAdapter adapts a resolvespec/restheadspec handler method to http.Handler
|
||||
// type ResolveSpecHandlerAdapter struct {
|
||||
// // HandlerMethod is the method to call (e.g., handler.Handle, handler.HandleGet)
|
||||
// HandlerMethod func(w any, r any, params map[string]string)
|
||||
// // Params are the route parameters (e.g., {"schema": "public", "entity": "users"})
|
||||
// Params map[string]string
|
||||
// // RequestAdapter converts *http.Request to the custom Request interface
|
||||
// // Use router.NewHTTPRequest from pkg/common/adapters/router
|
||||
// RequestAdapter func(*http.Request) any
|
||||
// // ResponseAdapter converts http.ResponseWriter to the custom ResponseWriter interface
|
||||
// // Use router.NewHTTPResponseWriter from pkg/common/adapters/router
|
||||
// ResponseAdapter func(http.ResponseWriter) any
|
||||
// }
|
||||
|
||||
// // AdaptToHTTPHandler implements SpecHandlerAdapter
|
||||
// func (a *ResolveSpecHandlerAdapter) AdaptToHTTPHandler() http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// req := a.RequestAdapter(r)
|
||||
// resp := a.ResponseAdapter(w)
|
||||
// a.HandlerMethod(resp, req, a.Params)
|
||||
// })
|
||||
// }
|
||||
|
||||
// // WrapSpecHandler wraps a spec handler adapter with authentication
|
||||
// // Use this to apply NewAuthHandler or NewOptionalAuthHandler to resolvespec/restheadspec handlers
|
||||
// //
|
||||
// // Example with required auth:
|
||||
// //
|
||||
// // adapter := &security.ResolveSpecHandlerAdapter{
|
||||
// // HandlerMethod: handler.Handle,
|
||||
// // Params: map[string]string{"schema": "public", "entity": "users"},
|
||||
// // RequestAdapter: func(r *http.Request) any { return router.NewHTTPRequest(r) },
|
||||
// // ResponseAdapter: func(w http.ResponseWriter) any { return router.NewHTTPResponseWriter(w) },
|
||||
// // }
|
||||
// // authHandler := security.WrapSpecHandler(securityList, adapter, false)
|
||||
// // muxRouter.Handle("/api/users", authHandler)
|
||||
// func WrapSpecHandler(securityList *SecurityList, adapter SpecHandlerAdapter, optional bool) http.Handler {
|
||||
// httpHandler := adapter.AdaptToHTTPHandler()
|
||||
// if optional {
|
||||
// return NewOptionalAuthHandler(securityList, httpHandler)
|
||||
// }
|
||||
// return NewAuthHandler(securityList, httpHandler)
|
||||
// }
|
||||
|
||||
// // MuxRouteBuilder helps build authenticated routes with Gorilla Mux
|
||||
// type MuxRouteBuilder struct {
|
||||
// securityList *SecurityList
|
||||
// requestAdapter func(*http.Request) any
|
||||
// responseAdapter func(http.ResponseWriter) any
|
||||
// paramExtractor func(*http.Request) map[string]string
|
||||
// }
|
||||
|
||||
// // NewMuxRouteBuilder creates a route builder for Gorilla Mux with standard router adapters
|
||||
// // Usage:
|
||||
// //
|
||||
// // builder := security.NewMuxRouteBuilder(securityList, router.NewHTTPRequest, router.NewHTTPResponseWriter)
|
||||
// func NewMuxRouteBuilder(
|
||||
// securityList *SecurityList,
|
||||
// requestAdapter func(*http.Request) any,
|
||||
// responseAdapter func(http.ResponseWriter) any,
|
||||
// ) *MuxRouteBuilder {
|
||||
// return &MuxRouteBuilder{
|
||||
// securityList: securityList,
|
||||
// requestAdapter: requestAdapter,
|
||||
// responseAdapter: responseAdapter,
|
||||
// paramExtractor: nil, // Will be set per route using mux.Vars
|
||||
// }
|
||||
// }
|
||||
|
||||
// // HandleAuth creates an authenticated route handler
|
||||
// // pattern: the route pattern (e.g., "/{schema}/{entity}")
|
||||
// // handler: the handler method to call (e.g., handler.Handle)
|
||||
// // optional: true for optional auth (guest fallback), false for required auth (401 on failure)
|
||||
// // methods: HTTP methods (e.g., "GET", "POST")
|
||||
// //
|
||||
// // Usage:
|
||||
// //
|
||||
// // builder.HandleAuth(router, "/{schema}/{entity}", handler.Handle, false, "POST")
|
||||
// func (b *MuxRouteBuilder) HandleAuth(
|
||||
// router interface {
|
||||
// HandleFunc(pattern string, f func(http.ResponseWriter, *http.Request)) interface{ Methods(...string) interface{} }
|
||||
// },
|
||||
// pattern string,
|
||||
// handlerMethod func(w any, r any, params map[string]string),
|
||||
// optional bool,
|
||||
// methods ...string,
|
||||
// ) {
|
||||
// router.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
||||
// // Extract params using the registered extractor or default to empty map
|
||||
// var params map[string]string
|
||||
// if b.paramExtractor != nil {
|
||||
// params = b.paramExtractor(r)
|
||||
// } else {
|
||||
// params = make(map[string]string)
|
||||
// }
|
||||
|
||||
// adapter := &ResolveSpecHandlerAdapter{
|
||||
// HandlerMethod: handlerMethod,
|
||||
// Params: params,
|
||||
// RequestAdapter: b.requestAdapter,
|
||||
// ResponseAdapter: b.responseAdapter,
|
||||
// }
|
||||
// authHandler := WrapSpecHandler(b.securityList, adapter, optional)
|
||||
// authHandler.ServeHTTP(w, r)
|
||||
// }).Methods(methods...)
|
||||
// }
|
||||
|
||||
// // SetParamExtractor sets a custom parameter extractor function
|
||||
// // For Gorilla Mux, you would use: builder.SetParamExtractor(mux.Vars)
|
||||
// func (b *MuxRouteBuilder) SetParamExtractor(extractor func(*http.Request) map[string]string) {
|
||||
// b.paramExtractor = extractor
|
||||
// }
|
||||
|
||||
// // SetupAuthenticatedSpecRoutes sets up all standard resolvespec/restheadspec routes with authentication
|
||||
// // This is a convenience function that sets up the common route patterns
|
||||
// //
|
||||
// // Usage:
|
||||
// //
|
||||
// // security.SetupAuthenticatedSpecRoutes(router, handler, securityList, router.NewHTTPRequest, router.NewHTTPResponseWriter, mux.Vars)
|
||||
// func SetupAuthenticatedSpecRoutes(
|
||||
// router interface {
|
||||
// HandleFunc(pattern string, f func(http.ResponseWriter, *http.Request)) interface{ Methods(...string) interface{} }
|
||||
// },
|
||||
// handler interface {
|
||||
// Handle(w any, r any, params map[string]string)
|
||||
// HandleGet(w any, r any, params map[string]string)
|
||||
// },
|
||||
// securityList *SecurityList,
|
||||
// requestAdapter func(*http.Request) any,
|
||||
// responseAdapter func(http.ResponseWriter) any,
|
||||
// paramExtractor func(*http.Request) map[string]string,
|
||||
// ) {
|
||||
// builder := NewMuxRouteBuilder(securityList, requestAdapter, responseAdapter)
|
||||
// builder.SetParamExtractor(paramExtractor)
|
||||
|
||||
// // POST /{schema}/{entity}
|
||||
// builder.HandleAuth(router, "/{schema}/{entity}", handler.Handle, false, "POST")
|
||||
|
||||
// // POST /{schema}/{entity}/{id}
|
||||
// builder.HandleAuth(router, "/{schema}/{entity}/{id}", handler.Handle, false, "POST")
|
||||
|
||||
// // GET /{schema}/{entity}
|
||||
// builder.HandleAuth(router, "/{schema}/{entity}", handler.HandleGet, false, "GET")
|
||||
// }
|
||||
|
||||
@@ -15,26 +15,26 @@ import (
|
||||
)
|
||||
|
||||
type ColumnSecurity struct {
|
||||
Schema string
|
||||
Tablename string
|
||||
Path []string
|
||||
ExtraFilters map[string]string
|
||||
UserID int
|
||||
Accesstype string `json:"accesstype"`
|
||||
MaskStart int
|
||||
MaskEnd int
|
||||
MaskInvert bool
|
||||
MaskChar string
|
||||
Control string `json:"control"`
|
||||
ID int `json:"id"`
|
||||
Schema string `json:"schema"`
|
||||
Tablename string `json:"tablename"`
|
||||
Path []string `json:"path"`
|
||||
ExtraFilters map[string]string `json:"extra_filters"`
|
||||
UserID int `json:"user_id"`
|
||||
Accesstype string `json:"accesstype"`
|
||||
MaskStart int `json:"mask_start"`
|
||||
MaskEnd int `json:"mask_end"`
|
||||
MaskInvert bool `json:"mask_invert"`
|
||||
MaskChar string `json:"mask_char"`
|
||||
Control string `json:"control"`
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
type RowSecurity struct {
|
||||
Schema string
|
||||
Tablename string
|
||||
Template string
|
||||
HasBlock bool
|
||||
UserID int
|
||||
Schema string `json:"schema"`
|
||||
Tablename string `json:"tablename"`
|
||||
Template string `json:"template"`
|
||||
HasBlock bool `json:"has_block"`
|
||||
UserID int `json:"user_id"`
|
||||
}
|
||||
|
||||
func (m *RowSecurity) GetTemplate(pPrimaryKeyName string, pModelType reflect.Type) string {
|
||||
|
||||
@@ -68,12 +68,14 @@ func ExampleDatabaseSecurity(gormDB interface{}, sqlDB *sql.DB) (http.Handler, e
|
||||
// Step 5: Setup security
|
||||
securityList := SetupSecurityProvider(handler, provider)
|
||||
|
||||
// Step 6: Create router and setup routes
|
||||
// Step 6: Create router and setup routes with authentication
|
||||
router := mux.NewRouter()
|
||||
restheadspec.SetupMuxRoutes(router, handler)
|
||||
authMiddleware := func(h http.Handler) http.Handler {
|
||||
return NewAuthHandler(securityList, h)
|
||||
}
|
||||
restheadspec.SetupMuxRoutes(router, handler, authMiddleware)
|
||||
|
||||
// Step 7: Apply middleware in correct order
|
||||
router.Use(NewAuthMiddleware(securityList))
|
||||
// Step 7: Apply additional security middleware
|
||||
router.Use(SetSecurityMiddleware(securityList))
|
||||
|
||||
return router, nil
|
||||
@@ -95,9 +97,11 @@ func ExampleHeaderAuthentication(gormDB interface{}, sqlDB *sql.DB) (*mux.Router
|
||||
securityList := SetupSecurityProvider(handler, provider)
|
||||
|
||||
router := mux.NewRouter()
|
||||
restheadspec.SetupMuxRoutes(router, handler)
|
||||
authMiddleware := func(h http.Handler) http.Handler {
|
||||
return NewAuthHandler(securityList, h)
|
||||
}
|
||||
restheadspec.SetupMuxRoutes(router, handler, authMiddleware)
|
||||
|
||||
router.Use(NewAuthMiddleware(securityList))
|
||||
router.Use(SetSecurityMiddleware(securityList))
|
||||
|
||||
return router, nil
|
||||
@@ -150,9 +154,11 @@ func ExampleConfigSecurity(gormDB interface{}) (*mux.Router, error) {
|
||||
securityList := SetupSecurityProvider(handler, provider)
|
||||
|
||||
router := mux.NewRouter()
|
||||
restheadspec.SetupMuxRoutes(router, handler)
|
||||
authMiddleware := func(h http.Handler) http.Handler {
|
||||
return NewAuthHandler(securityList, h)
|
||||
}
|
||||
restheadspec.SetupMuxRoutes(router, handler, authMiddleware)
|
||||
|
||||
router.Use(NewAuthMiddleware(securityList))
|
||||
router.Use(SetSecurityMiddleware(securityList))
|
||||
|
||||
return router, nil
|
||||
@@ -282,10 +288,12 @@ func CompleteServerExample(gormDB interface{}, sqlDB *sql.DB) http.Handler {
|
||||
// Add auth routes (login/logout)
|
||||
SetupAuthRoutes(router, securityList)
|
||||
|
||||
// Add API routes with security middleware
|
||||
// Add API routes with authentication
|
||||
apiRouter := router.PathPrefix("/api").Subrouter()
|
||||
restheadspec.SetupMuxRoutes(apiRouter, handler)
|
||||
apiRouter.Use(NewAuthMiddleware(securityList))
|
||||
authMiddleware := func(h http.Handler) http.Handler {
|
||||
return NewAuthHandler(securityList, h)
|
||||
}
|
||||
restheadspec.SetupMuxRoutes(apiRouter, handler, authMiddleware)
|
||||
apiRouter.Use(SetSecurityMiddleware(securityList))
|
||||
|
||||
return router
|
||||
|
||||
Reference in New Issue
Block a user