Documentation
¶
Overview ¶
Package auth provides service token caching and management for AppView. Service tokens are JWTs issued by a user's PDS to authorize AppView to act on their behalf when communicating with hold services. Tokens are cached with automatic expiry parsing and 10-second safety margins.
Package auth provides authentication and authorization for ATCR, including ATProto session validation, hold authorization (captain/crew membership), scope parsing, and token caching for OAuth and service tokens.
Index ¶
- Variables
- func CheckReadAccessWithCaptain(captain *atproto.CaptainRecord, userDID string) bool
- func CheckWriteAccessWithCaptain(captain *atproto.CaptainRecord, userDID string, isCrew bool) bool
- func CleanExpiredTokens()
- func CreateAppviewServiceToken(privateKey *atcrypto.PrivateKeyP256, appviewDID, holdDID, userDID string) (string, error)
- func DecodeDIDFromHyphens(s string) (string, bool)
- func GetCacheStats() map[string]any
- func GetOrFetchServiceToken(ctx context.Context, refresher *oauth.Refresher, ...) (string, error)
- func GetOrFetchServiceTokenWithAppPassword(ctx context.Context, did, holdDID, pdsEndpoint string) (string, error)
- func GetServiceToken(did, holdDID string) (token string, expiresAt time.Time)
- func InvalidateServiceToken(did, holdDID string)
- func P256ToECDSA(key *atcrypto.PrivateKeyP256) (*ecdsa.PrivateKey, error)
- func SetServiceToken(did, holdDID, token string) error
- func ValidateAccess(userDID, userHandle string, access []AccessEntry) error
- type AccessEntry
- type Cache
- type CachedSession
- type HoldAuthorizer
- type RemoteHoldAuthorizer
- func (a *RemoteHoldAuthorizer) CheckReadAccess(ctx context.Context, holdDID, userDID string) (bool, error)
- func (a *RemoteHoldAuthorizer) CheckWriteAccess(ctx context.Context, holdDID, userDID string) (bool, error)
- func (a *RemoteHoldAuthorizer) ClearAllDenials() error
- func (a *RemoteHoldAuthorizer) ClearCrewDenial(ctx context.Context, holdDID, userDID string) error
- func (a *RemoteHoldAuthorizer) GetCaptainRecord(ctx context.Context, holdDID string) (*atproto.CaptainRecord, error)
- func (a *RemoteHoldAuthorizer) IsCachedCrewMember(ctx context.Context, holdDID, userDID string) (bool, error)
- func (a *RemoteHoldAuthorizer) IsCrewMember(ctx context.Context, holdDID, userDID string) (bool, error)
- func (a *RemoteHoldAuthorizer) RecordCrewApproval(ctx context.Context, holdDID, userDID string) error
- type SessionResponse
- type SessionValidator
- type TokenCache
- type TokenCacheEntry
Constants ¶
This section is empty.
Variables ¶
var ( // ErrIdentityResolution indicates handle/DID resolution failed ErrIdentityResolution = errors.New("identity resolution failed") // ErrInvalidCredentials indicates PDS returned 401 (bad password/app-password) ErrInvalidCredentials = errors.New("invalid credentials") ErrPDSUnavailable = errors.New("PDS unavailable") )
Sentinel errors for authentication failures
var ErrHoldNotFound = fmt.Errorf("hold not found")
ErrHoldNotFound is returned when a hold's captain record cannot be found
ErrUnauthorized is returned when access is denied
Functions ¶
func CheckReadAccessWithCaptain ¶
func CheckReadAccessWithCaptain(captain *atproto.CaptainRecord, userDID string) bool
CheckReadAccessWithCaptain implements the standard read authorization logic This is shared across all HoldAuthorizer implementations Read access rules: - Public hold: allow anyone (even anonymous) - Private hold: require authentication (any authenticated user)
func CheckWriteAccessWithCaptain ¶
func CheckWriteAccessWithCaptain(captain *atproto.CaptainRecord, userDID string, isCrew bool) bool
CheckWriteAccessWithCaptain implements the standard write authorization logic This is shared across all HoldAuthorizer implementations Write access rules: - Must be authenticated - Must be hold owner OR crew member
func CleanExpiredTokens ¶
func CleanExpiredTokens()
CleanExpiredTokens prunes expired entries from the default cache.
func CreateAppviewServiceToken ¶
func CreateAppviewServiceToken(privateKey *atcrypto.PrivateKeyP256, appviewDID, holdDID, userDID string) (string, error)
CreateAppviewServiceToken creates a short-lived ES256 JWT for appview→hold communication. The token authenticates the appview when calling hold XRPC endpoints like updateCrewTier.
Claims:
- iss: appview DID (e.g. did:web:atcr.io)
- aud: hold DID (e.g. did:web:hold01.atcr.io)
- sub: user DID being acted upon
- exp: now + 60s
- iat: now
func DecodeDIDFromHyphens ¶ added in v0.1.3
DecodeDIDFromHyphens converts a hyphen-encoded DID back to colon-separated form. "did-plc-abc123" → "did:plc:abc123", "did-web-example.com" → "did:web:example.com" Returns the decoded DID and true if the input matched, or ("", false) otherwise.
func GetCacheStats ¶
GetCacheStats returns default-cache statistics for debugging.
func GetOrFetchServiceToken ¶
func GetOrFetchServiceToken( ctx context.Context, refresher *oauth.Refresher, did, holdDID, pdsEndpoint string, ) (string, error)
GetOrFetchServiceToken gets a service token for hold authentication. Checks cache first, then fetches from PDS with OAuth/DPoP if needed. This is the canonical implementation used by both middleware and crew registration.
IMPORTANT: Uses DoWithSession() to hold a per-DID lock through the entire PDS interaction. This prevents DPoP nonce race conditions when multiple Docker layers upload concurrently.
func GetOrFetchServiceTokenWithAppPassword ¶
func GetOrFetchServiceTokenWithAppPassword( ctx context.Context, did, holdDID, pdsEndpoint string, ) (string, error)
GetOrFetchServiceTokenWithAppPassword gets a service token using app-password Bearer authentication. Used when auth method is app_password instead of OAuth.
func GetServiceToken ¶
GetServiceToken returns the cached service token for (did, holdDID). Returns ("", zero time) if absent or expired. Delegates to the default package cache; tests that need isolation should construct a Cache.
func InvalidateServiceToken ¶
func InvalidateServiceToken(did, holdDID string)
InvalidateServiceToken removes (did, holdDID) from the default cache.
func P256ToECDSA ¶
func P256ToECDSA(key *atcrypto.PrivateKeyP256) (*ecdsa.PrivateKey, error)
P256ToECDSA converts an atcrypto P-256 private key to a stdlib *ecdsa.PrivateKey. This is needed because golang-jwt requires stdlib crypto types, while atcrypto wraps them in its own types. We re-parse via PKCS8 encoding round-trip.
func SetServiceToken ¶
SetServiceToken stores token under (did, holdDID) in the default cache, applying the standard 10s safety margin against the JWT's exp claim.
func ValidateAccess ¶
func ValidateAccess(userDID, userHandle string, access []AccessEntry) error
ValidateAccess checks if the requested access is allowed for the user For ATCR, users can only push to repositories under their own handle/DID
Types ¶
type AccessEntry ¶
type AccessEntry struct {
Type string `json:"type"` // "repository"
Name string `json:"name,omitempty"` // e.g., "alice/myapp"
Actions []string `json:"actions,omitempty"` // e.g., ["pull", "push"]
}
AccessEntry represents access permissions for a resource
func ParseScope ¶
func ParseScope(scopes []string) ([]AccessEntry, error)
ParseScope parses Docker registry scope strings into AccessEntry structures Scope format: "repository:alice/myapp:pull,push" Multiple scopes can be provided
type Cache ¶ added in v0.1.4
type Cache struct {
// contains filtered or unexported fields
}
Cache stores per-(DID, hold DID) service tokens with automatic expiry. The zero value is not usable; construct via NewCache. A default package instance backs the GetServiceToken/SetServiceToken/... wrappers; tests that need isolation can construct their own.
func DefaultCache ¶ added in v0.1.4
func DefaultCache() *Cache
DefaultCache returns the package-level cache that backs the GetServiceToken/SetServiceToken/... wrappers. Callers (notably ServiceAuthFetcher) use this when they want to read back a value that GetOrFetchServiceToken* wrote.
func (*Cache) CleanExpired ¶ added in v0.1.4
func (c *Cache) CleanExpired()
CleanExpired removes all expired entries.