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 theirWithHTTPClientoption, so a caller uses a connection without ever holding its credential. - Secrets cannot leak by construction.
Secretprints as[redacted]in every format verb, in JSON, in text and inslog. Errors carry a status and the provider's request ID but never a response body, and a test confinesRevealto 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_idon 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¶
Secretcopies on construction and onReveal, 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/apikeycovers services that issue a static key or token: where the token goes, which endpoint verifies it, and where the account is in the response.Classifymaps 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¶
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
Testis 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.