Custom Store
Implementing a custom Keysmith store backend.
Keysmith defines a composite store.Store interface that embeds five subsystem store interfaces plus lifecycle methods. Implement this interface to add a new storage backend.
Composite store interface
import "github.com/xraph/keysmith/store"
type Store interface {
Keys() key.Store
Policies() policy.Store
Scopes() scope.Store
Usage() usage.Store
Rotations() rotation.Store
Migrate(ctx context.Context) error
Ping(ctx context.Context) error
Close() error
}The accessor pattern (Keys(), Policies(), etc.) avoids method name collisions when composing five store interfaces into one.
Implementing a custom store
Step 1: Implement each subsystem store
Start with the key store, which is the most critical:
type MyKeyStore struct {
// your backend connection
}
func (s *MyKeyStore) Create(ctx context.Context, k *key.Key) error {
// Insert key into your backend
}
func (s *MyKeyStore) GetByHash(ctx context.Context, hash string) (*key.Key, error) {
// Look up key by SHA-256 hash
}
// ... implement remaining methodsStep 2: Compose into a Store
type MyStore struct {
keys *MyKeyStore
policies *MyPolicyStore
scopes *MyScopeStore
usage *MyUsageStore
rotations *MyRotationStore
}
func (s *MyStore) Keys() key.Store { return s.keys }
func (s *MyStore) Policies() policy.Store { return s.policies }
func (s *MyStore) Scopes() scope.Store { return s.scopes }
func (s *MyStore) Usage() usage.Store { return s.usage }
func (s *MyStore) Rotations() rotation.Store { return s.rotations }
func (s *MyStore) Migrate(ctx context.Context) error { return nil }
func (s *MyStore) Ping(ctx context.Context) error { return nil }
func (s *MyStore) Close() error { return nil }Step 3: Use with the engine
eng, err := keysmith.NewEngine(keysmith.WithStore(&MyStore{...}))Key store interface (critical path)
The key.Store.GetByHash method is the hot path for validation. Ensure it is optimized for O(1) or O(log n) lookup:
type Store interface {
Create(ctx context.Context, k *Key) error
GetByID(ctx context.Context, id id.KeyID) (*Key, error)
GetByHash(ctx context.Context, hash string) (*Key, error)
List(ctx context.Context, filter *ListFilter) ([]*Key, error)
UpdateState(ctx context.Context, id id.KeyID, state State) error
UpdateLastUsed(ctx context.Context, id id.KeyID, t time.Time) error
Delete(ctx context.Context, id id.KeyID) error
}Testing your store
Use the existing engine tests as a harness. Replace memory.New() with your custom store:
func TestMyStore(t *testing.T) {
store := NewMyStore(...)
eng, err := keysmith.NewEngine(keysmith.WithStore(store))
require.NoError(t, err)
ctx := keysmith.WithTenant(context.Background(), "test-app", "test-tenant")
// Run through the key lifecycle
result, err := eng.CreateKey(ctx, &keysmith.CreateKeyInput{
Name: "Test Key",
Prefix: "sk",
})
require.NoError(t, err)
vr, err := eng.ValidateKey(ctx, result.RawKey)
require.NoError(t, err)
assert.Equal(t, "test-tenant", vr.Key.TenantID)
}