Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

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

knotserver/xrpc: use new top-level xrpc packages

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

authored by

Anirudh Oppiliappan and committed by
oppiliappan
80f48ef6 e48b561a

+35 -131
+11 -7
knotserver/handler.go
··· 16 16 tlog "tangled.sh/tangled.sh/core/log" 17 17 "tangled.sh/tangled.sh/core/notifier" 18 18 "tangled.sh/tangled.sh/core/rbac" 19 + "tangled.sh/tangled.sh/core/types" 19 20 ) 20 21 21 22 type Handle struct { ··· 171 170 func (h *Handle) XrpcRouter() http.Handler { 172 171 logger := tlog.New("knots") 173 172 173 + serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String()) 174 + 174 175 xrpc := &xrpc.Xrpc{ 175 - Config: h.c, 176 - Db: h.db, 177 - Ingester: h.jc, 178 - Enforcer: h.e, 179 - Logger: logger, 180 - Notifier: h.n, 181 - Resolver: h.resolver, 176 + Config: h.c, 177 + Db: h.db, 178 + Ingester: h.jc, 179 + Enforcer: h.e, 180 + Logger: logger, 181 + Notifier: h.n, 182 + Resolver: h.resolver, 183 + ServiceAuth: serviceAuth, 182 184 } 183 185 return xrpc.Router() 184 186 }
+12 -114
knotserver/xrpc/router.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "context" 5 4 "encoding/json" 6 - "fmt" 7 5 "log/slog" 8 6 "net/http" 9 - "strings" 10 7 11 8 "tangled.sh/tangled.sh/core/api/tangled" 12 9 "tangled.sh/tangled.sh/core/idresolver" ··· 12 15 "tangled.sh/tangled.sh/core/knotserver/db" 13 16 "tangled.sh/tangled.sh/core/notifier" 14 17 "tangled.sh/tangled.sh/core/rbac" 18 + xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 19 + "tangled.sh/tangled.sh/core/xrpc/serviceauth" 15 20 16 - "github.com/bluesky-social/indigo/atproto/auth" 17 21 "github.com/go-chi/chi/v5" 18 22 ) 19 23 20 24 type Xrpc struct { 21 - Config *config.Config 22 - Db *db.DB 23 - Ingester *jetstream.JetstreamClient 24 - Enforcer *rbac.Enforcer 25 - Logger *slog.Logger 26 - Notifier *notifier.Notifier 27 - Resolver *idresolver.Resolver 25 + Config *config.Config 26 + Db *db.DB 27 + Ingester *jetstream.JetstreamClient 28 + Enforcer *rbac.Enforcer 29 + Logger *slog.Logger 30 + Notifier *notifier.Notifier 31 + Resolver *idresolver.Resolver 32 + ServiceAuth *serviceauth.ServiceAuth 28 33 } 29 34 30 35 func (x *Xrpc) Router() http.Handler { 31 36 r := chi.NewRouter() 32 37 33 - r.With(x.VerifyServiceAuth).Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 38 + r.With(x.ServiceAuth.VerifyServiceAuth).Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 34 39 35 40 return r 36 - } 37 - 38 - func (x *Xrpc) VerifyServiceAuth(next http.Handler) http.Handler { 39 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 - l := x.Logger.With("url", r.URL) 41 - 42 - token := r.Header.Get("Authorization") 43 - token = strings.TrimPrefix(token, "Bearer ") 44 - 45 - s := auth.ServiceAuthValidator{ 46 - Audience: x.Config.Server.Did().String(), 47 - Dir: x.Resolver.Directory(), 48 - } 49 - 50 - did, err := s.Validate(r.Context(), token, nil) 51 - if err != nil { 52 - l.Error("signature verification failed", "err", err) 53 - writeError(w, AuthError(err), http.StatusForbidden) 54 - return 55 - } 56 - 57 - r = r.WithContext( 58 - context.WithValue(r.Context(), ActorDid, did), 59 - ) 60 - 61 - next.ServeHTTP(w, r) 62 - }) 63 - } 64 - 65 - type XrpcError struct { 66 - Tag string `json:"error"` 67 - Message string `json:"message"` 68 - } 69 - 70 - func NewXrpcError(opts ...ErrOpt) XrpcError { 71 - x := XrpcError{} 72 - for _, o := range opts { 73 - o(&x) 74 - } 75 - 76 - return x 77 - } 78 - 79 - type ErrOpt = func(xerr *XrpcError) 80 - 81 - func WithTag(tag string) ErrOpt { 82 - return func(xerr *XrpcError) { 83 - xerr.Tag = tag 84 - } 85 - } 86 - 87 - func WithMessage[S ~string](s S) ErrOpt { 88 - return func(xerr *XrpcError) { 89 - xerr.Message = string(s) 90 - } 91 - } 92 - 93 - func WithError(e error) ErrOpt { 94 - return func(xerr *XrpcError) { 95 - xerr.Message = e.Error() 96 - } 97 - } 98 - 99 - var MissingActorDidError = NewXrpcError( 100 - WithTag("MissingActorDid"), 101 - WithMessage("actor DID not supplied"), 102 - ) 103 - 104 - var AuthError = func(err error) XrpcError { 105 - return NewXrpcError( 106 - WithTag("Auth"), 107 - WithError(fmt.Errorf("signature verification failed: %w", err)), 108 - ) 109 - } 110 - 111 - var InvalidRepoError = func(r string) XrpcError { 112 - return NewXrpcError( 113 - WithTag("InvalidRepo"), 114 - WithError(fmt.Errorf("supplied at-uri is not a repo: %s", r)), 115 - ) 116 - } 117 - 118 - var AccessControlError = func(d string) XrpcError { 119 - return NewXrpcError( 120 - WithTag("AccessControl"), 121 - WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)), 122 - ) 123 - } 124 - 125 - var GitError = func(e error) XrpcError { 126 - return NewXrpcError( 127 - WithTag("Git"), 128 - WithError(fmt.Errorf("git error: %w", e)), 129 - ) 130 - } 131 - 132 - func GenericError(err error) XrpcError { 133 - return NewXrpcError( 134 - WithTag("Generic"), 135 - WithError(err), 136 - ) 137 41 } 138 42 139 43 // this is slightly different from http_util::write_error to follow the spec: 140 44 // 141 45 // the json object returned must include an "error" and a "message" 142 - func writeError(w http.ResponseWriter, e XrpcError, status int) { 46 + func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) { 143 47 w.Header().Set("Content-Type", "application/json") 144 48 w.WriteHeader(status) 145 49 json.NewEncoder(w).Encode(e)
+12 -10
knotserver/xrpc/set_default_branch.go
··· 12 12 "tangled.sh/tangled.sh/core/api/tangled" 13 13 "tangled.sh/tangled.sh/core/knotserver/git" 14 14 "tangled.sh/tangled.sh/core/rbac" 15 + 16 + xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 15 17 ) 16 18 17 19 const ActorDid string = "ActorDid" 18 20 19 21 func (x *Xrpc) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 20 22 l := x.Logger 21 - fail := func(e XrpcError) { 23 + fail := func(e xrpcerr.XrpcError) { 22 24 l.Error("failed", "kind", e.Tag, "error", e.Message) 23 25 writeError(w, e, http.StatusBadRequest) 24 26 } 25 27 26 28 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID) 27 29 if !ok { 28 - fail(MissingActorDidError) 30 + fail(xrpcerr.MissingActorDidError) 29 31 return 30 32 } 31 33 32 34 var data tangled.RepoSetDefaultBranch_Input 33 35 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 34 - fail(GenericError(err)) 36 + fail(xrpcerr.GenericError(err)) 35 37 return 36 38 } 37 39 38 40 // unfortunately we have to resolve repo-at here 39 41 repoAt, err := syntax.ParseATURI(data.Repo) 40 42 if err != nil { 41 - fail(InvalidRepoError(data.Repo)) 43 + fail(xrpcerr.InvalidRepoError(data.Repo)) 42 44 return 43 45 } 44 46 45 47 // resolve this aturi to extract the repo record 46 48 ident, err := x.Resolver.ResolveIdent(r.Context(), repoAt.Authority().String()) 47 49 if err != nil || ident.Handle.IsInvalidHandle() { 48 - fail(GenericError(fmt.Errorf("failed to resolve handle: %w", err))) 50 + fail(xrpcerr.GenericError(fmt.Errorf("failed to resolve handle: %w", err))) 49 51 return 50 52 } 51 53 52 54 xrpcc := xrpc.Client{Host: ident.PDSEndpoint()} 53 55 resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String()) 54 56 if err != nil { 55 - fail(GenericError(err)) 57 + fail(xrpcerr.GenericError(err)) 56 58 return 57 59 } 58 60 59 61 repo := resp.Value.Val.(*tangled.Repo) 60 62 didPath, err := securejoin.SecureJoin(actorDid.String(), repo.Name) 61 63 if err != nil { 62 - fail(GenericError(err)) 64 + fail(xrpcerr.GenericError(err)) 63 65 return 64 66 } 65 67 66 68 if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil { 67 69 l.Error("insufficent permissions", "did", actorDid.String()) 68 - writeError(w, AccessControlError(actorDid.String()), http.StatusUnauthorized) 70 + writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized) 69 71 return 70 72 } 71 73 72 74 path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath) 73 75 gr, err := git.PlainOpen(path) 74 76 if err != nil { 75 - fail(InvalidRepoError(data.Repo)) 77 + fail(xrpcerr.GenericError(err)) 76 78 return 77 79 } 78 80 79 81 err = gr.SetDefaultBranch(data.DefaultBranch) 80 82 if err != nil { 81 83 l.Error("setting default branch", "error", err.Error()) 82 - writeError(w, GitError(err), http.StatusInternalServerError) 84 + writeError(w, xrpcerr.GitError(err), http.StatusInternalServerError) 83 85 return 84 86 } 85 87