storage

package
v0.9.4 Latest Latest
Warning

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

Go to latest
Published: Feb 16, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package storage provides storage interfaces and implementations for the OAuth authorization server. This package implements fosite's storage interfaces to persist OAuth tokens and related data.

Fosite Storage Architecture

Fosite uses Interface Segregation Principle to split storage into focused interfaces. Each OAuth feature (authorization codes, access tokens, refresh tokens, PKCE) has its own storage interface. This design allows:

  • Feature composition: Enable only the OAuth features you need
  • Testing isolation: Mock only the interfaces relevant to your test
  • Clear contracts: Each interface documents exactly what it requires

The main fosite storage interfaces we implement:

  • oauth2.AuthorizeCodeStorage: Authorization code grant (RFC 6749 Section 4.1)
  • oauth2.AccessTokenStorage: Access token persistence
  • oauth2.RefreshTokenStorage: Refresh token persistence
  • oauth2.TokenRevocationStorage: Token revocation (RFC 7009)
  • pkce.PKCERequestStorage: PKCE challenge storage (RFC 7636)
  • fosite.ClientManager: OAuth client lookup and JWT assertion tracking

fosite.Requester: The Central Type

fosite.Requester is the core abstraction representing an OAuth request context. All token storage methods store the full Requester, not just the token value, because:

  • Context preservation: Token validation requires the original request context (client, scopes, audience, session) to make authorization decisions
  • Introspection support: RFC 7662 token introspection returns metadata about the token (client_id, scope, exp, etc.) which lives in the Requester
  • Revocation support: Revoking by request ID requires finding all tokens from the same authorization grant, which means storing the grant context
  • Session data: The embedded Session contains expiration times per token type, subject, username, and custom claims needed for token generation

A Requester contains:

  • ID: Unique identifier for the authorization grant (request ID)
  • Client: The OAuth client that initiated the request
  • RequestedScopes/GrantedScopes: What scopes were requested and granted
  • RequestedAudience/GrantedAudience: What audiences were requested and granted
  • Session: Token expiration times, subject, and custom data
  • Form: Original request form values (sanitized for storage)

Signature vs Request ID: Two Lookup Keys

Storage methods use two different keys for different operations:

Signature (token-specific operations):

  • Used by: CreateAccessTokenSession, GetAccessTokenSession, DeleteAccessTokenSession
  • What it is: A cryptographic signature or hash derived from the token value
  • Purpose: Look up a specific token when you have the token value
  • Example flow: Client sends access token -> derive signature -> look up session

Request ID (grant-wide operations):

  • Used by: RevokeAccessToken, RevokeRefreshToken, RotateRefreshToken
  • What it is: The unique identifier of the original authorization grant
  • Purpose: Find ALL tokens issued from the same authorization grant
  • Example flow: Revoke refresh token -> find request ID -> delete all related tokens

Why two keys? RFC 7009 requires that revoking a refresh token SHOULD also revoke associated access tokens. This requires finding tokens by their common origin (request ID) rather than by their individual values. The request ID ties together:

  • The authorization code (one-time use)
  • All access tokens issued from that grant
  • All refresh tokens issued from that grant

Our implementation stores tokens keyed by signature for O(1) token lookup, but revocation requires O(n) scan by request ID. Production implementations often maintain a reverse index (request_id -> signatures) for efficient revocation.

fosite.Session: Token Metadata Container

fosite.Session is an interface for storing session data between OAuth requests. Key design points:

Why GetExpiresAt lives on Session:

  • Different token types have different lifetimes (access: hours, refresh: days)
  • Expiration is metadata ABOUT the token, not the token itself
  • Session is the natural place for token metadata
  • Usage: session.GetExpiresAt(fosite.AccessToken) vs session.GetExpiresAt(fosite.RefreshToken)

Session vs Requester:

  • Session: Token-specific metadata (expiration, subject, username, claims)
  • Requester: Full request context including Session, Client, scopes, etc.
  • Session is embedded in Requester: requester.GetSession() returns the Session

Our session.Session type extends fosite's oauth2.JWTSession to add:

  • UpstreamSessionID: Links to tokens from our upstream IDP
  • JWT claims: Custom claims like "tsid" for token session lookup

fosite.Client vs fosite.Requester

Client and Requester serve different roles in the OAuth lifecycle:

fosite.Client represents the registered OAuth application:

  • Static data: client_id, client_secret, redirect_uris, allowed scopes/grants
  • Loaded from ClientRegistry (our extension) or fosite.ClientManager
  • Used to validate incoming requests against client configuration

fosite.Requester represents a specific authorization request:

  • Dynamic data: specific scopes requested/granted, session, form values
  • Created during authorization, stored with tokens
  • Contains a reference to Client via GetClient()

The relationship:

Client (static config) <--- Requester (instance) ---> Session (token metadata)
      |                           |                         |
   "What can this app do?"   "What did this request grant?"   "When does it expire?"

When to use each:

  • GetClient: Validate client_id/secret, check allowed scopes/redirects
  • Requester: Issue tokens, check what was actually granted, introspect tokens

Get Methods Accept Session Parameter

Methods like GetAccessTokenSession(ctx, signature, session) accept a Session parameter. This session is a "prototype" that may be used for deserialization:

  • Some storage backends serialize the full Requester (including Session)
  • On retrieval, they need a session instance to deserialize into
  • The prototype provides the concrete type for JSON/gob deserialization
  • If your storage keeps Requesters in memory, this parameter may be unused

Our in-memory implementation ignores this parameter since we store live Requester objects. Persistent backends (SQL, Redis) would use it for deserialization.

ToolHive Extensions

Beyond fosite's interfaces, we add ToolHive-specific storage:

  • UpstreamTokenStorage: Store tokens from upstream IDPs for proxy token swap
  • PendingAuthorizationStorage: Track in-flight authorizations during IDP redirect
  • ClientRegistry: Dynamic client registration (RFC 7591) via RegisterClient

These integrate with fosite's token storage to provide end-to-end OAuth proxy functionality: store upstream tokens, link them to issued tokens via session IDs, and enable transparent token swap for backend requests.

Implementation Notes

Thread safety: MemoryStorage uses sync.RWMutex for all map access. Persistent backends should use appropriate transaction isolation.

