identity

module
v1.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 1, 2026 License: AGPL-3.0

README

Identity

Authentication and user-management service. Deploys as a single container; pulls a pinned image, points at a Postgres datastore, exposes Connect-RPC over HTTP/JSON.

Treat this like tenant-shard-db: one image, deployed once per product, fully isolated user pools.

v1.0 breaking changes

v1.0 is the Project/Tenant/Domain redesign. Upgrading from a pre-v1.0 deployment is a breaking schema reset — there is no in-place data migration in this release; a fresh install is the supported path. See docs/UPGRADE.md for the full upgrade guide. Postgres row-level-security defense-in-depth ships in v1.0 (migration 0016_enable_rls_data_plane). What changed:

  • OrganizationSignup removed, along with the Organization / OrganizationMembership tables. Multitenancy is now modelled by Projects (the isolation shard) with Tenants auto-formed from verified email domains inside a project.
  • mode=single | multi removed, together with its env vars (GATEWAY_IDENTITY_MODE, GATEWAY_TENANT_HOST_BASE_DOMAIN, GATEWAY_TENANT_RESOLUTION_SOURCES). One code path now resolves the project per request (from an X-Project-Key credential or the Host header), then the tenant from the user's email domain.
  • Data-plane storage re-keyed tenant_idproject_id (ADR-0002).
  • TenantAdmin / RepositoryForTenant embedding options removed; embedders inject Repo+DB and the per-project repository is resolved internally.

See docs/IDENTITY.md and the ADRs under docs/adr/ for the model in full.

What it provides

  • Email + password signup, login, change, reset, lockout
  • Passkeys (WebAuthn) registration and login
  • TOTP (2FA) setup, verify, recovery codes
  • QR cross-device login
  • OAuth login (Google, Microsoft, GitHub, Apple — server-owned authorization-code exchange)
  • Sessions with revoke and sign-out-everywhere
  • JWT issuance with key rotation, plus /.well-known/jwks.json for downstream services
  • User and Group CRUD, group membership data
  • Audit log of auth events
  • Email and phone verification flows

Identity authenticates users, issues JWTs, and assigns coarse roles (admin / member / guest). It stores groups and memberships, but it does not enforce per-group or per-resource ACLs. Calling applications are responsible for authorization decisions built on that data.

Storage

Identity persists to Postgres — the primary datastore, which carries the Project/Tenant/Domain control plane. An in-memory driver (GATEWAY_REPO_DRIVER=memory) is available for local development and tests. Both drivers implement the same graph-shaped repository contract and are held to one conformance suite (identical uniqueness/ordering/error-translation semantics across every method).

The service models its data as a small set of graph node types, addressed by stable numeric type_id. The relational drivers map these onto tables; the service layer treats node IDs as opaque strings. Current allocations:

type_id Node Notes
1 User email is unique
2 WorkingGroup identity's group
5 RefreshToken token_hash is unique
19 PasswordResetToken token_hash is unique
20 PasskeyCredential credential_id is unique
21 PasskeyChallenge one-time per ceremony
22 QrLoginSession session_id is unique
23 TotpCredential per-user TOTP secret
24 RecoveryCode code_hash is unique
25 LoginChallenge challenge_id is unique
26 AuditEvent append-only
27 UserInvitation token_hash, email unique
28 AdminHelpRequest
29 EmailVerificationToken token_hash is unique
30 EmailChangeToken token_hash is unique
31 OAuthIdentity (provider, provider_user_id) is composite unique
32 IdentityVerification verification_id is unique
35 Session sid is unique
36 OAuthOneTimeCode hosted-OAuth handover; code_hash unique
37 EmailLoginCode passwordless OTP; email unique
38 MagicLinkToken passwordless magic link; token_hash unique
39 PhoneVerificationCode SMS OTP; per-user

Type IDs 33 and 34 (formerly Organization / OrganizationMembership) are retired and unallocated — organizations were dropped in v1.0. Project, Tenant, Domain, and membership state live in dedicated Postgres tables in the control plane and per-project data plane, not in the node-type registry above.

Configuration

All config is via environment variables. See internal/config/config.go for the full list. Most-tweaked:

