Keysmith

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

  1. Registers keysmith.Engine in the Forge DI container via vessel.Provide
  2. Registers the API route handler

On start

  1. Runs database migrations if a PostgreSQL store is configured
  2. Starts any background processes (e.g., expired key cleanup)

On stop

  1. Fires the Shutdown plugin hook
  2. Closes the store connection

REST API routes

When mounted, the extension exposes the full Keysmith REST API:

MethodPathDescription
POST/v1/keysCreate API key
GET/v1/keysList API keys
GET/v1/keys/:keyIdGet API key
DELETE/v1/keys/:keyIdDelete API key
POST/v1/keys/:keyId/rotateRotate API key
POST/v1/keys/:keyId/revokeRevoke API key
POST/v1/keys/:keyId/suspendSuspend API key
POST/v1/keys/:keyId/reactivateReactivate API key
POST/v1/keys/validateValidate raw API key
POST/v1/policiesCreate policy
GET/v1/policiesList policies
GET/v1/policies/:policyIdGet policy
PUT/v1/policies/:policyIdUpdate policy
DELETE/v1/policies/:policyIdDelete policy
POST/v1/scopesCreate scope
GET/v1/scopesList scopes
DELETE/v1/scopes/:scopeIdDelete scope
POST/v1/keys/:keyId/scopesAssign scopes to key
DELETE/v1/keys/:keyId/scopesRemove scopes from key
GET/v1/keys/:keyId/usageGet key usage
GET/v1/keys/:keyId/usage/aggregateGet usage aggregation
GET/v1/usageList tenant usage
GET/v1/keys/:keyId/rotationsList 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:

  1. Explicit store -- if WithEngineOptions(keysmith.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.

Extension options

OptionTypeDefaultDescription
WithEngineOptions(opts...)...keysmith.Option--Engine configuration options
WithHookExtension(x)plugin.Plugin--Register a lifecycle plugin (repeatable)
WithConfig(cfg)ConfigdefaultsOverride default extension configuration
WithLogger(l)*slog.Loggerslog.Default()Set the logger
WithDisableRoutes()--falseSkip HTTP route registration
WithDisableMigrate()--falseSkip migrations on Start
WithBasePath(path)string""URL prefix for keysmith routes
WithGroveDatabase(name)string""Named grove.DB to resolve from DI
WithRequireConfig(b)boolfalseRequire 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):

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

Example

# forge.yaml
extensions:
  keysmith:
    disable_routes: false
    disable_migrate: false
    base_path: "/keysmith"
    grove_database: ""

Config fields

YAML KeyTypeDefaultDescription
disable_routesboolfalseSkip HTTP route registration
disable_migrateboolfalseSkip migrations on Start
base_pathstring""URL prefix for all routes
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.

On this page