Warden

Forge Extension

Use Warden as a Forge extension with DI, routes, and lifecycle management.

Warden provides a first-class Forge extension that handles lifecycle, dependency injection, and API route registration.

Registration

import (
    "github.com/xraph/forge"
    wardenext "github.com/xraph/warden/extension"
)

app := forge.New()
app.Use(wardenext.New(
    wardenext.WithConfig(wardenext.Config{
        DisableRoutes:  false,  // Set true to skip API route registration
        DisableMigrate: false,  // Set true to skip auto-migration
    }),
))
app.Start(context.Background())

What the Extension Does

Register Phase

  1. Resolves a store.Store from the Forge DI container
  2. Creates a warden.Engine with the store and any configured options
  3. Registers the engine in the DI container via vessel.Provide()
  4. If routes are enabled, registers REST API endpoints on forge.Router

Start Phase

  1. Runs database migrations (unless DisableMigrate is true)
  2. Starts the engine

Stop Phase

  1. Gracefully shuts down the engine
  2. Fires Shutdown hooks on all plugins

Extension options

OptionTypeDefaultDescription
WithStore(s)store.Store--Sets the persistence backend directly
WithConfig(cfg)ConfigdefaultsExtension configuration
WithEngineOptions(opts...)...warden.Option--Pass engine-level options
WithPlugin(x)plugin.Plugin--Register a lifecycle plugin (repeatable)
WithDisableRoutes()--falseSkip HTTP route registration
WithDisableMigrate()--falseSkip migrations on Start
WithBasePath(path)string""URL prefix for warden routes
WithGroveDatabase(name)string""Named grove.DB to resolve from DI
WithRequireConfig()--falseRequire config in YAML files

Accessing the Engine

After registration, resolve the engine from any Forge handler:

func myHandler(ctx forge.Context) error {
    eng := forge.Inject[*warden.Engine](ctx)
    result, _ := eng.Check(ctx.Context(), &warden.CheckRequest{
        Subject: warden.Subject{Kind: "user", ID: ctx.Get("userID")},
        Action:  "read",
        ResourceType: "document",
    })
    if !result.Allowed {
        return forge.Forbidden("access denied")
    }
    return ctx.JSON(200, data)
}

Grove database integration

When your Forge app uses the Grove extension to manage database connections, Warden can automatically resolve a grove.DB from the DI container and construct the correct store backend based on the driver type.

Using the default grove database

If the Grove extension registers a single database (or a default in multi-DB mode), use WithGroveDatabase with an empty name:

ext := wardenext.New(
    wardenext.WithGroveDatabase(""),
)

Using a named grove database

In multi-database setups, reference a specific database by name:

ext := wardenext.New(
    wardenext.WithGroveDatabase("warden"),
)

This resolves the grove.DB named "warden" from the DI container and auto-constructs the matching store. The driver type is detected automatically -- you do not need to import individual store packages.

Store resolution order

The extension resolves its store in this order:

  1. Explicit store -- if WithStore(s) was called, it is used directly and grove is ignored.
  2. Grove database -- if WithGroveDatabase(name) was called, the named or default grove.DB is resolved from DI.
  3. In-memory fallback -- if neither is configured, an in-memory store is used.

File-based configuration (YAML)

When running as a Forge extension, Warden automatically loads configuration from YAML config files. The extension looks for config under the following keys (in order):

  1. extensions.warden -- standard Forge extension config namespace
  2. warden -- top-level shorthand

Example

# forge.yaml
extensions:
  warden:
    disable_routes: false
    disable_migrate: false
    base_path: "/warden"
    max_graph_depth: 10
    grove_database: ""

Config fields

YAML KeyTypeDefaultDescription
disable_routesboolfalseSkip HTTP route registration
disable_migrateboolfalseSkip migrations on Start
base_pathstring""URL prefix for all routes
max_graph_depthint10Max depth for ReBAC graph traversal
grove_databasestring""Named grove.DB from DI

Merge behaviour

File-based configuration is merged with programmatic options. Programmatic boolean flags (DisableRoutes, DisableMigrate) always win when set to true. For other fields, YAML values take precedence, then programmatic values, then defaults.

REST API Endpoints

When routes are enabled, the extension registers these endpoints:

MethodPathDescription
POST/v1/authz/checkAuthorization check
POST/v1/authz/enforceAuthorization enforce
POST/v1/authz/batch-checkBatch authorization check
POST/GET/PUT/DELETE/v1/roles/*Role management
POST/GET/DELETE/v1/permissions/*Permission management
POST/GET/DELETE/v1/assignments/*Assignment management
POST/GET/v1/relations/*Relation tuple management
POST/GET/PUT/DELETE/v1/policies/*Policy management
POST/GET/PUT/DELETE/v1/resource-types/*Resource type management
GET/v1/check-logsQuery check audit logs

All endpoints include OpenAPI metadata for automatic documentation generation.

On this page