mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-06-07 07:42:35 +00:00
132 lines
3.4 KiB
Go
132 lines
3.4 KiB
Go
package hold
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
|
|
"atcr.io/pkg/atproto"
|
|
"github.com/bluesky-social/indigo/atproto/identity"
|
|
"github.com/bluesky-social/indigo/atproto/syntax"
|
|
)
|
|
|
|
// isAuthorizedRead checks if a DID can read from this hold
|
|
// Authorization:
|
|
// - Public hold: allow anonymous (empty DID) or any authenticated user
|
|
// - Private hold: require authentication (any user with sailor.profile)
|
|
func (s *HoldService) isAuthorizedRead(did string) bool {
|
|
// Check hold public flag
|
|
isPublic, err := s.isHoldPublic()
|
|
if err != nil {
|
|
log.Printf("ERROR: Failed to check hold public flag: %v", err)
|
|
// Fail secure - deny access on error
|
|
return false
|
|
}
|
|
|
|
if isPublic {
|
|
// Public hold - allow anyone (even anonymous)
|
|
return true
|
|
}
|
|
|
|
// Private hold - require authentication
|
|
// Any authenticated user with sailor.profile can read
|
|
if did == "" {
|
|
// 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
|
|
}
|
|
|
|
// isAuthorizedWrite checks if a DID can write to this hold
|
|
// Authorization: must be hold owner OR crew member
|
|
func (s *HoldService) isAuthorizedWrite(did string) bool {
|
|
if did == "" {
|
|
// Anonymous writes not allowed
|
|
return false
|
|
}
|
|
|
|
// Check if DID is the hold owner
|
|
ownerDID := s.config.Registration.OwnerDID
|
|
if ownerDID == "" {
|
|
log.Printf("ERROR: Hold owner DID not configured")
|
|
return false
|
|
}
|
|
|
|
if did == ownerDID {
|
|
// Owner always has write access
|
|
return true
|
|
}
|
|
|
|
// Check if DID is a crew member
|
|
isCrew, err := s.isCrewMember(did)
|
|
if err != nil {
|
|
log.Printf("ERROR: Failed to check crew membership: %v", err)
|
|
return false
|
|
}
|
|
|
|
return isCrew
|
|
}
|
|
|
|
// isHoldPublic checks if this hold allows public (anonymous) reads
|
|
func (s *HoldService) isHoldPublic() (bool, error) {
|
|
// Use cached config value for now
|
|
// Future: could query PDS for hold record to get live value
|
|
return s.config.Server.Public, nil
|
|
}
|
|
|
|
// isCrewMember checks if a DID is a crew member of this hold
|
|
func (s *HoldService) isCrewMember(did string) (bool, error) {
|
|
ownerDID := s.config.Registration.OwnerDID
|
|
if ownerDID == "" {
|
|
return false, fmt.Errorf("hold owner DID not configured")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Resolve owner's PDS endpoint using indigo
|
|
directory := identity.DefaultDirectory()
|
|
ownerDIDParsed, err := syntax.ParseDID(ownerDID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("invalid owner DID: %w", err)
|
|
}
|
|
|
|
ident, err := directory.LookupDID(ctx, ownerDIDParsed)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to resolve owner PDS: %w", err)
|
|
}
|
|
|
|
pdsEndpoint := ident.PDSEndpoint()
|
|
if pdsEndpoint == "" {
|
|
return false, fmt.Errorf("no PDS endpoint found for owner")
|
|
}
|
|
|
|
// Create unauthenticated client to read public records
|
|
client := atproto.NewClient(pdsEndpoint, ownerDID, "")
|
|
|
|
// List crew records for this hold
|
|
// Crew records are public, so we can read them without auth
|
|
records, err := client.ListRecords(ctx, atproto.HoldCrewCollection, 100)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to list crew records: %w", err)
|
|
}
|
|
|
|
// Check if DID is in crew list
|
|
for _, record := range records {
|
|
var crewRecord atproto.HoldCrewRecord
|
|
if err := json.Unmarshal(record.Value, &crewRecord); err != nil {
|
|
continue
|
|
}
|
|
|
|
if crewRecord.Member == did {
|
|
// Found crew membership
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|