Help us shape the future of Go! Take the 2026 Go Developer Survey

api

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 13 Imported by: 0

README

api

Русская версия

Embeddable M2M access management for Go: API token issuance, rotation, revocation, integration bans, and permission checks. Each project has its own access source, PostgreSQL schema, and cache instances.

Go >= 1.27.3 (security patch floor), toolchain 1.27.3. Tested PostgreSQL version: 18.6. Module: github.com/assurrussa/api. User sessions, OAuth, an administrative HTTP API, and a goadmin interface are outside the scope of the first version.

Installation and license

Specify an exact published version:

go get github.com/assurrussa/api@v0.1.0

License: MIT. Supported import paths: the root package github.com/assurrussa/api, postgres, httpapi, cache/arc, and cache/rcu under the same prefix. Versions v0.x allow refinements to the public API before the compatibility commitments of v1; contract changes are described in release notes.

Access model

  • Client: a service or integration, its state, and its allowed scopes.
  • Credential: a named client credential with a permanent ID and its own scopes. One client can have several independent credentials.
  • SecretVersion: a secret version with a mandatory expiration time. Rotation preserves the Credential, replaces the secret, and increments the generation.
  • Principal: verified identifiers and the intersection of the client's and Credential's scopes. The host project checks ownership of orders, files, and other objects.

Scopes are exact strings, with no wildcards or role inheritance. The catalog is defined through Config.Scopes; an empty set allows authentication, but not operations that require scopes. Middleware requires all listed scopes. The catalog restricts new administrative assignments. Changing the configured catalog does not revoke permissions already stored: to revoke them, change the client's scopes through SetClientScopes or revoke the Credential. During a rollout with mixed configuration versions, this rule applies equally to all replicas.

Tokens contain 256 random bits and are opaque to clients. The database and caches store SHA-256 verification values of random secrets. Secret comparison is constant-time. The full token is returned only through Issued at issuance and rotation; it cannot be recovered or displayed again. Do not log Issued.Token, Authorization, or issuance response bodies.

DefaultTTL and MaxTTL are mandatory and set by the project. A zero operation TTL uses DefaultTTL; values above MaxTTL are rejected. Tokens cannot be permanent. The PostgreSQL Store sets the issuance time and grace start after acquiring the required locks; Issued.ExpiresAt contains the expiration time actually stored. External Store implementations can implement the optional TimedStore for the same semantics. A slow write or commit still consumes part of TTL/grace before the response; choose durations with enough margin for the operation budget. An expired Credential can be renewed through administrative rotation if it has not been revoked and the client is not banned.

Rotation accepts ExpectedGeneration to protect against concurrent replacements and GracePeriod for the transition. Zero invalidates the previously current secret. A positive period is capped by that secret's original expiration time. Repeated rotations do not extend previously assigned transition deadlines; several unexpired transition versions may remain valid at the same time.

A ban is reversible: after unbanning, valid, unrevoked credentials work again. Credential revocation is irreversible and covers all of its versions. RevokeAll revokes all existing client credentials; subsequent issuance of new credentials is allowed. In a compromise, combine a ban with revocation if you also need to prevent new issuance. Requests already admitted are not canceled retroactively.

Integration

The project owns the database pool, configuration, migration execution, TLS, rate limiting, and administrative authorization. All Service management methods are a trusted Go API; there is no public issuance route using an ordinary bearer token.

store, err := postgres.New(pool, "orders_access")
if err != nil { return err }

// Call in an explicit migration step, not on every web replica startup.
if err := store.Migrate(ctx); err != nil { return err }

service, err := api.New(store, api.Config{
    Scopes:     []string{"orders:read", "orders:write"},
    DefaultTTL: 24 * time.Hour,
    MaxTTL:     30 * 24 * time.Hour,
})
if err != nil { return err }
defer service.Close()

// Start is optional in strict mode; for caches, it starts their lifecycle.
if err := service.Start(ctx); err != nil { return err }

mux.Handle("GET /orders",
    httpapi.Middleware(service, "orders:read")(ordersHandler))

Supported imports: the root api package, postgres, httpapi, cache/arc, and cache/rcu. Public Store/Source/Cache interfaces support custom adapters; consistency contracts are documented in the interfaces' Go doc. Caches return data to a shared validator and do not grant permissions themselves.

