usersessions

package
v0.0.0-...-9aa2fed Latest Latest
Warning

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

Go to latest
Published: May 20, 2026 License: AGPL-3.0 Imports: 41 Imported by: 0

Documentation

Overview

Package usersessions implements the management API services that surface user_session_issuer / user_session_client / user_session_consent / user_session resources. The four Goa services are authored under server/design/usersession{issuers,clients,consents}/ and server/design/usersessions/; a single Go package owns their shared implementation, dependencies, and lifecycle.

Index

Constants

View Source
const JWTSigningKeyFlag = "jwt-signing-key"

JWTSigningKeyFlag is the CLI flag (and env var via GRAM_JWT_SIGNING_KEY) that supplies the HMAC secret to NewSigner at server boot. Defining the flag name here keeps the start/worker command wiring and the signer's expectations in sync.

Variables

View Source
var (
	SupportedGrantTypes    = []string{"authorization_code", "refresh_token"}
	SupportedResponseTypes = []string{"code"}
	// `none` covers public PKCE-only clients (mobile, CLI, MCP SDK). Real
	// MCP clients in the wild use it. PKCE provides per-flow integrity; the
	// only guard against cross-flow client-id confusion is the consent
	// prompt itself, which we always render (HandleConsent never skips).
	SupportedAuthMethods          = []string{"client_secret_basic", "client_secret_post", "none"}
	SupportedCodeChallengeMethods = []string{"S256"}
)

SupportedGrantTypes / SupportedResponseTypes / SupportedAuthMethods / SupportedCodeChallengeMethods are the OAuth values the user-session AS supports. Mirrored into the RFC 8414 metadata document by mcp.HandleGetAuthorizationServer; enforced on the /register and /authorize handlers by the typed request Validate methods.

Functions

func Attach

func Attach(mux goahttp.Muxer, service *Service)

Attach wires every Goa service this package backs onto the shared mux: userSessionIssuers, userSessionClients, userSessionConsents, userSessions.

Types

type AuthCodeTokenRequest

type AuthCodeTokenRequest struct {
	Code         string
	RedirectURI  string
	CodeVerifier string
}

AuthCodeTokenRequest is the RFC 6749 §4.1.3 token request issued by a client exchanging an authorization code for a token pair. PKCE is mandatory on this surface — the code_verifier is required, and the /token handler matches it against the code_challenge stored on the authorization grant.

func AuthCodeTokenRequestFromForm

func AuthCodeTokenRequestFromForm(form url.Values) *AuthCodeTokenRequest

AuthCodeTokenRequestFromForm decodes from url.Values (typically r.PostForm).

func (*AuthCodeTokenRequest) SetDefaults

func (r *AuthCodeTokenRequest) SetDefaults()

SetDefaults is a no-op — all fields are required on this surface. Kept for symmetry with the other request types.

func (*AuthCodeTokenRequest) Validate

func (r *AuthCodeTokenRequest) Validate() error

Validate checks the presence of each required field. Returns an *OAuthError on rejection. The redirect_uri match against the authorization grant and the PKCE verifier match against the stored code_challenge live in the handler (they require grant-side state).

type AuthorizationRequest

type AuthorizationRequest struct {
	ClientID            string
	RedirectURI         string
	ResponseType        string
	State               string
	CodeChallenge       string
	CodeChallengeMethod string
}

AuthorizationRequest is the RFC 6749 §4.1.1 authorization request, parsed from the /authorize query string. PKCE is mandatory on this surface: code_challenge + code_challenge_method MUST be supplied.

func AuthorizationRequestFromQuery

func AuthorizationRequestFromQuery(q url.Values) *AuthorizationRequest

AuthorizationRequestFromQuery decodes an AuthorizationRequest from url.Values (typically r.URL.Query()).

func (*AuthorizationRequest) SetDefaults

func (r *AuthorizationRequest) SetDefaults()

SetDefaults applies RFC 6749 §4.1.1 defaults. response_type and code_challenge_method are both REQUIRED on this surface (no spec-defined default), so this is presently a no-op — kept for symmetry with the other request types and to give future spec additions a place to land.

func (*AuthorizationRequest) ValidatePostRedirect

func (r *AuthorizationRequest) ValidatePostRedirect() error

