Documentation
¶
Overview ¶
Package app holds Conduit's usecases. The token lifecycle is the core: it seals provider secrets into the vault, vends them (refreshing on read when near expiry), and refreshes ahead of expiry from a cron, flipping a connection to NEEDS_REAUTH and notifying when its refresh token dies.
Index ¶
- Variables
- func DefaultConnectors() []domain.Connector
- func NewHTTPExchanger(client *httpclient.Client, limiter ratelimit.Limiter, secrets ClientSecrets, ...) *httpExchanger
- func NewLogNotifier() *logNotifier
- type ClientSecrets
- type ConnectionRepository
- type ConnectionStore
- type ConnectionUsecase
- type ConnectorCatalog
- type ConnectorRegistry
- type CredentialStore
- type ExchangerOption
- type ReauthNotifier
- type Sealer
- type Secret
- type TokenExchanger
- type TokenOption
- type TokenUsecase
- func (u *TokenUsecase) RefreshDue(ctx context.Context, limit int) (int, error)
- func (u *TokenUsecase) StoreCredential(ctx context.Context, connectionID string, kind domain.CredentialKind, ...) (domain.Credential, error)
- func (u *TokenUsecase) Vend(ctx context.Context, owner, connectionID string) (Secret, error)
Constants ¶
This section is empty.
Variables ¶
var ErrNeedsReauth = errors.New("conduit: connection needs re-authorization")
ErrNeedsReauth means a connection's refresh token is dead; the owner must re-authorize the connector. The connection is flipped to NEEDS_REAUTH.
var ErrRevoked = errors.New("conduit: connection revoked")
ErrRevoked means the connection was revoked and can no longer vend secrets.
Functions ¶
func DefaultConnectors ¶
DefaultConnectors is the built-in provider catalog. Rate profiles default to each provider's published per-account API ceiling so all orgs' calls stay under it (enforced via kit/ratelimit).
func NewHTTPExchanger ¶
func NewHTTPExchanger(client *httpclient.Client, limiter ratelimit.Limiter, secrets ClientSecrets, opts ...ExchangerOption) *httpExchanger
func NewLogNotifier ¶
func NewLogNotifier() *logNotifier
Types ¶
type ClientSecrets ¶
ClientSecrets supplies a connector's OAuth app credentials (client id and secret) — deployment secrets, not part of the in-code connector catalog.
func NewEnvClientSecrets ¶
func NewEnvClientSecrets() ClientSecrets
type ConnectionRepository ¶
type ConnectionRepository interface {
repository.Creator[domain.Connection]
repository.Getter[domain.Connection]
repository.Lister[domain.Connection]
repository.Deleter
}
ConnectionRepository persists connections via kit generics.
type ConnectionStore ¶
type ConnectionStore interface {
Get(ctx context.Context, opts ...search.Option) (domain.Connection, error)
SetStatus(ctx context.Context, id string, status domain.ConnectionStatus) error
}
ConnectionStore reads connections and flips their lifecycle status.
type ConnectionUsecase ¶
type ConnectionUsecase interface {
repository.Getter[domain.Connection]
repository.Lister[domain.Connection]
repository.Deleter
Create(ctx context.Context, conn domain.Connection) (domain.Connection, error)
}
ConnectionUsecase is the management surface for connections. Reads and deletes flow through the kit generic handlers (List filtered by the caller's owner); Create validates the connector exists and requires an owner.
func NewConnectionUsecase ¶
func NewConnectionUsecase(conns ConnectionRepository, registry ConnectorRegistry) ConnectionUsecase
type ConnectorCatalog ¶
type ConnectorCatalog struct {
// contains filtered or unexported fields
}
ConnectorCatalog adapts the registry to a kit Lister so /api/connectors can reuse the generic JSON:API list handler.
func NewConnectorCatalog ¶
func NewConnectorCatalog(registry ConnectorRegistry) *ConnectorCatalog
type ConnectorRegistry ¶
type ConnectorRegistry interface {
Lookup(slug string) (domain.Connector, bool)
List() []domain.Connector
}
ConnectorRegistry resolves connector definitions; Lookup serves the token lifecycle, List serves the read-only /api/connectors catalog.
func NewConnectorRegistry ¶
func NewConnectorRegistry(connectors ...domain.Connector) ConnectorRegistry
NewConnectorRegistry builds a frozen registry from the given connectors.
func NewDefaultConnectorRegistry ¶
func NewDefaultConnectorRegistry() ConnectorRegistry
NewDefaultConnectorRegistry builds the registry from the built-in catalog.
type CredentialStore ¶
type CredentialStore interface {
Get(ctx context.Context, opts ...search.Option) (domain.Credential, error)
Create(ctx context.Context, c domain.Credential) (domain.Credential, error)
Replace(ctx context.Context, id string, ciphertext, wrappedKey []byte, keyID string, expiresAt *time.Time) error
ListDueForRefresh(ctx context.Context, before time.Time, limit int) ([]domain.Credential, error)
}
CredentialStore persists sealed credentials and finds those due for refresh.
type ExchangerOption ¶
type ExchangerOption func(*httpExchanger)
func WithExchangerClock ¶
func WithExchangerClock(now func() time.Time) ExchangerOption
WithExchangerClock overrides the time source (tests).
type ReauthNotifier ¶
type ReauthNotifier interface {
NeedsReauth(ctx context.Context, conn domain.Connection) error
}
ReauthNotifier is told when a connection needs re-authorization (→ Herald).
type Sealer ¶
type Sealer interface {
Seal(ctx context.Context, plaintext []byte) (ciphertext, wrappedKey []byte, err error)
Open(ctx context.Context, ciphertext, wrappedKey []byte) ([]byte, error)
KeyID() string
}
Sealer is the envelope vault; implemented by *vault.Vault.
type Secret ¶
type Secret struct {
AccessToken string `json:"accessToken,omitempty"`
RefreshToken string `json:"refreshToken,omitempty"`
APIKey string `json:"apiKey,omitempty"`
APISecret string `json:"apiSecret,omitempty"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
}
Secret is the plaintext credential material sealed in the vault. Only the fields relevant to a connector's AuthType are populated.
type TokenExchanger ¶
type TokenExchanger interface {
Refresh(ctx context.Context, connector domain.Connector, refreshToken string) (Secret, error)
}
TokenExchanger performs the provider-side OAuth refresh. It returns ErrNeedsReauth when the refresh token itself is rejected.
type TokenOption ¶
type TokenOption func(*TokenUsecase)
func WithClock ¶
func WithClock(now func() time.Time) TokenOption
WithClock overrides the time source (tests).
func WithRefreshLead ¶
func WithRefreshLead(d time.Duration) TokenOption
WithRefreshLead sets how far before expiry a token is eagerly refreshed.
type TokenUsecase ¶
type TokenUsecase struct {
// contains filtered or unexported fields
}
TokenUsecase orchestrates the token lifecycle.
func NewTokenUsecase ¶
func NewTokenUsecase( conns ConnectionStore, creds CredentialStore, vault Sealer, registry ConnectorRegistry, exchanger TokenExchanger, notifier ReauthNotifier, opts ...TokenOption, ) *TokenUsecase
func (*TokenUsecase) RefreshDue ¶
RefreshDue refreshes up to limit credentials that fall within the refresh lead, skipping non-ACTIVE connections. Intended to be driven by a cron so the vend hot path almost always finds a warm token. Returns the count refreshed.
func (*TokenUsecase) StoreCredential ¶
func (u *TokenUsecase) StoreCredential(ctx context.Context, connectionID string, kind domain.CredentialKind, secret Secret) (domain.Credential, error)
StoreCredential seals a secret and persists it as the connection's credential.
func (*TokenUsecase) Vend ¶
Vend returns the connection's current secret, refreshing on read if it is within the refresh lead of expiry. This is the hot path; a warm credential returns after a single decrypt with no provider round-trip. The owner must match the connection's owner — a foreign owner gets a not-found, never a secret.