v0.15.0¶
Release Date: 2026-10-13
This release covers everything since v0.14.0. It has five themes: key
management through keyforge, connections to external services through
connectionforge, invite-only sign-up with a waitlist, an app catalog
for product switchers, and the orgs claim with per-application roles in
embedded SystemAuth. It has no breaking changes. Run systemauth migrate for
the new tables.
Highlights¶
- Key management. The
keyspackage wires keyforge as the platform's one key library: envelope encryption, rotation without re-encrypting stored data, the root key read through omnivault, and key events sent to the audit trail. See Key management and ADR-013. - Connections to external services. The
connectionspackage composes connectionforge: an organization connects accounts at services such as a CDN or analytics product, credentials are sealed under managed keys, and an application receives an authenticated HTTP client, never the secret. See Connections and ADR-017. - A connections API.
/api/v1/organizations/{org}/connectionsis served by SystemAuth when the newconnectionsconfiguration section is set, behind thesystemforge:connections:readand:writescopes. - Invite-only sign-up. Social login can admit only verified emails that hold an invite. Everyone else reaches a waitlist that records the consent text they were shown.
systemauth inviteandsystemauth waitlistmanage both. See Deploying SystemAuth. - App catalog. A host publishes the applications it runs at
/.well-known/sf-apps, and/bff/sessionlists the catalog so each shell shows the products a person holds a role in for the current organization. - Per-application roles in tokens. Embedded SystemAuth emits the
orgsclaim with each organization's application roles, so relying parties keep no membership tables. - A PostgreSQL fix.
rls.Helper.SetTenantandSetUserfailed on every real PostgreSQL connection; they now work.
Upgrade notes¶
There are no breaking changes, but there are new tables and one new optional configuration section.
- Migrate. Run
systemauth migrate(orsystemauthsvc.Service.Migrate). It createssf_signup_invitesandsf_signup_waitlist. Existing tables are unchanged and there is no SQL migration file to apply by hand. - Enabling connections is optional. Without the
connectionssection nothing changes: no routes are mounted and no keys are opened. - To enable them, add the section, create a root key, and back up the keys directory with the database:
Connections need a database and the role-based authorization mode.
systemauth migrate then creates the connectionforge_* tables and, on
PostgreSQL, enables row-level security on them keyed on owner_id. Run the
migration with a role allowed to change the schema.
4. Register providers. An embedding application passes the providers it
supports through systemauthsvc.Options.Providers. Without any, the API
serves an empty provider list.
5. Request the new scopes. Clients of the connections API ask for
systemforge:connections:read and systemforge:connections:write.
Features¶
Key management¶
| Piece | Where it lives |
|---|---|
| Root key | Your secret store, read through omnivault at startup |
| Key ring | A file in the keys directory, sealed under the root key |
| Registry | A SQLite database in the same directory |
| Data | Your database, as ciphertext that names the key version |
k, err := keys.Open(ctx, keys.Options{
Dir: "/var/lib/myapp/keys",
Root: keys.VaultSource(vault, "MYAPP_KEYS_ROOT"),
Purposes: []keyforge.Purpose{"myapp.sessions"},
})
sealed, err := k.Seal(ctx, "myapp.sessions", []byte("value"), []byte("user:42"))
This release uses keyforge's single-host setup. Moving its registry to the platform database needs keyforge to accept an external registry and is recorded in ADR-013. keyforge is not externally audited.
Connections¶
- Each connection is validated with its provider before it is stored, and a rejected credential stores nothing.
- A connection holds no secret. Its credential is sealed under the
connectionforge.credentialpurpose with additional data binding the organization, the connection and the credential version. - Organization owners and admins can view, use and manage connections; members can view them. A connection's creator manages it.
- Requests made through a connection reach only its provider's hosts, over HTTPS, never a private address.
- Tenant isolation holds twice: queries filter on the organization, and on PostgreSQL row-level security refuses another organization's rows.
| Method and path | Action |
|---|---|
GET /api/v1/providers |
Provider manifests |
GET /api/v1/organizations/{org}/connections |
Connections the caller may view |
POST /api/v1/organizations/{org}/connections |
Connect and validate |
GET, PATCH, DELETE .../connections/{id} |
Read, rename, disconnect |
POST .../connections/{id}/test |
Validate again; the result is in health |
Sign-up gating¶
SignupGatedecides whether an upstream login may create a new account. It never affects people who already have one.- A refusal never says why, and goes to
WithSignupDeniedURL(validated at startup) or to the waitlist page. - The waitlist records the consent text shown and its version, answers the same whether or not an address was already listed, and is rate-limited per client and per address.
serve --invite-only,systemauth invite add|list|revoke, andsystemauth waitlist list [--csv]andremove.- A host binary embedding SystemAuth sets
systemauthsvc.Options.InviteOnly,SignupDeniedURLandWaitlistPage, and mounts the same commands fromsignupgate/signupcmdin its own CLI.
App catalog and the orgs claim¶
appcatalogdescribes the applications a deployment hosts: identifier, name, launch URL, icon and order.Staticserves one in the same process;Remotefetches what SystemAuth publishes, cached and kept through an outage.systemauthsvc.Options.Appspublishes a catalog and warns about entries with no registered vocabulary, since nobody can hold a role in them.systemauthsvc.Options.VocabulariesandDefaultEntitlementsinstall theorgsclaim source: application roles come fromapprole, limited to the default entitlements. An owner holds the top role of each entitled application, and a registered application outside the defaults stays closed.
Fixed¶
rls.Helper.SetTenantandSetUseron PostgreSQL. They issuedSET LOCAL ... = $1, which PostgreSQL rejects becauseSETtakes no bind parameters. They useset_config(name, value, true), which has the same transaction scope and takes the value as a parameter, so a value with a quote is safe. A test now runs it against a real server.
Dependencies¶
| Module | Version |
|---|---|
github.com/plexusone/keyforge |
v0.1.0 (new) |
github.com/plexusone/connectionforge |
v0.1.0 (new) |
github.com/plexusone/omnivault |
v0.5.0 (new) |
github.com/plexusone/omnistorage-core |
v0.5.2 |
github.com/go-chi/chi/v5 |
v5.4.0 |
github.com/grokify/guardsql |
v0.3.0 |
golang.org/x/crypto |
v0.58.0 |
github.com/yaronf/httpsign stays at v0.5.2: v0.6 removed NewJWSSignerV3,
which aauth-go v0.2.0 still calls, and pulls in jwx/v4. Requires Go 1.26.
Known limitations¶
- Connections support static API tokens only. GitHub App and OAuth connections follow in connectionforge.
- Role-based authorization only for connections. Startup refuses connections with the relationship-based mode.
- No grants API for connections yet. Only owners, admins and a connection's creator can use one; the resource-grant ladder is mapped but not exposed.
- SystemAuth passes no audit store to
keys, so key lifecycle events are not recorded there, and connection events go to the server log. - keyforge's control plane is not mounted, so there is no status or manual rotation endpoint yet.
- keyforge and connectionforge are not externally audited.