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
- Variables
- func Attach(mux goahttp.Muxer, service *Service)
- type AuthCodeTokenRequest
- type AuthorizationRequest
- type OAuthError
- type RefreshTokenRequest
- type RegistrationRequest
- type RevocationChecker
- type Service
- func (s *Service) APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error)
- func (s *Service) CreateUserSessionIssuer(ctx context.Context, payload *gen.CreateUserSessionIssuerPayload) (*types.UserSessionIssuer, error)
- func (s *Service) DeleteUserSessionIssuer(ctx context.Context, payload *gen.DeleteUserSessionIssuerPayload) error
- func (s *Service) GetUserSessionClient(ctx context.Context, payload *gen.GetUserSessionClientPayload) (*types.UserSessionClient, error)
- func (s *Service) GetUserSessionIssuer(ctx context.Context, payload *gen.GetUserSessionIssuerPayload) (*types.UserSessionIssuer, error)
- func (s *Service) ListUserSessionClients(ctx context.Context, payload *gen.ListUserSessionClientsPayload) (*gen.ListUserSessionClientsResult, error)
- func (s *Service) ListUserSessionConsents(ctx context.Context, payload *gen.ListUserSessionConsentsPayload) (*gen.ListUserSessionConsentsResult, error)
- func (s *Service) ListUserSessionIssuers(ctx context.Context, payload *gen.ListUserSessionIssuersPayload) (*gen.ListUserSessionIssuersResult, error)
- func (s *Service) ListUserSessions(ctx context.Context, payload *gen.ListUserSessionsPayload) (*gen.ListUserSessionsResult, error)
- func (s *Service) RevokeUserSession(ctx context.Context, payload *gen.RevokeUserSessionPayload) error
- func (s *Service) RevokeUserSessionClient(ctx context.Context, payload *gen.RevokeUserSessionClientPayload) error
- func (s *Service) RevokeUserSessionConsent(ctx context.Context, payload *gen.RevokeUserSessionConsentPayload) error
- func (s *Service) UpdateUserSessionIssuer(ctx context.Context, payload *gen.UpdateUserSessionIssuerPayload) (*types.UserSessionIssuer, error)
- type SessionClaims
- type Signer
- func (s *Signer) Mint(subject urn.SessionSubject, audience, issuer string, lifetime time.Duration) (token string, jti string, err error)
- func (s *Signer) ParseUnverifiedJTI(token string) (string, error)
- func (s *Signer) Validate(token, expectedAudience string) (*SessionClaims, error)
- func (s *Signer) ValidateBearer(ctx context.Context, token, expectedAudience string, ...) (urn.SessionSubject, string, error)
Constants ¶
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 ¶
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 ¶
Types ¶
type AuthCodeTokenRequest ¶
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 ¶
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 ¶
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 (*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 ¶
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).