ValidatePostRedirect checks the remaining fields, assumed to run AFTER the redirect_uri has been validated against the registered client. Errors here MAY be redirected back to the client per RFC 6749 §4.1.2.1 (the current handler still surfaces them inline; that's a forward- compatible choice).

func (*AuthorizationRequest) ValidateRedirectableFields

func (r *AuthorizationRequest) ValidateRedirectableFields() error

ValidateRedirectableFields checks the fields the AS MUST validate BEFORE it can safely redirect any error back to the caller. Per RFC 6749 §4.1.2.1, an unknown client_id or invalid redirect_uri means we can't trust the URI we'd redirect to — these errors MUST be surfaced inline. Callers should run this first; if it returns a *OAuthError, write it as an HTTP-level response and stop.

type OAuthError

type OAuthError struct {
	Code        string
	Description string
}

OAuthError carries an OAuth wire error.

func (*OAuthError) Error

func (e *OAuthError) Error() string

type RefreshTokenRequest

type RefreshTokenRequest struct {
	RefreshToken string
}

RefreshTokenRequest is the RFC 6749 §6 token request issued by a client rotating its refresh token. The scope parameter is intentionally absent — see usersessions.RegistrationRequest's comment on un-persisted scope state; the /token response likewise doesn't echo scope.

func RefreshTokenRequestFromForm

func RefreshTokenRequestFromForm(form url.Values) *RefreshTokenRequest

RefreshTokenRequestFromForm decodes from url.Values (typically r.PostForm).

func (*RefreshTokenRequest) SetDefaults

func (r *RefreshTokenRequest) SetDefaults()

SetDefaults is a no-op — refresh_token is required. Kept for symmetry with the other request types.

func (*RefreshTokenRequest) Validate

func (r *RefreshTokenRequest) Validate() error

Validate checks the presence of refresh_token. Returns an *OAuthError on rejection. Hash lookup + client-binding verification + expiry check live in the handler since they require database state.

type RegistrationRequest

type RegistrationRequest struct {
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	ResponseTypes           []string `json:"response_types,omitempty"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method,omitempty"`
}

RegistrationRequest is the RFC 7591 §3.1 client metadata document. Only the fields we honour are listed; unknown fields are ignored.

`scope` is intentionally absent: RFC 7591 §3.2.1 requires the registration response to reflect actually-registered metadata, and we have no scope enforcement to back it up — echoing a `scope` field would assert server state we don't hold. Add it back when we ship a scope-aware /token.

func (*RegistrationRequest) SetDefaults

func (r *RegistrationRequest) SetDefaults()

SetDefaults populates the RFC 7591 §2 defaults for fields the client didn't supply. Must be called before Validate so the §2.1 grant/response correlation check sees materialized values.

func (*RegistrationRequest) Validate

func (r *RegistrationRequest) Validate() error

Validate checks the (defaulted) fields of an RFC 7591 §3.1 client metadata document. Returns an *OAuthError on a spec-defined rejection. Callers must invoke SetDefaults first so grant_types / response_types / auth method are populated.

type RevocationChecker

type RevocationChecker interface {
	IsTokenRevoked(ctx context.Context, jti string) (bool, error)
}

RevocationChecker reports whether a JTI has been revoked. The user-session flow and the chat-session flow share a revocation keyspace today (`chat_session_revoked:{jti}`); this interface is the seam so the signer itself doesn't need to know about Redis or the chat-session manager.

type Service

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

Service implements all four Goa services. The split into four design packages keeps the management-API surface logically grouped while a single Service struct lets handlers share dependencies.

func NewService

func NewService(logger *slog.Logger, tracerProvider trace.TracerProvider, db *pgxpool.Pool, sessionManager *sessions.Manager, chatSessionsManager *chatsessions.Manager, authzEngine *authz.Engine, auditLogger *audit.Logger) *Service

NewService constructs a Service ready to be Attached against each of the four user_session* Goa services. chatSessionsManager is used by the userSessions revoke handler to push revoked jtis into the revocation cache.

func (*Service) APIKeyAuth

func (s *Service) APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error)

APIKeyAuth implements goa Auther for every Goa service this package backs; each generated package treats it as the same method.

func (*Service) CreateUserSessionIssuer

func (s *Service) CreateUserSessionIssuer(ctx context.Context, payload *gen.CreateUserSessionIssuerPayload) (*types.UserSessionIssuer, error)

Creates an issuer. authn_challenge_mode is "chain" (the issuer re-uses an upstream IdP without prompting) or "interactive" (the issuer collects user consent before issuing a session).

func (*Service) DeleteUserSessionIssuer

func (s *Service) DeleteUserSessionIssuer(ctx context.Context, payload *gen.DeleteUserSessionIssuerPayload) error

Soft-deletes an issuer and cascades to its user_sessions and user_session_consents.

func (*Service) GetUserSessionClient

func (s *Service) GetUserSessionClient(ctx context.Context, payload *gen.GetUserSessionClientPayload) (*types.UserSessionClient, error)

Fetches a client by id. client_secret_hash is stripped from the view.

func (*Service) GetUserSessionIssuer

