1package main
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7
8 comatproto "github.com/bluesky-social/indigo/api/atproto"
9 "github.com/bluesky-social/indigo/atproto/identity"
10 "github.com/bluesky-social/indigo/atproto/syntax"
11
12 "github.com/labstack/echo/v4"
13)
14
15// GET /xrpc/com.atproto.identity.resolveHandle
16func (srv *Server) ResolveHandle(c echo.Context) error {
17 ctx := c.Request().Context()
18
19 hdl, err := syntax.ParseHandle(c.QueryParam("handle"))
20 if err != nil {
21 return c.JSON(400, GenericError{
22 Error: "InvalidHandleSyntax",
23 Message: err.Error(),
24 })
25 }
26
27 did, err := srv.dir.ResolveHandle(ctx, hdl)
28 if err != nil && errors.Is(err, identity.ErrHandleNotFound) {
29 return c.JSON(404, GenericError{
30 Error: "HandleNotFound",
31 Message: err.Error(),
32 })
33 } else if err != nil {
34 return c.JSON(500, GenericError{
35 Error: "InternalError",
36 Message: err.Error(),
37 })
38 }
39 return c.JSON(200, comatproto.IdentityResolveHandle_Output{
40 Did: did.String(),
41 })
42}
43
44// GET /xrpc/com.atproto.identity.resolveDid
45func (srv *Server) ResolveDid(c echo.Context) error {
46 ctx := c.Request().Context()
47
48 did, err := syntax.ParseDID(c.QueryParam("did"))
49 if err != nil {
50 return c.JSON(400, GenericError{
51 Error: "InvalidDidSyntax",
52 Message: err.Error(),
53 })
54 }
55
56 rawDoc, err := srv.dir.ResolveDIDRaw(ctx, did)
57 if err != nil && errors.Is(err, identity.ErrDIDNotFound) {
58 return c.JSON(404, GenericError{
59 Error: "DidNotFound",
60 Message: err.Error(),
61 })
62 } else if err != nil {
63 return c.JSON(500, GenericError{
64 Error: "InternalError",
65 Message: err.Error(),
66 })
67 }
68 return c.JSON(200, comatproto.IdentityResolveDid_Output{
69 DidDoc: rawDoc,
70 })
71}
72
73// helper for resolveIdentity
74func (srv *Server) resolveIdentityFromHandle(c echo.Context, handle syntax.Handle) error {
75 ctx := c.Request().Context()
76
77 handle = handle.Normalize()
78
79 did, err := srv.dir.ResolveHandle(ctx, handle)
80 if err != nil && errors.Is(err, identity.ErrHandleNotFound) {
81 return c.JSON(404, GenericError{
82 Error: "HandleNotFound",
83 Message: err.Error(),
84 })
85 } else if err != nil {
86 srv.logger.Warn("failed handle resolution", "err", err, "handle", handle)
87 return c.JSON(502, GenericError{
88 Error: "HandleResolutionFailed",
89 Message: err.Error(),
90 })
91 }
92
93 rawDoc, err := srv.dir.ResolveDIDRaw(ctx, did)
94 if err != nil && errors.Is(err, identity.ErrDIDNotFound) {
95 return c.JSON(404, GenericError{
96 Error: "DidNotFound",
97 Message: err.Error(),
98 })
99 } else if err != nil {
100 return c.JSON(502, GenericError{
101 Error: "DIDResolutionFailed",
102 Message: err.Error(),
103 })
104 }
105
106 var doc identity.DIDDocument
107 if err := json.Unmarshal(rawDoc, &doc); err != nil {
108 return c.JSON(400, GenericError{
109 Error: "InvalidDidDocument",
110 Message: err.Error(),
111 })
112 }
113
114 ident := identity.ParseIdentity(&doc)
115 // NOTE: 'handle' was resolved above, and 'DeclaredHandle()' returns a normalized handle
116 declHandle, err := ident.DeclaredHandle()
117 if err != nil || declHandle != handle {
118 return c.JSON(400, GenericError{
119 Error: "HandleMismatch",
120 Message: err.Error(),
121 })
122 }
123
124 return c.JSON(200, comatproto.IdentityDefs_IdentityInfo{
125 Did: ident.DID.String(),
126 Handle: handle.String(),
127 DidDoc: rawDoc,
128 })
129}
130
131// helper for resolveIdentity
132func (srv *Server) resolveIdentityFromDID(c echo.Context, did syntax.DID) error {
133 ctx := c.Request().Context()
134
135 rawDoc, err := srv.dir.ResolveDIDRaw(ctx, did)
136 if err != nil && errors.Is(err, identity.ErrDIDNotFound) {
137 return c.JSON(404, GenericError{
138 Error: "DidNotFound",
139 Message: err.Error(),
140 })
141 } else if err != nil {
142 return c.JSON(502, GenericError{
143 Error: "DIDResolutionFailed",
144 Message: err.Error(),
145 })
146 }
147
148 var doc identity.DIDDocument
149 if err := json.Unmarshal(rawDoc, &doc); err != nil {
150 return c.JSON(400, GenericError{
151 Error: "InvalidDidDocument",
152 Message: err.Error(),
153 })
154 }
155
156 ident := identity.ParseIdentity(&doc)
157 handle, err := ident.DeclaredHandle()
158 if err != nil {
159 // no handle declared, or invalid syntax
160 handle = syntax.HandleInvalid
161 }
162
163 checkDID, err := srv.dir.ResolveHandle(ctx, handle)
164 if err != nil || checkDID != did {
165 handle = syntax.HandleInvalid
166 }
167
168 return c.JSON(200, comatproto.IdentityDefs_IdentityInfo{
169 Did: ident.DID.String(),
170 Handle: handle.String(),
171 DidDoc: rawDoc,
172 })
173}
174
175// GET /xrpc/com.atproto.identity.resolveIdentity
176func (srv *Server) ResolveIdentity(c echo.Context) error {
177 // we partially re-implement the "Lookup()" logic here, but returning the full DID document, not `identity.Identity`
178 atid, err := syntax.ParseAtIdentifier(c.QueryParam("identifier"))
179 if err != nil {
180 return c.JSON(400, GenericError{
181 Error: "InvalidIdentifierSyntax",
182 Message: err.Error(),
183 })
184 }
185
186 handle, err := atid.AsHandle()
187 if nil == err {
188 return srv.resolveIdentityFromHandle(c, handle)
189 }
190 did, err := atid.AsDID()
191 if nil == err {
192 return srv.resolveIdentityFromDID(c, did)
193 }
194 return fmt.Errorf("unreachable code path")
195}
196
197// POST /xrpc/com.atproto.identity.refreshIdentity
198func (srv *Server) RefreshIdentity(c echo.Context) error {
199 ctx := c.Request().Context()
200
201 var body comatproto.IdentityRefreshIdentity_Input
202 if err := c.Bind(&body); err != nil {
203 return c.JSON(400, GenericError{
204 Error: "InvalidRequestBody",
205 Message: err.Error(),
206 })
207 }
208
209 atid, err := syntax.ParseAtIdentifier(body.Identifier)
210 if err != nil {
211 return c.JSON(400, GenericError{
212 Error: "InvalidIdentifierSyntax",
213 Message: err.Error(),
214 })
215 }
216
217 did, err := atid.AsDID()
218 if nil == err {
219 if err := srv.dir.PurgeDID(ctx, did); err != nil {
220 return err
221 }
222 return srv.resolveIdentityFromDID(c, did)
223 }
224 handle, err := atid.AsHandle()
225 if nil == err {
226 if err := srv.dir.PurgeHandle(ctx, handle); err != nil {
227 return err
228 }
229 return srv.resolveIdentityFromHandle(c, handle)
230 }
231
232 return fmt.Errorf("unreachable code path")
233}
234
235type GenericStatus struct {
236 Daemon string `json:"daemon"`
237 Status string `json:"status"`
238 Message string `json:"msg,omitempty"`
239}
240
241func (s *Server) HandleHealthCheck(c echo.Context) error {
242 return c.JSON(200, GenericStatus{Status: "ok", Daemon: "bluepages"})
243}
244
245func (srv *Server) WebHome(c echo.Context) error {
246 return c.String(200, `
247eeeee e e e eeee eeeee eeeee eeeee eeee eeeee
2488 8 8 8 8 8 8 8 8 8 8 8 8 8 "
2498eee8e 8e 8e 8 8eee 8eee8 8eee8 8e 8eee 8eeee
25088 8 88 88 8 88 88 88 8 88 "8 88 88
25188eee8 88eee 88ee8 88ee 88 88 8 88ee8 88ee 8ee88
252
253This is an AT Protocol Identity Service
254
255Most API routes are under /xrpc/
256
257 Code: https://github.com/bluesky-social/indigo/tree/main/cmd/bluepages
258 Protocol: https://atproto.com
259 `)
260
261}