Expiration: We use timedEntry wrapper to track creation and expiration times. A background goroutine periodically cleans expired entries. Production backends might use database TTL features or scheduled jobs.

Defensive copies: Store and retrieve methods make deep copies to prevent aliasing issues where callers might modify returned data.

Error mapping: Storage errors are wrapped with both our sentinel errors (ErrNotFound, ErrExpired) and fosite errors (fosite.ErrNotFound) for compatibility with fosite's error handling.

References

Package storage provides storage interfaces and implementations for the OAuth authorization server.

Index

Constants

View Source
const (
	// TypeMemory uses in-memory storage (default).
	TypeMemory Type = "memory"

	// TypeRedis uses Redis Sentinel-backed storage for distributed deployments.
	TypeRedis Type = "redis"

	// DefaultCleanupInterval is how often the background cleanup runs.
	DefaultCleanupInterval = 5 * time.Minute

	// DefaultAccessTokenTTL is the default TTL for access tokens when not extractable from session.
	DefaultAccessTokenTTL = 1 * time.Hour

	// DefaultRefreshTokenTTL is the default TTL for refresh tokens when not extractable from session.
	DefaultRefreshTokenTTL = 30 * 24 * time.Hour // 30 days

	// DefaultAuthCodeTTL is the default TTL for authorization codes (RFC 6749 recommendation).
	DefaultAuthCodeTTL = 10 * time.Minute

	// DefaultInvalidatedCodeTTL is how long invalidated codes are kept for replay detection.
	DefaultInvalidatedCodeTTL = 30 * time.Minute

	// DefaultPKCETTL is the default TTL for PKCE requests (same as auth codes).
	DefaultPKCETTL = 10 * time.Minute

	// DefaultPublicClientTTL is the TTL for dynamically registered public clients.
	// This prevents unbounded growth from DCR. Confidential clients don't expire.
	DefaultPublicClientTTL = 30 * 24 * time.Hour // 30 days
)
View Source
const (
	DefaultDialTimeout  = 5 * time.Second
	DefaultReadTimeout  = 3 * time.Second
	DefaultWriteTimeout = 3 * time.Second
)

Default timeouts for Redis operations.

View Source
const (
	// KeyTypeAccess is the key type for access tokens.
	KeyTypeAccess = "access"

	// KeyTypeRefresh is the key type for refresh tokens.
	KeyTypeRefresh = "refresh"

	// KeyTypeAuthCode is the key type for authorization codes.
	KeyTypeAuthCode = "authcode"

	// KeyTypePKCE is the key type for PKCE requests.
	KeyTypePKCE = "pkce"

	// KeyTypeClient is the key type for OAuth clients.
	KeyTypeClient = "client"

	// KeyTypeUser is the key type for users.
	KeyTypeUser = "user"

	// KeyTypeProvider is the key type for provider identities.
	KeyTypeProvider = "provider"

	// KeyTypeUpstream is the key type for upstream tokens.
	KeyTypeUpstream = "upstream"

	// KeyTypePending is the key type for pending authorizations.
	KeyTypePending = "pending"

	// KeyTypeInvalidated is the key type for invalidated authorization codes.
	KeyTypeInvalidated = "invalidated"

	// KeyTypeJWT is the key type for client assertion JWTs.
	KeyTypeJWT = "jwt"

	// KeyTypeReqIDAccess is the key type for request ID to access token mappings.
	KeyTypeReqIDAccess = "reqid:access"

	// KeyTypeReqIDRefresh is the key type for request ID to refresh token mappings.
	KeyTypeReqIDRefresh = "reqid:refresh"

	// KeyTypeUserUpstream is the key type for user to upstream token reverse lookups.
	KeyTypeUserUpstream = "user:upstream"

	// KeyTypeUserProviders is the key type for user to provider identity reverse lookups.
	KeyTypeUserProviders = "user:providers"
)

Key type constants for Redis storage. These define the different types of data stored in Redis.

View Source
const DefaultPendingAuthorizationTTL = 10 * time.Minute

DefaultPendingAuthorizationTTL is the default TTL for pending authorization requests.

Variables

View Source
var (
	// ErrNotFound is returned when an item is not found in storage.
	ErrNotFound = errors.New("storage: item not found")

	// ErrExpired is returned when an item exists but has expired.
	ErrExpired = errors.New("storage: item expired")

	// ErrAlreadyExists is returned when attempting to create an item that already exists.
	ErrAlreadyExists = errors.New("storage: item already exists")

	// ErrInvalidBinding is returned when token binding validation fails
	// (e.g., subject or client ID mismatch).
	ErrInvalidBinding = errors.New("storage: token binding validation failed")
)

Sentinel errors for storage operations. Use errors.Is() to check for these error types.

Functions

func DeriveKeyPrefix added in v0.9.4

func DeriveKeyPrefix(namespace, name string) string

DeriveKeyPrefix creates the key prefix from server namespace and name. The format is "thv:auth:{ns:name}:" where {ns:name} is a Redis hash tag.

Note: The hash tag format {ns:name} intentionally combines namespace and name into a single tag. In Redis Cluster, only the first hash tag determines slot assignment. Using {ns}:{name} would only hash on namespace, potentially spreading a single server's keys across multiple slots. The combined format ensures all keys for a specific server (namespace+name pair) are placed in the same slot, enabling atomic multi-key operations like token revocation.

Types

type ACLUserConfig added in v0.9.4

type ACLUserConfig struct {
	Username string
	Password string
}

ACLUserConfig contains Redis ACL user authentication configuration.

type ACLUserRunConfig added in v0.9.4

type ACLUserRunConfig struct {
	// UsernameEnvVar is the environment variable containing the Redis username.
	UsernameEnvVar string `json:"usernameEnvVar" yaml:"usernameEnvVar"`

	// PasswordEnvVar is the environment variable containing the Redis password.
	PasswordEnvVar string `json:"passwordEnvVar" yaml:"passwordEnvVar"`
}

ACLUserRunConfig contains Redis ACL user authentication configuration. Credentials are read from environment variables for security.

type ClientRegistry

type ClientRegistry interface {
	// ClientManager provides client lookup (GetClient)
	fosite.ClientManager

	// RegisterClient registers a new OAuth client.
	// This supports both static configuration and dynamic client registration (RFC 7591).
	// Returns ErrAlreadyExists if a client with the same ID already exists.
	RegisterClient(ctx context.Context, client fosite.Client) error
}

