Files
at-container-registry/pkg/auth/hold_authorizer.go

100 lines
3.3 KiB
Go

package auth
import (
"context"
"fmt"
"log/slog"
"atcr.io/pkg/atproto"
)
// HoldAuthorizer checks if a DID has read/write access to a hold
// Implementations can query local PDS (hold service) or remote XRPC (appview)
type HoldAuthorizer interface {
// CheckReadAccess checks if userDID can read from holdDID
// Returns: (allowed bool, error)
CheckReadAccess(ctx context.Context, holdDID, userDID string) (bool, error)
// CheckWriteAccess checks if userDID can write to holdDID
// Returns: (allowed bool, error)
CheckWriteAccess(ctx context.Context, holdDID, userDID string) (bool, error)
// GetCaptainRecord retrieves the captain record for a hold
// Used to check public flag and allowAllCrew settings
GetCaptainRecord(ctx context.Context, holdDID string) (*atproto.CaptainRecord, error)
// IsCrewMember checks if userDID is a crew member of holdDID
IsCrewMember(ctx context.Context, holdDID, userDID string) (bool, error)
// ClearCrewDenial removes any cached denial for a user/hold pair
// Called when user successfully becomes a crew member to ensure immediate access
// Returns nil if no denial cache exists or invalidation succeeds
ClearCrewDenial(ctx context.Context, holdDID, userDID string) error
}
// 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 CheckReadAccessWithCaptain(captain *atproto.CaptainRecord, userDID string) bool {
if captain.Public {
// Public hold - allow anyone (even anonymous)
return true
}
// Private hold - require authentication
// Any authenticated user with a DID can read
if userDID == "" {
// Anonymous user trying to access private hold
return false
}
// For MVP: assume DID presence means they have sailor.profile
// Future: could query PDS to verify sailor.profile exists
return true
}
// 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 CheckWriteAccessWithCaptain(captain *atproto.CaptainRecord, userDID string, isCrew bool) bool {
slog.Debug("Checking write access", "userDID", userDID, "owner", captain.Owner, "isCrew", isCrew)
if userDID == "" {
// Anonymous writes not allowed
slog.Debug("Write access denied",
"userDID", userDID,
"denial_reason", "anonymous_user",
"message", "anonymous writes not allowed")
return false
}
// Check if DID is the hold owner
if userDID == captain.Owner {
// Owner always has write access
slog.Debug("Write access allowed: user is hold owner")
return true
}
// Check if DID is a crew member
if isCrew {
slog.Debug("Write access allowed: user is crew member")
} else {
slog.Debug("Write access denied",
"userDID", userDID,
"owner", captain.Owner,
"denial_reason", "not_owner_or_crew",
"message", "user is not owner or crew member")
}
return isCrew
}
// ErrHoldNotFound is returned when a hold's captain record cannot be found
var ErrHoldNotFound = fmt.Errorf("hold not found")
// ErrUnauthorized is returned when access is denied
var ErrUnauthorized = fmt.Errorf("unauthorized")