New does not access the network or start goroutines. Start is called once; canceling its context stops service operation. Close is idempotent, cancels and waits for workers and operations, including an unfinished Start. The Service cannot be restarted after Close. Finish incoming HTTP requests first, then close the Service and database pool. Errors() also exposes rate-limited reports of LISTEN failures and background RCU refresh failures; the channel is not a counter of every attempt.

Each project uses a separate schema. The schema name is passed explicitly to postgres.New; names with the pg_ prefix are forbidden. Migrations are embedded in the adapter, versioned, and applied transactionally under an advisory lock. All verifier reads must go to the primary; a pool with read replicas whose lag is unbounded does not satisfy the contract. State writes go through Store/Service.

Administrative methods: CreateClient, Client, Clients, SetClientScopes, SetClientBanned, Issue, Rotate, Credential, Credentials, Revoke, RevokeAll. Lists use ID-based keyset pagination with after and a limit of 1–1000. For goadmin, wrap these methods in its administrative authorization; business tokens do not replace operator authorization.

PostgreSQL retains secret version history. For the retention period chosen by the project, call Store.PruneExpiredVersions(ctx, cutoff, batchSize) from an administrative job in repeated small batches. The method deletes only expired historical versions; it preserves the current Credential version even after expiration so that administrative rotation remains possible. Define the audit retention period before enabling cleanup.

Strict verification, ARC, and RCU

By default, every request reads consistent state from PostgreSQL. A new verification after confirmed revocation observes it. A storage error does not grant access.

Enable caching explicitly:

// Up to 10,000 frequently used entries, with at most 10 seconds of staleness.
api.WithCache(arc.Factory(10_000), 10*time.Second)

// Full projection: refresh every 30 seconds, with at most 2 minutes of staleness.
api.WithCache(rcu.Factory(30*time.Second), 2*time.Minute)

// Same interval, with a separate 60-second budget for initial and background loads.
api.WithCache(rcu.FactoryWithTimeout(30*time.Second, 60*time.Second), 2*time.Minute)

These are options for api.New. One adapter is selected at a time; if several WithCache options are supplied, the last one applies. Uses gocache v0.2.1.

  • ARC limits capacity and loads entries on a miss. TTL jitter is disabled.
  • RCU loads a complete immutable snapshot; the refresh interval must be positive and below MaxStaleness. For compatibility, Factory also uses that interval as the load timeout; FactoryWithTimeout sets a separate positive timeout below MaxStaleness. After a failure, the scheduler delays event-driven and periodic retries using backoff with jitter, without losing one pending invalidation. An initial load failure aborts Start.
  • MaxStaleness is set by the project and can be seconds or minutes. Age is measured from the start of the read, not from publication to the cache.
  • Cache hits, TTL refreshes, and notifications do not extend trust in the data. Stale state requires a primary read; a failure returns an infrastructure error. Secret expiration is checked independently of cache expiration.
  • Local invalidation increments the generation. Old loads and snapshots cannot grant access after a change has already been processed.

Changes send PostgreSQL NOTIFY transactionally. Each cached Service opens one additional dedicated connection for LISTEN, outside the MaxConns limit of the supplied pool. Its BeforeConnect and AfterConnect also apply to LISTEN. The projection is invalidated when the connection is restored. Notifications are best-effort: a missed event is bounded by MaxStaleness, rather than a promise of immediate revocation. Strict mode does not open a listener.

In the first version, any change invalidates the entire local projection. ARC then loads entries as needed, while RCU rebuilds its snapshot. An unknown token in a fresh RCU snapshot does not trigger a full reload; negative responses are not cached. Frequent changes can eliminate the benefit of caching. The application must limit incoming invalid tokens; the package is not a rate limiter.

HTTP accepts exactly one Authorization: Bearer … header. Tokens from URLs, cookies, or request bodies are not supported. Responses: 401 for invalid access, 403 for insufficient scopes, and 503 when reliable verification is impossible. Responses do not expose internal database errors or ban details.

Working example

Use a separate local database; sslmode=disable below is intended only for the local example. External APIs must be served over HTTPS.