ClientRegistry provides client registration and lookup operations. It embeds fosite.ClientManager for client lookup (GetClient) and adds RegisterClient for dynamic client registration (RFC 7591).

type Config

type Config struct {
	// Type specifies the storage backend type. Defaults to memory.
	Type Type
}

Config configures the storage backend.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns sensible defaults.

type MemoryStorage added in v0.7.2

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

MemoryStorage implements the Storage interface with in-memory maps. This implementation is thread-safe and suitable for development and testing. For production use, consider implementing a persistent storage backend.

Fosite Storage Design

Token maps store fosite.Requester (not just token strings) because fosite needs the full authorization context for validation and introspection. The Requester contains the Client, granted scopes, Session (with expiration times), and more.

Maps are keyed by "signature" (cryptographic token identifier) for O(1) token lookup. Revocation by "request ID" requires O(n) scan; production implementations should maintain a reverse index for efficiency.

func NewMemoryStorage added in v0.7.2

func NewMemoryStorage(opts ...MemoryStorageOption) *MemoryStorage

NewMemoryStorage creates a new MemoryStorage instance with initialized maps and starts the background cleanup goroutine.

func (*MemoryStorage) ClientAssertionJWTValid added in v0.7.2

func (s *MemoryStorage) ClientAssertionJWTValid(_ context.Context, jti string) error

ClientAssertionJWTValid returns an error if the JTI is known or the DB check failed, and nil if the JTI is not known (meaning it can be used).

func (*MemoryStorage) Close added in v0.7.2

func (s *MemoryStorage) Close() error

Close stops the background cleanup goroutine and waits for it to finish. This should be called when the storage is no longer needed.

func (*MemoryStorage) CreateAccessTokenSession added in v0.7.2

func (s *MemoryStorage) CreateAccessTokenSession(_ context.Context, signature string, request fosite.Requester) error

CreateAccessTokenSession stores the access token session.

func (*MemoryStorage) CreateAuthorizeCodeSession added in v0.7.2

func (s *MemoryStorage) CreateAuthorizeCodeSession(_ context.Context, code string, request fosite.Requester) error

CreateAuthorizeCodeSession stores the authorization request for a given authorization code.

func (*MemoryStorage) CreatePKCERequestSession added in v0.7.2

func (s *MemoryStorage) CreatePKCERequestSession(_ context.Context, signature string, request fosite.Requester) error

CreatePKCERequestSession stores the PKCE request session.

func (*MemoryStorage) CreateProviderIdentity added in v0.8.0

func (s *MemoryStorage) CreateProviderIdentity(_ context.Context, identity *ProviderIdentity) error

CreateProviderIdentity links a provider identity to a user. Returns ErrAlreadyExists if this provider identity is already linked.

func (*MemoryStorage) CreateRefreshTokenSession added in v0.7.2

func (s *MemoryStorage) CreateRefreshTokenSession(_ context.Context, signature string, _ string, request fosite.Requester) error

CreateRefreshTokenSession stores the refresh token session. The accessSignature parameter is used to link the refresh token to its access token. TODO: Store the accessSignature in a refreshToAccess map to enable direct lookup during token rotation instead of O(n) scan by request ID in RotateRefreshToken.

func (*MemoryStorage) CreateUser added in v0.8.0

func (s *MemoryStorage) CreateUser(_ context.Context, user *User) error

CreateUser creates a new user account. Returns ErrAlreadyExists if a user with the same ID already exists.

func (*MemoryStorage) DeleteAccessTokenSession added in v0.7.2

func (s *MemoryStorage) DeleteAccessTokenSession(_ context.Context, signature string) error

DeleteAccessTokenSession removes the access token session.

func (*MemoryStorage) DeletePKCERequestSession added in v0.7.2

func (s *MemoryStorage) DeletePKCERequestSession(_ context.Context, signature string) error

DeletePKCERequestSession removes the PKCE request session.

func (*MemoryStorage) DeletePendingAuthorization added in v0.7.2

func (s *MemoryStorage) DeletePendingAuthorization(_ context.Context, state string) error

DeletePendingAuthorization removes a pending authorization.

func (*MemoryStorage) DeleteRefreshTokenSession added in v0.7.2

func (s *MemoryStorage) DeleteRefreshTokenSession(_ context.Context, signature string) error

DeleteRefreshTokenSession removes the refresh token session.

func (*MemoryStorage) DeleteUpstreamTokens added in v0.7.2

func (s *MemoryStorage) DeleteUpstreamTokens(_ context.Context, sessionID string) error

DeleteUpstreamTokens removes the upstream IDP tokens for a session.

func (*MemoryStorage) DeleteUser added in v0.8.0

func (s *MemoryStorage) DeleteUser(_ context.Context, id string) error

DeleteUser removes a user account and all associated provider identities. Returns ErrNotFound if the user does not exist.

func (*MemoryStorage) GetAccessTokenSession added in v0.7.2

func (s *MemoryStorage) GetAccessTokenSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetAccessTokenSession retrieves the access token session by its signature.

The session parameter is a prototype for deserialization in persistent backends. Our in-memory implementation ignores it since we store live Requester objects. Persistent backends (SQL, Redis) use it to know what concrete type to deserialize into.

func (*MemoryStorage) GetAuthorizeCodeSession added in v0.7.2

func (s *MemoryStorage) GetAuthorizeCodeSession(_ context.Context, code string, _ fosite.Session) (fosite.Requester, error)

GetAuthorizeCodeSession retrieves the authorization request for a given code. If the authorization code has been invalidated, it returns ErrInvalidatedAuthorizeCode along with the request (as required by fosite).

func (*MemoryStorage) GetClient added in v0.7.2

func (s *MemoryStorage) GetClient(_ context.Context, id string) (fosite.Client, error)

GetClient loads the client by its ID or returns an error if the client does not exist.

func (*MemoryStorage) GetPKCERequestSession added in v0.7.2

func (s *MemoryStorage) GetPKCERequestSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetPKCERequestSession retrieves the PKCE request session by its signature.

func (*MemoryStorage) GetProviderIdentity added in v0.8.0

