Documentation
¶
Index ¶
- func BuildAdminConsentURL(consentEndpoint, clientID, redirectURI, state string, extra map[string]string) (string, error)
- type Adapter
- type AppOnlyConfigFunc
- type ClientCertificateCredential
- type ClientCredential
- type ClientCredentialsTokenProvider
- func (p *ClientCredentialsTokenProvider) AuthMethod() domain.AuthMethod
- func (p *ClientCredentialsTokenProvider) GetAccessToken(ctx context.Context) (string, error)
- func (p *ClientCredentialsTokenProvider) GetCredentials(ctx context.Context) (*domain.Credentials, error)
- func (p *ClientCredentialsTokenProvider) IsValid(_ context.Context) bool
- type ClientSecretCredential
- type OAuthTokenProvider
- type StaticTokenProvider
- func (p *StaticTokenProvider) AuthMethod() domain.AuthMethod
- func (p *StaticTokenProvider) GetAccessToken(ctx context.Context) (string, error)
- func (p *StaticTokenProvider) GetCredentials(ctx context.Context) (*domain.Credentials, error)
- func (p *StaticTokenProvider) IsValid(ctx context.Context) bool
- type TokenProviderFactory
- func (f *TokenProviderFactory) Create(ctx context.Context, connectionID string) (driven.TokenProvider, error)
- func (f *TokenProviderFactory) CreateFromConnection(ctx context.Context, conn *domain.Connection) (driven.TokenProvider, error)
- func (f *TokenProviderFactory) RegisterAppOnlyConfig(platform domain.PlatformType, fn AppOnlyConfigFunc)
- func (f *TokenProviderFactory) RegisterRefresher(platform domain.PlatformType, refresher TokenRefresherFunc)
- func (f *TokenProviderFactory) RemoveProvider(connectionID string)
- type TokenRefresherFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildAdminConsentURL ¶ added in v0.4.0
func BuildAdminConsentURL( consentEndpoint, clientID, redirectURI, state string, extra map[string]string, ) (string, error)
BuildAdminConsentURL composes a consent-redirect URL from the supplied components. It is provider-agnostic: callers supply the consent endpoint and any extra query parameters specific to the provider (e.g. "prompt=admin_consent" for Microsoft, "approval_prompt=force" for Google Workspace). Standard OAuth parameters (client_id, redirect_uri, state) are always included.
All parameter values are URL-encoded. An error is returned only when the consentEndpoint cannot be parsed as a URL.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter handles authentication operations using bcrypt and JWT
func NewAdapter ¶
NewAdapter creates a new auth adapter with the given JWT secret
func NewAdapterWithCost ¶
NewAdapterWithCost creates a new auth adapter with custom bcrypt cost
func (*Adapter) GenerateToken ¶
func (a *Adapter) GenerateToken(claims *domain.TokenClaims) (string, error)
GenerateToken creates a signed JWT from domain claims
func (*Adapter) HashPassword ¶
HashPassword generates a bcrypt hash from a plaintext password
func (*Adapter) ParseToken ¶
func (a *Adapter) ParseToken(tokenString string) (*domain.TokenClaims, error)
ParseToken validates a JWT and extracts domain claims
func (*Adapter) VerifyPassword ¶
VerifyPassword checks if a password matches a bcrypt hash
type AppOnlyConfigFunc ¶ added in v0.4.0
type AppOnlyConfigFunc func(conn *domain.Connection) (tokenEndpoint, clientID, scope string, cred ClientCredential, err error)
AppOnlyConfigFunc resolves the per-platform configuration needed to perform a client_credentials token fetch for a given connection. The factory supplies the connection so that wiring code can inspect fields such as TenantID or AppCredentialsRef when constructing the credential. The function returns the token endpoint URL, OAuth client ID, OAuth scope string, and a ClientCredential (either a ClientSecretCredential or a ClientCertificateCredential). Wiring code is responsible for choosing between secret-based and certificate-based auth; the factory is agnostic to the distinction.
type ClientCertificateCredential ¶ added in v0.4.0
type ClientCertificateCredential struct {
// PrivateKey is the RSA private key used to sign the client assertion JWT.
PrivateKey *rsa.PrivateKey
// Thumbprint is the SHA-1 fingerprint of the DER-encoded certificate.
// It is base64url-encoded into the "x5t" JWT header field.
Thumbprint []byte
// Audience is the token endpoint URL (the "aud" claim in the assertion).
Audience string
// Issuer is typically the OAuth client ID (both "iss" and "sub" claims).
Issuer string
}
ClientCertificateCredential implements ClientCredential using a signed JWT client assertion (RFC 7521 §4.2). The assertion is signed with an RSA private key; the corresponding certificate is identified by its SHA-1 thumbprint in the JWT header (x5t), as required by providers such as Microsoft Entra ID.
func LoadClientCertificate ¶ added in v0.4.0
func LoadClientCertificate(pemBytes, keyPEMBytes []byte, audience, issuer string) (*ClientCertificateCredential, error)
LoadClientCertificate parses PEM-encoded certificate and private key bytes and returns a ClientCertificateCredential ready for use with ClientCredentialsTokenProvider.
pemBytes must contain at least one CERTIFICATE block. keyPEMBytes must contain exactly one RSA PRIVATE KEY or PKCS8 PRIVATE KEY block.
type ClientCredential ¶ added in v0.4.0
type ClientCredential interface {
// Apply sets the credential-specific form fields on v.
// For a secret credential: sets "client_secret".
// For a certificate credential: sets "client_assertion" and
// "client_assertion_type".
Apply(form url.Values) error
}
ClientCredential populates the credential fields of an OAuth 2.0 client_credentials token request form. Implementations choose between a client secret and a signed JWT client assertion.
type ClientCredentialsTokenProvider ¶ added in v0.4.0
type ClientCredentialsTokenProvider struct {
// contains filtered or unexported fields
}
ClientCredentialsTokenProvider fetches access tokens using the client_credentials grant (app-only flow). Tokens are cached in memory and re-fetched when fewer than 60 seconds remain before expiry. Because the credential is application-scoped rather than user-scoped, fetched tokens are never written back to the connection store.
The embedded mutex serialises concurrent calls to GetAccessToken so that only one HTTP round-trip to the token endpoint occurs at a time per provider instance.
func NewClientCredentialsTokenProvider ¶ added in v0.4.0
func NewClientCredentialsTokenProvider( tokenEndpoint, clientID, scope string, cred ClientCredential, httpClient *http.Client, ) *ClientCredentialsTokenProvider
NewClientCredentialsTokenProvider creates a ClientCredentialsTokenProvider. If httpClient is nil, http.DefaultClient is used.
func (*ClientCredentialsTokenProvider) AuthMethod ¶ added in v0.4.0
func (p *ClientCredentialsTokenProvider) AuthMethod() domain.AuthMethod
AuthMethod returns AuthMethodAppOnly.
func (*ClientCredentialsTokenProvider) GetAccessToken ¶ added in v0.4.0
func (p *ClientCredentialsTokenProvider) GetAccessToken(ctx context.Context) (string, error)
GetAccessToken returns a valid access token, fetching a new one when the cached token is absent or within 60 seconds of expiry.
func (*ClientCredentialsTokenProvider) GetCredentials ¶ added in v0.4.0
func (p *ClientCredentialsTokenProvider) GetCredentials(ctx context.Context) (*domain.Credentials, error)
GetCredentials fetches (or returns cached) credentials as a domain.Credentials value with AuthMethod set to AuthMethodAppOnly.
type ClientSecretCredential ¶ added in v0.4.0
type ClientSecretCredential struct {
Secret string
}
ClientSecretCredential is a ClientCredential backed by a shared secret.
type OAuthTokenProvider ¶
type OAuthTokenProvider struct {
// contains filtered or unexported fields
}
OAuthTokenProvider implements TokenProvider for OAuth2 credentials. It is intended to be shared across all consumers of the same connectionID within the process — TokenProviderFactory.Create ensures exactly one instance exists per connection. The embedded mutex serialises token refresh: while one caller is executing the HTTP round-trip to the token endpoint (typically ~200ms), other callers for the same connection block on the lock and then see the fresh token without issuing a second refresh. This prevents the token-endpoint race that occurs when N concurrent goroutines each hold a stale token and all decide to refresh simultaneously, causing Microsoft to rotate the refresh token and invalidate N-1 of the requests.
The mutex is held for the entire duration of the refresh HTTP call. This is an intentional design choice: the alternative (generation counters or refresh-in-flight channels) adds significantly more code for the same outcome. Document this explicitly so future readers do not reach for a singleflight rewrite without considering the trade-off.
Token state divergence: if anything mutates the connection's secrets in Postgres without going through refreshLocked (e.g. a direct DB write), the in-memory provider will go stale until the process restarts or RemoveProvider is called and Create re-loads the connection.
func NewOAuthTokenProvider ¶
func NewOAuthTokenProvider( connectionID string, accessToken string, refreshToken string, expiry *time.Time, refresher TokenRefresherFunc, connectionStore driven.ConnectionStore, ) *OAuthTokenProvider
NewOAuthTokenProvider creates a token provider for OAuth credentials.
func (*OAuthTokenProvider) AuthMethod ¶
func (p *OAuthTokenProvider) AuthMethod() domain.AuthMethod
AuthMethod returns OAuth2.
func (*OAuthTokenProvider) GetAccessToken ¶
func (p *OAuthTokenProvider) GetAccessToken(ctx context.Context) (string, error)
GetAccessToken returns a valid access token, refreshing if needed. It holds mu for the duration of a refresh so that concurrent callers serialise: the second caller blocks until the first refresh completes, then sees needsRefresh() == false and skips a redundant refresh.
func (*OAuthTokenProvider) GetCredentials ¶
func (p *OAuthTokenProvider) GetCredentials(ctx context.Context) (*domain.Credentials, error)
GetCredentials returns credentials for OAuth. It holds mu for the same reason as GetAccessToken.
func (*OAuthTokenProvider) IsValid ¶
func (p *OAuthTokenProvider) IsValid(ctx context.Context) bool
IsValid checks if credentials are valid (not expired or can be refreshed).
This method is lock-free and intentionally so: its contract is best-effort ("has a valid token or can be refreshed"). A concurrent refresh may flip isExpired() from true to false while IsValid runs; the race only makes the result more conservative (returns true when the token was just refreshed), never incorrectly false. Callers that need a guaranteed-valid token must call GetAccessToken instead.
type StaticTokenProvider ¶
type StaticTokenProvider struct {
// contains filtered or unexported fields
}
StaticTokenProvider implements TokenProvider for non-OAuth credentials. Used for API keys, PATs, and service accounts.
func NewStaticTokenProvider ¶
func NewStaticTokenProvider(token string, authMethod domain.AuthMethod) *StaticTokenProvider
NewStaticTokenProvider creates a token provider for static credentials.
func (*StaticTokenProvider) AuthMethod ¶
func (p *StaticTokenProvider) AuthMethod() domain.AuthMethod
AuthMethod returns the authentication method.
func (*StaticTokenProvider) GetAccessToken ¶
func (p *StaticTokenProvider) GetAccessToken(ctx context.Context) (string, error)
GetAccessToken returns the static token.
func (*StaticTokenProvider) GetCredentials ¶
func (p *StaticTokenProvider) GetCredentials(ctx context.Context) (*domain.Credentials, error)
GetCredentials returns nil for static tokens - use GetAccessToken instead.
type TokenProviderFactory ¶
type TokenProviderFactory struct {
// contains filtered or unexported fields
}
TokenProviderFactory creates TokenProviders from connection credentials. It maintains a per-connection cache (providers) so that all callers sharing the same connectionID get the exact same *OAuthTokenProvider instance. This ensures that OAuth token refresh is serialised per connection: only one goroutine refreshes at a time, and subsequent callers see the new token immediately instead of racing the same refresh token.
Cold-start race: if two goroutines call Create for the same connectionID simultaneously before either has populated the cache, both may call connectionStore.Get and construct a provider. Only one wins the LoadOrStore; the loser's instance is GC'd. This costs at most one extra Postgres read per connection per process lifetime and requires no synchronisation beyond the sync.Map itself.
func NewTokenProviderFactory ¶
func NewTokenProviderFactory( connectionStore driven.ConnectionStore, ) *TokenProviderFactory
NewTokenProviderFactory creates a new TokenProviderFactory.
func (*TokenProviderFactory) Create ¶
func (f *TokenProviderFactory) Create(ctx context.Context, connectionID string) (driven.TokenProvider, error)
Create returns a TokenProvider for the given connectionID.
If a provider for connectionID already exists in the process-level cache, it is returned immediately (O(1), no I/O). Otherwise the connection is loaded from the store, a new provider is constructed, and the result is stored via LoadOrStore so concurrent callers converge on a single instance.
func (*TokenProviderFactory) CreateFromConnection ¶
func (f *TokenProviderFactory) CreateFromConnection(ctx context.Context, conn *domain.Connection) (driven.TokenProvider, error)
CreateFromConnection creates a TokenProvider from a connection directly. Use this when you already have the connection loaded. The returned provider is NOT inserted into the per-connection cache — it is a fresh instance scoped to the caller's use (e.g. OAuth callback flows that have the connection in hand before it is registered in the cache).
The provider type depends on the connection's AuthMethod:
- AuthMethodOAuth2: returns an OAuthTokenProvider (delegated refresh flow).
- AuthMethodAPIKey, AuthMethodPAT, AuthMethodServiceAccount: returns a StaticTokenProvider backed by the stored secret.
- AuthMethodAppOnly: returns a ClientCredentialsTokenProvider. The token endpoint, scope, and credential are resolved via the AppOnlyConfigFunc registered for the connection's platform. Tokens are fetched on demand and cached in memory; they are never persisted back to the store.
func (*TokenProviderFactory) RegisterAppOnlyConfig ¶ added in v0.4.0
func (f *TokenProviderFactory) RegisterAppOnlyConfig( platform domain.PlatformType, fn AppOnlyConfigFunc, )
RegisterAppOnlyConfig registers an AppOnlyConfigFunc for a platform type. The function is called by CreateFromConnection when the connection uses AuthMethodAppOnly to resolve the token endpoint, scope, and credential for that platform.
func (*TokenProviderFactory) RegisterRefresher ¶
func (f *TokenProviderFactory) RegisterRefresher( platform domain.PlatformType, refresher TokenRefresherFunc, )
RegisterRefresher registers a token refresh function for a platform type.
func (*TokenProviderFactory) RemoveProvider ¶ added in v0.3.0
func (f *TokenProviderFactory) RemoveProvider(connectionID string)
RemoveProvider evicts the cached provider for a connection. It should be called by ConnectionService.Delete after the underlying connection row is removed, to prevent stale token state from persisting in memory.
RemoveProvider is a concrete-only method and does not appear on the driven.TokenProviderFactory port interface — callers that only hold the interface are unaffected.
type TokenRefresherFunc ¶
TokenRefresherFunc is a function type for token refresh operations.