Forge Integration
Using Keysmith with the Forge framework.
Keysmith ships with a Forge extension adapter that handles DI registration, REST API route mounting, and database migration on startup.
Mounting the extension
import (
"github.com/xraph/forge"
"github.com/xraph/keysmith"
"github.com/xraph/keysmith/extension"
"github.com/xraph/keysmith/store/postgres"
audithook "github.com/xraph/keysmith/audit_hook"
)
app := forge.New(
forge.WithExtension(
extension.New(
extension.WithEngineOptions(
keysmith.WithStore(pgStore),
),
extension.WithHookExtension(audithook.New(recorder)),
),
),
)What the extension does
On registration
- Registers
keysmith.Enginein the Forge DI container viavessel.Provide - Registers the API route handler
On start
- Runs database migrations if a PostgreSQL store is configured
- Starts any background processes (e.g., expired key cleanup)
On stop
- Fires the
Shutdownplugin hook - Closes the store connection
REST API routes
When mounted, the extension exposes the full Keysmith REST API:
| Method | Path | Description |
|---|---|---|
POST | /v1/keys | Create API key |
GET | /v1/keys | List API keys |
GET | /v1/keys/:keyId | Get API key |
DELETE | /v1/keys/:keyId | Delete API key |
POST | /v1/keys/:keyId/rotate | Rotate API key |
POST | /v1/keys/:keyId/revoke | Revoke API key |
POST | /v1/keys/:keyId/suspend | Suspend API key |
POST | /v1/keys/:keyId/reactivate | Reactivate API key |
POST | /v1/keys/validate | Validate raw API key |
POST | /v1/policies | Create policy |
GET | /v1/policies | List policies |
GET | /v1/policies/:policyId | Get policy |
PUT | /v1/policies/:policyId | Update policy |
DELETE | /v1/policies/:policyId | Delete policy |
POST | /v1/scopes | Create scope |
GET | /v1/scopes | List scopes |
DELETE | /v1/scopes/:scopeId | Delete scope |
POST | /v1/keys/:keyId/scopes | Assign scopes to key |
DELETE | /v1/keys/:keyId/scopes | Remove scopes from key |
GET | /v1/keys/:keyId/usage | Get key usage |
GET | /v1/keys/:keyId/usage/aggregate | Get usage aggregation |
GET | /v1/usage | List tenant usage |
GET | /v1/keys/:keyId/rotations | List key rotations |
Automatic tenant scoping
When running inside Forge, Keysmith automatically extracts the app ID and tenant ID from forge.Scope. No manual WithTenant call is needed.
DI integration
Access the engine from the Forge DI container:
// In a Forge handler
func myHandler(ctx forge.Context) error {
eng := vessel.MustResolve[*keysmith.Engine](ctx.Vessel())
// use eng...
}Grove database integration
When your Forge app uses the Grove extension to manage database connections, Keysmith 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 := extension.New(
extension.WithGroveDatabase(""),
)Using a named grove database
In multi-database setups, reference a specific database by name:
ext := extension.New(
extension.WithGroveDatabase("keysmith"),
)This resolves the grove.DB named "keysmith" 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:
- Explicit store -- if
WithEngineOptions(keysmith.WithStore(s))was called, it is used directly and grove is ignored. - Grove database -- if
WithGroveDatabase(name)was called, the named or defaultgrove.DBis resolved from DI. - In-memory fallback -- if neither is configured, an in-memory store is used.
Extension options
| Option | Type | Default | Description |
|---|---|---|---|
WithEngineOptions(opts...) | ...keysmith.Option | -- | Engine configuration options |
WithHookExtension(x) | plugin.Plugin | -- | Register a lifecycle plugin (repeatable) |
WithConfig(cfg) | Config | defaults | Override default extension configuration |
WithLogger(l) | *slog.Logger | slog.Default() | Set the logger |
WithDisableRoutes() | -- | false | Skip HTTP route registration |
WithDisableMigrate() | -- | false | Skip migrations on Start |
WithBasePath(path) | string | "" | URL prefix for keysmith routes |
WithGroveDatabase(name) | string | "" | Named grove.DB to resolve from DI |
WithRequireConfig(b) | bool | false | Require config in YAML files |
File-based configuration (YAML)
When running as a Forge extension, Keysmith automatically loads configuration from YAML config files. The extension looks for config under the following keys (in order):
extensions.keysmith-- standard Forge extension config namespacekeysmith-- top-level shorthand
Example
# forge.yaml
extensions:
keysmith:
disable_routes: false
disable_migrate: false
base_path: "/keysmith"
grove_database: ""Config fields
| YAML Key | Type | Default | Description |
|---|---|---|---|
disable_routes | bool | false | Skip HTTP route registration |
disable_migrate | bool | false | Skip migrations on Start |
base_path | string | "" | URL prefix for all routes |
grove_database | string | "" | 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.