func (s *MemoryStorage) GetProviderIdentity(_ context.Context, providerID, providerSubject string) (*ProviderIdentity, error)

GetProviderIdentity retrieves a provider identity by provider ID and subject. This is the primary lookup path during authentication callbacks. Returns ErrNotFound if the identity does not exist.

func (*MemoryStorage) GetRefreshTokenSession added in v0.7.2

func (s *MemoryStorage) GetRefreshTokenSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetRefreshTokenSession retrieves the refresh token session by its signature.

func (*MemoryStorage) GetUpstreamTokens added in v0.7.2

func (s *MemoryStorage) GetUpstreamTokens(_ context.Context, sessionID string) (*UpstreamTokens, error)

GetUpstreamTokens retrieves the upstream IDP tokens for a session. Returns a defensive copy to prevent aliasing issues.

func (*MemoryStorage) GetUser added in v0.8.0

func (s *MemoryStorage) GetUser(_ context.Context, id string) (*User, error)

GetUser retrieves a user by their internal ID. Returns ErrNotFound if the user does not exist.

func (*MemoryStorage) GetUserProviderIdentities added in v0.8.0

func (s *MemoryStorage) GetUserProviderIdentities(_ context.Context, userID string) ([]*ProviderIdentity, error)

GetUserProviderIdentities returns all provider identities linked to a user. Returns an empty slice (not error) if the user exists but has no linked identities. Returns ErrNotFound if the user does not exist.

func (*MemoryStorage) Health added in v0.9.4

func (*MemoryStorage) Health(_ context.Context) error

Health is a no-op for in-memory storage since it is always available.

func (*MemoryStorage) InvalidateAuthorizeCodeSession added in v0.7.2

func (s *MemoryStorage) InvalidateAuthorizeCodeSession(_ context.Context, code string) error

InvalidateAuthorizeCodeSession marks an authorization code as used/invalid. Subsequent calls to GetAuthorizeCodeSession will return ErrInvalidatedAuthorizeCode.

func (*MemoryStorage) LoadPendingAuthorization added in v0.7.2

func (s *MemoryStorage) LoadPendingAuthorization(_ context.Context, state string) (*PendingAuthorization, error)

LoadPendingAuthorization retrieves a pending authorization by internal state. Returns a defensive copy to prevent aliasing issues.

func (*MemoryStorage) RegisterClient added in v0.7.2

func (s *MemoryStorage) RegisterClient(_ context.Context, client fosite.Client) error

RegisterClient adds or updates a client in the storage. This is useful for setting up test clients.

func (*MemoryStorage) RevokeAccessToken added in v0.7.2

func (s *MemoryStorage) RevokeAccessToken(_ context.Context, requestID string) error

RevokeAccessToken marks an access token as revoked. This method implements the oauth2.TokenRevocationStorage interface.

Note: This takes requestID, not signature. Per RFC 7009, revoking by request ID enables revoking ALL tokens from the same authorization grant. This is why we store the full Requester (with its ID) rather than just token values.

The O(n) scan by request ID is acceptable for in-memory storage. Production implementations should maintain a reverse index (request_id -> signatures).

func (*MemoryStorage) RevokeRefreshToken added in v0.7.2

func (s *MemoryStorage) RevokeRefreshToken(_ context.Context, requestID string) error

RevokeRefreshToken marks a refresh token as revoked. This method implements the oauth2.TokenRevocationStorage interface.

Like RevokeAccessToken, this takes requestID to find all refresh tokens from the same authorization grant. Per RFC 7009 Section 2.1, implementations SHOULD also revoke associated access tokens, which RotateRefreshToken handles.

func (*MemoryStorage) RevokeRefreshTokenMaybeGracePeriod added in v0.7.2

func (s *MemoryStorage) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, _ string) error

RevokeRefreshTokenMaybeGracePeriod marks a refresh token as revoked, optionally allowing a grace period during which the old token is still valid. For this implementation, we don't support grace periods and revoke immediately.

func (*MemoryStorage) RotateRefreshToken added in v0.7.2

func (s *MemoryStorage) RotateRefreshToken(_ context.Context, requestID string, refreshTokenSignature string) error

RotateRefreshToken invalidates a refresh token and all its related token data. This is called during token refresh to implement refresh token rotation.

func (*MemoryStorage) SetClientAssertionJWT added in v0.7.2

func (s *MemoryStorage) SetClientAssertionJWT(_ context.Context, jti string, exp time.Time) error

SetClientAssertionJWT marks a JTI as known for the given expiry time. Before inserting the new JTI, it will clean up any existing JTIs that have expired.

func (*MemoryStorage) Stats added in v0.7.2

func (s *MemoryStorage) Stats() Stats

Stats returns current statistics about storage contents. This is useful for testing and monitoring.

func (*MemoryStorage) StorePendingAuthorization added in v0.7.2

func (s *MemoryStorage) StorePendingAuthorization(_ context.Context, state string, pending *PendingAuthorization) error

StorePendingAuthorization stores a pending authorization request. The pending authorization is keyed by the internal state used to correlate the upstream IDP callback.

func (*MemoryStorage) StoreUpstreamTokens added in v0.7.2

func (s *MemoryStorage) StoreUpstreamTokens(_ context.Context, sessionID string, tokens *UpstreamTokens) error

StoreUpstreamTokens stores the upstream IDP tokens for a session. A defensive copy is made to prevent aliasing issues.

func (*MemoryStorage) UpdateProviderIdentityLastUsed added in v0.8.0

func (s *MemoryStorage) UpdateProviderIdentityLastUsed(
	_ context.Context, providerID, providerSubject string, lastUsedAt time.Time,
) error

UpdateProviderIdentityLastUsed updates the LastUsedAt timestamp for a provider identity. This should be called after each successful authentication via this identity. Returns ErrNotFound if the identity does not exist.

type MemoryStorageOption added in v0.7.2

type MemoryStorageOption func(*MemoryStorage)

MemoryStorageOption configures a MemoryStorage instance.

func WithCleanupInterval added in v0.7.2

func WithCleanupInterval(interval time.Duration) MemoryStorageOption

WithCleanupInterval sets a custom cleanup interval.

type PendingAuthorization

