Gitfed
bastien-mrq/gitfed / internal / store / trust.go
package store

import (
	"time"

	bolt "go.etcd.io/bbolt"
)

// TrustStatus tracks whether a remote instance's CA has been approved.
type TrustStatus string

const (
	TrustPending TrustStatus = "pending"
	TrustTrusted TrustStatus = "trusted"
)

// TrustedCA records a remote instance's CA public key and whether we trust
// it, per DESIGN.md §5.2.
type TrustedCA struct {
	Domain      string      `json:"domain"`
	CAPublicKey string      `json:"ca_public_key"` // authorized_keys format
	Status      TrustStatus `json:"status"`
	FirstSeenAt time.Time   `json:"first_seen_at"`
	ApprovedAt  *time.Time  `json:"approved_at,omitempty"`
}

func (s *Store) GetTrustedCA(domain string) (TrustedCA, error) {
	var t TrustedCA
	err := s.db.View(func(tx *bolt.Tx) error {
		return getJSON(tx, bucketTrust, domain, &t)
	})
	return t, err
}

func (s *Store) PutTrustedCA(t TrustedCA) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		return putJSON(tx, bucketTrust, t.Domain, t)
	})
}

func (s *Store) ListTrustedCAs() ([]TrustedCA, error) {
	var out []TrustedCA
	err := s.db.View(func(tx *bolt.Tx) error {
		var err error
		out, err = listJSON[TrustedCA](tx, bucketTrust)
		return err
	})
	return out, err
}

// CountPendingTrust is a cheap version of ListTrustedCAs for the admin nav
// badge (see cmd/gitfed-web/render.go) — computed on every page render for
// a logged-in admin, so it stays a count rather than the full list.
func (s *Store) CountPendingTrust() (int, error) {
	all, err := s.ListTrustedCAs()
	if err != nil {
		return 0, err
	}
	n := 0
	for _, t := range all {
		if t.Status == TrustPending {
			n++
		}
	}
	return n, nil
}

// ApproveTrustedCA flips a pending CA record to trusted.
func (s *Store) ApproveTrustedCA(domain string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		var t TrustedCA
		if err := getJSON(tx, bucketTrust, domain, &t); err != nil {
			return err
		}
		now := time.Now().UTC()
		t.Status = TrustTrusted
		t.ApprovedAt = &now
		return putJSON(tx, bucketTrust, domain, t)
	})
}

func (s *Store) DeleteTrustedCA(domain string) error {
	return s.db.Update(func(tx *bolt.Tx) error {
		return deleteKey(tx, bucketTrust, domain)
	})
}