connectforge: Product Requirements¶
Initiative: INIT-CONNECTFORGE-001
Repository: github.com/plexusone/connectforge
Problem¶
Applications that integrate with third-party services (a code host, a CDN, an analytics product, a payment processor) each end up with a private, ad hoc way to hold the customer's credentials for that service. The same gaps recur:
- Credentials are scattered. An API token in one table, an OAuth refresh token in another, a GitHub App key in an environment variable. There is no single inventory of which tenant has connected what.
- Nobody knows whether a connection still works. A token is revoked or expires, and the first sign is a failed sync inside a feature.
- OAuth refresh is easy to get subtly wrong. Two workers refresh the same token at once, the rotated refresh token is lost, and the customer has to reconnect.
- Secrets leak through convenience. Raw tokens reach logs, API responses and browser code because the simplest way to use a credential is to hand it to the caller.
- Every provider is a special case. GitHub Apps mint short-lived installation tokens from a private key; Cloudflare uses scoped API tokens; Stripe uses restricted keys or Connect. Each new provider re-solves validation, account discovery and revocation.
Hosted automation products solve this with a connection framework: a connection is an authorized account that is reusable, tested and labelled. There is no equivalent library for Go applications that is multi-tenant, encrypts credentials under a managed key, and is ready for agent-delegated authorization.
Goals¶
- One model for every external connection: provider, connection, credential, grant, binding, with a single lifecycle.
- Own the credential lifecycle, not the API clients. Provider SDKs stay in their own modules; connectforge supplies an authenticated transport, validation and status.
- Secrets stay server-side and out of normal paths. Callers receive metadata and an authenticated HTTP client, not raw tokens; secrets are redacted in logs, errors and JSON by construction.
- Correct refresh and rotation. Refresh is single-flight and persists a rotated refresh token atomically; replacing a static credential is a separate, audited operation.
- Tested connections. Every connection can be validated on demand and on a schedule, with account discovery and a human-readable account label.
- Ports and adapters. Storage, encryption, authorization, audit and egress policy are interfaces supplied by the host; the library imports no host framework.
- Declarative provider manifests. A provider states its authentication methods, fields, scopes and validation operation as data, so a UI or an agent can render and repair a connection without provider-specific code.
- Room for delegated authorization. A connection is not synonymous with a stored secret, so workload identity and agent-delegated grants can be added without changing the model.
Non-goals¶
- Provider API clients. connectforge does not wrap the GitHub, Cloudflare or Stripe APIs.
- Cryptography. Encryption is a port; a key-management library implements it.
- A secret manager. Retrieving bootstrap secrets (client secrets, an app private key) is the host's job, through a port.
- A workflow or automation engine. A connection can be used by workflows; it is not one.
- Authenticating or authorizing the host's users. The host supplies an authorizer and a tenant scope.
- Shipping a UI in the core module. A UI kit built on the provider manifests is a separate, later module.
Users¶
| User | Needs |
|---|---|
| Application developer | Ask for an authenticated HTTP client for a tenant's connection; never handle a raw token |
| Tenant administrator | Connect, test, rename, reconnect, replace and revoke connections; see what depends on one |
| Operator | See which connections are failing or expiring; know that refresh and rotation are safe under concurrency |
| Provider author | Describe a provider and its validation once, with conformance tests, and have it work for every host |
| Security reviewer | Read a stated threat model and an audit trail that never contains a secret |
| Platform integrator | Plug in storage, encryption, authorization, audit and egress policy |
Requirements¶
Functional¶
| ID | Requirement |
|---|---|
| FR1 | A registry of provider definitions, each with a manifest: authentication methods, credential fields (marked secret or not), scopes, account-selection needs, validation operation |
| FR2 | Tenant-owned connections with a lifecycle state, a display name, an external account identifier and label, granted scopes and observed health, kept distinct |
| FR3 | Versioned, encrypted credentials separate from the connection record; the credential store never holds plaintext |
| FR4 | Static credentials: API keys and tokens, with validation and account discovery on create |
| FR5 | OAuth 2.0 authorization code with PKCE, state and a validated return destination, following the current OAuth security best practice |
| FR6 | Automatic access-token refresh, single-flight per credential, persisting a rotated refresh token atomically, with classification of permanent versus transient failure |
| FR7 | GitHub App installations as a first-class method: installation tokens minted from the app key and cached until near expiry |
| FR8 | Connection testing on demand and on a schedule, producing a validation result and updating observed health without changing lifecycle state |
| FR9 | Reconnect, replace credential, rotate, disconnect and revoke, each audited; revocation calls the provider where it supports it |
| FR10 | A credential broker: HTTPClient and Token-less access that injects authorization per request, so a caller never receives the secret |
| FR11 | Grants: which principals (users, services, agents) may use, manage, share or disconnect a connection, enforced through a host Authorizer |
| FR12 | Bindings: which consumers (application, workflow, agent) use a connection, so disconnecting can report what would stop working |
| FR13 | Multiple connections per provider per tenant, and multiple credentials per connection during replacement |
| FR14 | Audit events for create, validate, use (sampled or counted), refresh, replace, revoke and administrative access, with no secret values |
| FR15 | Egress policy: provider endpoints come from the manifest, redirects are restricted, and private and link-local destinations are refused |
| FR16 | Conformance suites for the stores, the cryptographer port and providers, with recorded fixtures for provider adapters |
| FR17 | Rate-limit observation from provider responses, surfaced as health (later) |
| FR18 | Deployment secrets (OAuth client secrets, app keys) read through a host SecretSource port, never stored with per-owner credentials |
Non-functional¶
| ID | Requirement |
|---|---|
| NFR1 | A secret value has a type that redacts itself in String, GoString, MarshalJSON, MarshalText and slog; it is never an error message |
| NFR2 | Fail closed: if decryption, authorization or validation cannot complete, the connection is not used |
| NFR3 | Tenant isolation is part of every store method signature; a store conformance test proves cross-tenant reads and writes fail |
| NFR4 | Library-first: HTTP handlers and a CLI are thin adapters over the library |
| NFR5 | No import of any host framework, and no import of a key-management or secret-manager module in the core |
| NFR6 | Public-ready repository and history from the first commit |
| NFR7 | Ships an Ent-backed store for SQLite and PostgreSQL that a host composes on its own database, applying its own tenancy policy; a host may still supply its own store |
| NFR8 | Context-aware, cancellable operations; no goroutines that outlive the caller's context except a documented, stoppable scheduler |
Success criteria¶
- A host can store a Cloudflare-style API token, see it validated with a
discovered account label, and obtain an authenticated
*http.Clientfor it without the token appearing in any API response, log or error. - Two processes refreshing the same OAuth connection at once result in one token exchange, and the rotated refresh token is not lost.
- A GitHub App connection produces installation tokens, caches them, and renews them before expiry without a stored long-lived token.
- Disconnecting a connection reports the bindings that depend on it before it acts, and the audit trail records who did it.
- A cross-tenant read, write or use is refused by every store implementation.
- A new provider can be added by describing a manifest and implementing the validation, without changing the core.
Out of scope for v0.1.0¶
The UI kit, rate-limit tracking, DPoP and mutual TLS, agent-delegated authorization and any provider beyond the two reference implementations (an API-token provider and GitHub App). The model is designed so each fits without changing stored data.
Open questions¶
- Whether the HTTP handlers live in this module or in the host. The default
is a small
connectforge/httpapipackage that a host may mount or ignore. - Whether usage events are recorded individually or as counters per connection per period.