Var Purpose
GATEWAY_REPO_DRIVER Persistence backend: postgres (default), sqlite, or memory (local dev / tests)
GATEWAY_POSTGRES_DSN Postgres connection string (required for the postgres driver)
GATEWAY_SQLITE_PATH SQLite database file path (when GATEWAY_REPO_DRIVER=sqlite). Required — set it to :memory: explicitly for an ephemeral in-process database; there is no implicit default. File-backed databases open in WAL mode (synchronous=NORMAL) so concurrent reads don't serialize behind writes
GATEWAY_SQLITE_MAX_CONNS Connection-pool size for a file-backed SQLite database (default 4). Ignored for :memory:, which is pinned to a single connection
GATEWAY_DEFAULT_TENANT_ID Storage scope ID (the physical shard) the default project maps onto
GATEWAY_DEFAULT_PROJECT_ID ID of the control-plane Project seeded on boot and used to pin zero-config requests (default default)
GATEWAY_DEFAULT_PROJECT_AUTH_DOMAINS Comma-separated serving hostnames seeded (verified) onto the default project; the first is primary. Lets the Host header resolve to the default project
GATEWAY_REQUIRE_VERIFIED_AUTH_DOMAIN When true (default), a project's primary auth-domain (which drives branded link URLs / cookie domains) must be DNS-verified — an unverified is_primary custom host is ignored. Set false to let an unverified primary host drive branded links
GATEWAY_ADMIN_API_SECRET Shared secret authenticating the control-plane admin RPCs (AdminCreateProject, …). Empty (default) disables them
GATEWAY_PUBLIC_EMAIL_DOMAINS Extra consumer/public email domains never auto-formed into a tenant (adds to the built-in gmail/outlook/… set)
GATEWAY_JWT_SIGNER JWT signer backend: file (default) or kms_aws
GATEWAY_JWT_KEYS_FILE Path to the file-backed signer's keys file (see docs/key-rotation.md)
GATEWAY_JWT_KMS_KEYS AWS KMS signer: CSV of kid=arn entries
GATEWAY_PASSKEY_RP_ID Passkey relying party ID — must match your domain
GATEWAY_PASSKEY_ORIGIN Allowed origin for passkey ceremonies
GATEWAY_TOTP_ISSUER Name shown in user authenticator apps
GATEWAY_DEFAULT_EMAIL_DOMAIN Default email domain for new accounts
GATEWAY_ALLOWED_ORIGINS CORS origins
GATEWAY_AUTH_ALLOW_LOCAL Set false in prod to disable username/password if you only want OAuth

Deployment

Pull the image and run it alongside a Postgres instance. Postgres carries the Project/Tenant/Domain control plane; the memory driver pins every request to the default project (no control-plane project registry), so it suits local dev and tests rather than production.

# 1. Run Postgres first
docker run -d --name identity-db -p 5432:5432 \
  -e POSTGRES_USER=identity -e POSTGRES_PASSWORD=password \
  -e POSTGRES_DB=identity \
  postgres:16.13-alpine3.23

# 2. Run identity pointing at it (apply migrations once, then serve)
docker run -p 80:80 -p 9090:9090 \
  -e GATEWAY_REPO_DRIVER=postgres \
  -e GATEWAY_POSTGRES_DSN='postgres://identity:password@identity-db:5432/identity?sslmode=disable' \
  -e GATEWAY_POSTGRES_AUTO_MIGRATE=true \
  -e GATEWAY_DEFAULT_TENANT_ID=my-product \
  -e GATEWAY_PASSKEY_RP_ID=my-product.com \
  -e GATEWAY_PASSKEY_ORIGIN=https://my-product.com \
  -e GATEWAY_TOTP_ISSUER="My Product" \
  ghcr.io/elloloop/identity:1.5.0

In production, run migrations out-of-band as a separate step (identity migrate) rather than relying on GATEWAY_POSTGRES_AUTO_MIGRATE on a rolling deploy. Or use docker-compose.yml at the repo root — it wires identity to Postgres with a persistent volume and a health-gated depends_on.

