lfxv2

package
v0.7.8 Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package lfxv2 provides client utilities for interacting with LFX v2 APIs, including OAuth2 token exchange.

Package lfxv2 provides client configuration for LFX v2 API services.

Token Exchange

The package supports automatic OAuth2 token exchange (RFC 8693) for converting MCP access tokens into LFX API tokens. Token exchange is performed per-request using tokens stored in the request context.

Usage in MCP Tools

A single *Clients instance should be created once at startup (via NewClients) and shared across all tool invocations. Per-request, call WithMCPToken to attach the caller's MCP bearer token to the context before making LFX API calls:

func handleMyTool(ctx context.Context, req *mcp.CallToolRequest, args MyToolArgs) (*mcp.CallToolResult, any, error) {
    // Extract raw MCP token from request.
    mcpToken, err := lfxv2.ExtractMCPToken(req.Extra.TokenInfo)
    if err != nil {
        return nil, nil, err
    }

    // Attach token to context; the shared clients instance handles exchange.
    ctx = sharedClients.WithMCPToken(ctx, mcpToken)

    // Make API calls - token exchange and caching happen automatically.
    result, err := sharedClients.Project.GetOneProjectBase(ctx, &projectservice.GetOneProjectBasePayload{})
    // ...
}

Token Caching

Exchanged tokens are cached per MCP token inside the long-lived *Clients instance to avoid redundant token-exchange round-trips on every request. The cache is goroutine-safe and automatically expires tokens with a fixed buffer of 5 minutes before their exp claim.

Package lfxv2 provides client utilities for interacting with LFX v2 APIs, including OAuth2 token exchange.

Package lfxv2 provides client utilities for interacting with LFX v2 APIs, including OAuth2 token exchange.

Package lfxv2 provides client utilities for interacting with LFX v2 APIs, including OAuth2 token exchange.

Index

Constants

This section is empty.

Variables

View Source
var ErrProjectNotFound = errors.New("project not found")

ErrProjectNotFound is returned by Resolve when the query service returns no results for the given slug. Callers can use errors.Is to distinguish this "not found" case from transport or other operational errors.

Functions

func ExtractMCPToken

func ExtractMCPToken(tokenInfo *auth.TokenInfo) (string, error)

ExtractMCPToken extracts the raw MCP token from auth.TokenInfo.Extra. This token can be passed to WithMCPToken for use in LFX API calls. Returns an error if the token cannot be extracted.

For requests authenticated via a static API key (Extra["api_key_auth"]==true), the apiKeyMCPToken sentinel is returned so that the token-exchange layer uses the client_credentials grant rather than attempting to exchange a bearer token.

func WithMCPToken

func WithMCPToken(ctx context.Context, token string) context.Context

WithMCPToken returns a context with the MCP bearer token attached. This token will be used for OAuth2 token exchange when making LFX API calls.

Types

type AccessCheckClient added in v0.6.0

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

AccessCheckClient calls the V2 access-check endpoint to verify that the authenticated user has a particular relationship to a resource.

The access-check endpoint is backed by OpenFGA and evaluates relationships transitively. For example, an owner of a parent project is implicitly a writer on child projects.

func NewAccessCheckClient added in v0.6.0

func NewAccessCheckClient(apiURL string, httpClient *http.Client) *AccessCheckClient

NewAccessCheckClient creates a client for V2 access-check calls.

apiURL is the base URL of the V2 API (e.g., "https://lfx-api.v2.cluster.lfx.dev"). httpClient should be a plain HTTP client — the user's exchanged V2 token is passed explicitly per-request, not via an auth interceptor.

func (*AccessCheckClient) CheckAccess added in v0.6.0

func (c *AccessCheckClient) CheckAccess(ctx context.Context, token string, requests []string) (map[string]bool, error)

CheckAccess sends a batch of access-check requests and returns the results as a map from request string to granted status.

token is the user's exchanged V2 bearer token (not the MCP token or API key). requests are formatted as "object:id#relation" (e.g., "project:uuid#writer").

The V2 access-check endpoint returns results in an unordered list. Each result is a tab-delimited string in the format:

<request>@<user>\ttrue|false

This method parses those results and matches them back to the original requests. The returned map is keyed by the original request string.

func (*AccessCheckClient) CheckProjectAccess added in v0.6.0

func (c *AccessCheckClient) CheckProjectAccess(ctx context.Context, token string, projectUUID, relation string) error

CheckProjectAccess verifies the user has the specified relation to a project.

Returns nil if access is allowed. Returns an error describing the denial if access is denied or if the check fails.

type ClientConfig

type ClientConfig struct {
	// APIDomain is the LFX API base domain.
	APIDomain string

	// HTTPClient is the HTTP client to use for API calls.
	// If nil, a default client with 30s timeout will be created.
	HTTPClient *http.Client

	// TokenExchangeClient is the RFC 8693 OAuth2 token exchange client.
	// If provided, the client will automatically exchange MCP tokens (from request context)
	// for target API tokens.
	TokenExchangeClient *TokenExchangeClient

	// DebugLogger is used for debug-level HTTP request/response logging.
	// If nil, debug logging is disabled.
	DebugLogger *slog.Logger
}

ClientConfig holds configuration for LFX v2 API clients.

type ClientCredentialsClient added in v0.6.0

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

ClientCredentialsClient handles OAuth2 client credentials grants for M2M authentication. It caches tokens and refreshes them automatically when they expire.

func NewClientCredentialsClient added in v0.6.0

func NewClientCredentialsClient(cfg ClientCredentialsConfig) (*ClientCredentialsClient, error)

NewClientCredentialsClient creates a new OAuth2 client credentials client.

func (*ClientCredentialsClient) GetToken added in v0.6.0

func (c *ClientCredentialsClient) GetToken(ctx context.Context) (string, error)

GetToken returns a valid bearer token, fetching a new one if the cached token has expired.

type ClientCredentialsConfig added in v0.6.0

type ClientCredentialsConfig struct {
	// TokenEndpoint is the OAuth2 token endpoint URL (e.g., "https://example.auth0.com/oauth/token").
	TokenEndpoint string

	// ClientID is the M2M client ID.
	ClientID string

	// ClientSecret is the M2M client secret.
	// Ignored if ClientAssertionSigningKey is provided.
	ClientSecret string

	// ClientAssertionSigningKey is the PEM-encoded RSA private key for generating client assertions.
	// If provided, this takes precedence over ClientSecret for client authentication.
	ClientAssertionSigningKey string

	// Audience is the resource server identifier (the "aud" claim in the issued token).
	Audience string

	// HTTPClient is the HTTP client to use. If nil, a default client with 30s timeout is created.
	HTTPClient *http.Client
}

ClientCredentialsConfig holds configuration for OAuth2 client credentials grants.

type Clients

type Clients struct {
	Committee   *committeeservice.Client
	MailingList *mailinglist.Client
	Member      *memberservice.Client
	Project     *projectservice.Client
	QuerySvc    *querysvc.Client
	// contains filtered or unexported fields
}

Clients holds initialized LFX v2 API service clients.

func NewClients

func NewClients(_ context.Context, cfg ClientConfig) (*Clients, error)

NewClients initializes and returns LFX v2 API service clients.

func (*Clients) GetExchangedToken added in v0.6.0

func (c *Clients) GetExchangedToken(ctx context.Context) (string, error)

GetExchangedToken returns the exchanged V2 API token for the MCP token stored in the context. It reuses the token cache, so repeated calls within the same request are cheap. This is used by the access-check client which needs the V2 token as an explicit string rather than via the auth interceptor.

func (*Clients) WithMCPToken added in v0.4.6

func (c *Clients) WithMCPToken(ctx context.Context, mcpToken string) context.Context

WithMCPToken attaches mcpToken to ctx so the auth interceptor can retrieve it when forwarding requests to LFX APIs. Call this once per inbound request before invoking any LFX API method on the shared *Clients instance.

type SlugResolver added in v0.6.0

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

SlugResolver resolves project slugs to V2 UUIDs with in-memory caching.

Slug-to-UUID mappings are stable, so the cache has no TTL. The resolver accepts a Clients instance per call because each call may use a different user's auth context for the V2 query service, but the cached mappings are user-independent.

func NewSlugResolver added in v0.6.0

func NewSlugResolver() *SlugResolver

NewSlugResolver creates a new resolver with an empty cache.

func (*SlugResolver) Resolve added in v0.6.0

func (r *SlugResolver) Resolve(ctx context.Context, clients *Clients, slug string) (string, error)

Resolve looks up the V2 UUID for a project slug. It checks the in-memory cache first and falls back to the V2 query service on a cache miss.

type TokenExchangeClient

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

TokenExchangeClient handles OAuth2 token exchange per RFC 8693.

func NewTokenExchangeClient

func NewTokenExchangeClient(cfg TokenExchangeConfig) (*TokenExchangeClient, error)

NewTokenExchangeClient creates a new RFC 8693 token exchange client.

func (*TokenExchangeClient) ClientCredentials added in v0.4.1

func (c *TokenExchangeClient) ClientCredentials(ctx context.Context) (*TokenExchangeResponse, error)

ClientCredentials obtains an LFX API token using the client_credentials grant. This is used when the caller presents an M2M token, which Auth0 cannot exchange via RFC 8693 token exchange (it requires a user subject). Instead, the MCP server mints a fresh LFX token using its own client identity.

func (*TokenExchangeClient) ExchangeToken

func (c *TokenExchangeClient) ExchangeToken(ctx context.Context, subjectToken string) (*TokenExchangeResponse, error)

ExchangeToken exchanges a subject token for a new access token per RFC 8693.

type TokenExchangeConfig

type TokenExchangeConfig struct {
	// TokenEndpoint is the OAuth2 token endpoint URL (e.g., "https://example.auth0.com/oauth/token").
	TokenEndpoint string

	// ClientID is the M2M client ID for token exchange.
	ClientID string

	// ClientSecret is the M2M client secret for token exchange.
	// Ignored if ClientAssertionSigningKey is provided.
	ClientSecret string

	// ClientAssertionSigningKey is the PEM-encoded RSA private key for generating client assertions.
	// If provided, this takes precedence over ClientSecret for client authentication.
	// The key is used to sign a JWT assertion per RFC 7523.
	ClientAssertionSigningKey string

	// SubjectTokenType is the token type of the incoming subject token (e.g., LFX MCP API identifier).
	SubjectTokenType string

	// Audience is the target audience for the exchanged token (e.g., LFX V2 API identifier).
	Audience string

	// HTTPClient is the HTTP client to use for token exchange.
	// If nil, a default client with 30s timeout will be created.
	HTTPClient *http.Client
}

TokenExchangeConfig holds configuration for OAuth2 token exchange (RFC 8693).

type TokenExchangeResponse

type TokenExchangeResponse struct {
	AccessToken     string `json:"access_token"`
	IssuedTokenType string `json:"issued_token_type,omitempty"`
	TokenType       string `json:"token_type"`
	ExpiresIn       int    `json:"expires_in"`
	Scope           string `json:"scope,omitempty"`
	RefreshToken    string `json:"refresh_token,omitempty"`
}

TokenExchangeResponse represents the response from OAuth2 token exchange per RFC 8693.

Jump to

Keyboard shortcuts

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