forked from tangled.org/core
this repo has no description
1package xrpc 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log/slog" 8 "net/http" 9 "strings" 10 11 "tangled.sh/tangled.sh/core/api/tangled" 12 "tangled.sh/tangled.sh/core/idresolver" 13 "tangled.sh/tangled.sh/core/jetstream" 14 "tangled.sh/tangled.sh/core/knotserver/config" 15 "tangled.sh/tangled.sh/core/knotserver/db" 16 "tangled.sh/tangled.sh/core/notifier" 17 "tangled.sh/tangled.sh/core/rbac" 18 19 "github.com/bluesky-social/indigo/atproto/auth" 20 "github.com/go-chi/chi/v5" 21) 22 23type Xrpc struct { 24 Config *config.Config 25 Db *db.DB 26 Ingester *jetstream.JetstreamClient 27 Enforcer *rbac.Enforcer 28 Logger *slog.Logger 29 Notifier *notifier.Notifier 30 Resolver *idresolver.Resolver 31} 32 33func (x *Xrpc) Router() http.Handler { 34 r := chi.NewRouter() 35 36 r.With(x.VerifyServiceAuth).Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 37 38 return r 39} 40 41func (x *Xrpc) VerifyServiceAuth(next http.Handler) http.Handler { 42 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 l := x.Logger.With("url", r.URL) 44 45 token := r.Header.Get("Authorization") 46 token = strings.TrimPrefix(token, "Bearer ") 47 48 s := auth.ServiceAuthValidator{ 49 Audience: x.Config.Server.Did().String(), 50 Dir: x.Resolver.Directory(), 51 } 52 53 did, err := s.Validate(r.Context(), token, nil) 54 if err != nil { 55 l.Error("signature verification failed", "err", err) 56 writeError(w, AuthError(err), http.StatusForbidden) 57 return 58 } 59 60 r = r.WithContext( 61 context.WithValue(r.Context(), ActorDid, did), 62 ) 63 64 next.ServeHTTP(w, r) 65 }) 66} 67 68type XrpcError struct { 69 Tag string `json:"error"` 70 Message string `json:"message"` 71} 72 73func NewXrpcError(opts ...ErrOpt) XrpcError { 74 x := XrpcError{} 75 for _, o := range opts { 76 o(&x) 77 } 78 79 return x 80} 81 82type ErrOpt = func(xerr *XrpcError) 83 84func WithTag(tag string) ErrOpt { 85 return func(xerr *XrpcError) { 86 xerr.Tag = tag 87 } 88} 89 90func WithMessage[S ~string](s S) ErrOpt { 91 return func(xerr *XrpcError) { 92 xerr.Message = string(s) 93 } 94} 95 96func WithError(e error) ErrOpt { 97 return func(xerr *XrpcError) { 98 xerr.Message = e.Error() 99 } 100} 101 102var MissingActorDidError = NewXrpcError( 103 WithTag("MissingActorDid"), 104 WithMessage("actor DID not supplied"), 105) 106 107var AuthError = func(err error) XrpcError { 108 return NewXrpcError( 109 WithTag("Auth"), 110 WithError(fmt.Errorf("signature verification failed: %w", err)), 111 ) 112} 113 114var InvalidRepoError = func(r string) XrpcError { 115 return NewXrpcError( 116 WithTag("InvalidRepo"), 117 WithError(fmt.Errorf("supplied at-uri is not a repo: %s", r)), 118 ) 119} 120 121var AccessControlError = func(d string) XrpcError { 122 return NewXrpcError( 123 WithTag("AccessControl"), 124 WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)), 125 ) 126} 127 128var GitError = func(e error) XrpcError { 129 return NewXrpcError( 130 WithTag("Git"), 131 WithError(fmt.Errorf("git error: %w", e)), 132 ) 133} 134 135func GenericError(err error) XrpcError { 136 return NewXrpcError( 137 WithTag("InvalidRepo"), 138 WithError(err), 139 ) 140} 141 142// this is slightly different from http_util::write_error to follow the spec: 143// 144// the json object returned must include an "error" and a "message" 145func writeError(w http.ResponseWriter, e XrpcError, status int) { 146 w.Header().Set("Content-Type", "application/json") 147 w.WriteHeader(status) 148 json.NewEncoder(w).Encode(e) 149}