export API_DATABASE_URL='postgres://USER:PASSWORD@127.0.0.1:5432/DATABASE?sslmode=disable'
export API_SCHEMA='api_demo'
go run ./examples/access migrate
go run ./examples/access create-client -name warehouse -scopes orders:read
go run ./examples/access issue -client CLIENT_ID -name reader -scopes orders:read -ttl 24h
go run ./examples/access serve -cache arc -max-staleness 10s

For RCU, the example accepts -refresh and -snapshot-timeout. A zero -snapshot-timeout preserves the previous mode, where the load budget equals -refresh; with a separate timeout, both values must be below -max-staleness.

Save the issued token in the integration's secret store. The example returns it in JSON only once. GET /orders checks orders:read and returns the Principal. There are no administrative HTTP endpoints; the default bind address is 127.0.0.1:8080.

go run ./examples/access rotate -credential CREDENTIAL_ID -generation 1 -grace 10m
go run ./examples/access ban -client CLIENT_ID
go run ./examples/access unban -client CLIENT_ID
go run ./examples/access revoke -credential CREDENTIAL_ID
go run ./examples/access revoke-all -client CLIENT_ID
go run ./examples/access scopes -client CLIENT_ID -scopes orders:read
go run ./examples/access clients -limit 100
go run ./examples/access credentials -client CLIENT_ID -limit 100

The example's TTL policy is 24 hours by default, with a maximum of 30 days. This is example configuration, not hidden library defaults. If the issuance response is lost after commit, the secret cannot be recovered: rotate the corresponding credential, or revoke it and issue a new one. Lists return metadata, without secrets or hashes.

Checks

Requires the golangci-lint version from .golangci-lint-version (2.14.0). The gate and make lint / make lint-fix check for an exact version match: they first use bin/golangci-lint, then the binary from PATH. To install the pinned official binary from the repository root:

api_lint_version=$(cat .golangci-lint-version)
api_lint_installer=$(mktemp)
curl -fsSL "https://raw.githubusercontent.com/golangci/golangci-lint/v${api_lint_version}/install.sh" -o "$api_lint_installer"
sh "$api_lint_installer" -b ./bin "v${api_lint_version}"
rm "$api_lint_installer"
bash scripts/lint.sh version

The installer verifies the release archive checksum. Regular checks use the installed binary without downloading it again.

make check

The gate checks release-script regressions, the pinned golangci-lint, formatting, vet, all race tests with PostgreSQL, the external consumer probe, and process E2E. If API_TEST_DATABASE_URL is not set, the script starts an isolated postgres:18.6-alpine container with a temporary port and removes it after the check. With an explicit URL, use a dedicated test database: tests create and remove only their own unique schemas.

make unit without a URL skips PostgreSQL tests and does not replace make check. The GitHub workflow installs the same golangci-lint version, runs the full gate and pinned govulncheck for PRs targeting main and pushes to main; separately, it runs a one-minute token parser fuzz test once a week. Required merge status is configured in branch settings. The consumer probe uses a temporary module with a local replace; it proves the public surface of the current checkout. To check a published version:

make consumer-release API_VERSION=v0.1.0

Requires Git and curl. For tagged versions, the command first checks the tag in the public Git repository. An unpublished tag fails immediately, before querying Go services. Canonical pseudo-versions remain supported for pre-tag checks.

The command automatically waits for the version's metadata, manifest, archive, and checksum record to become available in the public Go services. It uses lightweight HEAD requests, reports progress, and honors cache lifetimes and Retry-After. The wait is bounded to 35 minutes by default; set API_RELEASE_WAIT_TIMEOUT to a limit in seconds (1..86400) to change it. An early request made before a tag exists can leave a cached 404 for up to 30 minutes. Only after availability is confirmed does the clean consumer probe start. Checksum, compatibility, and test failures stop immediately rather than being retried.

This command uses Go 1.27.3, a separate temporary module, and a fresh dependency cache, and disables the workspace, local Go settings, and private module overrides. Downloads use proxy.golang.org with verification through sum.golang.org. The probe checks the exact version, absence of active replacements in the consumer graph and replace directives in api's downloaded go.mod, checksums, supported imports, Service creation in strict, ARC, and RCU modes, tests, and builds. go list -m all does not show directives inside dependencies: Go ignores them. The published manifest is therefore read separately through go mod edit -json without changing the file. Access to public Go services is required; latest, branch names, and an empty version are rejected. The temporary module and its cache are removed on exit.