type PendingAuthorization struct {
	// ClientID is the ID of the OAuth client making the authorization request.
	ClientID string

	// RedirectURI is the client's callback URL where we'll redirect after authentication.
	RedirectURI string

	// State is the client's original state parameter for CSRF protection.
	State string

	// PKCEChallenge is the client's PKCE code challenge.
	PKCEChallenge string

	// PKCEMethod is the PKCE challenge method (must be "S256").
	PKCEMethod string

	// Scopes are the OAuth scopes requested by the client.
	Scopes []string

	// InternalState is our randomly generated state for correlating upstream callback.
	InternalState string

	// UpstreamPKCEVerifier is the PKCE code_verifier for the upstream IDP authorization.
	// This is generated when redirecting to the upstream IDP and used when exchanging
	// the authorization code. See RFC 7636.
	UpstreamPKCEVerifier string

	// UpstreamNonce is the OIDC nonce parameter sent to the upstream IDP for ID Token
	// replay protection. This is validated against the nonce claim in the returned
	// ID Token. See OIDC Core Section 3.1.2.1.
	UpstreamNonce string

	// CreatedAt is when the pending authorization was created.
	CreatedAt time.Time
}

PendingAuthorization tracks a client's authorization request while they authenticate with the upstream IDP.

type PendingAuthorizationStorage

type PendingAuthorizationStorage interface {
	// StorePendingAuthorization stores a pending authorization request.
	// The state is used to correlate the upstream IDP callback.
	StorePendingAuthorization(ctx context.Context, state string, pending *PendingAuthorization) error

	// LoadPendingAuthorization retrieves a pending authorization by internal state.
	// Returns ErrNotFound if the state does not exist.
	// Returns ErrExpired if the pending authorization has expired.
	LoadPendingAuthorization(ctx context.Context, state string) (*PendingAuthorization, error)

	// DeletePendingAuthorization removes a pending authorization.
	// Returns ErrNotFound if the state does not exist.
	DeletePendingAuthorization(ctx context.Context, state string) error
}

PendingAuthorizationStorage provides storage operations for pending authorization requests. These track the state of in-flight authorization requests while users authenticate with the upstream IDP, correlating the upstream callback with the original client request.

type ProviderIdentity added in v0.8.0

type ProviderIdentity struct {
	// UserID is the internal user ID this identity belongs to.
	UserID string

	// ProviderID identifies the upstream provider (e.g., "google", "github").
	ProviderID string

	// ProviderSubject is the subject identifier from the upstream provider.
	ProviderSubject string

	// LinkedAt is when this identity was linked to the user.
	LinkedAt time.Time

	// LastUsedAt is when this identity was last used to authenticate.
	LastUsedAt time.Time
}

ProviderIdentity links a user to an upstream identity provider. Multiple identities can be linked to a single user for account linking, enabling users to authenticate via different providers (e.g., Google and GitHub) while maintaining a single ToolHive identity.

type RedisConfig added in v0.9.4

type RedisConfig struct {
	// SentinelConfig is required - Sentinel-only deployment.
	SentinelConfig *SentinelConfig

	// ACLUserConfig is required - ACL user authentication only.
	ACLUserConfig *ACLUserConfig

	// KeyPrefix for multi-tenancy: "thv:auth:{ns}:{name}:".
	KeyPrefix string

	// Timeouts (defaults: Dial=5s, Read=3s, Write=3s).
	DialTimeout  time.Duration
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
}

RedisConfig holds Redis connection configuration for runtime use.

type RedisRunConfig added in v0.9.4

type RedisRunConfig struct {
	// SentinelConfig contains Sentinel-specific configuration.
	SentinelConfig *SentinelRunConfig `json:"sentinelConfig,omitempty" yaml:"sentinelConfig,omitempty"`

	// AuthType must be "aclUser" - only ACL user authentication is supported.
	AuthType string `json:"authType" yaml:"authType"`

	// ACLUserConfig contains ACL user authentication configuration.
	ACLUserConfig *ACLUserRunConfig `json:"aclUserConfig,omitempty" yaml:"aclUserConfig,omitempty"`

	// KeyPrefix for multi-tenancy, typically "thv:auth:{ns}:{name}:".
	KeyPrefix string `json:"keyPrefix" yaml:"keyPrefix"`

	// DialTimeout is the timeout for establishing connections (e.g., "5s").
	DialTimeout string `json:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty"`

	// ReadTimeout is the timeout for read operations (e.g., "3s").
	ReadTimeout string `json:"readTimeout,omitempty" yaml:"readTimeout,omitempty"`

	// WriteTimeout is the timeout for write operations (e.g., "3s").
	WriteTimeout string `json:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty"`
}

RedisRunConfig is the serializable Redis configuration for RunConfig. This is designed for Sentinel-only deployments with ACL user authentication.

type RedisStorage added in v0.9.4

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

RedisStorage implements the Storage interface with Redis Sentinel backend. It provides distributed storage for OAuth2 tokens, authorization codes, user data, and pending authorizations, enabling horizontal scaling.

func NewRedisStorage added in v0.9.4

func NewRedisStorage(ctx context.Context, cfg RedisConfig) (*RedisStorage, error)

NewRedisStorage creates Redis-backed storage with Sentinel failover support. Returns error if configuration validation fails or connection cannot be established.

func NewRedisStorageWithClient added in v0.9.4

func NewRedisStorageWithClient(client redis.UniversalClient, keyPrefix string) *RedisStorage

NewRedisStorageWithClient creates a RedisStorage with a pre-configured client. This is useful for testing with miniredis.

func (*RedisStorage) ClientAssertionJWTValid added in v0.9.4

func (s *RedisStorage) ClientAssertionJWTValid(ctx context.Context, jti string) error

ClientAssertionJWTValid returns an error if the JTI is known.

func (*RedisStorage) Close added in v0.9.4

func (s *RedisStorage) Close() error

Close closes the Redis client connection.

func (*RedisStorage) CreateAccessTokenSession added in v0.9.4

func (s *RedisStorage) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) error

CreateAccessTokenSession stores the access token session.

func (*RedisStorage) CreateAuthorizeCodeSession added in v0.9.4

func (s *RedisStorage) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) error

CreateAuthorizeCodeSession stores the authorization request for a given authorization code.

func (*RedisStorage) CreatePKCERequestSession added in v0.9.4

