Documentation
¶
Overview ¶
Package oauth provides OAuth client configuration and helper functions for ATCR. It provides helpers for setting up indigo's OAuth library with ATCR-specific configuration, including default scopes, confidential client setup, and interactive browser-based authentication flows.
Index ¶
- func ExpandIncludeScopes(scopes []string) []string
- func GenerateKeyID(privateKey *atcrypto.PrivateKeyP256) (string, error)
- func GenerateOrLoadClientKey(keyPath string) (*atcrypto.PrivateKeyP256, error)
- func GenerateOrLoadKey(keyPath string, keyType KeyType) (atcrypto.PrivateKey, error)
- func GenerateOrLoadPDSKey(keyPath string) (*atcrypto.PrivateKeyK256, error)
- func GetDefaultScopes(did string) []string
- func IsPEMFormat(data []byte) bool
- func MultibaseToPrivateKey(encoded string) (*atcrypto.PrivateKeyP256, error)
- func NewClientApp(baseURL string, store oauth.ClientAuthStore, scopes []string, keyPath string, ...) (*oauth.ClientApp, error)
- func NewClientAppWithKey(baseURL string, store oauth.ClientAuthStore, scopes []string, ...) (*oauth.ClientApp, error)
- func OpenBrowser(url string) error
- func PrivateKeyToMultibase(key *atcrypto.PrivateKeyP256) string
- func RedirectURI(baseURL string) string
- func ScopesMatch(stored, desired []string) bool
- func StartSignupFlow(ctx context.Context, clientApp *oauth.ClientApp, authServerHost string) (string, error)
- type CommandExecutor
- type InteractiveResult
- type KeyType
- type PostAuthCallback
- type Refresher
- func (r *Refresher) DeleteSession(ctx context.Context, did string) error
- func (r *Refresher) DoWithSession(ctx context.Context, did string, fn func(session *oauth.ClientSession) error) error
- func (r *Refresher) SetUISessionStore(store UISessionStore)
- func (r *Refresher) ValidateSession(ctx context.Context, did string) error
- type Server
- func (s *Server) ServeAuthorize(w http.ResponseWriter, r *http.Request)
- func (s *Server) ServeCallback(w http.ResponseWriter, r *http.Request)
- func (s *Server) SetPostAuthCallback(callback PostAuthCallback)
- func (s *Server) SetRefresher(refresher *Refresher)
- func (s *Server) SetUISessionStore(store UISessionStore)
- type SessionCache
- type UISessionStore
- type UserStore
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExpandIncludeScopes ¶
ExpandIncludeScopes expands any "include:" prefixed scopes to their full form by looking up the corresponding permission-set in the embedded lexicon files. For example, "include:io.atcr.authFullApp" expands to "repo?collection=io.atcr.manifest&..."
func GenerateKeyID ¶
func GenerateKeyID(privateKey *atcrypto.PrivateKeyP256) (string, error)
GenerateKeyID generates a stable key ID from a P256 public key Uses the first 8 characters of the hex-encoded SHA256 hash of the public key bytes
func GenerateOrLoadClientKey ¶
func GenerateOrLoadClientKey(keyPath string) (*atcrypto.PrivateKeyP256, error)
GenerateOrLoadClientKey generates a new P256 key pair or loads an existing one This is a convenience wrapper for OAuth client keys
func GenerateOrLoadKey ¶
func GenerateOrLoadKey(keyPath string, keyType KeyType) (atcrypto.PrivateKey, error)
GenerateOrLoadKey generates a new key pair or loads an existing one Supports both P-256 (OAuth) and K-256 (ATProto PDS) key types
func GenerateOrLoadPDSKey ¶
func GenerateOrLoadPDSKey(keyPath string) (*atcrypto.PrivateKeyK256, error)
GenerateOrLoadPDSKey generates a new K256 key pair or loads an existing one This is a convenience wrapper for ATProto PDS signing keys
func GetDefaultScopes ¶
GetDefaultScopes returns the default OAuth scopes for ATCR registry operations. Includes io.atcr.authFullApp permission-set plus individual scopes for PDS compatibility. Blob scopes are listed explicitly (not supported in Lexicon permission-sets).
func IsPEMFormat ¶
IsPEMFormat checks if bytes are in PEM format (for migration detection) Exported for testing and migration utilities
func MultibaseToPrivateKey ¶
func MultibaseToPrivateKey(encoded string) (*atcrypto.PrivateKeyP256, error)
MultibaseToPrivateKey parses a multibase-encoded P256 private key
func NewClientApp ¶
func NewClientApp(baseURL string, store oauth.ClientAuthStore, scopes []string, keyPath string, clientName string) (*oauth.ClientApp, error)
NewClientApp creates an indigo OAuth ClientApp with ATCR-specific configuration Automatically configures confidential client for production deployments keyPath specifies where to store/load the OAuth client P-256 key (ignored for localhost) clientName is added to OAuth client metadata (currently unused, reserved for future)
func NewClientAppWithKey ¶
func NewClientAppWithKey(baseURL string, store oauth.ClientAuthStore, scopes []string, privateKey *atcrypto.PrivateKeyP256, clientName string) (*oauth.ClientApp, error)
NewClientAppWithKey creates an indigo OAuth ClientApp with a pre-loaded P-256 key. Used by AppView when loading keys from the database instead of disk. For localhost development, privateKey is ignored (public client).
func OpenBrowser ¶
OpenBrowser opens the default browser to the given URL. This is the public API that maintains backward compatibility.
func PrivateKeyToMultibase ¶
func PrivateKeyToMultibase(key *atcrypto.PrivateKeyP256) string
PrivateKeyToMultibase converts a P256 private key to multibase format Required by indigo's SetClientSecret() API
func RedirectURI ¶
RedirectURI returns the OAuth redirect URI for ATCR
func ScopesMatch ¶
ScopesMatch checks if two scope lists are equivalent (order-independent) Returns true if both lists contain the same scopes, regardless of order. Expands any "include:" prefixed scopes in the desired list before comparing, since the PDS returns expanded scopes in the stored session.
func StartSignupFlow ¶ added in v0.1.3
func StartSignupFlow(ctx context.Context, clientApp *oauth.ClientApp, authServerHost string) (string, error)
StartSignupFlow starts an OAuth authorization flow pointed at a specific authorization server with prompt=create, asking the server to show its signup UI rather than its login UI.
indigo's ClientApp.StartAuthFlow does not expose prompt, so this duplicates the PAR + redirect build using only exported indigo helpers. The resulting AuthRequestData is persisted via clientApp.Store so the existing ServeCallback path handles the return leg unchanged.
authServerHost is the PDS origin (e.g. "https://eurosky.social"). It gets resolved to the actual OAuth auth server URL before the PAR call.
Types ¶
type CommandExecutor ¶
CommandExecutor is an interface for executing system commands. This allows for dependency injection and mocking in tests.
type InteractiveResult ¶
type InteractiveResult struct {
SessionData *oauth.ClientSessionData
Session *oauth.ClientSession
ClientApp *oauth.ClientApp
}
InteractiveResult contains the result of an interactive OAuth flow
func InteractiveFlowWithCallback ¶
func InteractiveFlowWithCallback( ctx context.Context, baseURL string, handle string, scopes []string, clientName string, registerCallback func(handler http.HandlerFunc) error, displayAuthURL func(string) error, ) (*InteractiveResult, error)
InteractiveFlowWithCallback runs an interactive OAuth flow with explicit callback handling This version allows the caller to register the callback handler before starting the flow
type PostAuthCallback ¶
PostAuthCallback is called after successful OAuth authentication. Parameters: ctx, did, handle, pdsEndpoint, sessionID This allows AppView to perform business logic (profile creation, avatar fetch, etc.) without coupling the OAuth package to AppView-specific dependencies.
type Refresher ¶
type Refresher struct {
// contains filtered or unexported fields
}
Refresher manages OAuth sessions and token refresh for AppView Sessions are loaded fresh from database on every request (database is source of truth)
func NewRefresher ¶
NewRefresher creates a new session refresher
func (*Refresher) DeleteSession ¶
DeleteSession removes an OAuth session from storage and optionally invalidates the UI session This is called when OAuth authentication fails to force re-authentication
func (*Refresher) DoWithSession ¶
func (r *Refresher) DoWithSession(ctx context.Context, did string, fn func(session *oauth.ClientSession) error) error
DoWithSession executes a function with a locked OAuth session. The lock is held for the entire duration of the function, preventing DPoP nonce races.
This is the preferred way to make PDS requests that require OAuth/DPoP authentication. The lock is held through the entire PDS interaction, ensuring that:
- Only one goroutine at a time can negotiate DPoP nonces with the PDS for a given DID
- The session's PersistSessionCallback saves the updated nonce before other goroutines load
- Concurrent layer uploads don't race on stale nonces
Why locking is critical: During docker push, multiple layers upload concurrently. Each layer creates a new ClientSession by loading from database. Without locking, this race condition occurs:
- Layer A loads session with stale DPoP nonce from DB
- Layer B loads session with same stale nonce (A hasn't updated DB yet)
- Layer A makes request → 401 "use_dpop_nonce" → gets fresh nonce → saves to DB
- Layer B makes request → 401 "use_dpop_nonce" (using stale nonce from step 2)
- DPoP nonce thrashing continues, eventually causing 500 errors
With per-DID locking:
- Layer A acquires lock, loads session, handles nonce negotiation, saves, releases lock
- Layer B acquires lock AFTER A releases, loads fresh nonce from DB, succeeds
Example usage:
var result MyResult
err := refresher.DoWithSession(ctx, did, func(session *oauth.ClientSession) error {
resp, err := session.DoWithAuth(session.Client, req, "com.atproto.server.getServiceAuth")
if err != nil {
return err
}
// Parse response into result...
return nil
})
func (*Refresher) SetUISessionStore ¶
func (r *Refresher) SetUISessionStore(store UISessionStore)
SetUISessionStore sets the UI session store for invalidating sessions on OAuth failures
func (*Refresher) ValidateSession ¶
ValidateSession checks if an OAuth session is usable by attempting to load it. This triggers token refresh if needed (via indigo's auto-refresh in DoWithSession). Returns nil if session is valid, error if session is invalid/expired/needs re-auth.
This is used by the token handler to validate OAuth sessions before issuing JWTs, preventing the flood of errors that occurs when a stale session is discovered during parallel layer uploads.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server handles OAuth authorization for the AppView
func (*Server) ServeAuthorize ¶
func (s *Server) ServeAuthorize(w http.ResponseWriter, r *http.Request)
ServeAuthorize handles GET /auth/oauth/authorize
func (*Server) ServeCallback ¶
func (s *Server) ServeCallback(w http.ResponseWriter, r *http.Request)
ServeCallback handles GET /auth/oauth/callback
func (*Server) SetPostAuthCallback ¶
func (s *Server) SetPostAuthCallback(callback PostAuthCallback)
SetPostAuthCallback sets the callback to be invoked after successful OAuth authentication This allows AppView to inject business logic without coupling the OAuth package
func (*Server) SetRefresher ¶
SetRefresher sets the refresher for invalidating session cache
func (*Server) SetUISessionStore ¶
func (s *Server) SetUISessionStore(store UISessionStore)
SetUISessionStore sets the UI session store for web login
type SessionCache ¶
type SessionCache struct {
Session *oauth.ClientSession
SessionID string
}
SessionCache represents a cached OAuth session