Skip to content

v0.1.0

Release Date: 2026-10-13

Highlights

  • Connections without handing out secrets. A broker returns an authenticated *http.Client, which provider SDKs accept through their WithHTTPClient option, so a caller uses a connection without ever holding its credential.
  • Secrets cannot leak by construction. Secret prints as [redacted] in every format verb, in JSON, in text and in slog. Errors carry a status and the provider's request ID but never a response body, and a test confines Reveal to the broker and the providers.
  • Ports and adapters. Encryption, authorization, audit, leasing and deployment secrets are interfaces the host supplies. The module imports no key manager, secret manager or application framework.
  • A SQL store a host composes. An Ent-backed store for SQLite and PostgreSQL runs on the host's own database, with owner_id on every owned table and a per-transaction hook for row-level security.

Status

connectforge v0.1.0 has not been externally audited. It supports static API-token connections. GitHub App installations, OAuth with refresh, credential replacement, scheduled validation and an HTTP API follow in later releases; the roadmap lists them.

Overview

connectforge manages a tenant's connections to external services: the provider, the connection, its sealed credential, who may use it and what depends on it. It owns the credential lifecycle, not the provider's API client. The design is in the PRD and TRD.

Features

Model and lifecycle

Record Holds
Connection Provider, auth method, display name, discovered account, scopes, state and health. No secret
Credential One sealed version of the authorization material, with its expiry
Grant What a principal may do with a connection: view, use, manage, share, disconnect
Binding A consumer that depends on a connection, and why

State is what was decided (pending, connected, needs_attention, disconnected, revoked); health is what was last observed. A transient failure changes health and never state, and a transition table is enforced by every store.

Secrets

  • Secret copies on construction and on Reveal, and can be cleared.
  • Credentials are sealed with additional data that binds the owner, the connection and the credential version, so a ciphertext cannot be moved to another record.
  • A credential that cannot be read or decrypted makes the connection unusable. There is no fallback.

Providers

  • A provider is a declarative Manifest (fields, scopes, base URLs, help) plus validation and request authentication.
  • provider/apikey covers services that issue a static key or token: where the token goes, which endpoint verifies it, and where the account is in the response.
  • Classify maps HTTP refusals to typed errors: an invalid credential, an insufficient scope, rate limiting or a transient failure.

Egress

The HTTP client the broker hands out reaches only the hosts in the provider's manifest, over HTTPS. It refuses a redirect to another host and refuses private, loopback, link-local and shared addresses after DNS resolution, so a rebinding name cannot reach them.

Operations

Operation Behaviour
Create Validates with the provider and stores nothing if it is rejected
Get, List Return what the actor may view
Rename Changes the display name
Test Validates again. A permanent failure moves the connection to needs_attention; success moves it back; a transient failure changes only health
Disconnect Destroys the credentials and keeps the record for the audit trail

Each operation is authorized through the host's Authorizer and audited. A change whose audit event could not be recorded returns ErrNotAudited: the change was made, and the gap is reported rather than hidden.

Stores

Store Use
store/memstore Tests and examples; the reference for behaviour
sqlstore SQLite and PostgreSQL through Ent, on the host's database

Both pass the same conformance suite, which covers owner isolation, compare-and-swap under concurrency, the transition table, cascade on delete and leases. The PostgreSQL run is opt-in with CONNECTFORGE_TEST_POSTGRES_DSN.

Installation

go get github.com/plexusone/connectforge@v0.1.0

Requires Go 1.26.

Quick start

p, err := apikey.New(apikey.Config{
    Manifest: provider.Manifest{
        Key: "example", DisplayName: "Example", BaseURLs: []string{"https://api.example.com"},
        AuthMethods: []provider.AuthMethod{{
            Key:    "api_token",
            Fields: []provider.Field{{Name: "token", Label: "API token", Secret: true, Required: true}},
        }},
    },
    Verify: apikey.Request{Path: "/v1/me", AccountIDPath: "id", AccountLabelPath: "name"},
})
if err != nil {
    return err
}
providers := provider.NewRegistry()
if err := providers.Register(p); err != nil {
    return err
}

store := memstore.New() // or sqlstore.New(db, sqlstore.Postgres), then Migrate
b, err := broker.New(broker.Options{
    Store: store, Crypto: crypto, Providers: providers, Authorizer: authz, Auditor: audit,
})
if err != nil {
    return err
}
svc, err := service.New(service.Options{
    Store: store, Broker: b, Providers: providers, Authorizer: authz, Auditor: audit,
})
if err != nil {
    return err
}

conn, err := svc.Create(ctx, service.CreateInput{
    Owner: owner, Actor: actor, Provider: "example", AuthMethod: "api_token",
    Fields: map[string]connectforge.Secret{"token": connectforge.SecretString(token)},
})
if err != nil {
    return err
}
hc, err := b.HTTPClient(ctx, owner, actor, conn.ID, connectforge.Use{
    Consumer: connectforge.Consumer{Kind: "application", ID: "dashboards"}, Purpose: "traffic",
})
// hc authenticates each request; pass it to a provider SDK's WithHTTPClient.

crypto, authz and audit are the host's implementations of Cryptographer, Authorizer and Auditor. cryptotest.New() is an in-memory Cryptographer for tests.

Known limitations

  • Static API tokens only. GitHub App installations, OAuth with PKCE and single-flight refresh, and credential replacement are not in this release.
  • No scheduled validation. A connection is tested when Test is called.
  • No HTTP API or command line. A host exposes the service itself.
  • Grants and bindings are stored, not yet managed by the service. A host authorizes through its Authorizer.
  • A client holds the credential it was made with. A disconnect or a replaced credential applies to the next client, so make one per unit of work.
  • SQLite needs foreign keys on and one connection or immediate transactions so that concurrent writers queue instead of failing.
  • CI runs the SQLite conformance suite; the PostgreSQL suite is opt-in.

Dependencies

Module Use
entgo.io/ent The SQL store

The module imports no database driver; the host chooses one. pgx and modernc.org/sqlite appear in go.mod only for the store tests.

Documentation

A user guide covers connections and their lifecycle, using a connection, writing a provider, integrating with a host, storage and security, starting from Getting started.