SQLite backend (lightweight / embedded)

For embedded, single-node, and development deployments, identity ships a pure-Go SQLite driver (modernc.org/sqlite, no cgo — cross-compiles cleanly). It runs the per-project data plane in a single file (or fully in-process) with no external service. Like the memory driver it is single-project (pinned to the default project — the Project/Tenant/Domain control plane is Postgres-only); unlike it the SQLite driver persists to durable SQL. Select it with GATEWAY_REPO_DRIVER=sqlite:

docker run -p 80:80 -p 9090:9090 \
  -e GATEWAY_REPO_DRIVER=sqlite \
  -e GATEWAY_SQLITE_PATH=/data/identity.db \
  -e GATEWAY_DEFAULT_TENANT_ID=my-product \
  -v identity-data:/data \
  ghcr.io/elloloop/identity:1.5.0

Set GATEWAY_SQLITE_PATH=:memory: for an ephemeral in-process database (tests / throwaway dev). The SQLite driver passes the same conformance suite as Postgres and memory — identical uniqueness, ordering, and ErrNotFound/ErrAlreadyExists/ErrInvalidArgument semantics. There is no Row-Level Security on SQLite (that stays Postgres-only defense-in-depth); the mandatory WHERE project_id = $1 repo boundary is the backend-agnostic isolation.

Backend tiers at a glance:

Driver GATEWAY_REPO_DRIVER Use case Control plane
Postgres postgres Production, multi-node Yes (Project/Tenant/Domain)
SQLite sqlite Single-file embedded, single-node, dev No (single-project)
memory memory Tests, smoke No (single-project)

Releasing

Push a v* tag — .github/workflows/release.yml builds and pushes a multi-arch image to ghcr.io/elloloop/identity:<version>.

git tag v1.5.0
git push origin v1.5.0

Local development

go build ./...
go test ./...

Directories

