~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
117 lines · 3.9 KB Raw History
package atproto

import (
	"encoding/json"
	"slices"
	"strings"

	"k8s.io/apimachinery/pkg/api/meta"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/conds"
)

func Associate(id *v1alpha1.MarketIdentity, watch *v1alpha1.AtprotoRecordWatch, status *v1alpha1.MarketIdentityStatus) {
	declared := uniqueDIDs(id.Spec.ApprovedAssociations)
	previous := make([]string, 0, len(status.Associations))
	for _, did := range uniqueDIDs(status.Associations) {
		if !slices.Contains(declared, did) {
			previous = append(previous, did)
		}
	}
	verified, observed := verifiedClaims(id, watch, status)
	if !observed {
		verified = previous
	}
	removed := false
	for _, did := range previous {
		if !slices.Contains(verified, did) {
			removed = true
		}
	}
	status.Associations = uniqueDIDs(append(slices.Clone(declared), verified...))
	if removed && len(status.Associations) == 0 {
		conds.Set(&status.Conditions, id.Generation, v1alpha1.ConditionAssociated, false, ReasonRevoked, "the operator's verified claim record is gone")
		return
	}
	setAssociated(status, id.Generation, id.Spec.Association)
}

func verifiedClaims(id *v1alpha1.MarketIdentity, watch *v1alpha1.AtprotoRecordWatch, status *v1alpha1.MarketIdentityStatus) ([]string, bool) {
	if watch == nil || id.Spec.Mode != v1alpha1.MarketIdentityModeLocalRepo || id.Spec.Association == v1alpha1.AssociationSkip {
		return nil, false
	}
	subject := strings.TrimSpace(status.Did)
	if subject == "" {
		return nil, false
	}
	if !meta.IsStatusConditionTrue(watch.Status.Conditions, v1alpha1.ConditionVerified) && len(watch.Status.Deleted) == 0 {
		return nil, false
	}
	var out []string
	for _, record := range watch.Status.Records {
		did := associationClaimDid(record, subject, id.Spec.AssociationService)
		if did == "" {
			continue
		}
		if operator := strings.TrimSpace(id.Spec.AssociateWith); strings.HasPrefix(operator, "did:") && did != operator {
			continue
		}
		out = append(out, did)
	}
	slices.Sort(out)
	return slices.Compact(out), true
}

func uniqueDIDs(in []string) []string {
	out := make([]string, 0, len(in))
	for _, did := range in {
		if did = strings.TrimSpace(did); did != "" && !slices.Contains(out, did) {
			out = append(out, did)
		}
	}
	return out
}

func associationClaimDid(record v1alpha1.WatchedRecord, subject, service string) string {
	var value struct {
		Challenge string `json:"challenge"`
		KeyID     string `json:"keyId"`
		Service   string `json:"service"`
	}
	if err := json.Unmarshal(record.Value, &value); err != nil {
		return ""
	}
	challenge := strings.TrimSpace(value.Challenge)
	keyID := strings.TrimSpace(value.KeyID)
	if strings.TrimSpace(service) == "" || strings.TrimSpace(value.Service) != service {
		return ""
	}
	if strings.HasPrefix(challenge, "did:") && keyID == subject && record.Did == challenge {
		return challenge
	}
	return ""
}

func associatedTrue(status *v1alpha1.MarketIdentityStatus) bool {
	return meta.IsStatusConditionTrue(status.Conditions, v1alpha1.ConditionAssociated)
}

func setAssociated(status *v1alpha1.MarketIdentityStatus, generation int64, mode v1alpha1.AssociationMode) {
	if refCondition(status, generation) {
		return
	}
	if mode == v1alpha1.AssociationSkip {
		conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, true, "AssociationSkipped", "the association is skipped")
		return
	}
	if len(status.Associations) > 0 {
		conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, true, "Associated", "the operator association is approved")
		return
	}
	if revoked := meta.FindStatusCondition(status.Conditions, v1alpha1.ConditionAssociated); revoked != nil && revoked.Reason == ReasonRevoked {
		conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, false, ReasonRevoked, revoked.Message)
		return
	}
	conds.Set(&status.Conditions, generation, v1alpha1.ConditionAssociated, false, "AwaitingAssociation", "the PDS is ready; the association is pending")
}