cleanup more auth

This commit is contained in:
Evan Jarrett
2025-10-07 10:58:11 -05:00
parent 5b18538a8b
commit 2d16bbfee3
31 changed files with 2524 additions and 918 deletions
-243
View File
@@ -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")
}