diff --git a/README.md b/README.md index fce99ec..8ab90c8 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ To build locally instead: `docker build -t asterism .` ## API -Asterism implements all five current endpoints from the [microcosm links XRPC namespace](https://constellation.microcosm.blue/) (the older `/links/*` REST endpoints are deprecated upstream in favor of these and aren't implemented here): +Asterism implements all five current endpoints from the [microcosm links XRPC namespace](https://constellation.microcosm.blue/) (the older `/links/*` REST endpoints are deprecated upstream in favor of these and aren't implemented here), plus one identity endpoint borrowed from [Slingshot](https://slingshot.microcosm.blue/): ### `GET /xrpc/blue.microcosm.links.getBacklinksCount` @@ -168,6 +168,16 @@ Response: `{"counts_by_other_subject": [{"subject": "...", "total": 42, "distinc Note the DID filter parameter is `did` here, not `linkDid` like `getManyToMany` — a real inconsistency in the upstream Constellation API (their own source flags it as a known TODO), preserved here for compatibility rather than "fixed." +### `GET /xrpc/blue.microcosm.identity.resolveMiniDoc` + +Resolve a handle or DID to its identity. Asterism already resolves DIDs to verify commit signatures against the repo's signing key, so this endpoint is essentially free. + +| Parameter | Description | +| ------------ | ------------------------------------- | +| `identifier` | Handle or DID to resolve (required) | + +Response: `{"did": "...", "handle": "...", "pds": "...", "signing_key": "..."}` + ## Roadmap **Near term** diff --git a/cmd/asterism/main.go b/cmd/asterism/main.go index ff8bc1f..377c22e 100644 --- a/cmd/asterism/main.go +++ b/cmd/asterism/main.go @@ -68,7 +68,9 @@ func main() { panic(err) } - server := &api.Server{Store: linkStore} + directory := identity.DefaultDirectory() + + server := &api.Server{Store: linkStore, Directory: directory} go func() { if err := server.Run(cli.Listen); err != nil { panic(err) @@ -86,7 +88,7 @@ func main() { bf := &backfill.Backfill{ Client: &xrpc.Client{Host: relayHTTPHost(cli.Relay)}, - Directory: identity.DefaultDirectory(), + Directory: directory, Store: linkStore, } @@ -103,7 +105,7 @@ func main() { consumer := &firehose.Consumer{ WantedCollections: wantedCollections, Store: linkStore, - Directory: bf.Directory, + Directory: directory, Backfill: bf, } diff --git a/internal/api/resolve_mini_doc.go b/internal/api/resolve_mini_doc.go new file mode 100644 index 0000000..f32aaff --- /dev/null +++ b/internal/api/resolve_mini_doc.go @@ -0,0 +1,57 @@ +package api + +import ( + "context" + "encoding/json" + "log" + "net/http" + "time" + + "github.com/bluesky-social/indigo/atproto/syntax" +) + +type getMiniDidDocResponse struct { + Did string `json:"did"` + Handle string `json:"handle"` + Pds string `json:"pds"` + SigningKey string `json:"signing_key"` +} + +func (s *Server) GetMiniDidDoc(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + + identifier := query.Get("identifier") + if identifier == "" { + http.Error(w, "identifier is required", http.StatusBadRequest) + return + } + + atID, err := syntax.ParseAtIdentifier(identifier) + if err != nil { + http.Error(w, "invalid identifier", http.StatusBadRequest) + return + } + + resolveCtx, cancel := context.WithTimeout(r.Context(), 10*time.Second) + defer cancel() + + identity, err := s.Directory.Lookup(resolveCtx, atID) + if err != nil { + log.Println("resolve mini doc:", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + + var signingKey string + if key, ok := identity.Keys["atproto"]; ok { + signingKey = key.PublicKeyMultibase + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(getMiniDidDocResponse{ + Did: identity.DID.String(), + Handle: identity.Handle.String(), + Pds: identity.PDSEndpoint(), + SigningKey: signingKey, + }) +} diff --git a/internal/api/server.go b/internal/api/server.go index 4b35eb8..955f9b0 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -4,10 +4,12 @@ import ( "net/http" "github.com/alyraffauf/asterism/internal/store" + "github.com/bluesky-social/indigo/atproto/identity" ) type Server struct { - Store *store.Store + Store *store.Store + Directory identity.Directory } func (s *Server) Run(addr string) error { @@ -20,6 +22,8 @@ func (s *Server) Run(addr string) error { mux.HandleFunc("GET /xrpc/blue.microcosm.links.getManyToMany", s.GetManyToMany) mux.HandleFunc("GET /xrpc/blue.microcosm.links.getManyToManyCounts", s.GetManyToManyCounts) + mux.HandleFunc("GET /xrpc/blue.microcosm.identity.resolveMiniDoc", s.GetMiniDidDoc) + return http.ListenAndServe(addr, withCORS(mux)) }