Documentation
¶
Overview ¶
Package personserver implements the Person Server role of AAuth (draft-hardt-oauth-aauth-protocol-11 §7): the person token endpoint, the auth token endpoint, deferred (pending) requests with clarification chat, and the metadata document and JWKS a PS publishes.
The package is library-first and storage-agnostic. A Server is an http.Handler configured with:
- a Store holding agent-person bindings, directed identifiers, token records for revocation, missions, and pending requests (NewMemoryStore is a reference implementation for tests);
- a Decider that makes every consent decision. The library never decides policy: a decision allows, denies, or defers, and a deferred request is resolved later by the hosting application through Server.Approve, Server.Deny, Server.Fail, or Server.Ask — from its own approval UI, a push notification, or an administrator queue.
The PS authenticates every agent request by its HTTP message signature and agent token (§11.3), derives a pairwise directed identifier per resource with a keyed HMAC (DirectedSubject), binds each agent to exactly one person (§13.14), records the tokens it issues for revocation (§11.12.4), and verifies resource tokens together with the presented token they name (§6.7.2), call-chaining upstream tokens (§9.4.5), and sub-agent tokens (§10.2).
One origin can host a PS, an agent provider, and an access server: each role has its own key, metadata document, and JWKS, and Server.Register adds the PS routes (under /ps/ by default) to a shared mux.
A minimal deployment:
ps, err := personserver.New(personserver.Config{
Issuer: "https://ps.example",
Key: psKey,
SubjectKey: subjectKey, // 32+ random bytes, kept stable
Store: store,
InteractionURL: "https://ps.example/consent",
Decider: personserver.DeciderFunc(func(ctx context.Context, r *personserver.TokenRequest) (personserver.Decision, error) {
if r.Person == "" {
return personserver.DeferInteraction(), nil // enroll a new agent
}
return personserver.Allow(personserver.Grant{}), nil
}),
})
http.Handle("/", ps)
The consent page reads ?code=, calls Server.ConsumeCode, authenticates the person (§13.13), and calls Server.Approve with the person's identifier, which binds the agent.
Example (CollapsedDeployment) ¶
Example_collapsedDeployment hosts an agent provider, a person server, and an access server on one origin (draft -11 §4.3, §9.3.3). Storage and policy are the hosting application's: the in-memory stores and limiters here are for a single instance only, and the Decider and Authorizer stand in for real consent and access policy.
package main
import (
"context"
"crypto/rand"
"log"
"net/http"
"time"
aauth "github.com/aauth-dev/aauth-go"
"github.com/aauth-dev/aauth-go/accessserver"
"github.com/aauth-dev/aauth-go/agentprovider"
"github.com/aauth-dev/aauth-go/personserver"
"github.com/aauth-dev/aauth-go/ratelimit"
)
func main() {
const issuer = "https://example.com" // shared by the collocated roles
ctx := context.Background()
// Each role has its own signing key, metadata document, and JWKS.
apKey, err := aauth.GenerateKey(aauth.AlgEd25519)
if err != nil {
log.Fatal(err)
}
psKey, err := aauth.GenerateKey(aauth.AlgEd25519)
if err != nil {
log.Fatal(err)
}
asKey, err := aauth.GenerateKey(aauth.AlgES256)
if err != nil {
log.Fatal(err)
}
subjectKey := make([]byte, 32) // keep stable: it derives directed sub values
if _, err := rand.Read(subjectKey); err != nil {
log.Fatal(err)
}
ap, err := agentprovider.New(agentprovider.Config{
Issuer: issuer,
Key: apKey,
Limiter: ratelimit.Per(10, time.Minute),
})
if err != nil {
log.Fatal(err)
}
as, err := accessserver.New(accessserver.Config{
Issuer: issuer,
Key: asKey,
Store: accessserver.NewMemoryStore(), // implement accessserver.Store over your database
Authorizer: accessserver.AuthorizerFunc(func(ctx context.Context, r *accessserver.AuthorizationRequest) (accessserver.Decision, error) {
return accessserver.Allow(""), nil // the resource token's scope
}),
Limiter: ratelimit.Per(5, time.Second),
})
if err != nil {
log.Fatal(err)
}
var ps *personserver.Server
ps, err = personserver.New(personserver.Config{
Issuer: issuer,
Key: psKey,
SubjectKey: subjectKey,
Store: personserver.NewMemoryStore(), // implement personserver.Store over your database
InteractionURL: issuer + "/consent",
Decider: personserver.DeciderFunc(func(ctx context.Context, r *personserver.TokenRequest) (personserver.Decision, error) {
if r.Person == "" {
return personserver.DeferInteraction(), nil // a new agent: the person signs in at /consent
}
return personserver.Allow(personserver.Grant{}), nil
}),
// Resources governed by the collocated access server are federated
// in process rather than over HTTP.
Federator: as.Local(issuer),
CollocatedAS: func(resource string) bool { return resource == "https://ledger.example" },
Limiter: ratelimit.Per(5, time.Second),
})
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
ap.Register(mux)
ps.Register(mux)
as.Register(mux)
mux.HandleFunc("/consent", func(w http.ResponseWriter, r *http.Request) {
p, err := ps.ConsumeCode(r.Context(), r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "unknown code", http.StatusBadRequest)
return
}
// Authenticate the person with the application's own login, then
// approve; approval binds the agent to that person.
if err := ps.Approve(r.Context(), p.ID, personserver.Grant{Person: "alice"}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
// The application issues agent tokens for keys it has authorized, for
// example a key bound to one of its work sessions.
agentKey, err := aauth.GenerateKey(aauth.AlgEd25519)
if err != nil {
log.Fatal(err)
}
jwk, err := aauth.NewJWK(agentKey.Public())
if err != nil {
log.Fatal(err)
}
if _, _, err := ap.IssueAgentToken(ctx, agentprovider.AgentTokenParams{Name: "session-7", Key: jwk, PS: issuer}); err != nil {
log.Fatal(err)
}
srv := &http.Server{Addr: ":8443", Handler: mux, ReadHeaderTimeout: 10 * time.Second}
_ = srv // srv.ListenAndServeTLS(certFile, keyFile)
}
Output:
Index ¶
- Constants
- Variables
- func DirectedSubject(key []byte, person, resource string) string
- func ResourceInteractionURL(p *Pending, callback string) (string, error)
- type AgentRef
- type AgentTokenRecord
- type AuthTokenRecord
- type BindingStore
- type ClaimsProvider
- type Config
- type Decider
- type DeciderFunc
- type Decision
- type Exchange
- type FederationRequest
- type FederationResponse
- type FederationState
- type Federator
- type Grant
- type HTTPRevoker
- type InteractionRelay
- type InteractionRelayFunc
- type InteractionRequest
- type Kind
- type MemoryStore
- func (m *MemoryStore) AgentToken(_ context.Context, iss, jti string) (*AgentTokenRecord, error)
- func (m *MemoryStore) AppendMissionLog(_ context.Context, s256 string, e MissionLogEntry) error
- func (m *MemoryStore) AuthToken(_ context.Context, iss, jti string) (*AuthTokenRecord, error)
- func (m *MemoryStore) AuthTokensForMission(_ context.Context, s256 string) ([]AuthTokenRecord, error)
- func (m *MemoryStore) AuthTokensForPersonToken(_ context.Context, jti string) ([]AuthTokenRecord, error)
- func (m *MemoryStore) Bind(_ context.Context, agent AgentRef, person string) error
- func (m *MemoryStore) BoundPerson(_ context.Context, agent AgentRef) (string, error)
- func (m *MemoryStore) CreateMission(_ context.Context, r *MissionRecord) error
- func (m *MemoryStore) CreatePending(_ context.Context, p *Pending) error
- func (m *MemoryStore) IsRevoked(_ context.Context, iss, jti string) (bool, error)
- func (m *MemoryStore) MarkPresented(_ context.Context, jti, as string) error
- func (m *MemoryStore) Mission(_ context.Context, s256 string) (*MissionRecord, error)
- func (m *MemoryStore) MissionLog(_ context.Context, s256 string) ([]MissionLogEntry, error)
- func (m *MemoryStore) Pending(_ context.Context, id string) (*Pending, error)
- func (m *MemoryStore) PendingByCode(_ context.Context, code string) (*Pending, error)
- func (m *MemoryStore) PendingForMission(_ context.Context, s256 string) ([]*Pending, error)
- func (m *MemoryStore) PendingForResourceToken(_ context.Context, iss, jti string) ([]*Pending, error)
- func (m *MemoryStore) PersonToken(_ context.Context, jti string) (*PersonTokenRecord, error)
- func (m *MemoryStore) PersonTokensForSubject(_ context.Context, resource, sub string) ([]PersonTokenRecord, error)
- func (m *MemoryStore) PersonTokensFromUpstream(_ context.Context, iss, jti string) ([]PersonTokenRecord, error)
- func (m *MemoryStore) RecordAgentToken(_ context.Context, r AgentTokenRecord) error
- func (m *MemoryStore) RecordAuthToken(_ context.Context, r AuthTokenRecord) error
- func (m *MemoryStore) RecordPersonToken(_ context.Context, r PersonTokenRecord) error
- func (m *MemoryStore) RecordSubject(_ context.Context, resource, sub, person string) error
- func (m *MemoryStore) ResolvePendingIfMissionActive(_ context.Context, p *Pending) error
- func (m *MemoryStore) Revoke(_ context.Context, iss, jti string, exp time.Time) error
- func (m *MemoryStore) SubjectPerson(_ context.Context, resource, sub string) (string, error)
- func (m *MemoryStore) TerminateMission(_ context.Context, s256, reason string) error
- func (m *MemoryStore) TokensForAgent(_ context.Context, agent AgentRef) ([]PersonTokenRecord, []AuthTokenRecord, error)
- func (m *MemoryStore) Unbind(_ context.Context, agent AgentRef) error
- func (m *MemoryStore) UpdatePending(_ context.Context, p *Pending) error
- type MissionApprover
- type MissionApproverFunc
- type MissionGrant
- type MissionLogEntry
- type MissionRecord
- type MissionRequest
- type MissionStore
- type Outcome
- type Paths
- type Pending
- type PendingStore
- type PermissionDecider
- type PermissionDeciderFunc
- type PermissionRequest
- type PersonTokenRecord
- type Question
- type RateLimitError
- type Result
- type Revoker
- type Server
- func (s *Server) Approve(ctx context.Context, id string, g Grant) error
- func (s *Server) Ask(ctx context.Context, id string, q Question) error
- func (s *Server) CompleteResourceInteraction(ctx context.Context, id, callbackError string) error
- func (s *Server) ConsumeCode(ctx context.Context, code string) (*Pending, error)
- func (s *Server) ConsumeCodeFor(ctx context.Context, code, client string) (*Pending, error)
- func (s *Server) Deny(ctx context.Context, id, reason string) error
- func (s *Server) Fail(ctx context.Context, id, code, detail string) error
- func (s *Server) Issuer() string
- func (s *Server) JWKS() aauth.JWKS
- func (s *Server) MarkInteracting(ctx context.Context, id string) error
- func (s *Server) Metadata() aauth.PersonServerMetadata
- func (s *Server) Mission(ctx context.Context, s256 string) (*MissionRecord, error)
- func (s *Server) MissionLog(ctx context.Context, s256 string) ([]MissionLogEntry, error)
- func (s *Server) PendingRequest(ctx context.Context, id string) (*Pending, error)
- func (s *Server) Register(mux *http.ServeMux)
- func (s *Server) RevokeAgent(ctx context.Context, agent AgentRef) ([]aauth.RevocationOutcome, error)
- func (s *Server) RevokeAuthToken(ctx context.Context, jti string) ([]aauth.RevocationOutcome, error)
- func (s *Server) RevokeBinding(ctx context.Context, agent AgentRef) ([]aauth.RevocationOutcome, error)
- func (s *Server) RevokePersonToken(ctx context.Context, jti string) ([]aauth.RevocationOutcome, error)
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) Signer() aauth.ServerSigner
- func (s *Server) TerminateMission(ctx context.Context, s256, reason string) ([]aauth.RevocationOutcome, error)
- type State
- type Store
- type SubjectStore
- type TokenRequest
- type TokenStore
Examples ¶
Constants ¶
const ( MissionActive = "active" MissionTerminated = "terminated" )
Mission states (draft -11 §8.6).
const ( LogTokenRequest = "token_request" LogUpdate = "update" LogCompletion = "completion" LogPermission = "permission" LogAudit = "audit" LogInteraction = "interaction" LogClarification = "clarification" LogTermination = "termination" )
Mission log entry kinds (draft -11 §8.3).
const LogApproval = "approval"
LogApproval is the mission log kind of a mission's approval.
Variables ¶
var ( // ErrResolved means the pending request is no longer open. ErrResolved = errors.New("personserver: pending request already resolved") // ErrClarificationLimit means the request reached the maximum number // of clarification rounds (§7.5.3). ErrClarificationLimit = errors.New("personserver: clarification round limit reached") // ErrInvalidCode means an interaction code is unknown, malformed, or // already consumed (§11.6.3.1); the interaction page answers it with // invalid_code. ErrInvalidCode = errors.New("personserver: invalid_code") // ErrQuestionPending means a clarification is already awaiting the // agent's answer. ErrQuestionPending = errors.New("personserver: a clarification is already pending") // ErrFederating means the PS has already consented and the request // waits on the access server (§9.1.2); it resolves when the AS // answers, not by Approve. ErrFederating = errors.New("personserver: the request is waiting on the access server") )
Errors returned by the pending-request API.
var ( // ErrNotFound means no record exists for the key. ErrNotFound = errors.New("personserver: not found") // ErrConflict means an optimistic update lost a race: the record's // Version changed since it was read. ErrConflict = errors.New("personserver: version conflict") // ErrBindingConflict means the agent is already bound to a different // person (draft -11 §13.14: once established, the PS MUST NOT allow a // different person to claim the same agent). ErrBindingConflict = errors.New("personserver: agent is bound to another person") // ErrExists means a record with the key already exists. ErrExists = errors.New("personserver: already exists") // ErrRevoked means the person token at the root of a grant has been // revoked, so an auth token issued under it was not recorded. ErrRevoked = errors.New("personserver: person token revoked") // ErrMissionTerminated means the mission a token was issued under has // been terminated, so the token was not recorded. ErrMissionTerminated = errors.New("personserver: mission terminated") // ErrAgentRevoked means the agent token (or sub-agent token) a person // token was requested with has been revoked, so the person token was not // recorded. ErrAgentRevoked = errors.New("personserver: agent token revoked") // ErrBindingRevoked means the agent's binding no longer names the // person a person token was issued for, so the token was not recorded. ErrBindingRevoked = errors.New("personserver: agent binding revoked") )
Store errors. Implementations return these (or wrap them) so the server can tell an absent record from a failure to reach storage.
Functions ¶
func DirectedSubject ¶
DirectedSubject derives the person's directed identifier at resource (draft -11 §14.1, §10.1.1.2): a keyed HMAC-SHA-256 over the resource and the person, base64url-encoded without padding. Each resource sees a different value for the same person, the value is the same in every person token and auth token for that resource, and it does not vary with the agent or its key. key is the PS's secret derivation key; anyone without it cannot correlate values across resources.
func ResourceInteractionURL ¶
ResourceInteractionURL is where the interaction page sends the person for the resource's own interaction (draft -11 §7.2.3): {interaction.url}?code={interaction.code}&callback={callback}, where callback is a PS-generated, per-flow URL. The interaction URL must be https (it is checked again here); apply egress admission before redirecting.
Types ¶
type AgentRef ¶
AgentRef identifies an agent the way a PS recognizes it: by its agent token's (iss, sub) (draft -11 §13.14).
type AgentTokenRecord ¶
type AgentTokenRecord struct {
Issuer string `json:"iss"`
JTI string `json:"jti"`
Subject string `json:"sub"`
Exp time.Time `json:"exp"`
}
AgentTokenRecord is an agent token the PS accepted (draft -11 §11.12.4: the PS records each agent token's (iss, jti) and the sub it carried until its exp plus clock skew), so an agent provider's revocation cascades by agent identity.
type AuthTokenRecord ¶
type AuthTokenRecord struct {
Issuer string `json:"iss"` // the PS, or the AS it federated with
JTI string `json:"jti"`
Resource string `json:"aud"`
Exp time.Time `json:"exp"`
// PresentedJTI is the jti of the token the request presented, and
// (PersonIssuer, PersonJTI) the person token at the root of the grant.
PresentedJTI string `json:"presented_jti"`
PersonIssuer string `json:"person_iss"`
PersonJTI string `json:"person_jti"`
Agent AgentRef `json:"agent"`
Subagent AgentRef `json:"subagent,omitempty"`
Person string `json:"person"`
Subject string `json:"sub"`
MissionS256 string `json:"mission_s256,omitempty"`
}
AuthTokenRecord is an auth token the PS issued (three-party) or obtained by federation (four-party) (draft -11 §11.12.4).
type BindingStore ¶
type BindingStore interface {
// BoundPerson returns the person agent is bound to, or ErrNotFound.
BoundPerson(ctx context.Context, agent AgentRef) (string, error)
// Bind binds agent to person. Binding an agent to the person it is
// already bound to succeeds; binding it to another person MUST fail
// with ErrBindingConflict.
Bind(ctx context.Context, agent AgentRef, person string) error
// Unbind removes agent's binding (revoking it); a later Bind may bind
// the agent to a different person. Unbinding an unbound agent succeeds.
Unbind(ctx context.Context, agent AgentRef) error
}
BindingStore holds the agent-person binding (draft -11 §13.14): each agent, identified by (iss, sub), is associated with exactly one person. Person identifiers are the hosting application's own and opaque to the library.
type ClaimsProvider ¶
type ClaimsProvider func(ctx context.Context, person string, required []string) (map[string]any, error)
ClaimsProvider supplies identity claims an access server requires about the person (draft -11 §9.2). It is never asked for sub. Nil answers with no claims; the AS ignores what it does not get.
type Config ¶
type Config struct {
// Issuer is the PS's server identifier (§11.1.1): the iss of the
// tokens it issues and the base of its endpoint URLs.
Issuer string
// Key signs person and auth tokens and server-signed requests; KeyID
// is its kid in the PS's JWKS (default: the key's RFC 7638
// thumbprint). When one origin hosts several roles, give each its own
// key: each role publishes its own metadata and JWKS (§9.3.3).
Key crypto.Signer
KeyID string
// PublishedKeys are extra keys to publish in the JWKS, for rotation.
PublishedKeys []aauth.JWK
// SubjectKey keys the derivation of directed identifiers (see
// [DirectedSubject]); at least 32 bytes, stable for the PS's lifetime.
SubjectKey []byte
// Store persists PS state. Required.
Store Store
// Decider decides person and auth token requests. Required.
Decider Decider
// MissionApprover enables the mission endpoint (§8).
MissionApprover MissionApprover
// PermissionDecider enables the permission endpoint (§7.7).
PermissionDecider PermissionDecider
// InteractionRelay enables the interaction endpoint (§7.6).
InteractionRelay InteractionRelay
// Audit enables the audit endpoint (§7.8). Records go to the mission
// log; AuditSink, when set, also receives them.
Audit bool
AuditSink func(ctx context.Context, agent AgentRef, r aauth.AuditRequest)
// Federator enables four-party access (§9.1): resource tokens
// addressed to an access server are federated through it. Without
// one, such resource tokens are refused. ClaimsProvider answers the
// identity claims an access server requires (§9.2).
Federator Federator
ClaimsProvider ClaimsProvider
// CollocatedAS, for PS-AS collapse on one origin (§4.3, §9.3.3),
// reports whether the access server sharing this PS's issuer governs
// resource. Resource tokens addressed to the shared issuer are then
// federated through Federator (typically accessserver.Server.Local)
// instead of handled three-party, and the agent receives the AS's auth
// token (dwk aauth-access.json). Roles that share an origin share an
// identifier, so aud alone cannot tell them apart.
CollocatedAS func(resource string) bool
// InteractionURL is the PS's user-facing interaction page (https, no
// query or fragment), sent as the url of requirement=interaction. The
// page reads ?code= and calls [Server.ConsumeCode]. Required for
// requirement=interaction decisions.
InteractionURL string
// Paths overrides endpoint paths; zero fields take DefaultPaths.
Paths Paths
// Metadata supplies the display members of the metadata document
// (name, description, logos, ...); issuer, jwks_uri, and the endpoints
// are filled in.
Metadata aauth.ServerMetadata
// ScopesSupported, ClaimsSupported, and MissionControlEndpoint are
// published in the metadata document when set.
ScopesSupported []string
ClaimsSupported []string
MissionControlEndpoint string
// AgentResolver resolves agent token keys; nil discovers them at
// {iss}/.well-known/aauth-agent.json through HTTPClient.
AgentResolver aauth.KeyResolver
// TokenResolver resolves resource token keys and access-server auth
// token keys; nil discovers them through HTTPClient. Tokens this PS
// issued are always verified against its own keys.
TokenResolver aauth.KeyResolver
// ServerResolver resolves the keys of servers signing under the
// jwks_uri scheme (revocations); nil discovers them through HTTPClient.
ServerResolver aauth.KeyResolver
// AcceptRevocation, when set, decides whether a verified agent
// provider or resource may revoke at this PS; refused callers get
// unsupported_iss (§11.12.3). Nil accepts every verified caller.
AcceptRevocation func(ctx context.Context, caller aauth.ServerCaller) bool
// Revoker delivers the PS's own revocations downstream; nil uses an
// HTTPRevoker signing as the PS.
Revoker Revoker
// HTTPClient makes outbound requests (discovery, revocation); nil uses
// aauth.DiscoveryClient, which reaches only public https destinations.
// Supply a client to reach others, such as local servers in
// development.
HTTPClient *http.Client
// SignatureWindow is the HTTP signature validity window (default
// aauth.DefaultSignatureWindow).
SignatureWindow time.Duration
// PendingTTL bounds how long a deferred request stays open (default
// ten minutes); MaxWait caps how long a request is held for Prefer:
// wait (default 30 seconds).
PendingTTL time.Duration
MaxWait time.Duration
// MaxClarificationRounds bounds clarification questions per request
// (§7.5.3; default five).
MaxClarificationRounds int
// PersonTokenTTL and AuthTokenTTL are the default token lifetimes
// (at most one hour; default one hour).
PersonTokenTTL time.Duration
AuthTokenTTL time.Duration
// Limiter, when set, rate-limits the actions the protocol defines a
// refusal for: interaction code attempts per client ("code:"), polls
// per pending request ("poll:", answered slow_down), revocations per
// issuer ("revoke:", answered rate_limited), and new resources per
// agent at the person token endpoint ("resources:"). See the
// ratelimit package for an in-memory implementation.
Limiter aauth.Limiter
// Notify, when set, is called after a pending request is created or
// changes (a clarification answer, an updated request, a resolution),
// so the hosting application can update its approval UI.
Notify func(ctx context.Context, p *Pending)
// Logger receives security events (failed mission lookups, rejected
// requests); nil discards them.
Logger *slog.Logger
// Now returns the current time; nil means time.Now.
Now func() time.Time
// InsecureSkipIdentifierCheck accepts identifiers that are not server
// identifiers (http, ports), for development and tests. It also
// relaxes the agent-token profile check. Never set it in production.
InsecureSkipIdentifierCheck bool
}
Config configures a Server. Issuer, Key, SubjectKey, Store, and Decider are required.
type Decider ¶
type Decider interface {
Decide(ctx context.Context, r *TokenRequest) (Decision, error)
}
Decider decides person token and auth token requests: whether this agent may act at the resource as the person, and with what scope (draft -11 §7.1, §7.2, §7.4). A Deferred decision becomes a pending request.
type DeciderFunc ¶
type DeciderFunc func(ctx context.Context, r *TokenRequest) (Decision, error)
DeciderFunc adapts a function to Decider.
func (DeciderFunc) Decide ¶
func (f DeciderFunc) Decide(ctx context.Context, r *TokenRequest) (Decision, error)
Decide calls f.
type Decision ¶
type Decision struct {
Outcome Outcome
// Grant qualifies an Allowed decision.
Grant Grant
// Code and Reason qualify a Denied decision. Code defaults to the
// endpoint's denial: denied (§11.9.4) at the token endpoints, a 200
// {"permission": "denied"} at the permission endpoint, and
// interaction_unavailable at the interaction endpoint. Reason is a
// human-readable detail (Markdown for a permission denial).
Code string
Reason string
// Requirement qualifies a Deferred decision: aauth.RequirementInteraction
// (the person is directed to InteractionURL with a code), or
// aauth.RequirementApproval (the PS reaches the person itself), or
// aauth.RequirementClarification with Question. Empty means approval.
Requirement string
// Question is the clarification put to the agent (§7.5.1).
Question *Question
// RetryAfter is the polling interval to suggest, in seconds.
RetryAfter int
}
Decision is a policy verdict on one request.
func DeferApproval ¶
func DeferApproval() Decision
DeferApproval defers with requirement=approval: the PS obtains the decision itself, by push, email, or an administrator (§11.6.4).
func DeferClarification ¶
DeferClarification defers by asking the agent a question (§7.5.1).
func DeferInteraction ¶
func DeferInteraction() Decision
DeferInteraction defers with requirement=interaction: the agent directs the person to the PS's interaction page with a code (§11.6.3).
type Exchange ¶
type Exchange struct {
Time time.Time `json:"time"`
Question string `json:"question"`
Answer string `json:"answer,omitempty"`
Updated bool `json:"updated,omitempty"` // an updated_request replaced the request
Justification string `json:"justification,omitempty"`
// FromAS marks a question an access server asked (four-party).
FromAS bool `json:"from_as,omitempty"`
}
Exchange is one round of clarification chat (draft -11 §7.5): the question put to the agent and the agent's answer, or the updated request it sent instead. Answers are agent-asserted (§7.4) and untrusted: sanitize before display (§13.3).
type FederationRequest ¶
type FederationRequest struct {
AccessServer string `json:"-"`
ResourceToken string `json:"resource_token"`
AgentToken string `json:"agent_token"`
PresentedToken string `json:"presented_token"`
SubagentToken string `json:"subagent_token,omitempty"`
UpstreamToken string `json:"upstream_token,omitempty"`
}
FederationRequest is the body of the PS-to-AS token request (draft -11 §9.1.1), addressed to AccessServer.
type FederationResponse ¶
type FederationResponse struct {
// AuthToken and ExpiresIn are set on a direct grant (200).
AuthToken string
ExpiresIn int64
// PendingURL is set on a deferred response (202): the AS's pending
// URL (absolute), with what it requires.
PendingURL string
Requirement aauth.Requirement
Status string
Question *Question
RequiredClaims []string
RetryAfter int
// ErrorStatus and ErrorCode are set on a terminal AS error, which the
// PS relays to the agent in its own problem body (§9.1.3).
ErrorStatus int
ErrorCode string
ErrorDetail string
}
FederationResponse is the access server's answer (draft -11 §9.1.2): an auth token, a pending request, or a terminal error to relay.
type FederationState ¶
type FederationState struct {
AccessServer string `json:"access_server"`
PendingURL string `json:"pending_url"`
Requirement aauth.Requirement `json:"requirement,omitzero"`
Status string `json:"status,omitempty"`
Question *Question `json:"question,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
// GrantedScope is the scope the PS consented to; the AS's token may
// not be broader.
GrantedScope string `json:"granted_scope,omitempty"`
}
FederationState is an access server's pending request the PS is following on the agent's behalf (draft -11 §9.1.2).
type Federator ¶
type Federator interface {
// Federate sends the token request to the access server.
Federate(ctx context.Context, r *FederationRequest) (*FederationResponse, error)
// Poll fetches the current state of the AS's pending request.
Poll(ctx context.Context, pendingURL string) (*FederationResponse, error)
// Answer POSTs body to the AS's pending URL: the claims it required
// (§9.2), or a clarification response or updated request (§7.5.2).
Answer(ctx context.Context, pendingURL string, body any) error
}
Federator makes PS-to-AS token requests (draft -11 §9.1.1) and follows the AS's pending URL. An error means the PS could not obtain a usable response — connection failure, timeout, a malformed response — and is answered as_unreachable (§11.9.3).
type Grant ¶
type Grant struct {
// Person binds an unbound agent to this person (person and auth
// tokens, missions; §13.14). It MUST equal an existing binding.
Person string
// Tenant is the person's organization, carried in person tokens.
Tenant string
// Scope is the scope an auth token grants; empty grants the resource
// token's scope. It may not be broader than what was requested.
Scope string
// TTL caps the lifetime of the issued token (at most one hour).
TTL time.Duration
// Mission shapes an approved mission (mission proposals).
Mission *MissionGrant
// Answer is the person's answer to an interaction question.
Answer string
}
Grant is what an Allowed decision, or an approval of a pending request, grants. Each field applies to the request kinds named.
type HTTPRevoker ¶
type HTTPRevoker struct {
Client aauth.RevocationClient
HTTPClient *http.Client
}
HTTPRevoker is the default Revoker: endpoint discovery plus an aauth.RevocationClient.
func (HTTPRevoker) Revoke ¶
func (h HTTPRevoker) Revoke(ctx context.Context, recipient, dwk string, req aauth.RevocationRequest) (*aauth.RevocationResponse, error)
Revoke implements Revoker.
type InteractionRelay ¶
type InteractionRelay interface {
Relay(ctx context.Context, r *InteractionRequest) (Decision, error)
}
InteractionRelay delivers interaction requests to the person. For a question, Allowed answers with Grant.Answer at once and Deferred waits for Server.Approve with the answer. For an interaction or payment relay, Deferred means the PS is relaying it: call Server.MarkInteracting when the person engages and Server.Approve when the PS has done all it can. Denied answers interaction_unavailable (424, the agent directs the person itself) unless Code is user_unreachable.
type InteractionRelayFunc ¶
type InteractionRelayFunc func(ctx context.Context, r *InteractionRequest) (Decision, error)
InteractionRelayFunc adapts a function to InteractionRelay.
func (InteractionRelayFunc) Relay ¶
func (f InteractionRelayFunc) Relay(ctx context.Context, r *InteractionRequest) (Decision, error)
Relay calls f.
type InteractionRequest ¶
type InteractionRequest struct {
Agent *aauth.AgentClaims
Person string
Request *aauth.InteractionRequest
Mission *MissionRecord
}
InteractionRequest is a request to reach the person through the PS (draft -11 §7.6): relay an interaction or payment URL, or ask a question.
type Kind ¶
type Kind string
Kind is the kind of request a pending request holds.
const ( KindPersonToken Kind = "person_token" // §7.1 KindAuthToken Kind = "auth_token" // §7.2 KindMissionProposal Kind = "mission_proposal" // §8.1 KindMissionUpdate Kind = "mission_update" // §8.4 KindMissionCompletion Kind = "mission_completion" // §8.5 KindPermission Kind = "permission" // §7.7 KindInteraction Kind = "interaction" // §7.6 )
Pending request kinds.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-memory Store for tests and examples. It keeps everything in maps guarded by one mutex, copies records in and out (as a database would), and never prunes. Do not use it in production: state is lost on restart and is not shared between instances.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty MemoryStore.
func (*MemoryStore) AgentToken ¶
func (m *MemoryStore) AgentToken(_ context.Context, iss, jti string) (*AgentTokenRecord, error)
AgentToken implements TokenStore.
func (*MemoryStore) AppendMissionLog ¶
func (m *MemoryStore) AppendMissionLog(_ context.Context, s256 string, e MissionLogEntry) error
AppendMissionLog implements MissionStore.
func (*MemoryStore) AuthToken ¶
func (m *MemoryStore) AuthToken(_ context.Context, iss, jti string) (*AuthTokenRecord, error)
AuthToken implements TokenStore.
func (*MemoryStore) AuthTokensForMission ¶
func (m *MemoryStore) AuthTokensForMission(_ context.Context, s256 string) ([]AuthTokenRecord, error)
AuthTokensForMission implements TokenStore.
func (*MemoryStore) AuthTokensForPersonToken ¶
func (m *MemoryStore) AuthTokensForPersonToken(_ context.Context, jti string) ([]AuthTokenRecord, error)
AuthTokensForPersonToken implements TokenStore.
func (*MemoryStore) BoundPerson ¶
BoundPerson implements BindingStore.
func (*MemoryStore) CreateMission ¶
func (m *MemoryStore) CreateMission(_ context.Context, r *MissionRecord) error
CreateMission implements MissionStore.
func (*MemoryStore) CreatePending ¶
func (m *MemoryStore) CreatePending(_ context.Context, p *Pending) error
CreatePending implements PendingStore.
func (*MemoryStore) MarkPresented ¶
func (m *MemoryStore) MarkPresented(_ context.Context, jti, as string) error
MarkPresented implements TokenStore.
func (*MemoryStore) Mission ¶
func (m *MemoryStore) Mission(_ context.Context, s256 string) (*MissionRecord, error)
Mission implements MissionStore.
func (*MemoryStore) MissionLog ¶
func (m *MemoryStore) MissionLog(_ context.Context, s256 string) ([]MissionLogEntry, error)
MissionLog implements MissionStore.
func (*MemoryStore) PendingByCode ¶
PendingByCode implements PendingStore.
func (*MemoryStore) PendingForMission ¶
PendingForMission implements PendingStore.
func (*MemoryStore) PendingForResourceToken ¶
func (m *MemoryStore) PendingForResourceToken(_ context.Context, iss, jti string) ([]*Pending, error)
PendingForResourceToken implements PendingStore.
func (*MemoryStore) PersonToken ¶
func (m *MemoryStore) PersonToken(_ context.Context, jti string) (*PersonTokenRecord, error)
PersonToken implements TokenStore.
func (*MemoryStore) PersonTokensForSubject ¶
func (m *MemoryStore) PersonTokensForSubject(_ context.Context, resource, sub string) ([]PersonTokenRecord, error)
PersonTokensForSubject implements TokenStore.
func (*MemoryStore) PersonTokensFromUpstream ¶
func (m *MemoryStore) PersonTokensFromUpstream(_ context.Context, iss, jti string) ([]PersonTokenRecord, error)
PersonTokensFromUpstream implements TokenStore.
func (*MemoryStore) RecordAgentToken ¶
func (m *MemoryStore) RecordAgentToken(_ context.Context, r AgentTokenRecord) error
RecordAgentToken implements TokenStore.
func (*MemoryStore) RecordAuthToken ¶
func (m *MemoryStore) RecordAuthToken(_ context.Context, r AuthTokenRecord) error
RecordAuthToken implements TokenStore.
func (*MemoryStore) RecordPersonToken ¶
func (m *MemoryStore) RecordPersonToken(_ context.Context, r PersonTokenRecord) error
RecordPersonToken implements TokenStore.
func (*MemoryStore) RecordSubject ¶
func (m *MemoryStore) RecordSubject(_ context.Context, resource, sub, person string) error
RecordSubject implements SubjectStore.
func (*MemoryStore) ResolvePendingIfMissionActive ¶
func (m *MemoryStore) ResolvePendingIfMissionActive(_ context.Context, p *Pending) error
ResolvePendingIfMissionActive implements PendingStore.
func (*MemoryStore) SubjectPerson ¶
SubjectPerson implements SubjectStore.
func (*MemoryStore) TerminateMission ¶
func (m *MemoryStore) TerminateMission(_ context.Context, s256, reason string) error
TerminateMission implements MissionStore.
func (*MemoryStore) TokensForAgent ¶
func (m *MemoryStore) TokensForAgent(_ context.Context, agent AgentRef) ([]PersonTokenRecord, []AuthTokenRecord, error)
TokensForAgent implements TokenStore.
func (*MemoryStore) Unbind ¶
func (m *MemoryStore) Unbind(_ context.Context, agent AgentRef) error
Unbind implements BindingStore.
func (*MemoryStore) UpdatePending ¶
func (m *MemoryStore) UpdatePending(_ context.Context, p *Pending) error
UpdatePending implements PendingStore.
type MissionApprover ¶
type MissionApprover interface {
DecideMission(ctx context.Context, r *MissionRequest) (Decision, error)
}
MissionApprover decides mission proposals, updates, and completions. An Allowed proposal is shaped by Grant.Mission; an Allowed update is accepted into the log; an Allowed completion terminates the mission as completed. Follow-up questions are put with Server.Ask.
type MissionApproverFunc ¶
type MissionApproverFunc func(ctx context.Context, r *MissionRequest) (Decision, error)
MissionApproverFunc adapts a function to MissionApprover.
func (MissionApproverFunc) DecideMission ¶
func (f MissionApproverFunc) DecideMission(ctx context.Context, r *MissionRequest) (Decision, error)
DecideMission calls f.
type MissionGrant ¶
type MissionGrant struct {
// Description is the approved scope; empty keeps the proposal's.
Description string
// Tools are the approved_tools; nil keeps the proposal's, an empty
// non-nil slice approves none.
Tools []aauth.MissionTool
// Resources are the approved_resources, for each of which the PS
// issues a person token; nil keeps the proposal's.
Resources []string
// ExpiresAt, when set, is the mission's expires_at.
ExpiresAt time.Time
// Capabilities are those the PS can provide on the person's behalf now
// (not part of the blob).
Capabilities []string
// Extra adds members to the blob; they become part of its identity.
Extra map[string]any
}
MissionGrant shapes the mission blob the PS approves (draft -11 §8.2). The approved mission MAY differ from the proposal.
type MissionLogEntry ¶
type MissionLogEntry struct {
Time time.Time `json:"time"`
Kind string `json:"kind"`
Agent AgentRef `json:"agent,omitzero"`
// Body is the entry's content: the request or the decision, as JSON.
// For an accepted update it is the exact update bytes S256 covers.
Body json.RawMessage `json:"body,omitempty"`
// S256 is set on accepted updates (§8.4).
S256 string `json:"s256,omitempty"`
}
MissionLogEntry is one entry of a mission's log (draft -11 §8.3): an ordered record of the agent's actions and the supervision decisions.
type MissionRecord ¶
type MissionRecord struct {
S256 string `json:"s256"`
Blob []byte `json:"blob"`
Owner AgentRef `json:"owner"` // the agent that proposed it
// Person is the person who approved it.
Person string `json:"person"`
// Status is MissionActive or MissionTerminated; a terminated mission
// never returns to active.
Status string `json:"status"`
// TerminationReason records why it terminated (§8.6), outside the
// immutable blob.
TerminationReason string `json:"termination_reason,omitempty"`
ApprovedAt time.Time `json:"approved_at"`
// ExpiresAt is the blob's expires_at, zero when it has none.
ExpiresAt time.Time `json:"expires_at,omitempty"`
// Capabilities the PS reported at approval (§8.2).
Capabilities []string `json:"capabilities,omitempty"`
}
MissionRecord is an approved mission (draft -11 §8.2). Blob is the exact bytes S256 covers; the PS serves those same bytes wherever it later exposes the mission.
type MissionRequest ¶
type MissionRequest struct {
// Kind is KindMissionProposal, KindMissionUpdate, or
// KindMissionCompletion.
Kind Kind
// Agent proposed or owns the mission; Person is its bound person,
// empty for an unbound agent's proposal (the decision then names one).
Agent *aauth.AgentClaims
Person string
// Proposal is the agent-asserted proposal (proposals only).
Proposal *aauth.MissionProposal
// Mission and Action are the mission and the update or completion
// (updates and completions only). Read the mission's log with
// [Server.MissionLog] to judge them in context.
Mission *MissionRecord
Action *aauth.MissionAction
}
MissionRequest is a mission operation awaiting a decision (draft -11 §8): a proposal (§8.1), an update (§8.4), or a completion (§8.5).
type MissionStore ¶
type MissionStore interface {
// CreateMission stores a newly approved mission; ErrExists if S256 is
// already taken.
CreateMission(ctx context.Context, m *MissionRecord) error
// Mission returns the mission s256, or ErrNotFound. The lookup SHOULD
// cost the same whether or not the mission exists: the server compares
// ownership after it in constant time so that a missing mission and
// another agent's mission are answered alike (§8.7).
Mission(ctx context.Context, s256 string) (*MissionRecord, error)
// TerminateMission marks the mission terminated with reason. A
// terminated mission stays terminated (its first reason is kept).
TerminateMission(ctx context.Context, s256, reason string) error
// AppendMissionLog appends e to the mission's log.
AppendMissionLog(ctx context.Context, s256 string, e MissionLogEntry) error
// MissionLog returns the log in order.
MissionLog(ctx context.Context, s256 string) ([]MissionLogEntry, error)
}
MissionStore persists missions and their logs (draft -11 §8).
type Paths ¶
type Paths struct {
JWKS string
PersonToken string
AuthToken string
Mission string
Permission string
Audit string
Interaction string
Revocation string
// Pending is the prefix of pending URLs; a pending request's URL is
// Pending + id.
Pending string
}
Paths are the URL paths the PS serves, relative to its issuer. The metadata document is always at /.well-known/aauth-person.json. The defaults live under /ps/ so a PS can share an origin with an agent provider and an access server (role collocation, §4.3).
type Pending ¶
type Pending struct {
ID string `json:"id"`
Kind Kind `json:"kind"`
State State `json:"state"`
// Agent is the agent that made the request; only it may poll.
// AgentJTI is the agent token it presented; Subagent the sub-agent a
// parent requested for.
Agent AgentRef `json:"agent"`
AgentJTI string `json:"agent_jti,omitempty"`
Subagent AgentRef `json:"subagent,omitzero"`
// Person is the person the request is for, when known.
Person string `json:"person,omitempty"`
MissionS256 string `json:"mission_s256,omitempty"`
// What is asked, for display. Resource, Scope, and AccessServer are
// resource-asserted (from the resource token); Hints, Proposal,
// MissionAction, Permission, and Interaction are agent-asserted (§7.4).
Resource string `json:"resource,omitempty"`
Scope string `json:"scope,omitempty"`
AccessServer string `json:"access_server,omitempty"`
Hints aauth.TokenRequestHints `json:"hints,omitzero"`
Proposal *aauth.MissionProposal `json:"proposal,omitempty"`
MissionAction *aauth.MissionAction `json:"mission_action,omitempty"`
Permission *aauth.PermissionRequest `json:"permission,omitempty"`
Interaction *aauth.InteractionRequest `json:"interaction,omitempty"`
// Requirement is the AAuth-Requirement of the 202 (interaction,
// approval), and InteractionURL and Code its parameters. CodeCanonical
// is the canonical code PendingByCode looks up; CodeConsumed is set
// once the person arrived with it (single use, §11.6.3.1).
Requirement string `json:"requirement,omitempty"`
InteractionURL string `json:"interaction_url,omitempty"`
Code string `json:"code,omitempty"`
CodeCanonical string `json:"code_canonical,omitempty"`
CodeConsumed bool `json:"code_consumed,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
// Question is the clarification awaiting the agent's answer, asked at
// QuestionAt; Rounds counts the questions asked (§7.5.3).
Question *Question `json:"question,omitempty"`
QuestionAt time.Time `json:"question_at,omitzero"`
QuestionFromAS bool `json:"question_from_as,omitempty"`
Rounds int `json:"rounds,omitempty"`
Transcript []Exchange `json:"transcript,omitempty"`
// ResourceInteraction is the resource's own interaction (§7.2.3),
// which the person completes before the PS's consent;
// ResourceInteractionDone is set when its callback succeeded.
ResourceInteraction *aauth.ResourceInteraction `json:"resource_interaction,omitempty"`
ResourceInteractionDone bool `json:"resource_interaction_done,omitempty"`
// Federation is the access server's pending request, in four-party.
Federation *FederationState `json:"federation,omitempty"`
// The tokens the request depends on, for revocation (§11.12.4).
ResourceTokenIssuer string `json:"resource_token_iss,omitempty"`
ResourceTokenJTI string `json:"resource_token_jti,omitempty"`
UpstreamIssuer string `json:"upstream_iss,omitempty"`
UpstreamJTI string `json:"upstream_jti,omitempty"`
// Request is the PS's snapshot of the original request.
Request json.RawMessage `json:"request,omitempty"`
CreatedAt time.Time `json:"created_at"`
ReceivedAt time.Time `json:"received_at"`
ExpiresAt time.Time `json:"expires_at"`
// RelayDeadline, for an interaction relay with max_wait, is when the
// relay resolves even if the person has not engaged (§7.6.2).
RelayDeadline time.Time `json:"relay_deadline,omitzero"`
// Result is the terminal response, once resolved.
Result *Result `json:"result,omitempty"`
// Version supports optimistic updates (see PendingStore).
Version int64 `json:"version"`
}
Pending is a deferred request (draft -11 §11.8). The exported fields are what an approval UI needs to show the person; Request is the PS's opaque snapshot of the original request.
type PendingStore ¶
type PendingStore interface {
// CreatePending stores a new pending request (Version 1).
CreatePending(ctx context.Context, p *Pending) error
// Pending returns the pending request id, or ErrNotFound.
Pending(ctx context.Context, id string) (*Pending, error)
// PendingByCode returns the pending request whose canonical
// interaction code is code, or ErrNotFound.
PendingByCode(ctx context.Context, code string) (*Pending, error)
// UpdatePending replaces p if its Version matches (ErrConflict
// otherwise) and increments p.Version.
UpdatePending(ctx context.Context, p *Pending) error
// ResolvePendingIfMissionActive is UpdatePending for a request that
// belongs to a mission (p.MissionS256), done only if that mission is
// still active: otherwise it returns ErrMissionTerminated and changes
// nothing. The mission check and the update MUST be one atomic step with
// respect to TerminateMission, so that a request is never resolved with a
// grant after its mission ended.
ResolvePendingIfMissionActive(ctx context.Context, p *Pending) error
// PendingForResourceToken returns the unresolved pending requests
// started for the resource token (iss, jti) (§11.12.4).
PendingForResourceToken(ctx context.Context, iss, jti string) ([]*Pending, error)
// PendingForMission returns the unresolved pending requests that
// belong to the mission s256 (Pending.MissionS256).
PendingForMission(ctx context.Context, s256 string) ([]*Pending, error)
}
PendingStore persists deferred requests (draft -11 §11.8). Updates are optimistic: UpdatePending succeeds only when the stored Version equals the given record's Version, and then increments it.
type PermissionDecider ¶
type PermissionDecider interface {
DecidePermission(ctx context.Context, r *PermissionRequest) (Decision, error)
}
PermissionDecider decides permission requests. Allowed answers granted; Denied answers {"permission": "denied"} with Reason (a 200, §7.7.2).
type PermissionDeciderFunc ¶
type PermissionDeciderFunc func(ctx context.Context, r *PermissionRequest) (Decision, error)
PermissionDeciderFunc adapts a function to PermissionDecider.
func (PermissionDeciderFunc) DecidePermission ¶
func (f PermissionDeciderFunc) DecidePermission(ctx context.Context, r *PermissionRequest) (Decision, error)
DecidePermission calls f.
type PermissionRequest ¶
type PermissionRequest struct {
Agent *aauth.AgentClaims
Person string
Request *aauth.PermissionRequest
// Mission is the mission the request belongs to, if any; its
// approved_tools need no per-call permission.
Mission *MissionRecord
}
PermissionRequest is a permission request awaiting a decision (draft -11 §7.7): an action no remote resource governs.
type PersonTokenRecord ¶
type PersonTokenRecord struct {
JTI string `json:"jti"`
Resource string `json:"aud"` // the token's aud
Exp time.Time `json:"exp"`
Agent AgentRef `json:"agent"` // the agent that requested it
Subagent AgentRef `json:"subagent,omitempty"` // the sub-agent whose key it binds, if any
// AgentJTI and SubagentJTI are the jti of the agent token that signed
// the request and of the subagent_token parameter, if any; the issuers
// are Agent.Issuer and Subagent.Issuer. They let RecordPersonToken
// refuse a token requested with a credential revoked meanwhile.
AgentJTI string `json:"agent_jti,omitempty"`
SubagentJTI string `json:"subagent_jti,omitempty"`
Person string `json:"person"`
Subject string `json:"sub"`
// MissionS256 is the mission the token was issued under.
MissionS256 string `json:"mission_s256,omitempty"`
// UpstreamIssuer and UpstreamJTI name the upstream token of a call
// chaining request (§10.1.1), so revoking it walks the chain.
UpstreamIssuer string `json:"upstream_iss,omitempty"`
UpstreamJTI string `json:"upstream_jti,omitempty"`
// PresentedTo lists the access servers the PS presented the token to
// (§9.1.1); it is revoked there too.
PresentedTo []string `json:"presented_to,omitempty"`
}
PersonTokenRecord is a person token the PS issued (draft -11 §7.1 retention, §11.12.4).
type Question ¶
type Question struct {
Text string `json:"clarification"`
Options []string `json:"options,omitempty"`
// Timeout is how long the recipient has to answer, in seconds; zero
// means no deadline beyond the pending request's own.
Timeout int `json:"timeout,omitempty"`
}
Question is a clarification question (draft -11 §7.5.1).
type RateLimitError ¶
RateLimitError is a refusal by Config.Limiter, with how long to wait.
type Result ¶
type Result struct {
Status int `json:"status"`
ContentType string `json:"content_type,omitempty"`
Body []byte `json:"body,omitempty"`
}
Result is a terminal response stored for a pending request: what the pending URL answers once the request is resolved.
type Revoker ¶
type Revoker interface {
Revoke(ctx context.Context, recipient, dwk string, req aauth.RevocationRequest) (*aauth.RevocationResponse, error)
}
Revoker delivers a revocation to another server (draft -11 §11.12.1). The default discovers recipient's revocation_endpoint from its metadata document dwk and signs as the PS under the jwks_uri scheme.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a Person Server (draft -11 §7). It is an http.Handler serving the PS endpoints, its metadata document, and its JWKS; Server.Register adds the same routes to a shared mux when one origin hosts several roles.
func (*Server) Approve ¶
Approve resolves pending id as approved with g: the PS completes the request — issues the token, records the mission, grants the permission — and the agent's next poll receives the result. A completion that fails (a token expired while the person decided, a mission was terminated) resolves the request with that error instead. In four-party, approval of an auth token request starts federation and the request stays open until the access server answers.
func (*Server) Ask ¶
Ask puts a clarification question to the agent (draft -11 §7.5): the next poll answers 202 with requirement=clarification, and the agent's answer arrives in the request's Transcript. At most MaxClarificationRounds questions are asked per request.
func (*Server) CompleteResourceInteraction ¶
CompleteResourceInteraction records the outcome of a resource's own interaction (draft -11 §7.2.3), reached by redirecting the person to ResourceInteractionURL. callbackError is the error parameter of the callback, empty on success; an error abandons the request with the mapped polling error (§7.3.1).
func (*Server) ConsumeCode ¶
ConsumeCode is ConsumeCodeFor with no client key: attempts are limited as one pool.
func (*Server) ConsumeCodeFor ¶
ConsumeCodeFor is called by the interaction page when the person arrives with ?code= (draft -11 §11.6.3.1). It canonicalizes the code (case, hyphens, Crockford aliases), finds the pending request, consumes the code (single use), and marks the request interacting. An unknown, malformed, or already consumed code is ErrInvalidCode; an expired request is ErrResolved. client identifies who is entering codes (the page's session, or its address): with Config.Limiter set, attempts are limited per client — key "code:<client>" — and a limited attempt is *RateLimitError, checked before the code is looked up. The code only correlates the browser with the request: the page MUST authenticate the person before acting on their decision (§13.13).
func (*Server) Deny ¶
Deny resolves pending id as denied by the person or approver: denied (403) at the pending URL, or {"permission": "denied"} for a permission request (§7.7.2). reason is shown to the agent.
func (*Server) Fail ¶
Fail resolves pending id with a polling error (draft -11 §11.9.4): aauth.PollErrAbandoned when the person opened the interaction but did not complete it, aauth.PollErrExpired, or aauth.ErrCodeServerError.
func (*Server) MarkInteracting ¶
MarkInteracting records that the person has engaged with the request through a channel the PS controls (a notification they opened), so polls report status interacting.
func (*Server) Metadata ¶
func (s *Server) Metadata() aauth.PersonServerMetadata
Metadata returns the PS's metadata document (§11.2.2).
func (*Server) Mission ¶
Mission returns the mission s256 as the PS approved it: Blob is the exact bytes its identifier covers (§8.2.1). For the mission control plane and audit views; not exposed to agents.
func (*Server) MissionLog ¶
MissionLog returns the mission's log (§8.3). A party auditing a mission reads the blob and its accepted updates together.
func (*Server) PendingRequest ¶
PendingRequest returns pending request id, for an approval UI.
func (*Server) Register ¶
Register adds the PS routes to mux: the metadata document, the JWKS, the endpoints, and the pending URLs. Optional endpoints are registered only when configured.
func (*Server) RevokeAgent ¶
func (s *Server) RevokeAgent(ctx context.Context, agent AgentRef) ([]aauth.RevocationOutcome, error)
RevokeAgent revokes every person token and auth token the PS issued to agent — as the requesting agent or as the sub-agent they bind — and terminates what it federated (§11.12.4). It is the PS side of an agent provider's revocation, and what a person removing an agent triggers.
func (*Server) RevokeAuthToken ¶
func (s *Server) RevokeAuthToken(ctx context.Context, jti string) ([]aauth.RevocationOutcome, error)
RevokeAuthToken revokes a three-party auth token the PS issued, at its resource (§11.12.2).
func (*Server) RevokeBinding ¶
func (s *Server) RevokeBinding(ctx context.Context, agent AgentRef) ([]aauth.RevocationOutcome, error)
RevokeBinding ends the agent's association with its person (§13.14) — the agent can obtain no new tokens until a new binding is established — and revokes the tokens issued to it.
func (*Server) RevokePersonToken ¶
func (s *Server) RevokePersonToken(ctx context.Context, jti string) ([]aauth.RevocationOutcome, error)
RevokePersonToken revokes a person token the PS issued, with its cascade (draft -11 §11.12.4), and returns the outcome at each recipient. An outcome with an error may be retried by revoking again.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves the PS routes.
func (*Server) Signer ¶
func (s *Server) Signer() aauth.ServerSigner
Signer returns the server signer the PS uses for requests it makes in its own right (revocations, federation; §11.3.2).
func (*Server) TerminateMission ¶
func (s *Server) TerminateMission(ctx context.Context, s256, reason string) ([]aauth.RevocationOutcome, error)
TerminateMission terminates a mission (draft -11 §8.6) — the control plane operation for the person, the owning agent's operator, or an administrator — with reason (aauth.TerminationRevoked, aauth.TerminationSuperseded, aauth.TerminationAdministrative, ...). Later requests naming it are refused with mission_terminated, and the auth tokens issued under it are revoked (§11.12.4): three-party tokens at their resource, federated ones through their person token at the access server.
type State ¶
type State string
State is the lifecycle state of a pending request.
const ( StatePending State = "pending" // waiting for a decision StateInteracting State = "interacting" // the person has arrived at the interaction page StateDone State = "done" // resolved; Result holds the terminal response StateCancelled State = "cancelled" // withdrawn by the agent (§7.5.2.3) )
Pending request states. A request is open while pending or interacting; done and cancelled are terminal.
type Store ¶
type Store interface {
BindingStore
SubjectStore
TokenStore
MissionStore
PendingStore
}
Store is everything the PS persists. A hosting application implements it over its own database; see NewMemoryStore for the reference semantics.
type SubjectStore ¶
type SubjectStore interface {
// RecordSubject records that sub is person's directed identifier at
// resource. Recording the same triple again succeeds.
RecordSubject(ctx context.Context, resource, sub, person string) error
// SubjectPerson returns the person whose directed identifier at
// resource is sub, or ErrNotFound.
SubjectPerson(ctx context.Context, resource, sub string) (string, error)
}
SubjectStore records the directed identifiers the PS minted (draft -11 §7.1, §14.1), so it can resolve the person behind an upstream token's (aud, sub) in call chaining.
type TokenRequest ¶
type TokenRequest struct {
// Kind is KindPersonToken or KindAuthToken.
Kind Kind
// Agent is the agent that signed the request; Subagent, when set, the
// sub-agent the parent is requesting for (§10.2.3).
Agent *aauth.AgentClaims
Subagent *aauth.AgentClaims
// Person is the person the token would be issued for: the agent's
// bound person, or the person behind the upstream token in call
// chaining. Empty when the agent is not yet bound: the decision then
// names the person in its Grant (typically after an interaction).
Person string
// Resource is the resource the token is for.
Resource string
// ResourceToken is the verified resource token (auth tokens only), and
// Scope the scope it requests. AccessServer is its aud when the
// resource has its own AS (four-party), else empty.
ResourceToken *aauth.ResourceClaims
Scope string
AccessServer string
// Mission is the mission the request is made under, if any.
Mission *MissionRecord
// Chained reports a call chaining request (an upstream_token was
// verified, §10.1.1); Upstream is that token.
Chained bool
Upstream aauth.PresentedToken
// Capabilities are the agent's capabilities for this request, from the
// request or, within a mission, from its approval (§7.1).
Capabilities []string
// Hints are the agent-asserted optional parameters (justification,
// platform, device, ...). Present them as the agent's claims (§7.4).
Hints aauth.TokenRequestHints
}
TokenRequest is a person token or auth token request awaiting a consent decision (draft -11 §7.1, §7.2). Everything in it has been verified, except the agent-asserted Hints.
type TokenStore ¶
type TokenStore interface {
RecordAgentToken(ctx context.Context, r AgentTokenRecord) error
// AgentToken returns the accepted agent token (iss, jti), or
// ErrNotFound.
AgentToken(ctx context.Context, iss, jti string) (*AgentTokenRecord, error)
// RecordPersonToken records a person token the PS issued, unless
// issuing it is no longer allowed: for a token not issued on an
// upstream token, the agent r.Agent is no longer bound to r.Person
// (ErrBindingRevoked); for one issued on an upstream token, that token
// (r.UpstreamIssuer, r.UpstreamJTI) has been revoked (ErrRevoked); in
// either case the agent token (Agent.Issuer, r.AgentJTI) or sub-agent
// token (Subagent.Issuer, r.SubagentJTI) it was requested with has been
// revoked (ErrAgentRevoked), or its mission r.MissionS256 has been
// terminated (ErrMissionTerminated). Then it records nothing. As with
// RecordAuthToken, the checks and the insert MUST be atomic with
// respect to Unbind, Revoke and TerminateMission, so that a revocation
// stored before the insert is seen here and one stored after it finds
// r through TokensForAgent, PersonTokensFromUpstream or the mission's
// tokens. Otherwise an issuance racing a revocation escapes the
// cascade (§11.12.4).
RecordPersonToken(ctx context.Context, r PersonTokenRecord) error
// PersonToken returns the issued person token jti, or ErrNotFound.
PersonToken(ctx context.Context, jti string) (*PersonTokenRecord, error)
// PersonTokensForSubject returns the person tokens issued for the
// directed identifier sub at resource.
PersonTokensForSubject(ctx context.Context, resource, sub string) ([]PersonTokenRecord, error)
// MarkPresented records that the person token jti was presented to the
// access server as (§9.1.1).
MarkPresented(ctx context.Context, jti, as string) error
// RecordAuthToken records an auth token the PS issued or federated,
// unless the person token at the root of its grant (r.PersonIssuer,
// r.PersonJTI) has been revoked (ErrRevoked) or its mission
// r.MissionS256 terminated (ErrMissionTerminated): then it records
// nothing. The check and the insert MUST be atomic with respect to
// Revoke and TerminateMission, so that a revocation or termination
// stored before the insert is seen here and one stored after it finds
// r through AuthTokensForPersonToken or AuthTokensForMission.
// Otherwise an issuance racing a revocation escapes the cascade
// (§11.12.4).
RecordAuthToken(ctx context.Context, r AuthTokenRecord) error
// AuthToken returns the auth token (iss, jti), or ErrNotFound.
AuthToken(ctx context.Context, iss, jti string) (*AuthTokenRecord, error)
// TokensForAgent returns the person and auth tokens issued to agent,
// as the requesting agent or as the sub-agent they bind (§11.12.4: the
// cascade is by agent identity).
TokensForAgent(ctx context.Context, agent AgentRef) ([]PersonTokenRecord, []AuthTokenRecord, error)
// PersonTokensFromUpstream returns the person tokens issued with the
// upstream token (iss, jti).
PersonTokensFromUpstream(ctx context.Context, iss, jti string) ([]PersonTokenRecord, error)
// AuthTokensForPersonToken returns the auth tokens whose grant is
// rooted in the person token jti.
AuthTokensForPersonToken(ctx context.Context, jti string) ([]AuthTokenRecord, error)
// AuthTokensForMission returns the auth tokens issued under the
// mission s256.
AuthTokensForMission(ctx context.Context, s256 string) ([]AuthTokenRecord, error)
// Revoke records the revocation of (iss, jti), remembered until exp
// (plus clock skew). Revoking again succeeds.
Revoke(ctx context.Context, iss, jti string, exp time.Time) error
// IsRevoked reports whether (iss, jti) was revoked.
IsRevoked(ctx context.Context, iss, jti string) (bool, error)
}
TokenStore keeps the records revocation needs: the tokens the PS accepted and issued, and the revocations it received or made, keyed by (iss, jti) (draft -11 §11.12.1).