A community based topic aggregation platform built on atproto

refactor(atproto): remove DID generator (V2.0 uses PDS-managed DIDs)

Remove Coves-side DID generator in favor of PDS-managed DID generation.

Removed Files:
- internal/atproto/did/generator.go
- internal/atproto/did/generator_test.go

Rationale:
V2.0 architecture delegates all DID and key management to the PDS for:
- Bluesky PDS cannot handle record imports created outside the PDS.
- No complex cryptography
- Standard atProto compliance (PDS owns community identity)

The PDS now handles:
- DID generation (did:plc format)
- Signing key generation and storage
- Rotation key generation and storage
- PLC directory registration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
-207
internal
-80
internal/atproto/did/generator.go
···
-
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 != ""
-
}
-127
internal/atproto/did/generator_test.go
···
-
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)
-
}
-
})
-
}
-
}