cloudflare-native port of the tangled knot server
0

Configure Feed

Select the types of activity you want to include in your feed.

aerie / xrpc / serviceauth / service_auth.go
2.5 kB 91 lines
1package serviceauth 2 3import ( 4 "context" 5 "encoding/json" 6 "log/slog" 7 "net/http" 8 "path" 9 "strings" 10 11 "github.com/bluesky-social/indigo/atproto/auth" 12 "github.com/bluesky-social/indigo/atproto/identity" 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 "tangled.org/core/log" 15 xrpcerr "tangled.org/core/xrpc/errors" 16) 17 18type contextKey string 19 20const ActorDid contextKey = "ActorDid" 21 22func DidWeb(hostname string) syntax.DID { 23 return syntax.DID("did:web:" + strings.ReplaceAll(hostname, ":", "%3A")) 24} 25 26type ServiceAuth struct { 27 logger *slog.Logger 28 dir identity.Directory 29 audienceDid string 30} 31 32func NewServiceAuth(logger *slog.Logger, dir identity.Directory, audienceDid string) *ServiceAuth { 33 return &ServiceAuth{ 34 logger: log.SubLogger(logger, "serviceauth"), 35 dir: dir, 36 audienceDid: audienceDid, 37 } 38} 39 40func (sa *ServiceAuth) VerifyServiceAuth(next http.Handler) http.Handler { 41 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 lxm, err := syntax.ParseNSID(path.Base(r.URL.Path)) 43 if err != nil { 44 sa.logger.Error("could not derive lexicon method from request path", "path", r.URL.Path, "err", err) 45 writeError(w, xrpcerr.AuthError(err), http.StatusForbidden) 46 return 47 } 48 49 sa.verifyServiceAuthFor(next, lxm).ServeHTTP(w, r) 50 }) 51} 52 53func (sa *ServiceAuth) VerifyServiceAuthFor(next http.Handler, lxm syntax.NSID) http.Handler { 54 return sa.verifyServiceAuthFor(next, lxm) 55} 56 57func (sa *ServiceAuth) verifyServiceAuthFor(next http.Handler, lxm syntax.NSID) http.Handler { 58 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 59 token := r.Header.Get("Authorization") 60 token = strings.TrimPrefix(token, "Bearer ") 61 62 s := auth.ServiceAuthValidator{ 63 Audience: sa.audienceDid, 64 Dir: sa.dir, 65 } 66 67 did, err := s.Validate(r.Context(), token, &lxm) 68 if err != nil { 69 sa.logger.Error("signature verification failed", "err", err) 70 writeError(w, xrpcerr.AuthError(err), http.StatusForbidden) 71 return 72 } 73 74 sa.logger.Debug("valid signature", "did", did) 75 76 r = r.WithContext( 77 context.WithValue(r.Context(), ActorDid, did), 78 ) 79 80 next.ServeHTTP(w, r) 81 }) 82} 83 84// this is slightly different from http_util::write_error to follow the spec: 85// 86// the json object returned must include an "error" and a "message" 87func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) { 88 w.Header().Set("Content-Type", "application/json") 89 w.WriteHeader(status) 90 json.NewEncoder(w).Encode(e) 91}