mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-03-07 05:58:55 +00:00
feat(security): add BeforeHandle hook for auth checks after model resolution
- Implement BeforeHandle hook to enforce authentication based on model rules. - Integrate with existing security mechanisms to allow or deny access. - Update documentation to reflect new hook and its usage.
This commit is contained in:
@@ -147,6 +147,7 @@ handler.Hooks.Register(restheadspec.BeforeCreate, func(ctx *restheadspec.HookCon
|
||||
```
|
||||
|
||||
**Available Hook Types**:
|
||||
* `BeforeHandle` — fires after model resolution, before operation dispatch (auth checks)
|
||||
* `BeforeRead`, `AfterRead`
|
||||
* `BeforeCreate`, `AfterCreate`
|
||||
* `BeforeUpdate`, `AfterUpdate`
|
||||
@@ -157,11 +158,13 @@ handler.Hooks.Register(restheadspec.BeforeCreate, func(ctx *restheadspec.HookCon
|
||||
* `Handler`: Access to handler, database, and registry
|
||||
* `Schema`, `Entity`, `TableName`: Request info
|
||||
* `Model`: The registered model type
|
||||
* `Operation`: Current operation string (`"read"`, `"create"`, `"update"`, `"delete"`)
|
||||
* `Options`: Parsed request options (filters, sorting, etc.)
|
||||
* `ID`: Record ID (for single-record operations)
|
||||
* `Data`: Request data (for create/update)
|
||||
* `Result`: Operation result (for after hooks)
|
||||
* `Writer`: Response writer (allows hooks to modify response)
|
||||
* `Abort`, `AbortMessage`, `AbortCode`: Set in hook to abort with an error response
|
||||
|
||||
## Cursor Pagination
|
||||
|
||||
|
||||
@@ -133,6 +133,41 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
||||
// Add request-scoped data to context (including options)
|
||||
ctx = WithRequestData(ctx, schema, entity, tableName, model, modelPtr, options)
|
||||
|
||||
// Derive operation for auth check
|
||||
var operation string
|
||||
switch method {
|
||||
case "GET":
|
||||
operation = "read"
|
||||
case "POST":
|
||||
operation = "create"
|
||||
case "PUT", "PATCH":
|
||||
operation = "update"
|
||||
case "DELETE":
|
||||
operation = "delete"
|
||||
default:
|
||||
operation = "read"
|
||||
}
|
||||
|
||||
// Execute BeforeHandle hook - auth check fires here, after model resolution
|
||||
beforeCtx := &HookContext{
|
||||
Context: ctx,
|
||||
Handler: h,
|
||||
Schema: schema,
|
||||
Entity: entity,
|
||||
Model: model,
|
||||
Writer: w,
|
||||
Request: r,
|
||||
Operation: operation,
|
||||
}
|
||||
if err := h.hooks.Execute(BeforeHandle, beforeCtx); err != nil {
|
||||
code := http.StatusUnauthorized
|
||||
if beforeCtx.AbortCode != 0 {
|
||||
code = beforeCtx.AbortCode
|
||||
}
|
||||
h.sendError(w, code, "unauthorized", beforeCtx.AbortMessage, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
if id != "" {
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
type HookType string
|
||||
|
||||
const (
|
||||
// BeforeHandle fires after model resolution, before operation dispatch.
|
||||
// Use this for auth checks that need model rules and user context simultaneously.
|
||||
BeforeHandle HookType = "before_handle"
|
||||
|
||||
// Read operation hooks
|
||||
BeforeRead HookType = "before_read"
|
||||
AfterRead HookType = "after_read"
|
||||
@@ -42,6 +46,9 @@ type HookContext struct {
|
||||
Model interface{}
|
||||
Options ExtendedRequestOptions
|
||||
|
||||
// Operation being dispatched (e.g. "read", "create", "update", "delete")
|
||||
Operation string
|
||||
|
||||
// Operation-specific fields
|
||||
ID string
|
||||
Data interface{} // For create/update operations
|
||||
@@ -56,6 +63,14 @@ type HookContext struct {
|
||||
// Response writer - allows hooks to modify response
|
||||
Writer common.ResponseWriter
|
||||
|
||||
// Request - the original HTTP request
|
||||
Request common.Request
|
||||
|
||||
// Allow hooks to abort the operation
|
||||
Abort bool // If set to true, the operation will be aborted
|
||||
AbortMessage string // Message to return if aborted
|
||||
AbortCode int // HTTP status code if aborted
|
||||
|
||||
// Tx provides access to the database/transaction for executing additional SQL
|
||||
// This allows hooks to run custom queries in addition to the main Query chain
|
||||
Tx common.Database
|
||||
@@ -110,6 +125,12 @@ func (r *HookRegistry) Execute(hookType HookType, ctx *HookContext) error {
|
||||
logger.Error("Hook %d for %s failed: %v", i+1, hookType, err)
|
||||
return fmt.Errorf("hook execution failed: %w", err)
|
||||
}
|
||||
|
||||
// Check if hook requested abort
|
||||
if ctx.Abort {
|
||||
logger.Warn("Hook %d for %s requested abort: %s", i+1, hookType, ctx.AbortMessage)
|
||||
return fmt.Errorf("operation aborted by hook: %s", ctx.AbortMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// logger.Debug("All hooks for %s executed successfully", hookType)
|
||||
|
||||
@@ -2,6 +2,7 @@ package restheadspec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/security"
|
||||
@@ -9,6 +10,17 @@ import (
|
||||
|
||||
// RegisterSecurityHooks registers all security-related hooks with the handler
|
||||
func RegisterSecurityHooks(handler *Handler, securityList *security.SecurityList) {
|
||||
// Hook 0: BeforeHandle - enforce auth after model resolution
|
||||
handler.Hooks().Register(BeforeHandle, func(hookCtx *HookContext) error {
|
||||
if err := security.CheckModelAuthAllowed(newSecurityContext(hookCtx), hookCtx.Operation); err != nil {
|
||||
hookCtx.Abort = true
|
||||
hookCtx.AbortMessage = err.Error()
|
||||
hookCtx.AbortCode = http.StatusUnauthorized
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Hook 1: BeforeRead - Load security rules
|
||||
handler.Hooks().Register(BeforeRead, func(hookCtx *HookContext) error {
|
||||
secCtx := newSecurityContext(hookCtx)
|
||||
|
||||
Reference in New Issue
Block a user