mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-30 14:05:35 +00:00
cleanup more auth
This commit is contained in:
@@ -1,243 +0,0 @@
|
||||
package atproto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Resolver handles DID/handle resolution for ATProto
|
||||
type Resolver struct {
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewResolver creates a new DID/handle resolver
|
||||
func NewResolver() *Resolver {
|
||||
return &Resolver{
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveIdentity resolves a handle or DID to a DID and PDS endpoint
|
||||
// Input can be:
|
||||
// - Handle: "alice.bsky.social" or "alice"
|
||||
// - DID: "did:plc:xyz123abc"
|
||||
func (r *Resolver) ResolveIdentity(ctx context.Context, identity string) (did string, pdsEndpoint string, err error) {
|
||||
// Check if it's already a DID
|
||||
if strings.HasPrefix(identity, "did:") {
|
||||
did = identity
|
||||
pdsEndpoint, err = r.ResolvePDS(ctx, did)
|
||||
return did, pdsEndpoint, err
|
||||
}
|
||||
|
||||
// Otherwise, resolve handle to DID
|
||||
did, err = r.ResolveHandle(ctx, identity)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to resolve handle %s: %w", identity, err)
|
||||
}
|
||||
|
||||
// Then resolve DID to PDS
|
||||
pdsEndpoint, err = r.ResolvePDS(ctx, did)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to resolve PDS for DID %s: %w", did, err)
|
||||
}
|
||||
|
||||
return did, pdsEndpoint, nil
|
||||
}
|
||||
|
||||
// ResolveHandle resolves a handle to a DID using DNS TXT records or .well-known
|
||||
func (r *Resolver) ResolveHandle(ctx context.Context, handle string) (string, error) {
|
||||
// Normalize handle
|
||||
if !strings.Contains(handle, ".") {
|
||||
// Default to .bsky.social if no domain provided
|
||||
handle = handle + ".bsky.social"
|
||||
}
|
||||
|
||||
// Try DNS TXT record first (faster)
|
||||
if did, err := r.resolveHandleViaDNS(handle); err == nil && did != "" {
|
||||
return did, nil
|
||||
}
|
||||
|
||||
// Fall back to HTTPS .well-known method
|
||||
url := fmt.Sprintf("https://%s/.well-known/atproto-did", handle)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch .well-known: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
did := strings.TrimSpace(string(body))
|
||||
if strings.HasPrefix(did, "did:") {
|
||||
return did, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not resolve handle %s to DID", handle)
|
||||
}
|
||||
|
||||
// resolveHandleViaDNS attempts to resolve handle via DNS TXT record at _atproto.<handle>
|
||||
func (r *Resolver) resolveHandleViaDNS(handle string) (string, error) {
|
||||
txtRecords, err := net.LookupTXT("_atproto." + handle)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Look for a TXT record that starts with "did="
|
||||
for _, record := range txtRecords {
|
||||
if strings.HasPrefix(record, "did=") {
|
||||
did := strings.TrimPrefix(record, "did=")
|
||||
if strings.HasPrefix(did, "did:") {
|
||||
return did, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no valid DID found in DNS TXT records")
|
||||
}
|
||||
|
||||
// DIDDocument represents a simplified ATProto DID document
|
||||
type DIDDocument struct {
|
||||
ID string `json:"id"`
|
||||
AlsoKnownAs []string `json:"alsoKnownAs,omitempty"`
|
||||
Service []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
ServiceEndpoint string `json:"serviceEndpoint"`
|
||||
} `json:"service"`
|
||||
}
|
||||
|
||||
// ResolvePDS resolves a DID to its PDS endpoint
|
||||
func (r *Resolver) ResolvePDS(ctx context.Context, did string) (string, error) {
|
||||
if !strings.HasPrefix(did, "did:") {
|
||||
return "", fmt.Errorf("invalid DID format: %s", did)
|
||||
}
|
||||
|
||||
// Parse DID method
|
||||
parts := strings.Split(did, ":")
|
||||
if len(parts) < 3 {
|
||||
return "", fmt.Errorf("invalid DID format: %s", did)
|
||||
}
|
||||
|
||||
method := parts[1]
|
||||
|
||||
var resolverURL string
|
||||
switch method {
|
||||
case "plc":
|
||||
// Use PLC directory
|
||||
resolverURL = fmt.Sprintf("https://plc.directory/%s", did)
|
||||
case "web":
|
||||
// For did:web, convert to HTTPS URL
|
||||
domain := parts[2]
|
||||
resolverURL = fmt.Sprintf("https://%s/.well-known/did.json", domain)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported DID method: %s", method)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", resolverURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch DID document: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("DID resolution failed with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var didDoc DIDDocument
|
||||
if err := json.NewDecoder(resp.Body).Decode(&didDoc); err != nil {
|
||||
return "", fmt.Errorf("failed to parse DID document: %w", err)
|
||||
}
|
||||
|
||||
// Find PDS service endpoint
|
||||
for _, service := range didDoc.Service {
|
||||
if service.Type == "AtprotoPersonalDataServer" {
|
||||
return service.ServiceEndpoint, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no PDS endpoint found in DID document")
|
||||
}
|
||||
|
||||
// ResolveDIDDocument fetches the full DID document for a DID
|
||||
func (r *Resolver) ResolveDIDDocument(ctx context.Context, did string) (*DIDDocument, error) {
|
||||
if !strings.HasPrefix(did, "did:") {
|
||||
return nil, fmt.Errorf("invalid DID format: %s", did)
|
||||
}
|
||||
|
||||
parts := strings.Split(did, ":")
|
||||
if len(parts) < 3 {
|
||||
return nil, fmt.Errorf("invalid DID format: %s", did)
|
||||
}
|
||||
|
||||
method := parts[1]
|
||||
|
||||
var resolverURL string
|
||||
switch method {
|
||||
case "plc":
|
||||
resolverURL = fmt.Sprintf("https://plc.directory/%s", did)
|
||||
case "web":
|
||||
domain := parts[2]
|
||||
resolverURL = fmt.Sprintf("https://%s/.well-known/did.json", domain)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported DID method: %s", method)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", resolverURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch DID document: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("DID resolution failed with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var didDoc DIDDocument
|
||||
if err := json.NewDecoder(resp.Body).Decode(&didDoc); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DID document: %w", err)
|
||||
}
|
||||
|
||||
return &didDoc, nil
|
||||
}
|
||||
|
||||
// ResolveHandle extracts the handle from a DID's alsoKnownAs field
|
||||
func (r *Resolver) ResolveHandleFromDID(ctx context.Context, did string) (string, error) {
|
||||
didDoc, err := r.ResolveDIDDocument(ctx, did)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Look for handle in alsoKnownAs (format: "at://handle.bsky.social")
|
||||
for _, aka := range didDoc.AlsoKnownAs {
|
||||
if strings.HasPrefix(aka, "at://") {
|
||||
handle := strings.TrimPrefix(aka, "at://")
|
||||
return handle, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no handle found in DID document")
|
||||
}
|
||||
Reference in New Issue
Block a user