func (s *Service) GetUserSessionIssuer(ctx context.Context, payload *gen.GetUserSessionIssuerPayload) (*types.UserSessionIssuer, error)

Fetches an issuer by id or slug. Exactly one must be supplied.

func (*Service) ListUserSessionClients

func (s *Service) ListUserSessionClients(ctx context.Context, payload *gen.ListUserSessionClientsPayload) (*gen.ListUserSessionClientsResult, error)

Lists DCR-registered clients; keyset paginated by id (descending). client_secret_hash is stripped from the view.

func (*Service) ListUserSessionConsents

func (s *Service) ListUserSessionConsents(ctx context.Context, payload *gen.ListUserSessionConsentsPayload) (*gen.ListUserSessionConsentsResult, error)

Lists consent records; keyset paginated by id (descending).

func (*Service) ListUserSessionIssuers

func (s *Service) ListUserSessionIssuers(ctx context.Context, payload *gen.ListUserSessionIssuersPayload) (*gen.ListUserSessionIssuersResult, error)

Lists issuers; keyset paginated by id (descending).

func (*Service) ListUserSessions

func (s *Service) ListUserSessions(ctx context.Context, payload *gen.ListUserSessionsPayload) (*gen.ListUserSessionsResult, error)

Lists issued sessions; keyset paginated by id (descending). refresh_token_hash is excluded from the projection.

func (*Service) RevokeUserSession

func (s *Service) RevokeUserSession(ctx context.Context, payload *gen.RevokeUserSessionPayload) error

Soft-deletes the session and pushes its jti into the revocation cache so the access token stops validating before its TTL expires.

func (*Service) RevokeUserSessionClient

func (s *Service) RevokeUserSessionClient(ctx context.Context, payload *gen.RevokeUserSessionClientPayload) error

Soft-deletes a client registration and cascades to every user_session issued through it. Future tokens minted for this client_id are rejected.

func (*Service) RevokeUserSessionConsent

func (s *Service) RevokeUserSessionConsent(ctx context.Context, payload *gen.RevokeUserSessionConsentPayload) error

Withdraws consent. Subsequent authorization requests for matching (subject, client) pairs re-prompt.

func (*Service) UpdateUserSessionIssuer

func (s *Service) UpdateUserSessionIssuer(ctx context.Context, payload *gen.UpdateUserSessionIssuerPayload) (*types.UserSessionIssuer, error)

Patches an issuer; nil fields are no-ops.

type SessionClaims

type SessionClaims struct {
	jwt.RegisteredClaims
}

SessionClaims is the unified JWT claim shape for Gram-issued user sessions (and, as the chat-session path retires, all Gram session tokens). Carries only the standard OIDC registered claims.

type Signer

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

Signer mints HS256-signed user-session JWTs. Constructed once at server boot from the GRAM_JWT_SIGNING_KEY env var; safe to share across goroutines.

func NewSigner

func NewSigner(secret string) *Signer

NewSigner builds a user-session JWT signer with the supplied HMAC secret.

func (*Signer) Mint

func (s *Signer) Mint(subject urn.SessionSubject, audience, issuer string, lifetime time.Duration) (token string, jti string, err error)

Mint produces an HS256-signed JWT carrying the supplied subject + audience + issuer + lifetime. Returns the signed token plus the JTI so the caller can persist it on the user_sessions row for later revocation.

func (*Signer) ParseUnverifiedJTI

func (s *Signer) ParseUnverifiedJTI(token string) (string, error)

ParseUnverifiedJTI extracts the `jti` claim from a token without verifying the signature. Used by /revoke to push the JTI into the revocation cache — the token's authenticity is established by the client_secret check in the revoke handler, not by signature verification (RFC 7009 doesn't require it).

func (*Signer) Validate

func (s *Signer) Validate(token, expectedAudience string) (*SessionClaims, error)

Validate parses + verifies an HS256 SessionClaims JWT. Verifies signature, expiry/notBefore (via the jwt library), and that the audience contains the expected value (typically the toolset slug). Revocation (jti against the shared `chat_session_revoked:{jti}` cache) is the caller's responsibility — this signer doesn't reach into Redis.

func (*Signer) ValidateBearer

func (s *Signer) ValidateBearer(ctx context.Context, token, expectedAudience string, revocation RevocationChecker) (urn.SessionSubject, string, error)

ValidateBearer bundles the full /mcp/{slug} Bearer validation path: signature + expiry + audience match (via Validate), revocation cache lookup (via the RevocationChecker), and URN parse of the `sub` claim. Returns the parsed subject and JTI on success.

Callers stay free of JWT primitives and revocation plumbing — they only need a Signer + a RevocationChecker (typically *chatsessions.Manager while the unified revocation keyspace is shared with chat sessions).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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