func (s *RedisStorage) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) error

CreatePKCERequestSession stores the PKCE request session.

func (*RedisStorage) CreateProviderIdentity added in v0.9.4

func (s *RedisStorage) CreateProviderIdentity(ctx context.Context, identity *ProviderIdentity) error

CreateProviderIdentity links a provider identity to a user.

func (*RedisStorage) CreateRefreshTokenSession added in v0.9.4

func (s *RedisStorage) CreateRefreshTokenSession(
	ctx context.Context, signature string, _ string, request fosite.Requester,
) error

CreateRefreshTokenSession stores the refresh token session.

func (*RedisStorage) CreateUser added in v0.9.4

func (s *RedisStorage) CreateUser(ctx context.Context, user *User) error

CreateUser creates a new user account.

func (*RedisStorage) DeleteAccessTokenSession added in v0.9.4

func (s *RedisStorage) DeleteAccessTokenSession(ctx context.Context, signature string) error

DeleteAccessTokenSession removes the access token session.

func (*RedisStorage) DeletePKCERequestSession added in v0.9.4

func (s *RedisStorage) DeletePKCERequestSession(ctx context.Context, signature string) error

DeletePKCERequestSession removes the PKCE request session.

func (*RedisStorage) DeletePendingAuthorization added in v0.9.4

func (s *RedisStorage) DeletePendingAuthorization(ctx context.Context, state string) error

DeletePendingAuthorization removes a pending authorization.

func (*RedisStorage) DeleteRefreshTokenSession added in v0.9.4

func (s *RedisStorage) DeleteRefreshTokenSession(ctx context.Context, signature string) error

DeleteRefreshTokenSession removes the refresh token session.

func (*RedisStorage) DeleteUpstreamTokens added in v0.9.4

func (s *RedisStorage) DeleteUpstreamTokens(ctx context.Context, sessionID string) error

DeleteUpstreamTokens removes the upstream IDP tokens for a session.

func (*RedisStorage) DeleteUser added in v0.9.4

func (s *RedisStorage) DeleteUser(ctx context.Context, id string) error

DeleteUser removes a user account and all associated data.

func (*RedisStorage) GetAccessTokenSession added in v0.9.4

func (s *RedisStorage) GetAccessTokenSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetAccessTokenSession retrieves the access token session by its signature.

func (*RedisStorage) GetAuthorizeCodeSession added in v0.9.4

func (s *RedisStorage) GetAuthorizeCodeSession(ctx context.Context, code string, _ fosite.Session) (fosite.Requester, error)

GetAuthorizeCodeSession retrieves the authorization request for a given code. Matches memory.go's pattern: get the auth code data first, then check if invalidated. InvalidateAuthorizeCodeSession extends the auth code TTL to match the invalidation marker, so the data is always available when the marker exists.

func (*RedisStorage) GetClient added in v0.9.4

func (s *RedisStorage) GetClient(ctx context.Context, id string) (fosite.Client, error)

GetClient loads the client by its ID.

func (*RedisStorage) GetPKCERequestSession added in v0.9.4

func (s *RedisStorage) GetPKCERequestSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetPKCERequestSession retrieves the PKCE request session by its signature.

func (*RedisStorage) GetProviderIdentity added in v0.9.4

func (s *RedisStorage) GetProviderIdentity(ctx context.Context, providerID, providerSubject string) (*ProviderIdentity, error)

GetProviderIdentity retrieves a provider identity by provider ID and subject.

func (*RedisStorage) GetRefreshTokenSession added in v0.9.4

func (s *RedisStorage) GetRefreshTokenSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error)

GetRefreshTokenSession retrieves the refresh token session by its signature.

func (*RedisStorage) GetUpstreamTokens added in v0.9.4

func (s *RedisStorage) GetUpstreamTokens(ctx context.Context, sessionID string) (*UpstreamTokens, error)

GetUpstreamTokens retrieves the upstream IDP tokens for a session. Returns a new UpstreamTokens struct deserialized from Redis, which acts as a defensive copy - callers cannot modify the stored data by mutating the return value.

func (*RedisStorage) GetUser added in v0.9.4

func (s *RedisStorage) GetUser(ctx context.Context, id string) (*User, error)

GetUser retrieves a user by their internal ID.

func (*RedisStorage) GetUserProviderIdentities added in v0.9.4

func (s *RedisStorage) GetUserProviderIdentities(ctx context.Context, userID string) ([]*ProviderIdentity, error)

GetUserProviderIdentities returns all provider identities linked to a user.

func (*RedisStorage) Health added in v0.9.4

func (s *RedisStorage) Health(ctx context.Context) error

Health checks Redis connectivity.

func (*RedisStorage) InvalidateAuthorizeCodeSession added in v0.9.4

func (s *RedisStorage) InvalidateAuthorizeCodeSession(ctx context.Context, code string) error

InvalidateAuthorizeCodeSession marks an authorization code as used/invalid. It extends the auth code key's TTL to match the invalidation marker, ensuring GetAuthorizeCodeSession can always return the Requester alongside ErrInvalidatedAuthorizeCode as required by fosite for token revocation.

func (*RedisStorage) LoadPendingAuthorization added in v0.9.4

func (s *RedisStorage) LoadPendingAuthorization(ctx context.Context, state string) (*PendingAuthorization, error)

LoadPendingAuthorization retrieves a pending authorization by internal state.

func (*RedisStorage) RegisterClient added in v0.9.4

func (s *RedisStorage) RegisterClient(ctx context.Context, client fosite.Client) error

RegisterClient adds or updates a client in the storage.

func (*RedisStorage) RevokeAccessToken added in v0.9.4

func (s *RedisStorage) RevokeAccessToken(ctx context.Context, requestID string) error

RevokeAccessToken marks an access token as revoked by request ID.

func (*RedisStorage) RevokeRefreshToken added in v0.9.4

func (s *RedisStorage) RevokeRefreshToken(ctx context.Context, requestID string) error

RevokeRefreshToken marks a refresh token as revoked by request ID.

func (*RedisStorage) RevokeRefreshTokenMaybeGracePeriod added in v0.9.4

func (s *RedisStorage) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, _ string) error

RevokeRefreshTokenMaybeGracePeriod marks a refresh token as revoked, optionally allowing a grace period. For this implementation, we revoke immediately.