Releasing a version
  1. Prepare changes on a branch and run make check. Create a PR targeting main, complete the required review, and wait for green CI after merge.
  2. Before publishing the tag, check the release probe against the exact canonical pseudo-version of the public commit, obtained through go list -m -f '{{.Version}}' github.com/assurrussa/api@<commit-sha>.
  3. Create an annotated tag for the chosen version on the verified merge commit, publish it, and verify that it points to the correct commit.
  4. Run make consumer-release API_VERSION=<version>. The command automatically waits for public Go services and then checks the version with a fresh cache. A timeout or a checksum, compatibility, or test failure stops the release.
  5. After successful verification, publish a GitHub Release with the commit SHA, requirements, and checks actually performed.

Published Go versions are immutable: tags must not be moved or recreated. Fixes to a published release receive a new version. The release probe confirms external consumption of the module; a production pilot in a real service is verified separately.

Process E2E can be run separately:

make e2e

Requires Go, Bash, and a working Docker installation. The script creates its own PostgreSQL 18.6 instance with a temporary loopback port, builds the CLI with the race detector, and starts two HTTP replicas for each mode: strict, ARC, and RCU. API_DATABASE_URL, API_SCHEMA, API_TEST_DATABASE_URL, and the local demo file do not determine the database used by this setup: E2E always uses its own container. Tokens remain in test memory; the report contains scenario names and results.

Checks cover issuance and administrative rejections, HTTP 200/401/403/503, scope intersection, bans/unbans, rotation with zero and positive grace, TTL and its independence from the cache, concurrent rotation, irreversible revocation, revoke-all followed by issuance, pagination, and schema isolation. Additional failures: authoritative state changes without NOTIFY, termination of only the Service's own LISTEN backend, PostgreSQL shutdown, initial RCU snapshot failure, and recovery of verification without restarting HTTP processes.

Waits are bounded by timeouts; HTTP processes are stopped during test cleanup. The shell trap removes the container and its anonymous volume and terminates remaining processes in the test run's process group, including after a go test failure or timeout. E2E data is disposable. The check uses local HTTP; TLS, an external reverse proxy, network partitions, and sustained load are outside this suite. The E2E matrix describes criteria and limits.

API_TEST_DATABASE_URL='postgres://…' make bench

Benchmarks use real PostgreSQL queries and 1/10/100 thousand credentials, a working set of 128 tokens, ARC capacity of 1024, and one sequential stream. They compare reads without changes and one mutation per 100 verifications. The report includes mean time, sampled verification p50/p95, allocations, database reads, and database snapshots. Mutation time is included in ns/op, but not in sampled verification p50/p95. init-allocated-B is the total allocation during initialization and warmup, not retained heap/RSS. BenchmarkPostgresSnapshot measures the full database load separately. This compares scenarios; it is not a production SLO or a sustainable load limit. Measured results and limitations: benchmark report.

The transitive dependency golang.org/x/text is pinned to v0.41.0 to fix GO-2026-5970, GO-2026-6629. Vulnerability checks must be repeated when dependencies change. Token expiration requires synchronized server clocks; the monotonic age of the local cache is unaffected by clock adjustments.

Documentation

Overview

Package api manages project-local machine-to-machine API credentials. Administrative methods are trusted host operations, never bearer capabilities.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalid      = errors.New("api: invalid input")
	ErrNotFound     = errors.New("api: not found")
	ErrConflict     = errors.New("api: conflict")
	ErrUnauthorized = errors.New("api: unauthorized")
	ErrForbidden    = errors.New("api: forbidden")
	ErrUnavailable  = errors.New("api: verification unavailable")
	ErrClosed       = errors.New("api: closed")
)

Functions

This section is empty.

Types

type Cache

type Cache interface {
	Start(ctx context.Context) error
	Lookup(ctx context.Context, key string, change uint64) (ReadResult, error)
	Invalidate()
	Close() error
}

Cache implementations never decide authorization. Start owns workers until Close; constructors must be side-effect free. Returned values are immutable.

type CacheFactory

type CacheFactory func(Source) (Cache, error)

type Client

