mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-19 00:34:16 +00:00
create identity resolver to reduce duplicate lookups
This commit is contained in:
@@ -14,7 +14,6 @@ import (
|
||||
"atcr.io/pkg/atproto"
|
||||
"atcr.io/pkg/auth/oauth"
|
||||
"github.com/bluesky-social/indigo/atproto/identity"
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
@@ -257,17 +256,6 @@ func (h *ManifestDetailHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// resolveIdentityToDID is a helper function that resolves a handle or DID to a DID
|
||||
func resolveIdentityToDID(ctx context.Context, directory identity.Directory, identityStr string) (string, error) {
|
||||
// Parse as AT identifier (handle or DID)
|
||||
atID, err := syntax.ParseAtIdentifier(identityStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Resolve to DID via directory
|
||||
ident, err := directory.Lookup(ctx, *atID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ident.DID.String(), nil
|
||||
// Resolve to DID via directory (handles both handles and DIDs)
|
||||
return atproto.ResolveHandleToDID(ctx, identityStr)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
|
||||
"atcr.io/pkg/appview/db"
|
||||
"atcr.io/pkg/atproto"
|
||||
)
|
||||
@@ -137,21 +135,11 @@ func (b *BackfillWorker) backfillRepo(ctx context.Context, did, collection strin
|
||||
}
|
||||
|
||||
// Resolve DID to get user's PDS endpoint
|
||||
didParsed, err := syntax.ParseDID(did)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid DID %s: %w", did, err)
|
||||
}
|
||||
|
||||
ident, err := b.processor.directory.LookupDID(ctx, didParsed)
|
||||
pdsEndpoint, err := atproto.ResolveDIDToPDS(ctx, did)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to resolve DID to PDS: %w", err)
|
||||
}
|
||||
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
if pdsEndpoint == "" {
|
||||
return 0, fmt.Errorf("no PDS endpoint found for DID %s", did)
|
||||
}
|
||||
|
||||
// Create a client for this user's PDS with the user's DID
|
||||
// This allows GetRecord to work properly with the repo parameter
|
||||
pdsClient := atproto.NewClient(pdsEndpoint, did, "")
|
||||
|
||||
@@ -9,9 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bluesky-social/indigo/atproto/identity"
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
|
||||
"atcr.io/pkg/appview/db"
|
||||
"atcr.io/pkg/atproto"
|
||||
)
|
||||
@@ -20,7 +17,6 @@ import (
|
||||
// This eliminates code duplication between the two data ingestion paths
|
||||
type Processor struct {
|
||||
db *sql.DB
|
||||
directory identity.Directory
|
||||
userCache *UserCache // Optional - enabled for Worker, disabled for Backfill
|
||||
useCache bool
|
||||
}
|
||||
@@ -30,7 +26,6 @@ type Processor struct {
|
||||
func NewProcessor(database *sql.DB, useCache bool) *Processor {
|
||||
p := &Processor{
|
||||
db: database,
|
||||
directory: atproto.GetDirectory(),
|
||||
useCache: useCache,
|
||||
}
|
||||
|
||||
@@ -62,28 +57,9 @@ func (p *Processor) EnsureUser(ctx context.Context, did string) error {
|
||||
}
|
||||
|
||||
// Resolve DID to get handle and PDS endpoint
|
||||
didParsed, err := syntax.ParseDID(did)
|
||||
resolvedDID, handle, pdsEndpoint, err := atproto.ResolveIdentity(ctx, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse DID: %w", err)
|
||||
}
|
||||
|
||||
ident, err := p.directory.LookupDID(ctx, didParsed)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup DID: %w", err)
|
||||
}
|
||||
|
||||
resolvedDID := ident.DID.String()
|
||||
handle := ident.Handle.String()
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
|
||||
// If handle is invalid, use DID as display name
|
||||
if handle == "handle.invalid" || handle == "" {
|
||||
handle = resolvedDID
|
||||
}
|
||||
|
||||
// PDS endpoint is required - we can't make XRPC calls without it
|
||||
if pdsEndpoint == "" {
|
||||
return fmt.Errorf("no PDS endpoint found for DID: %s", resolvedDID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch user's Bluesky profile record from their PDS (including avatar)
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bluesky-social/indigo/atproto/identity"
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
"github.com/distribution/distribution/v3"
|
||||
"github.com/distribution/distribution/v3/registry/api/errcode"
|
||||
registrymw "github.com/distribution/distribution/v3/registry/middleware/registry"
|
||||
@@ -68,7 +66,6 @@ func init() {
|
||||
// NamespaceResolver wraps a namespace and resolves names
|
||||
type NamespaceResolver struct {
|
||||
distribution.Namespace
|
||||
directory identity.Directory
|
||||
defaultHoldDID string // Default hold DID (e.g., "did:web:hold01.atcr.io")
|
||||
baseURL string // Base URL for error messages (e.g., "https://atcr.io")
|
||||
testMode bool // If true, fallback to default hold when user's hold is unreachable
|
||||
@@ -81,9 +78,6 @@ type NamespaceResolver struct {
|
||||
|
||||
// initATProtoResolver initializes the name resolution middleware
|
||||
func initATProtoResolver(ctx context.Context, ns distribution.Namespace, _ driver.StorageDriver, options map[string]any) (distribution.Namespace, error) {
|
||||
// Use shared directory with 8h cache TTL
|
||||
directory := atproto.GetDirectory()
|
||||
|
||||
// Get default hold DID from config (required)
|
||||
// Expected format: "did:web:hold01.atcr.io"
|
||||
defaultHoldDID := ""
|
||||
@@ -107,7 +101,6 @@ func initATProtoResolver(ctx context.Context, ns distribution.Namespace, _ drive
|
||||
// This avoids accessing globals during request handling
|
||||
return &NamespaceResolver{
|
||||
Namespace: ns,
|
||||
directory: directory,
|
||||
defaultHoldDID: defaultHoldDID,
|
||||
baseURL: baseURL,
|
||||
testMode: testMode,
|
||||
@@ -142,23 +135,10 @@ func (nr *NamespaceResolver) Repository(ctx context.Context, name reference.Name
|
||||
identityStr := parts[0]
|
||||
imageName := parts[1]
|
||||
|
||||
// Parse identity (handle or DID)
|
||||
atID, err := syntax.ParseAtIdentifier(identityStr)
|
||||
// Resolve identity to DID, handle, and PDS endpoint
|
||||
did, handle, pdsEndpoint, err := atproto.ResolveIdentity(ctx, identityStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid identity %s: %w", identityStr, err)
|
||||
}
|
||||
|
||||
// Resolve identity to DID and PDS using indigo's directory
|
||||
ident, err := nr.directory.Lookup(ctx, *atID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve identity %s: %w", identityStr, err)
|
||||
}
|
||||
|
||||
did := ident.DID.String()
|
||||
handle := ident.Handle.String()
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
if pdsEndpoint == "" {
|
||||
return nil, fmt.Errorf("no PDS endpoint found for %s", identityStr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Debug("Resolved identity", "component", "registry/middleware", "did", did, "pds", pdsEndpoint, "handle", handle)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package atproto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
)
|
||||
|
||||
// ResolveDIDToPDS resolves a DID to its PDS endpoint.
|
||||
// Uses the shared identity directory with 8h cache TTL.
|
||||
func ResolveDIDToPDS(ctx context.Context, did string) (string, error) {
|
||||
directory := GetDirectory()
|
||||
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)
|
||||
}
|
||||
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
if pdsEndpoint == "" {
|
||||
return "", fmt.Errorf("no PDS endpoint found for DID")
|
||||
}
|
||||
|
||||
return pdsEndpoint, nil
|
||||
}
|
||||
|
||||
// ResolveIdentity resolves an ATProto identifier (handle or DID) to DID, handle, and PDS endpoint.
|
||||
// Uses the shared identity directory with 8h cache TTL.
|
||||
//
|
||||
// If the handle is invalid (handle.invalid), it returns the DID as the handle for display purposes.
|
||||
// Returns: did, handle, pdsEndpoint, error
|
||||
func ResolveIdentity(ctx context.Context, identifier string) (string, string, string, error) {
|
||||
directory := GetDirectory()
|
||||
atID, err := syntax.ParseAtIdentifier(identifier)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid identifier %q: %w", identifier, err)
|
||||
}
|
||||
|
||||
ident, err := directory.Lookup(ctx, *atID)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to resolve identity %q: %w", identifier, err)
|
||||
}
|
||||
|
||||
did := ident.DID.String()
|
||||
handle := ident.Handle.String()
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
|
||||
// If handle is invalid, use DID as display name
|
||||
if handle == "handle.invalid" || handle == "" {
|
||||
handle = did
|
||||
}
|
||||
|
||||
// PDS endpoint is required for XRPC calls
|
||||
if pdsEndpoint == "" {
|
||||
return "", "", "", fmt.Errorf("no PDS endpoint found for identifier %q", identifier)
|
||||
}
|
||||
|
||||
return did, handle, pdsEndpoint, nil
|
||||
}
|
||||
|
||||
// ResolveHandleToDID resolves a handle or DID to just the DID.
|
||||
// Uses the shared identity directory with 8h cache TTL.
|
||||
// This is useful when you only need the DID and don't care about handle/PDS.
|
||||
func ResolveHandleToDID(ctx context.Context, identifier string) (string, error) {
|
||||
directory := GetDirectory()
|
||||
atID, err := syntax.ParseAtIdentifier(identifier)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid identifier: %w", err)
|
||||
}
|
||||
|
||||
ident, err := directory.Lookup(ctx, *atID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ident.DID.String(), nil
|
||||
}
|
||||
@@ -177,25 +177,3 @@ func ScopesMatch(stored, desired []string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ResolveDIDToPDS resolves a DID to its PDS endpoint (for reference)
|
||||
// This is an alternative approach if we don't trust the token's issuer claim
|
||||
func ResolveDIDToPDS(ctx context.Context, did string) (string, error) {
|
||||
directory := atproto.GetDirectory()
|
||||
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)
|
||||
}
|
||||
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
if pdsEndpoint == "" {
|
||||
return "", fmt.Errorf("no PDS endpoint found for DID")
|
||||
}
|
||||
|
||||
return pdsEndpoint, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"atcr.io/pkg/atproto"
|
||||
)
|
||||
|
||||
// UISessionStore is the interface for UI session management
|
||||
@@ -126,9 +128,8 @@ func (s *Server) ServeCallback(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("Invalidated cached session after creating new session", "did", did)
|
||||
}
|
||||
|
||||
// Look up identity
|
||||
ident, err := s.app.directory.LookupDID(r.Context(), sessionData.AccountDID)
|
||||
handle := ident.Handle.String()
|
||||
// Look up identity (resolve DID to handle)
|
||||
_, handle, _, err := atproto.ResolveIdentity(r.Context(), did)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to resolve DID to handle, using DID as fallback", "error", err, "did", did)
|
||||
handle = did // Fallback to DID if resolution fails
|
||||
|
||||
+2
-17
@@ -17,9 +17,6 @@ import (
|
||||
"time"
|
||||
|
||||
"atcr.io/pkg/atproto"
|
||||
|
||||
"github.com/bluesky-social/indigo/atproto/identity"
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
)
|
||||
|
||||
// CachedSession represents a cached session
|
||||
@@ -33,7 +30,6 @@ type CachedSession struct {
|
||||
|
||||
// SessionValidator validates ATProto credentials
|
||||
type SessionValidator struct {
|
||||
directory identity.Directory
|
||||
httpClient *http.Client
|
||||
cache map[string]*CachedSession
|
||||
cacheMu sync.RWMutex
|
||||
@@ -42,7 +38,6 @@ type SessionValidator struct {
|
||||
// NewSessionValidator creates a new ATProto session validator
|
||||
func NewSessionValidator() *SessionValidator {
|
||||
return &SessionValidator{
|
||||
directory: atproto.GetDirectory(),
|
||||
httpClient: &http.Client{},
|
||||
cache: make(map[string]*CachedSession),
|
||||
}
|
||||
@@ -102,19 +97,9 @@ func (v *SessionValidator) CreateSessionAndGetToken(ctx context.Context, identif
|
||||
slog.Debug("No cached session, creating new session", "identifier", identifier)
|
||||
|
||||
// Resolve identifier to PDS endpoint
|
||||
atID, err := syntax.ParseAtIdentifier(identifier)
|
||||
_, _, pds, err := atproto.ResolveIdentity(ctx, identifier)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid identifier %q: %w", identifier, err)
|
||||
}
|
||||
|
||||
ident, err := v.directory.Lookup(ctx, *atID)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to resolve identity %q: %w", identifier, err)
|
||||
}
|
||||
|
||||
pds := ident.PDSEndpoint()
|
||||
if pds == "" {
|
||||
return "", "", "", fmt.Errorf("no PDS endpoint found for %q", identifier)
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
// Create session
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bluesky-social/indigo/atproto/syntax"
|
||||
|
||||
"atcr.io/pkg/appview/db"
|
||||
"atcr.io/pkg/atproto"
|
||||
"atcr.io/pkg/auth"
|
||||
@@ -158,21 +156,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Call post-auth callback for AppView business logic (profile management, etc.)
|
||||
if h.postAuthCallback != nil {
|
||||
// Resolve PDS endpoint for callback
|
||||
directory := atproto.GetDirectory()
|
||||
atID, err := syntax.ParseAtIdentifier(username)
|
||||
if err == nil {
|
||||
ident, err := directory.Lookup(r.Context(), *atID)
|
||||
if err != nil {
|
||||
// Log error but don't fail auth - profile management is not critical
|
||||
slog.Warn("Failed to resolve PDS for callback", "error", err, "username", username)
|
||||
} else {
|
||||
pdsEndpoint := ident.PDSEndpoint()
|
||||
if pdsEndpoint != "" {
|
||||
if err := h.postAuthCallback(r.Context(), did, handle, pdsEndpoint, accessToken); err != nil {
|
||||
// Log error but don't fail auth - business logic is non-critical
|
||||
slog.Warn("Post-auth callback failed", "error", err, "did", did)
|
||||
}
|
||||
}
|
||||
_, _, pdsEndpoint, err := atproto.ResolveIdentity(r.Context(), username)
|
||||
if err != nil {
|
||||
// Log error but don't fail auth - profile management is not critical
|
||||
slog.Warn("Failed to resolve PDS for callback", "error", err, "username", username)
|
||||
} else {
|
||||
if err := h.postAuthCallback(r.Context(), did, handle, pdsEndpoint, accessToken); err != nil {
|
||||
// Log error but don't fail auth - business logic is non-critical
|
||||
slog.Warn("Post-auth callback failed", "error", err, "did", did)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user