func (*RedisStorage) RotateRefreshToken added in v0.9.4

func (s *RedisStorage) RotateRefreshToken(ctx context.Context, requestID string, refreshTokenSignature string) error

RotateRefreshToken invalidates a refresh token and all its related token data. This is a no-op if the token does not exist (returns nil), matching the behavior of the in-memory implementation. All cleanup operations are best-effort (see warnOnCleanupErr); the new refresh token has already been issued by fosite, so partial cleanup is acceptable.

func (*RedisStorage) SetClientAssertionJWT added in v0.9.4

func (s *RedisStorage) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error

SetClientAssertionJWT marks a JTI as known for the given expiry time. If the JWT has already expired (exp is in the past), this is a no-op: there is no need to store it for replay detection since it will be rejected on expiry checks before reaching the JTI lookup.

func (*RedisStorage) StorePendingAuthorization added in v0.9.4

func (s *RedisStorage) StorePendingAuthorization(ctx context.Context, state string, pending *PendingAuthorization) error

StorePendingAuthorization stores a pending authorization request.

func (*RedisStorage) StoreUpstreamTokens added in v0.9.4

func (s *RedisStorage) StoreUpstreamTokens(ctx context.Context, sessionID string, tokens *UpstreamTokens) error

StoreUpstreamTokens stores the upstream IDP tokens for a session. Uses a Lua script to atomically read the old UserID, write new token data, and update user reverse-index sets, preventing race conditions on concurrent writes.

func (*RedisStorage) UpdateProviderIdentityLastUsed added in v0.9.4

func (s *RedisStorage) UpdateProviderIdentityLastUsed(
	ctx context.Context, providerID, providerSubject string, lastUsedAt time.Time,
) error

UpdateProviderIdentityLastUsed updates the LastUsedAt timestamp for a provider identity. Uses a Lua script to ensure atomicity and prevent race conditions.

type RunConfig

type RunConfig struct {
	// Type specifies the storage backend type. Defaults to "memory".
	Type string `json:"type,omitempty" yaml:"type,omitempty"`

	// RedisConfig is the Redis-specific configuration when Type is "redis".
	RedisConfig *RedisRunConfig `json:"redisConfig,omitempty" yaml:"redisConfig,omitempty"`
}

RunConfig is the serializable storage configuration for RunConfig. This is used when the config needs to be passed across process boundaries (e.g., in Kubernetes operator).

type SentinelConfig added in v0.9.4

type SentinelConfig struct {
	MasterName    string
	SentinelAddrs []string
	DB            int
}

SentinelConfig contains Redis Sentinel configuration.

type SentinelRunConfig added in v0.9.4

type SentinelRunConfig struct {
	// MasterName is the name of the Redis Sentinel master.
	MasterName string `json:"masterName" yaml:"masterName"`

	// SentinelAddrs is the list of Sentinel addresses (host:port).
	SentinelAddrs []string `json:"sentinelAddrs" yaml:"sentinelAddrs"`

	// DB is the Redis database number (default: 0).
	DB int `json:"db,omitempty" yaml:"db,omitempty"`
}

SentinelRunConfig contains Redis Sentinel configuration.

type Stats added in v0.7.2

type Stats struct {
	Clients               int
	AuthCodes             int
	AccessTokens          int
	RefreshTokens         int
	PKCERequests          int
	UpstreamTokens        int
	PendingAuthorizations int
	InvalidatedCodes      int
	ClientAssertionJWTs   int
	Users                 int
	ProviderIdentities    int
}

Stats contains statistics about the storage contents.

type Storage

type Storage interface {
	// Embed segregated interfaces for IDP tokens, pending authorizations, client registry,
	// and user management for multi-IDP support.
	UpstreamTokenStorage
	PendingAuthorizationStorage
	ClientRegistry
	UserStorage

	// AuthorizeCodeStorage provides authorization code storage for the Authorization Code
	// Grant (RFC 6749 Section 4.1). Authorization codes are one-time-use and short-lived.
	// CreateAuthorizeCodeSession stores by code, GetAuthorizeCodeSession retrieves by code,
	// InvalidateAuthorizeCodeSession marks as used (subsequent Gets return ErrInvalidatedAuthorizeCode).
	oauth2.AuthorizeCodeStorage

	// AccessTokenStorage provides access token session storage. Methods use "signature"
	// (derived from token value) as the key for O(1) lookup when validating tokens.
	// The stored fosite.Requester contains the full authorization context including
	// the Session with expiration times via session.GetExpiresAt(fosite.AccessToken).
	oauth2.AccessTokenStorage

	// RefreshTokenStorage provides refresh token session storage. CreateRefreshTokenSession
	// accepts an accessSignature to link refresh tokens to their access tokens for rotation.
	// RotateRefreshToken uses requestID to invalidate both the refresh token and all
	// related access tokens from the same authorization grant.
	oauth2.RefreshTokenStorage

	// TokenRevocationStorage provides token revocation per RFC 7009. RevokeAccessToken
	// and RevokeRefreshToken take requestID (not signature) because RFC 7009 requires
	// revoking a refresh token SHOULD also invalidate associated access tokens, which
	// requires finding all tokens by their common grant identifier.
	oauth2.TokenRevocationStorage

	// PKCERequestStorage provides PKCE challenge/verifier storage (RFC 7636).
	// Stores the code_challenge during authorization, validates code_verifier during
	// token exchange. Keyed by the same code/signature as the authorization code.
	pkce.PKCERequestStorage

	// Health checks connectivity to the storage backend.
	// Returns nil if the storage is healthy and reachable.
	Health(ctx context.Context) error

	// Close releases any resources held by the storage implementation.
	// This should be called when the storage is no longer needed.
	Close() error
}

Storage combines fosite storage interfaces with ToolHive-specific storage for upstream IDP tokens, pending authorization requests, and client registration. The auth server requires a Storage implementation; use NewMemoryStorage() for single-instance deployments or NewRedisStorage() for distributed deployments.

Fosite Interface Segregation

Fosite splits storage into separate interfaces (AuthorizeCodeStorage, AccessTokenStorage, RefreshTokenStorage, PKCERequestStorage) following the Interface Segregation Principle. This enables:

  • Feature composition: Only enable OAuth features you need
  • Testing isolation: Mock specific interfaces for focused tests
  • Clear contracts: Each interface documents its requirements