type Client struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	Scopes    []string  `json:"scopes"`
	Banned    bool      `json:"banned"`
	CreatedAt time.Time `json:"created_at"`
}

type Config

type Config struct {
	Scopes     []string
	DefaultTTL time.Duration
	MaxTTL     time.Duration
}

type Credential

type Credential struct {
	ID         string     `json:"id"`
	ClientID   string     `json:"client_id"`
	Name       string     `json:"name"`
	Scopes     []string   `json:"scopes"`
	Generation int64      `json:"generation"`
	RevokedAt  *time.Time `json:"revoked_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`
}

Credential is a stable access grant. Rotation changes its secret, not its ID.

type IssueRequest

type IssueRequest struct {
	ClientID string
	Name     string
	Scopes   []string
	TTL      time.Duration // Zero uses Config.DefaultTTL.
}

type Issued

type Issued struct {
	Credential Credential `json:"credential"`
	Token      string     `json:"token"`
	ExpiresAt  time.Time  `json:"expires_at"`
}

Issued is the only administrative result containing the plaintext token. Do not log or persist Token. It cannot be recovered from the store.

type Option

type Option func(*settings) error

func WithCache

func WithCache(factory CacheFactory, maxStaleness time.Duration) Option

WithCache opts into bounded-staleness verification. The duration is mandatory and may be seconds or minutes. Expiry of the actual secret is always enforced.

type Principal

type Principal struct {
	ClientID     string   `json:"client_id"`
	CredentialID string   `json:"credential_id"`
	Scopes       []string `json:"scopes"`
}

type ReadResult

type ReadResult struct {
	Record     Record
	ReadAt     time.Time
	Generation uint64
}

ReadResult and Snapshot carry conservative monotonic freshness metadata.

type Record

type Record struct {
	Client     Client
	Credential Credential
	Secret     SecretVersion
}

Record must come from one consistent database statement/snapshot.

type RotateRequest

type RotateRequest struct {
	CredentialID       string
	ExpectedGeneration int64
	TTL                time.Duration // Zero uses Config.DefaultTTL.
	GracePeriod        time.Duration // Zero immediately retires the current secret.
}

type SecretVersion

type SecretVersion struct {
	ID           string
	CredentialID string
	Generation   int64
	Digest       [32]byte
	ExpiresAt    time.Time
	CreatedAt    time.Time
}

SecretVersion contains verification material, never a plaintext secret. ExpiresAt includes any earlier retirement deadline introduced by rotation.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service owns verification and optional workers, but never the Store's pool. New is side-effect free. Strict verification needs no Start; caches do.

func New

func New(store Store, cfg Config, options ...Option) (*Service, error)

func (*Service) Authenticate

func (s *Service) Authenticate(ctx context.Context, token string, required ...string) (Principal, error)

Authenticate verifies the secret and every requested scope. No returned slice aliases the Store or cache. Errors never include the supplied token.

func (*Service) Client

func (s *Service) Client(ctx context.Context, id string) (Client, error)

func (*Service) Clients

func (s *Service) Clients(ctx context.Context, after string, limit int) ([]Client, error)

func (*Service) Close

func (s *Service) Close() error

Close cancels workers and in-flight operations and waits for their completion.

func (*Service) CreateClient

func (s *Service) CreateClient(ctx context.Context, name string, scopes []string) (Client, error)

func (*Service) Credential

func (s *Service) Credential(ctx context.Context, id string) (Credential, error)

func (*Service) Credentials

func (s *Service) Credentials(ctx context.Context, clientID, after string, limit int) ([]Credential, error)

func (*Service) Errors

func (s *Service) Errors() <-chan error

Errors reports recoverable cache/notification failures and unexpected watcher termination. Delivery is nonblocking and rate limited; use separate metrics for counting every failure. Freshness deadlines still apply.

func (*Service) Issue

func (s *Service) Issue(ctx context.Context, req IssueRequest) (Issued, error)

func (*Service) Revoke

func (s *Service) Revoke(ctx context.Context, id string) error

func (*Service) RevokeAll

func (s *Service) RevokeAll(ctx context.Context, clientID string) error

func (*Service) Rotate

func (s *Service) Rotate(ctx context.Context, req RotateRequest) (Issued, error)

func (*Service) SetClientBanned

