diff --git a/internal/atproto/did/generator.go b/internal/atproto/did/generator.go deleted file mode 100644 index c738947..0000000 --- a/internal/atproto/did/generator.go +++ /dev/null @@ -1,80 +0,0 @@ -package did - -import ( - "crypto/rand" - "encoding/base32" - "fmt" - "strings" -) - -// Generator creates DIDs for Coves entities -type Generator struct { - plcDirectoryURL string - isDevEnv bool -} - -// NewGenerator creates a new DID generator -// isDevEnv: true for local development (no PLC registration), false for production (register with PLC) -// plcDirectoryURL: URL for PLC directory (e.g., "https://plc.directory") -func NewGenerator(isDevEnv bool, plcDirectoryURL string) *Generator { - return &Generator{ - isDevEnv: isDevEnv, - plcDirectoryURL: plcDirectoryURL, - } -} - -// GenerateCommunityDID creates a new random DID for a community -// Format: did:plc:{base32-random} -// -// Dev mode (isDevEnv=true): Generates did:plc:xxx without registering to PLC -// Prod mode (isDevEnv=false): Generates did:plc:xxx AND registers with PLC directory -// -// See: https://github.com/bluesky-social/did-method-plc -func (g *Generator) GenerateCommunityDID() (string, error) { - // Generate 16 random bytes for the DID identifier - randomBytes := make([]byte, 16) - if _, err := rand.Read(randomBytes); err != nil { - return "", fmt.Errorf("failed to generate random DID: %w", err) - } - - // Encode as base32 (lowercase, no padding) - matches PLC format - encoded := base32.StdEncoding.EncodeToString(randomBytes) - encoded = strings.ToLower(strings.TrimRight(encoded, "=")) - - did := fmt.Sprintf("did:plc:%s", encoded) - - // TODO: In production (isDevEnv=false), register this DID with PLC directory - // This would involve: - // 1. Generate signing keypair for the DID - // 2. Create DID document with service endpoints - // 3. POST to plcDirectoryURL to register - // 4. Store keypair securely for future DID updates - // - // For now, we just generate the identifier (works fine for local dev) - // Production PLC registration is not yet implemented - DIDs are generated - // locally but not registered with the PLC directory. This is acceptable - // for development and private instances, but production deployments should - // implement full PLC registration to ensure DIDs are globally resolvable. - _ = g.isDevEnv // Acknowledge that isDevEnv will be used when PLC registration is implemented - - return did, nil -} - -// ValidateDID checks if a DID string is properly formatted -// Supports did:plc, did:web (for instances) -func ValidateDID(did string) bool { - if !strings.HasPrefix(did, "did:") { - return false - } - - parts := strings.Split(did, ":") - if len(parts) < 3 { - return false - } - - method := parts[1] - identifier := parts[2] - - // Basic validation: method and identifier must not be empty - return method != "" && identifier != "" -} diff --git a/internal/atproto/did/generator_test.go b/internal/atproto/did/generator_test.go deleted file mode 100644 index 6dcb8c2..0000000 --- a/internal/atproto/did/generator_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package did - -import ( - "strings" - "testing" -) - -func TestGenerateCommunityDID(t *testing.T) { - tests := []struct { - name string - plcDirectoryURL string - want string - isDevEnv bool - }{ - { - name: "generates did:plc in dev mode", - isDevEnv: true, - plcDirectoryURL: "https://plc.directory", - want: "did:plc:", - }, - { - name: "generates did:plc in prod mode", - isDevEnv: false, - plcDirectoryURL: "https://plc.directory", - want: "did:plc:", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewGenerator(tt.isDevEnv, tt.plcDirectoryURL) - did, err := g.GenerateCommunityDID() - if err != nil { - t.Fatalf("GenerateCommunityDID() error = %v", err) - } - - if !strings.HasPrefix(did, tt.want) { - t.Errorf("GenerateCommunityDID() = %v, want prefix %v", did, tt.want) - } - - // Verify it's a valid DID - if !ValidateDID(did) { - t.Errorf("Generated DID failed validation: %v", did) - } - }) - } -} - -func TestGenerateCommunityDID_Uniqueness(t *testing.T) { - g := NewGenerator(true, "https://plc.directory") - - // Generate 100 DIDs and ensure they're all unique - dids := make(map[string]bool) - for i := 0; i < 100; i++ { - did, err := g.GenerateCommunityDID() - if err != nil { - t.Fatalf("GenerateCommunityDID() error = %v", err) - } - - if dids[did] { - t.Errorf("Duplicate DID generated: %v", did) - } - dids[did] = true - } -} - -func TestValidateDID(t *testing.T) { - tests := []struct { - name string - did string - want bool - }{ - { - name: "valid did:plc", - did: "did:plc:z72i7hdynmk6r22z27h6tvur", - want: true, - }, - { - name: "valid did:plc with base32", - did: "did:plc:abc123xyz", - want: true, - }, - { - name: "valid did:web", - did: "did:web:coves.social", - want: true, - }, - { - name: "valid did:web with path", - did: "did:web:coves.social:community:gaming", - want: true, - }, - { - name: "invalid: missing prefix", - did: "plc:abc123", - want: false, - }, - { - name: "invalid: missing method", - did: "did::abc123", - want: false, - }, - { - name: "invalid: missing identifier", - did: "did:plc:", - want: false, - }, - { - name: "invalid: only did", - did: "did:", - want: false, - }, - { - name: "invalid: empty string", - did: "", - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := ValidateDID(tt.did); got != tt.want { - t.Errorf("ValidateDID(%v) = %v, want %v", tt.did, got, tt.want) - } - }) - } -}