~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
97 lines · 2.6 KB Raw History
package componentcore

import (
	"cmp"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/ingress"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/market"
)

const (
	ExposureIngressIdentityDir = "exposure-identity"

	ExposureServiceAuthTTL = 5 * time.Minute
)

type ExposureIngressIdentity struct {
	stateDir string

	mu sync.Mutex

	signers map[string]*exposureIngressSigner
}

type exposureIngressSigner struct {
	signer *market.Signer

	did string
}

var _ ingress.Keypair = (*exposureIngressSigner)(nil)

func NewExposureIngressIdentity(stateDir string) *ExposureIngressIdentity {
	return &ExposureIngressIdentity{stateDir: stateDir, signers: map[string]*exposureIngressSigner{}}
}

func (s *exposureIngressSigner) DID() string {
	return s.did
}

func (s *exposureIngressSigner) Sign(data []byte) ([]byte, error) {
	return s.signer.PrivateKey.HashAndSign(data)
}

func (i *ExposureIngressIdentity) Keypair(ctx context.Context, workspace, name string) (ingress.Keypair, error) {
	signer, err := i.signer(workspace, name)
	if err != nil {
		return nil, err
	}
	return signer, nil
}

func (i *ExposureIngressIdentity) ServiceAuthToken(ctx context.Context, workspace, name, aud, nsid string) (string, error) {
	signer, err := i.signer(workspace, name)
	if err != nil {
		return "", err
	}
	return ingress.SignServiceAuth(signer.signer.PrivateKey, signer.did, aud, nsid, ExposureServiceAuthTTL)
}

func (i *ExposureIngressIdentity) signer(workspace, name string) (*exposureIngressSigner, error) {
	if i == nil || i.stateDir == "" {
		return nil, errors.New("componentcore: the exposure ingress identity has no state dir")
	}
	workspace = cmp.Or(workspace, DefaultWorkspace)
	key := workspace + "/" + name
	i.mu.Lock()
	defer i.mu.Unlock()
	if existing := i.signers[key]; existing != nil {
		return existing, nil
	}
	dir := filepath.Join(i.stateDir, ExposureIngressIdentityDir)
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return nil, fmt.Errorf("componentcore: the exposure ingress identity dir: %w", err)
	}
	sum := sha256.Sum256([]byte(key))
	path := filepath.Join(dir, hex.EncodeToString(sum[:8])+".jwk")
	signer, err := market.LoadOrCreateSignerFile(path)
	if err != nil {
		return nil, fmt.Errorf("componentcore: the exposure ingress identity for %s: %w", key, err)
	}
	did, err := signer.PublicDID()
	if err != nil {
		return nil, fmt.Errorf("componentcore: the exposure ingress DID for %s: %w", key, err)
	}
	out := &exposureIngressSigner{signer: signer, did: did}
	i.signers[key] = out
	return out, nil
}