Path Synopsis
cmd
docsgen command
Command docsgen generates the reference-data JSON that the docs site renders, so those tables can never drift from the implementation.
Command docsgen generates the reference-data JSON that the docs site renders, so those tables can never drift from the implementation.
identity command
Command identity is the identity service container entry point.
Command identity is the identity service container entry point.
examples
embedded command
Command embedded shows how a host program mounts identity into its own Go server instead of running the dedicated container.
Command embedded shows how a host program mounts identity into its own Go server instead of running the dedicated container.
gen
Package identityserver is identity's embeddable public API.
Package identityserver is identity's embeddable public API.
internal
app
Package app builds the identity service HTTP handler from injected dependencies.
Package app builds the identity service HTTP handler from injected dependencies.
config
Package config loads the identity service configuration from environment variables with the GATEWAY_ prefix.
Package config loads the identity service configuration from environment variables with the GATEWAY_ prefix.
connect
Package connect provides the Connect-Go RPC handler layer for the identity service.
Package connect provides the Connect-Go RPC handler layer for the identity service.
graph
Package graph defines the in-process value types exchanged between identity's services and the relational repository drivers.
Package graph defines the in-process value types exchanged between identity's services and the relational repository drivers.
observability
Package observability wires OpenTelemetry traces for the identity server.
Package observability wires OpenTelemetry traces for the identity server.
repo
Package repo selects between concrete service.Repository / service.DB drivers (postgres, sqlite, memory) for the production binary's wiring code.
Package repo selects between concrete service.Repository / service.DB drivers (postgres, sqlite, memory) for the production binary's wiring code.
repo/conformance
Package conformance is a driver-agnostic test suite for service.Repository implementations.
Package conformance is a driver-agnostic test suite for service.Repository implementations.
repo/memory
Package memory is the in-process Repository driver.
Package memory is the in-process Repository driver.
repo/postgres
Package postgres provides a Postgres-backed implementation of service.Repository for the identity service.
Package postgres provides a Postgres-backed implementation of service.Repository for the identity service.
repo/sqlite
Package sqlite is the pure-Go SQLite Repository driver — the lightweight / embedded backend tier (single file or :memory:), sitting between Postgres (production, multi-node) and memory (tests).
Package sqlite is the pure-Go SQLite Repository driver — the lightweight / embedded backend tier (single file or :memory:), sitting between Postgres (production, multi-node) and memory (tests).
service
Package service implements the business logic for the identity service.
Package service implements the business logic for the identity service.
pkg
agegate
Package agegate defines the pluggable age-determination provider used to support COPPA-style age-gating at signup.
Package agegate defines the pluggable age-determination provider used to support COPPA-style age-gating at signup.
audit
Package audit provides best-effort audit event logging to the datastore.
Package audit provides best-effort audit event logging to the datastore.
captcha
Package captcha defines the pluggable CAPTCHA-verification provider interface and its in-tree implementations (Cloudflare Turnstile and Google reCAPTCHA v3) plus a no-op verifier for deployments that leave CAPTCHA disabled.
Package captcha defines the pluggable CAPTCHA-verification provider interface and its in-tree implementations (Cloudflare Turnstile and Google reCAPTCHA v3) plus a no-op verifier for deployments that leave CAPTCHA disabled.
email
Package email provides a provider-agnostic email transport layer.
Package email provides a provider-agnostic email transport layer.
events
Package events is the pluggable user-lifecycle eventing primitive.
Package events is the pluggable user-lifecycle eventing primitive.
idv
Package idv defines the pluggable identity-verification provider interface.
Package idv defines the pluggable identity-verification provider interface.
jwt
Package jwt provides RS256 JWT signing, verification, and JWKS publishing.
Package jwt provides RS256 JWT signing, verification, and JWKS publishing.
jwt/file
Package file implements the default file-backed jwt.Signer for the identity service.
Package file implements the default file-backed jwt.Signer for the identity service.
jwt/jwttest
Package jwttest provides an in-process jwt.Signer for tests.
Package jwttest provides an in-process jwt.Signer for tests.
jwt/kmsaws
Package kmsaws implements a jwt.Signer that delegates the signature operation to AWS KMS.
Package kmsaws implements a jwt.Signer that delegates the signature operation to AWS KMS.
oauth
Package oauth implements provider-side OAuth code-exchange flows for authenticating end users into the identity service.
Package oauth implements provider-side OAuth code-exchange flows for authenticating end users into the identity service.
passkeys
Package passkeys provides WebAuthn/FIDO2 passkey registration and authentication using the go-webauthn library.
Package passkeys provides WebAuthn/FIDO2 passkey registration and authentication using the go-webauthn library.
passwords
Package passwords provides bcrypt password hashing and strength validation.
Package passwords provides bcrypt password hashing and strength validation.
samlidp
Package samlidp implements the SAML 2.0 Identity Provider (IdP) core: IdP-metadata generation, AuthnRequest parsing, and the issuance of XML-DSig-signed SAML assertions/responses for a registered Service Provider (SP).
Package samlidp implements the SAML 2.0 Identity Provider (IdP) core: IdP-metadata generation, AuthnRequest parsing, and the issuance of XML-DSig-signed SAML assertions/responses for a registered Service Provider (SP).
scim
Package scim implements the inbound (server) side of SCIM 2.0 (RFC 7643 schema, RFC 7644 protocol) for the /scim/v2 surface: an external IdP (Okta, Entra ID, Google Workspace) lifecycle-manages users in a project by POST/GET/PUT/PATCH/DELETE-ing the /Users resource and the discovery endpoints (/ServiceProviderConfig, /Schemas, /ResourceTypes).
Package scim implements the inbound (server) side of SCIM 2.0 (RFC 7643 schema, RFC 7644 protocol) for the /scim/v2 surface: an external IdP (Okta, Entra ID, Google Workspace) lifecycle-manages users in a project by POST/GET/PUT/PATCH/DELETE-ing the /Users resource and the discovery endpoints (/ServiceProviderConfig, /Schemas, /ResourceTypes).
sms
Package sms is the abstraction over an SMS backend, mirroring pkg/email.
Package sms is the abstraction over an SMS backend, mirroring pkg/email.
totp
Package totp provides TOTP (RFC 6238) utilities for two-factor authentication.
Package totp provides TOTP (RFC 6238) utilities for two-factor authentication.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL