core

package
v1.99.2 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	AuthorizeCodePrefix = "gliamac-"
	AccessTokenPrefix   = "gliamat-"
	RefreshTokenPrefix  = "gliamrt-"
)

Prefixes identify IAM-issued tokens by inspection, without decoding them. See docs/routable-tokens.md for the byte layout and why Workhorse and the Cells HTTP Router each care.

View Source
const RefreshTokenNeverExpires time.Duration = 0

RefreshTokenNeverExpires is the RefreshTokenLifespan of a refresh token that never expires, as doorkeeper's do.

Variables

View Source
var (
	ErrTokenNotOwned = errors.New("token was not issued to the calling client")
	// A token minted for an exchange that failed after the claims fetch is
	// denylisted but keeps its row, so the lookup alone reports it active.
	ErrTokenRevoked = errors.New("access token is on the revocation denylist")
)

Inactive reasons are for the operator log only: RFC 7662 §2.2 forbids telling the client why a token is inactive.

View Source
var ErrInvalidBearerToken = errors.New("bearer token did not resolve")

ErrInvalidBearerToken marks a bearer caller whose token did not resolve. fosite compares RFC errors by code and status, which ErrTokenExpired shares, so a plain sentinel is needed to tell this one apart.

View Source
var ErrLookupFailed = errors.New("token lookup failed")

ErrLookupFailed is a server error, never an inactive answer: active=false would make the resource server deny a token that may be fine, and it may cache that answer.

View Source
var ErrPKCERequiredForDynamicClients = fosite.ErrInvalidRequest.
	WithHint("This client must use PKCE with code_challenge_method=S256 when performing the authorize code flow.").
	WithDebug("PKCE with S256 is required for dynamically registered clients.")

Functions

func ForcedScope added in v1.55.3

func ForcedScope(client fosite.Client) (string, bool)

func InvalidBearerTokenError added in v1.93.0

func InvalidBearerTokenError(cause error) error

InvalidBearerTokenError is the 401 for a bearer that did not resolve. The client gets one fixed description; the reason goes into the wrapped cause for the log.

func IsDynamicClient added in v1.55.3

func IsDynamicClient(c fosite.Client) bool

func LegacyPKCEFactory

func LegacyPKCEFactory(config fosite.Configurator, storage any, strategy any) any

LegacyPKCEFactory satisfies the fosite compose.Factory signature. The storage and strategy parameters are interface{} because fosite's compose pipeline passes them untyped. The Factory signature does not allow returning an error, so incorrect wiring panics at startup with a clear message rather than silently producing a broken handler.

func RefreshTokenGrantFactory added in v1.82.0

func RefreshTokenGrantFactory(config fosite.Configurator, store any, strategy any) any

RefreshTokenGrantFactory builds fosite's refresh handler over a storage that hides inactive tokens from its reuse detection.

func TokenGrantTypes added in v1.79.0

func TokenGrantTypes() []string

TokenGrantTypes returns the grant_type values the token endpoint handlers composed in NewOAuthProviderWithConfig accept. A test fails if the two drift.

func ValidateDynamicClientPKCE added in v1.55.3

func ValidateDynamicClientPKCE(client fosite.Client, challenge, method string) error

Types

type AccessTokenDenylister added in v1.77.1

type AccessTokenDenylister interface {
	DenylistAccessToken(ctx context.Context, jti uuid.UUID, expiresAt time.Time) error
	IsAccessTokenRevoked(ctx context.Context, jti uuid.UUID) (bool, error)
}

AccessTokenDenylister records a minted access token as revoked and reports whether one is. Storage implements it over the revoked_tokens table.

type Config

type Config struct {
	AccessTokenLifespan time.Duration

	// <= 0 never expires, stored as a NULL expires_at. Doorkeeper parity.
	RefreshTokenLifespan time.Duration

	AuthorizeCodeLifespan time.Duration
	IDTokenLifespan       time.Duration

	HashCost            int
	HMACSecret          []byte
	AllowInsecureHTTP   bool
	EnforcePKCE         bool
	EnableOpenIDConnect bool

	IssuerURL  string
	LoginURL   string
	ConsentURL string

	// AccessTokenAudience identifies the resource server that consumes IAM-issued
	// JWT access tokens. Set as the `aud` claim per RFC 9068 §2.2.
	AccessTokenAudience string

	ClientSecretsHasher fosite.Hasher
	JwkProvider         jwk.Provider

	IDTokenClaimsFetcher UserInfoFetcher
}

func NewOAuthConfigFromConfig

func NewOAuthConfigFromConfig(cfg authconfig.OAuthServerConfig) (*Config, error)

type FositeStorage

FositeStorage is the union of fosite storage interfaces that the OAuth provider needs to drive the authorize and token endpoints. It lives here (rather than in the concrete storage package) so the provider depends on the contract, not on a specific storage implementation.

type IntrospectionResponse added in v1.81.0

type IntrospectionResponse struct {
	Active         bool
	CallerClientID string
	TokenUse       fosite.TokenUse
	Requester      fosite.AccessRequester
	InactiveReason error
}

CallerClientID is the client that authenticated, which with Basic credentials is not the form's client_id. InactiveReason is the token's validation error, ErrTokenRevoked, or ErrTokenNotOwned.

type Issuer

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

Issuer turns a resolved user identity into an OAuth authorization-code redirect. It is the small slice of the authorize flow that runs after the user has been authenticated — granting requested scopes, attaching a session to the request, and writing the response that carries the code back to the client.

Flows that already have a user identity in hand — notably the federated login callback — use the Issuer to emit a code directly, without round-tripping the user agent through /oauth/authorize.

func (*Issuer) IssueAuthorizationCode

func (i *Issuer) IssueAuthorizationCode(ctx context.Context, w http.ResponseWriter, ar fosite.AuthorizeRequester, userInfo identity.UserInfo) error

func (*Issuer) IssueFromFederationSession

func (i *Issuer) IssueFromFederationSession(ctx context.Context, w http.ResponseWriter, session *federation.Session, userInfo identity.UserInfo, additionalParams map[string]string) error

IssueFromFederationSession synthesizes an authorize request from the stored federation session and issues an authorization code for userInfo. Callers use this to skip the extra /oauth/authorize round trip that would otherwise be needed after a federated login completes.

func (*Issuer) WriteErrorFromFederationSession added in v1.10.0

func (i *Issuer) WriteErrorFromFederationSession(ctx context.Context, w http.ResponseWriter, session *federation.Session, err error) error

WriteErrorFromFederationSession synthesizes an authorize request from the stored federation session and asks fosite to write an RFC 6749 error response to w. In the normal case fosite emits a 303 redirect back to the session's redirect URI; if it cannot reconstruct a valid client and redirect URI on the AuthorizeRequester (e.g. the referenced client has since been removed), it degrades to a JSON body instead. That degradation is intentional — we accept it rather than re-implementing a parallel redirect path. The returned error, when non-nil, describes a failure encountered while preparing the response and is intended for the caller to log; the response itself is best-effort.

type LegacyPKCEHandler

type LegacyPKCEHandler struct {
	*pkce.Handler
}

LegacyPKCEHandler replaces fosite's stock PKCE handler so the authorization code flow matches the doorkeeper provider IAM is replacing: doorkeeper is laxer than fosite's RFC 7636-strict checks and clients rely on that. The specific deviations are documented on the forked methods below.

It does not enforce PKCE; that belongs at the authorize endpoint via fosite's EnforcePKCE, which rejects a no-challenge request before a code is minted. The exception is dynamically registered clients, which must always use S256.

func (*LegacyPKCEHandler) HandleAuthorizeEndpointRequest

func (c *LegacyPKCEHandler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error

HandleAuthorizeEndpointRequest rejects a challenge that omits code_challenge_method before delegating to fosite: doorkeeper requires an explicit method (it does not default to "plain"), whereas fosite with plain enabled would treat an empty method as plain.

func (*LegacyPKCEHandler) HandleTokenEndpointRequest

func (c *LegacyPKCEHandler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error

type Provider

type Provider struct {
	fosite.OAuth2Provider
	// contains filtered or unexported fields
}

func NewOAuthProviderWithConfig

func NewOAuthProviderWithConfig(store FositeStorage, oauthConfig *Config) (*Provider, error)

func (*Provider) AccessTokenAudience added in v1.10.0

func (p *Provider) AccessTokenAudience() string

AccessTokenAudience returns the resource-server identifier set as the `aud` claim on issued JWT access tokens (RFC 9068 §2.2).

func (*Provider) ClientByID added in v1.55.3

func (p *Provider) ClientByID(ctx context.Context, id string) (fosite.Client, error)

func (*Provider) CreateSession

func (p *Provider) CreateSession(userInfo identity.UserInfo, scopes []string) *Session

func (*Provider) GetConfig

func (p *Provider) GetConfig() *fosite.Config

func (*Provider) Introspect added in v1.81.0

func (p *Provider) Introspect(ctx context.Context, r *http.Request) (*IntrospectionResponse, error)

Introspect answers an RFC 7662 request. fosite's NewIntrospectionRequest admits confidential clients only and never says which client authenticated, so Doorkeeper's rules cannot be applied through it: public clients introspect on client_id alone, and a token the caller does not own is inactive.

func (*Provider) NewAccessToken added in v1.52.0

NewAccessToken runs a token endpoint request. Every outbound call the exchange needs happens before fosite writes (see claimsEnrichingStrategy), so a failure leaves nothing to compensate for except the access token the claims fetch sent to Rails, which is denylisted.

func (*Provider) NewAuthorizeRequest added in v1.68.1

func (p *Provider) NewAuthorizeRequest(ctx context.Context, r *http.Request) (fosite.AuthorizeRequester, error)

doorkeeper treats state as optional and leaves a blank one out of the redirect. fosite requires state, but checks it last in NewAuthorizeRequest, so ignoring that one error keeps every other check.

func (*Provider) NewAuthorizeResponse added in v1.68.1

func (p *Provider) NewAuthorizeResponse(ctx context.Context, ar fosite.AuthorizeRequester, session fosite.Session) (fosite.AuthorizeResponder, error)

Every authorize path issues its code here, so this is where a code without a subject is refused. fosite checks the subject only for ID tokens.

func (*Provider) NewIssuer

func (p *Provider) NewIssuer() *Issuer

NewIssuer returns an authorization-code issuer wired up against this provider. The handler packages call this so they don't need to construct the issuer (or know how this provider builds sessions) themselves.

func (*Provider) WriteAuthorizeError added in v1.68.1

func (p *Provider) WriteAuthorizeError(ctx context.Context, rw http.ResponseWriter, ar fosite.AuthorizeRequester, err error)

fosite writes the error redirect itself, so a blank state is removed from its Location header.

type RefreshTokenGrantHandler added in v1.82.0

type RefreshTokenGrantHandler struct {
	*oauth2.RefreshTokenGrantHandler
}

RefreshTokenGrantHandler rejects a reused or revoked refresh token on its own, as GitLab's Doorkeeper does (gitlab-org/gitlab#604892). fosite treats it as theft per RFC 6819 §5.2.2.3 and revokes the grant's whole token family. With no revocation, the log line is the only trace of a stolen token.

func (*RefreshTokenGrantHandler) HandleTokenEndpointRequest added in v1.82.0

func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error

HandleTokenEndpointRequest restores the hint for an inactive token, which fosite answered as not found, and logs it.

func (*RefreshTokenGrantHandler) PopulateTokenEndpointResponse added in v1.82.0

func (c *RefreshTokenGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, request fosite.AccessRequester, responder fosite.AccessResponder) error

PopulateTokenEndpointResponse maps ErrInactiveToken from the rotation write, which is how a de-authorization during the exchange surfaces, to invalid_grant as Doorkeeper does. fosite answers invalid_request with a retry hint.

type Session

type Session struct {
	*openid.DefaultSession

	JWTClaims *jwt.JWTClaims
	JWTHeader *jwt.Headers
}

func NewEmptySession

func NewEmptySession() *Session

NewEmptySession returns a zero-valued Session with nested pointer fields pre-allocated. It is used as the unmarshal target when restoring a stored session from the authorization-code store — the session type must implement JWTSessionContainer so fosite's JWT strategy can mint access tokens.

func NewSessionWithClaims

func NewSessionWithClaims(userInfo identity.UserInfo, scopes []string, kid string) *Session

func (*Session) Clone added in v1.39.0

func (s *Session) Clone() fosite.Session

Clone deep-copies the session while preserving the concrete *Session type, so the JWT access-token strategy still sees a JWTSessionContainer. Without it the embedded *openid.DefaultSession.Clone() is promoted and returns a bare *openid.DefaultSession, which breaks the refresh-token grant.

func (*Session) GetJWTClaims

func (s *Session) GetJWTClaims() jwt.JWTClaimsContainer

func (*Session) GetJWTHeader

func (s *Session) GetJWTHeader() *jwt.Headers

func (*Session) SetExtraClaim

func (s *Session) SetExtraClaim(key string, value any)

type UserInfoFetcher added in v1.45.2

type UserInfoFetcher interface {
	FetchUserInfo(ctx context.Context, accessToken string) (map[string]any, error)
}

UserInfoFetcher fetches user info from an external source, authenticated with the access token minted for the same exchange.

Jump to

Keyboard shortcuts

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