Key Design Patterns

All token storage methods store fosite.Requester (not just token values) because token validation requires the full authorization context (client, scopes, session).

Methods use two key types:

  • Signature: Cryptographic token identifier for token-specific operations
  • Request ID: Grant identifier for finding all tokens from one authorization

See doc.go for comprehensive documentation of fosite's storage design.

type Type

type Type string

Type defines the type of storage backend.

type UpstreamTokenStorage

type UpstreamTokenStorage interface {
	// StoreUpstreamTokens stores the upstream IDP tokens for a session.
	StoreUpstreamTokens(ctx context.Context, sessionID string, tokens *UpstreamTokens) error

	// GetUpstreamTokens retrieves the upstream IDP tokens for a session.
	// Returns ErrNotFound if the session does not exist.
	// Returns ErrExpired if the tokens have expired.
	// Returns ErrInvalidBinding if binding validation fails.
	GetUpstreamTokens(ctx context.Context, sessionID string) (*UpstreamTokens, error)

	// DeleteUpstreamTokens removes the upstream IDP tokens for a session.
	// Returns ErrNotFound if the session does not exist.
	DeleteUpstreamTokens(ctx context.Context, sessionID string) error
}

UpstreamTokenStorage provides storage for tokens obtained from upstream identity providers. The auth server exposes this interface via Server.UpstreamTokenStorage() for use by middleware that needs to retrieve upstream tokens (e.g., token swap middleware that replaces JWT auth with upstream IDP tokens for backend requests).

type UpstreamTokens

type UpstreamTokens struct {
	// ProviderID identifies which upstream provider issued these tokens.
	// Example values: "google", "github", "okta"
	ProviderID string

	// Token values from the upstream IDP
	AccessToken  string
	RefreshToken string
	IDToken      string
	ExpiresAt    time.Time

	// UserID is the internal ToolHive user ID (references User.ID).
	// This is NOT the upstream provider's subject - it's our stable internal identifier.
	// In multi-IDP scenarios, the same UserID may have tokens from multiple providers.
	UserID string

	// UpstreamSubject is the "sub" claim from the upstream IDP's ID token.
	// This enables validation that tokens match the expected upstream identity
	// and supports audit logging of which upstream identity was used.
	UpstreamSubject string

	// ClientID is the OAuth client that initiated the authorization.
	// Tokens should only be accessible to the same client that obtained them.
	ClientID string
}

UpstreamTokens represents tokens obtained from an upstream Identity Provider. These tokens are stored with binding fields for security validation and ProviderID for multi-IDP support.

func (*UpstreamTokens) IsExpired

func (t *UpstreamTokens) IsExpired(now time.Time) bool

IsExpired returns true if the access token has expired. The now parameter allows for deterministic testing.

type User added in v0.8.0

type User struct {
	// ID is the internal, stable user identifier (UUID format).
	// This value is used as the "sub" claim in ToolHive-issued JWTs.
	ID string

	// CreatedAt is when the user account was created.
	CreatedAt time.Time

	// UpdatedAt is when the user account was last modified.
	UpdatedAt time.Time
}

User represents a user account in the authorization server. A user can have multiple linked provider identities. The User.ID is used as the "sub" claim in JWTs issued by ToolHive, providing a stable identity across multiple upstream identity providers.

type UserStorage added in v0.8.0

type UserStorage interface {
	// CreateUser creates a new user account.
	// Returns ErrAlreadyExists if a user with the same ID already exists.
	CreateUser(ctx context.Context, user *User) error

	// GetUser retrieves a user by their internal ID.
	// Returns ErrNotFound if the user does not exist.
	GetUser(ctx context.Context, id string) (*User, error)

	// DeleteUser removes a user account.
	// Returns ErrNotFound if the user does not exist.
	DeleteUser(ctx context.Context, id string) error

	// CreateProviderIdentity links a provider identity to a user.
	// For account linking scenarios, caller MUST verify user owns the target User
	// (typically via active authenticated session) before linking a new provider.
	// Returns ErrAlreadyExists if this provider identity is already linked.
	CreateProviderIdentity(ctx context.Context, identity *ProviderIdentity) error

	// GetProviderIdentity retrieves a provider identity by provider ID and subject.
	// This is the primary lookup path during authentication callbacks.
	// Returns ErrNotFound if the identity does not exist.
	GetProviderIdentity(ctx context.Context, providerID, providerSubject string) (*ProviderIdentity, error)

	// UpdateProviderIdentityLastUsed updates the LastUsedAt timestamp for a provider identity.
	// This should be called after each successful authentication via this identity.
	// The timestamp supports OIDC auth_time claim when clients use max_age parameter.
	// Returns ErrNotFound if the identity does not exist.
	UpdateProviderIdentityLastUsed(ctx context.Context, providerID, providerSubject string, lastUsedAt time.Time) error

	// GetUserProviderIdentities returns all provider identities linked to a user.
	// This enables queries like "when did this user last authenticate via any provider"
	// which is needed for OIDC max_age enforcement.
	// Returns an empty slice (not error) if the user exists but has no linked identities.
	// Returns ErrNotFound if the user does not exist.
	GetUserProviderIdentities(ctx context.Context, userID string) ([]*ProviderIdentity, error)
}

UserStorage provides user and provider identity management operations. This interface supports multi-IDP scenarios where a single user can authenticate via multiple upstream identity providers (e.g., Google and GitHub).

The User type represents the internal ToolHive identity. Its ID becomes the "sub" claim in issued JWTs, providing a stable identity across multiple providers.

ProviderIdentity links a user to a specific upstream provider. The (ProviderID, ProviderSubject) pair uniquely identifies an upstream identity.

Account Linking Security

When implementing account linking (one User with multiple ProviderIdentities), callers MUST verify user consent before linking. See OAuth 2.0 Security BCP.

TODO(auth): When implementing double-hop auth (Company IDP -> External IDP), add the following to this interface:

  • DeleteProviderIdentity(providerID, subject) - unlink specific provider
  • Add Primary field to ProviderIdentity to distinguish Company IDP from linked External IDPs
  • Add ConsentRecord tracking for external provider linking

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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