Files
at-container-registry/pkg/hold/resolve.go
2025-10-13 10:40:03 -05:00

89 lines
1.9 KiB
Go

package hold
import (
"context"
"fmt"
"sync"
"time"
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
)
// handleCache provides caching for DID → handle resolution
// This reduces latency for pattern matching authorization checks
type handleCache struct {
mu sync.RWMutex
cache map[string]cacheEntry // did → handle
}
type cacheEntry struct {
handle string
expiresAt time.Time
}
const handleCacheTTL = 10 * time.Minute
var (
// Global handle cache instance
globalHandleCache = &handleCache{
cache: make(map[string]cacheEntry),
}
)
// get retrieves a cached handle for a DID
func (c *handleCache) get(did string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
entry, ok := c.cache[did]
if !ok || time.Now().After(entry.expiresAt) {
return "", false
}
return entry.handle, true
}
// set stores a handle in the cache
func (c *handleCache) set(did, handle string) {
c.mu.Lock()
defer c.mu.Unlock()
c.cache[did] = cacheEntry{
handle: handle,
expiresAt: time.Now().Add(handleCacheTTL),
}
}
// resolveHandle resolves a DID to its current handle using ATProto identity resolution
// Results are cached for 10 minutes to reduce latency
func resolveHandle(did string) (string, error) {
// Check cache first
if handle, ok := globalHandleCache.get(did); ok {
return handle, nil
}
// Cache miss - resolve from network
ctx := context.Background()
directory := identity.DefaultDirectory()
didParsed, err := syntax.ParseDID(did)
if err != nil {
return "", fmt.Errorf("invalid DID: %w", err)
}
ident, err := directory.LookupDID(ctx, didParsed)
if err != nil {
return "", fmt.Errorf("failed to resolve DID: %w", err)
}
handle := ident.Handle.String()
if handle == "" || handle == "handle.invalid" {
return "", fmt.Errorf("no valid handle found for DID")
}
// Cache the result
globalHandleCache.set(did, handle)
return handle, nil
}