mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-31 05:07:09 +00:00
146 lines
4.2 KiB
Go
146 lines
4.2 KiB
Go
// Package did provides shared DID document types and utilities for ATProto services.
|
|
// Both AppView and Hold use this package for did:web document generation.
|
|
package did
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/bluesky-social/indigo/atproto/atcrypto"
|
|
)
|
|
|
|
// DIDDocument represents a did:web document
|
|
type DIDDocument struct {
|
|
Context []string `json:"@context"`
|
|
ID string `json:"id"`
|
|
AlsoKnownAs []string `json:"alsoKnownAs,omitempty"`
|
|
VerificationMethod []VerificationMethod `json:"verificationMethod"`
|
|
Authentication []string `json:"authentication,omitempty"`
|
|
AssertionMethod []string `json:"assertionMethod,omitempty"`
|
|
Service []Service `json:"service,omitempty"`
|
|
}
|
|
|
|
// VerificationMethod represents a public key in a DID document
|
|
type VerificationMethod struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Controller string `json:"controller"`
|
|
PublicKeyMultibase string `json:"publicKeyMultibase"`
|
|
}
|
|
|
|
// Service represents a service endpoint in a DID document
|
|
type Service struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
ServiceEndpoint string `json:"serviceEndpoint"`
|
|
}
|
|
|
|
// GenerateDIDFromURL creates a did:web identifier from a public URL
|
|
// Example: "https://atcr.io" -> "did:web:atcr.io"
|
|
// Example: "http://hold1.example.com:8080" -> "did:web:hold1.example.com:8080"
|
|
// Note: Non-standard ports are included in the DID
|
|
func GenerateDIDFromURL(publicURL string) string {
|
|
u, err := url.Parse(publicURL)
|
|
if err != nil {
|
|
// Fallback: assume it's just a hostname
|
|
return fmt.Sprintf("did:web:%s", publicURL)
|
|
}
|
|
|
|
hostname := u.Hostname()
|
|
if hostname == "" {
|
|
hostname = "localhost"
|
|
}
|
|
|
|
port := u.Port()
|
|
|
|
// Include port in DID if it's non-standard (not 80 for http, not 443 for https)
|
|
if port != "" && port != "80" && port != "443" {
|
|
return fmt.Sprintf("did:web:%s:%s", hostname, port)
|
|
}
|
|
|
|
return fmt.Sprintf("did:web:%s", hostname)
|
|
}
|
|
|
|
// GenerateDIDDocument creates a DID document for a did:web identity
|
|
// This is a standalone function that can be used by any ATProto service.
|
|
// The services parameter allows customizing which service endpoints to include.
|
|
func GenerateDIDDocument(publicURL string, publicKey atcrypto.PublicKey, services []Service) (*DIDDocument, error) {
|
|
u, err := url.Parse(publicURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse public URL: %w", err)
|
|
}
|
|
|
|
hostname := u.Hostname()
|
|
port := u.Port()
|
|
|
|
// Build host string (include non-standard ports)
|
|
host := hostname
|
|
if port != "" && port != "80" && port != "443" {
|
|
host = fmt.Sprintf("%s:%s", hostname, port)
|
|
}
|
|
|
|
did := fmt.Sprintf("did:web:%s", host)
|
|
|
|
// Get public key in multibase format
|
|
publicKeyMultibase := publicKey.Multibase()
|
|
|
|
doc := &DIDDocument{
|
|
Context: []string{
|
|
"https://www.w3.org/ns/did/v1",
|
|
"https://w3id.org/security/multikey/v1",
|
|
"https://w3id.org/security/suites/secp256k1-2019/v1",
|
|
},
|
|
ID: did,
|
|
AlsoKnownAs: []string{
|
|
fmt.Sprintf("at://%s", host),
|
|
},
|
|
VerificationMethod: []VerificationMethod{
|
|
{
|
|
ID: fmt.Sprintf("%s#atproto", did),
|
|
Type: "Multikey",
|
|
Controller: did,
|
|
PublicKeyMultibase: publicKeyMultibase,
|
|
},
|
|
},
|
|
Authentication: []string{
|
|
fmt.Sprintf("%s#atproto", did),
|
|
},
|
|
Service: services,
|
|
}
|
|
|
|
return doc, nil
|
|
}
|
|
|
|
// MarshalDIDDocument converts a DID document to JSON bytes
|
|
func MarshalDIDDocument(doc *DIDDocument) ([]byte, error) {
|
|
return json.MarshalIndent(doc, "", " ")
|
|
}
|
|
|
|
// DefaultHoldServices returns the standard service endpoints for a Hold service
|
|
func DefaultHoldServices(publicURL string) []Service {
|
|
return []Service{
|
|
{
|
|
ID: "#atproto_pds",
|
|
Type: "AtprotoPersonalDataServer",
|
|
ServiceEndpoint: publicURL,
|
|
},
|
|
{
|
|
ID: "#atcr_hold",
|
|
Type: "AtcrHoldService",
|
|
ServiceEndpoint: publicURL,
|
|
},
|
|
}
|
|
}
|
|
|
|
// DefaultAppViewServices returns the standard service endpoints for AppView
|
|
func DefaultAppViewServices(publicURL string) []Service {
|
|
return []Service{
|
|
{
|
|
ID: "#atcr_registry",
|
|
Type: "AtcrRegistryService",
|
|
ServiceEndpoint: publicURL,
|
|
},
|
|
}
|
|
}
|