Documentation
¶
Overview ¶
Package postgres provides a Postgres-backed implementation of service.Repository for the identity service.
This is an alternative to the EntDB-backed repository. The two are interchangeable from the AuthService's point of view: both implement the same service.Repository interface.
Why Postgres ¶
Postgres is well understood, ubiquitous in production environments, and ships with a mature ops toolkit (pg_dump, pg_basebackup, replicas, PITR via WAL archiving). It is the right default for teams that want to run identity without taking a dependency on the tenant-shard-db stack.
Driver choice ¶
The implementation uses pgx/v5 directly (not via database/sql). pgxpool.Pool gives us a tuneable connection pool; pgx is also faster than database/sql and exposes Postgres-specific features (LISTEN / NOTIFY, COPY, JSONB native typing) that we may want later.
Migrations ¶
Schema DDL lives under migrations/ and is applied via golang-migrate/migrate using the embed.FS source. By default New() does NOT run pending migrations on connect — production deploys run migrations out-of-band as a separate Job, so a rolling rollout never races two replicas to apply the same change. Set Config.AutoMigrate to true (or GATEWAY_POSTGRES_AUTO_MIGRATE=true) only for local dev and single-replica environments.
Multi-tenancy ¶
Every table carries a tenant_id text not null column and uniqueness constraints are scoped to (tenant_id, ...). A pgRepository instance is constructed with a single tenant_id and writes/reads only that tenant's rows.
Error mapping ¶
Postgres unique-violation errors (SQLSTATE 23505) are mapped to service.ErrAlreadyExists by errors.go::wrapPgErr. ErrNoRows is surfaced as a nil result (not an error), matching the existing in-memory and EntDB drivers.
Index ¶
- Constants
- func Migrate(dsn string) error
- func New(ctx context.Context, cfg Config) (*pgRepository, error)
- type Config
- type Project
- type ProjectAuthDomain
- type ProjectCredential
- type ProjectStore
- func (s *ProjectStore) CreateProject(ctx context.Context, p *Project) (string, error)
- func (s *ProjectStore) CreateProjectAuthDomain(ctx context.Context, d *ProjectAuthDomain) (string, error)
- func (s *ProjectStore) CreateProjectCredential(ctx context.Context, c *ProjectCredential) (string, error)
- func (s *ProjectStore) EnsureDefaultProject(ctx context.Context, projectID, storageScopeID, name string) (*Project, error)
- func (s *ProjectStore) GetProjectByAuthHostname(ctx context.Context, hostname string) (*Project, error)
- func (s *ProjectStore) GetProjectByID(ctx context.Context, projectID string) (*Project, error)
- func (s *ProjectStore) GetProjectByStorageScope(ctx context.Context, storageScopeID string) (*Project, error)
- func (s *ProjectStore) GetProjectCredentialByPublicID(ctx context.Context, publicID string) (*ProjectCredential, error)
- func (s *ProjectStore) ListProjectAuthDomains(ctx context.Context, projectID string) ([]*ProjectAuthDomain, error)
- func (s *ProjectStore) RevokeProjectCredential(ctx context.Context, credentialID string, atMs int64) error
Constants ¶
const DefaultConnTimeout = 5 * time.Second
DefaultConnTimeout is used when Config.ConnTimeout is zero.
const DefaultMaxConns int32 = 25
DefaultMaxConns is used when Config.MaxConns is zero.
Variables ¶
This section is empty.
Functions ¶
func Migrate ¶ added in v0.15.0
Migrate applies all pending schema migrations to the Postgres database at dsn, then returns. It is idempotent — a fully-migrated database is a no-op — and safe to run concurrently with other instances: the underlying runner holds a Postgres advisory lock for the duration, so exactly one caller applies the migrations and the rest wait, then no-op. It is the entry point behind the `identity migrate` deploy step (the explicit alternative to GATEWAY_POSTGRES_AUTO_MIGRATE).
func New ¶
New constructs a Postgres-backed repository:
- Parse / validate cfg.
- Open a pgxpool with cfg.MaxConns, cfg.ConnTimeout.
- Optionally run pending migrations (cfg.AutoMigrate=true).
- Ping to fail fast on a misconfigured DSN.
The returned store implements both service.Repository and service.DB. The caller is responsible for keeping the *pgRepository alive for the lifetime of the service; pool resources are released by Close().
Types ¶
type Config ¶
type Config struct {
DSN string
MaxConns int32
ConnTimeout time.Duration
AutoMigrate bool
TenantID string
}
Config controls how the postgres repository connects to its database.
DSN is the libpq-style connection string, e.g.
postgres://user:pass@host:5432/dbname?sslmode=disable
MaxConns caps the underlying pgxpool. A zero value means "use the pgxpool default" (currently 4 + GOMAXPROCS-ish). 25 is the suggested default for an identity service node (see DefaultMaxConns).
ConnTimeout is the per-acquire timeout used when checking a connection out of the pool. It does NOT bound the total query time — callers are still responsible for passing a context with the appropriate deadline.
AutoMigrate controls whether New() applies pending schema migrations on first connect. In CI / dev / test we want true (the default); in strict production deploys teams may flip it to false and run `migrate ... up` from a deploy pipeline instead.
TenantID is the tenant whose rows this repository instance writes and reads. Multi-tenant deployments construct one repository per tenant; the most common single-tenant config plumbs cfg.DefaultTenantID straight through.
func ConfigFromEnv ¶
ConfigFromEnv reads Config values from GATEWAY_POSTGRES_* env vars. It is a convenience for callers that don't want to plumb each field through their own config struct. tenantID is passed in (rather than read from env) because identity already plumbs cfg.DefaultTenantID.
type Project ¶ added in v0.15.0
type Project struct {
ID string
StorageScopeID string
Name string
Status string // active | suspended
ConfigJSON string // JSON object; "" is normalised to "{}".
CreatedAtMs int64
UpdatedAtMs int64
}
Project is a control-plane registry row: one logical, control-plane isolation entity (a Firebase-style project) mapped onto exactly one physical storage scope (shard) via StorageScopeID.
type ProjectAuthDomain ¶ added in v0.15.0
type ProjectAuthDomain struct {
ID string
ProjectID string
Hostname string
IsPrimary bool
VerifiedAtMs int64
CreatedAtMs int64
}
ProjectAuthDomain is a per-project serving hostname. One host resolves to exactly one project (Hostname is globally unique, case-insensitive), so the Host header alone can resolve a project.
type ProjectCredential ¶ added in v0.15.0
type ProjectCredential struct {
ID string
ProjectID string
Kind string // publishable | secret | mtls
PublicID string
SecretHash string
Status string // active | revoked
CreatedAtMs int64
LastUsedAtMs int64
RevokedAtMs int64
}
ProjectCredential is a lookup key used to resolve a project on a request, by its globally-unique PublicID.
type ProjectStore ¶ added in v0.15.0
type ProjectStore struct {
// contains filtered or unexported fields
}
ProjectStore is the Postgres-backed, control-plane registry store. It is platform-global (not project/tenant-scoped) and shares its caller's connection pool.
func NewProjectStore ¶ added in v0.15.0
func NewProjectStore(r *pgRepository) *ProjectStore
NewProjectStore builds a control-plane store that shares the given repository's connection pool. The store must NOT be closed independently — closing the owning *pgRepository releases the pool for every derived store.
func (*ProjectStore) CreateProject ¶ added in v0.15.0
CreateProject inserts a project. StorageScopeID is required and globally unique; a duplicate surfaces service.ErrAlreadyExists. The id is caller-supplied (random hex when empty) so it is known without a RETURNING round-trip; the assigned id is written back to p.ID.
func (*ProjectStore) CreateProjectAuthDomain ¶ added in v0.15.0
func (s *ProjectStore) CreateProjectAuthDomain(ctx context.Context, d *ProjectAuthDomain) (string, error)
CreateProjectAuthDomain inserts a serving hostname for a project. Hostname is globally unique on lower(hostname) — a duplicate (in any case) surfaces service.ErrAlreadyExists. At most one is_primary domain is allowed per project (partial unique index); a second primary for the same project likewise surfaces service.ErrAlreadyExists. The id is caller-supplied (random hex when empty) and written back to d.ID.
func (*ProjectStore) CreateProjectCredential ¶ added in v0.15.0
func (s *ProjectStore) CreateProjectCredential(ctx context.Context, c *ProjectCredential) (string, error)
CreateProjectCredential inserts a lookup credential for a project. PublicID is required and globally unique; a duplicate surfaces service.ErrAlreadyExists. The id is caller-supplied (random hex when empty) and written back to c.ID.
func (*ProjectStore) EnsureDefaultProject ¶ added in v0.15.0
func (s *ProjectStore) EnsureDefaultProject(ctx context.Context, projectID, storageScopeID, name string) (*Project, error)
EnsureDefaultProject idempotently ensures the default Project exists, mapped onto the given storage scope (typically GATEWAY_DEFAULT_TENANT_ID). It is safe to call on every boot and from multiple instances at once: it returns the existing project when one is already present (looked up by id, then by storage scope), and otherwise creates it, tolerating a concurrent creator (an ErrAlreadyExists race is resolved by re-reading the row).
The default project is a logical control-plane entity that POINTS AT the storage scope via StorageScopeID — it is not the same value as the storage id, and the two must not be conflated.
If the storage scope is already mapped to a project (storage_scope_id is globally unique), that existing project is returned even when its id differs from projectID — the scope binding wins and no second project is created for the same scope.
func (*ProjectStore) GetProjectByAuthHostname ¶ added in v0.15.0
func (s *ProjectStore) GetProjectByAuthHostname(ctx context.Context, hostname string) (*Project, error)
GetProjectByAuthHostname resolves a project from a request's Host header. The hostname match is case-insensitive (lower(hostname)), matching the global unique index, so one host resolves to exactly one project. Returns (nil, nil) when no auth domain matches the host.
func (*ProjectStore) GetProjectByID ¶ added in v0.15.0
GetProjectByID returns the project with the given id, or (nil, nil) when no such project exists.
func (*ProjectStore) GetProjectByStorageScope ¶ added in v0.15.0
func (s *ProjectStore) GetProjectByStorageScope(ctx context.Context, storageScopeID string) (*Project, error)
GetProjectByStorageScope returns the single project mapped onto the given physical storage scope, or (nil, nil) when none is. storage_scope_id is globally unique, so this resolves at most one project.
func (*ProjectStore) GetProjectCredentialByPublicID ¶ added in v0.15.0
func (s *ProjectStore) GetProjectCredentialByPublicID(ctx context.Context, publicID string) (*ProjectCredential, error)
GetProjectCredentialByPublicID resolves a credential (and thus its owning project_id, kind and status) by its globally-unique public_id, or returns (nil, nil) when none matches. This is the key-based per-request project-resolution path.
func (*ProjectStore) ListProjectAuthDomains ¶ added in v0.15.0
func (s *ProjectStore) ListProjectAuthDomains(ctx context.Context, projectID string) ([]*ProjectAuthDomain, error)
ListProjectAuthDomains returns every auth domain for a project, ordered primary-first then by creation time, so callers can pick the link-building host deterministically. An unknown project yields an empty slice.
func (*ProjectStore) RevokeProjectCredential ¶ added in v0.15.0
func (s *ProjectStore) RevokeProjectCredential(ctx context.Context, credentialID string, atMs int64) error
RevokeProjectCredential marks a credential revoked at atMs (defaulting to now when zero). Revoking is idempotent: an already-revoked or non-existent credential is a no-op that returns nil. A revoked credential no longer resolves a project for new requests, though the row is retained for audit.
Source Files
¶
- config.go
- db.go
- doc.go
- email_change.go
- email_verification.go
- errors.go
- identity_verification.go
- invitation.go
- login_challenge.go
- migrate.go
- migrations.go
- oauth_identity.go
- oauth_one_time_code.go
- organization.go
- passkey.go
- password_reset.go
- passwordless.go
- phone.go
- project_store.go
- qr_login.go
- recovery_code.go
- refresh_token.go
- repo.go
- session.go
- sweeper.go
- totp.go
- tracepool.go
- user.go