mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-04-20 16:40:29 +00:00
100 lines
3.3 KiB
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")
|