func (s *Service) SetClientBanned(ctx context.Context, id string, banned bool) error

func (*Service) SetClientScopes

func (s *Service) SetClientScopes(ctx context.Context, id string, scopes []string) error

func (*Service) Start

func (s *Service) Start(ctx context.Context) error

Start begins optional cache and notification workers. It may be called once. Canceling ctx stops workers and makes further operations unavailable.

type Snapshot

type Snapshot struct {
	Records    map[string]Record
	ReadAt     time.Time
	Generation uint64
}

type Source

type Source interface {
	Lookup(ctx context.Context, secretID string) (ReadResult, error)
	Snapshot(ctx context.Context) (Snapshot, error)
	Now() time.Time
	MaxStaleness() time.Duration
}

Source binds a cache to exactly the Service and Store it was constructed for.

type Store

type Store interface {
	CreateClient(ctx context.Context, client Client) error
	Client(ctx context.Context, id string) (Client, error)
	Clients(ctx context.Context, afterID string, limit int) ([]Client, error)
	SetClientScopes(ctx context.Context, id string, scopes []string) error
	SetClientBanned(ctx context.Context, id string, banned bool) error
	Issue(ctx context.Context, cred Credential, version SecretVersion) error
	Credential(ctx context.Context, id string) (Credential, error)
	Credentials(ctx context.Context, clientID, afterID string, limit int) ([]Credential, error)
	// Rotate locks client then credential; rejects banned/revoked state and a
	// mismatched generation. Only the preceding current secret's expiry is
	// shortened to min(original expiry, at+grace). Older deadlines never extend.
	Rotate(
		ctx context.Context, credID string, expectedGeneration int64, version SecretVersion, gracePeriod time.Duration, at time.Time,
	) (Credential, error)
	Revoke(ctx context.Context, id string, at time.Time) error
	RevokeAll(ctx context.Context, clientID string, at time.Time) error
	Lookup(ctx context.Context, secretID string) (Record, error)
	// Snapshot is all-or-nothing, keyed by secret version ID; expired or revoked
	// rows may be omitted. Partial query/scan/iteration results are errors.
	Snapshot(ctx context.Context) (map[string]Record, error)
}

Store implementations are concurrent-safe and enforce administrative invariants transactionally, not only in Service prechecks. Reads must use the authority's primary. Returned records belong to the caller, including slices and pointers.

type TimedStore

type TimedStore interface {
	IssueWithLifetime(
		ctx context.Context, cred Credential, version SecretVersion, ttl time.Duration,
	) (Credential, SecretVersion, error)
	RotateWithLifetime(
		ctx context.Context, credID string, expectedGeneration int64, version SecretVersion, ttl, grace time.Duration,
	) (Credential, SecretVersion, error)
}

TimedStore is an optional Store capability for assigning Issue and Rotate deadlines after authority locks are acquired. Returned times are the values actually committed. Stores without it retain the original Store contract.

type Watcher

type Watcher interface {
	Watch(ctx context.Context, invalidate func()) error
}

Watcher is an optional best-effort invalidation source. Watch calls invalidate after initial subscription and every reconnection, then on committed changes. It reconnects until ctx is canceled; callbacks must be serialized.

type WatcherWithErrors

type WatcherWithErrors interface {
	Watcher
	WatchWithErrors(ctx context.Context, invalidate func(), report func(error)) error
}

WatcherWithErrors optionally reports recoverable listener failures. The callback must not block; Watch continues reconnecting until cancellation.

Directories

Path Synopsis
cache
arc
Package arc provides an optional bounded-staleness ARC adapter.
Package arc provides an optional bounded-staleness ARC adapter.
rcu
Package rcu provides a whole-snapshot bounded-staleness cache adapter.
Package rcu provides a whole-snapshot bounded-staleness cache adapter.
examples
access command
Command access demonstrates project-local M2M credential administration and a single protected HTTP endpoint.
Command access demonstrates project-local M2M credential administration and a single protected HTTP endpoint.
Package httpapi adapts API credential verification to net/http.
Package httpapi adapts API credential verification to net/http.
Package postgres provides the PostgreSQL authority for api credentials.
Package postgres provides the PostgreSQL authority for api credentials.

Jump to

Keyboard shortcuts

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