Documentation
¶
Overview ¶
Package oauth signs MCP clients in (R-413, design 06 §3.6).
Pando serves MCP itself, and an MCP client that has no token gets one the way the MCP specification's authorization section says: OAuth 2.1, with Pando as the authorization server and the console as its consent page.
- The client registers itself (RFC 7591): a name and the addresses it may be sent back to. No secret: every client is public.
- It sends the person's browser to the console with a PKCE challenge (RFC 7636, S256 only). The person, signed in however their account signs in, sees the client's name and where it will be sent back to, and approves. The browser goes back to the client with a one-time code.
- The client trades the code and the verifier for a delegated token (R-058), named for the client, under host policy's lifetime (R-061).
It is R-406's flow (package clisignin) with the client's own redirect address in place of the CLI's loopback port, and the same binding: a code is worth nothing without the verifier, which never left the client.
Index ¶
- Constants
- type AccountReader
- type Approved
- type ClientStore
- type CodeStore
- type Consent
- type ErrorResponse
- type Exchanged
- type Registered
- type Registration
- type Request
- type Service
- func (s *Service) Approve(ctx context.Context, userID, approvedBy string, req Request, ...) (Approved, error)
- func (s *Service) Check(ctx context.Context, req Request, resources []string) (Consent, error)
- func (s *Service) Deny(ctx context.Context, req Request, resources []string, issuer string) (string, error)
- func (s *Service) Exchange(ctx context.Context, req TokenRequest) (Exchanged, *ErrorResponse)
- func (s *Service) Register(ctx context.Context, reg Registration) (Registered, *ErrorResponse)
- type Token
- type TokenMinter
- type TokenRequest
Constants ¶
const CodeLifetime = clisignin.CodeLifetime
CodeLifetime is how long an approval waits for the client, as a CLI sign-in's (clisignin.CodeLifetime).
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AccountReader ¶
AccountReader reads accounts (state.Users).
type ClientStore ¶
type ClientStore interface {
Create(ctx context.Context, c state.OAuthClient) error
ByID(ctx context.Context, id string) (state.OAuthClient, bool, error)
}
ClientStore keeps registered clients (state.OAuthClients).
type CodeStore ¶
type CodeStore interface {
Create(ctx context.Context, c state.OAuthCode) error
Take(ctx context.Context, codeHash string) (state.OAuthCode, bool, error)
}
CodeStore keeps approvals (state.OAuthCodes).
type Consent ¶
type Consent struct {
ClientID string `json:"client_id"`
ClientName string `json:"client_name"`
// ReturnsTo is the host, or native app scheme, the browser goes back to.
ReturnsTo string `json:"returns_to"`
// contains filtered or unexported fields
}
Consent is what the person approving is shown.
type ErrorResponse ¶
type ErrorResponse struct {
Status int `json:"-"`
Code string `json:"error"`
Description string `json:"error_description,omitempty"`
}
ErrorResponse is an OAuth error response (RFC 6749 §5.2, RFC 7591 §3.2.2): what registration and the token endpoint answer, since a client reads those, not Pando's error envelope.
func (*ErrorResponse) Error ¶
func (e *ErrorResponse) Error() string
type Exchanged ¶
type Exchanged struct {
Issued state.Issued
// UserID is the account the token acts as (R-058).
UserID string
ApprovedBy string
ClientID string
ClientName string
}
Exchanged is a token made for an approved client.
type Registered ¶
type Registered struct {
ClientID string `json:"client_id"`
ClientIDIssuedAt int64 `json:"client_id_issued_at"`
ClientName string `json:"client_name"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types"`
ResponseTypes []string `json:"response_types"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"`
}
Registered is the answer to a registration (RFC 7591 §3.2.1).
type Registration ¶
type Registration 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"`
}
Registration is a client's registration request (RFC 7591 §2). Fields Pando does not use are ignored; the answer says what was registered.
type Request ¶
type Request struct {
ClientID string `json:"client_id"`
RedirectURI string `json:"redirect_uri"`
ResponseType string `json:"response_type"`
CodeChallenge string `json:"code_challenge"`
CodeChallengeMethod string `json:"code_challenge_method"`
State string `json:"state"`
Resource string `json:"resource"`
Scope string `json:"scope"`
}
Request is an authorization request (RFC 6749 §4.1.1, RFC 7636 §4.3, RFC 8707 §2), as the console's approval page sends it on from its address.
type Service ¶
type Service struct {
Clients ClientStore
Codes CodeStore
Users AccountReader
Tokens TokenMinter
Policy interface {
Load(ctx context.Context) (policy.Document, error)
}
Clock clock.Clock
}
Service registers clients, approves their sign-ins and trades them for tokens. Like clisignin, it decides nothing about who is asking: the caller has established that a signed-in person, not a token, approved, and audits.
func (*Service) Approve ¶
func (s *Service) Approve(ctx context.Context, userID, approvedBy string, req Request, resources []string, issuer string) (Approved, error)
Approve records that userID, signed in as approvedBy, lets the client sign in as them, and says where to send the browser. issuer goes on the redirect as iss (RFC 9207), so a client can tell which server answered.
func (*Service) Check ¶
Check validates an authorization request against its client and says what to show the person. resources are the values RFC 8707's resource parameter may name: Pando's MCP endpoint and Pando itself.
Every refusal is shown on the approval page rather than sent back to the client [P]: one with an unknown client or redirect address must be (RFC 6749 §4.1.2.1), and drawing the rest the same way keeps one path.
func (*Service) Deny ¶
func (s *Service) Deny(ctx context.Context, req Request, resources []string, issuer string) (string, error)
Deny says where to send the browser when the person cancels: the client's address with access_denied (RFC 6749 §4.1.2.1), once the request is shown to be a real client's, so Cancel cannot be used to send a browser anywhere else.
func (*Service) Exchange ¶
func (s *Service) Exchange(ctx context.Context, req TokenRequest) (Exchanged, *ErrorResponse)
Exchange trades a code and its verifier for a delegated token. The approval is gone whatever the outcome. When a code that named an approval is refused, the Exchanged returned with the error says whose it was, so the caller can audit that and only that, as clisignin.Exchange.
func (*Service) Register ¶
func (s *Service) Register(ctx context.Context, reg Registration) (Registered, *ErrorResponse)
Register records a client. Open to anyone (R-413): what a client can do is ask a person to approve it, and the person sees its name and where it will be sent back to. A client nobody approves is removed after a day.
type Token ¶
type Token struct {
AccessToken secret.Value `json:"-"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in,omitempty"`
}
Token is the token endpoint's answer (RFC 6749 §5.1). No refresh token: the token lives as long as host policy lets any token live, and a client signs in again after that.