Skip to main content
Accounts are the human half of identity: an email and password, a session, and a role. tai42-accounts-postgres provides them on Postgres and contributes the Studio’s Users screen; tai42-accounts-oidc adds single sign-on through your own OIDC providers.

Postgres accounts

Install and wire it

manifest.yml
Mounting the routes without registering the provider raises at boot, naming both settings — the two halves are never half-wired.

Connect the database

There is no single database URL. The connection is composed from discrete fields, and the prefix stacks on the inherited field names, which is why each name carries PG_ twice:
Sessions live in Postgres, in the accounts_sessions table below. Login backoff and the bootstrap token live in Redis, which arrives from the access-control configuration rather than from a variable of this package’s own.

Apply the schema

Three tables — users, sessions, and invites — are created by an idempotent apply step. The provider’s health check asserts the schema at boot and never applies it for you:

Create the first admin

The first owner is created through POST /api/login/bootstrap, and the Studio’s login screen offers it while no user exists. The call is gated by a bootstrap token:
Set the token yourself, or leave it unset and let the server mint one at startup — it is generated once, stored in Redis, and logged by the process that wins the race, so read it from that process’s logs. Either way the route is token-gated; the one way to open it is the development switch below.
The call creates the owner with the admin role and refuses with a conflict once any user exists. Passwords are at least ten characters.
TAI_ACCOUNTS_BOOTSTRAP_OPEN=true disables the token gate entirely. It exists for local development. Never set it on a reachable deployment.

Invite the rest of the team

There is no outbound email. Creating a user returns a one-time invite link you deliver yourself. role names an existing access-control role template — an unknown name is rejected and no user is created:
The response carries an invite token and a /login?invite=... path relative to your origin. The invitee sets a password through POST /api/login/invite/accept, which consumes the token and mints a session in one step. Re-issue an invite with POST /api/auth/users/{user_id}/invite — it refuses once that user has a password. The Studio’s Users screen drives the same routes, and the last enabled admin cannot be demoted, disabled, or deleted.

Tune sessions and login limits

OIDC single sign-on

tai42-accounts-oidc adds “sign in with…” buttons to the login screen. It runs the authorization-code flow with PKCE, then hands the browser a short-lived code the Studio exchanges for a session.
manifest.yml
Each provider row takes a name (lowercase slug, unique) and the client credentials, plus optionally preset, issuer, scopes, claim, and a display label and icon. The presets are google, auth0, okta, keycloak, azure, and github; google and github carry fixed endpoints, and the others need a per-tenant issuer. A row with no preset at all is a raw OIDC provider and needs its own issuer.

Register the redirect URI

Every provider row uses one callback URL, built from the public base URL and the row’s name:
Register that exact URL in the provider’s console. The authorize request always sends PKCE with S256, and OIDC presets additionally send a nonce.
Signing in does not create an account. A successful sign-in resolves to the user id oidc:{provider}:{subject}, and until a policy exists for that id every protected route denies the request. Provision the policy first, then invite the person to sign in.
Both providers mint sessions with the same token prefix and are told apart by a store lookup, so the order of ACCESS_CONTROL_AUTH_PROVIDERS matters: an outage in an earlier member fails closed for the members after it.

See also