this repo has no description
1package xrpc 2 3import ( 4 "encoding/json" 5 "log/slog" 6 "net/http" 7 "net/url" 8 "strings" 9 10 securejoin "github.com/cyphar/filepath-securejoin" 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 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 19 "tangled.sh/tangled.sh/core/xrpc/serviceauth" 20 21 "github.com/go-chi/chi/v5" 22) 23 24type Xrpc struct { 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 33} 34 35func (x *Xrpc) Router() http.Handler { 36 r := chi.NewRouter() 37 38 r.Group(func(r chi.Router) { 39 r.Use(x.ServiceAuth.VerifyServiceAuth) 40 41 r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 42 r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo) 43 r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo) 44 r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus) 45 r.Post("/"+tangled.RepoForkSyncNSID, x.ForkSync) 46 r.Post("/"+tangled.RepoHiddenRefNSID, x.HiddenRef) 47 r.Post("/"+tangled.RepoMergeNSID, x.Merge) 48 }) 49 50 // merge check is an open endpoint 51 // 52 // TODO: should we constrain this more? 53 // - we can calculate on PR submit/resubmit/gitRefUpdate etc. 54 // - use ETags on clients to keep requests to a minimum 55 r.Post("/"+tangled.RepoMergeCheckNSID, x.MergeCheck) 56 57 // repo query endpoints (no auth required) 58 r.Get("/"+tangled.RepoTreeNSID, x.RepoTree) 59 r.Get("/"+tangled.RepoLogNSID, x.RepoLog) 60 r.Get("/"+tangled.RepoBranchesNSID, x.RepoBranches) 61 r.Get("/"+tangled.RepoTagsNSID, x.RepoTags) 62 r.Get("/"+tangled.RepoBlobNSID, x.RepoBlob) 63 r.Get("/"+tangled.RepoDiffNSID, x.RepoDiff) 64 r.Get("/"+tangled.RepoCompareNSID, x.RepoCompare) 65 r.Get("/"+tangled.RepoGetDefaultBranchNSID, x.RepoGetDefaultBranch) 66 r.Get("/"+tangled.RepoBranchNSID, x.RepoBranch) 67 r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive) 68 r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages) 69 70 // knot query endpoints (no auth required) 71 r.Get("/"+tangled.KnotListKeysNSID, x.ListKeys) 72 73 return r 74} 75 76// parseRepoParam parses a repo parameter in 'did/repoName' format and returns 77// the full repository path on disk 78func (x *Xrpc) parseRepoParam(repo string) (string, error) { 79 if repo == "" { 80 return "", xrpcerr.NewXrpcError( 81 xrpcerr.WithTag("InvalidRequest"), 82 xrpcerr.WithMessage("missing repo parameter"), 83 ) 84 } 85 86 // Parse repo string (did/repoName format) 87 parts := strings.Split(repo, "/") 88 if len(parts) < 2 { 89 return "", xrpcerr.NewXrpcError( 90 xrpcerr.WithTag("InvalidRequest"), 91 xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), 92 ) 93 } 94 95 did := strings.Join(parts[:len(parts)-1], "/") 96 repoName := parts[len(parts)-1] 97 98 // Construct repository path using the same logic as didPath 99 didRepoPath, err := securejoin.SecureJoin(did, repoName) 100 if err != nil { 101 return "", xrpcerr.NewXrpcError( 102 xrpcerr.WithTag("RepoNotFound"), 103 xrpcerr.WithMessage("failed to access repository"), 104 ) 105 } 106 107 repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath) 108 if err != nil { 109 return "", xrpcerr.NewXrpcError( 110 xrpcerr.WithTag("RepoNotFound"), 111 xrpcerr.WithMessage("failed to access repository"), 112 ) 113 } 114 115 return repoPath, nil 116} 117 118// parseStandardParams parses common query parameters used by most handlers 119func (x *Xrpc) parseStandardParams(r *http.Request) (repo, repoPath, ref string, err error) { 120 // Parse repo parameter 121 repo = r.URL.Query().Get("repo") 122 repoPath, err = x.parseRepoParam(repo) 123 if err != nil { 124 return "", "", "", err 125 } 126 127 // Parse and unescape ref parameter 128 refParam := r.URL.Query().Get("ref") 129 if refParam == "" { 130 return "", "", "", xrpcerr.NewXrpcError( 131 xrpcerr.WithTag("InvalidRequest"), 132 xrpcerr.WithMessage("missing ref parameter"), 133 ) 134 } 135 136 ref, _ = url.QueryUnescape(refParam) 137 return repo, repoPath, ref, nil 138} 139 140func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) { 141 w.Header().Set("Content-Type", "application/json") 142 w.WriteHeader(status) 143 json.NewEncoder(w).Encode(e) 144}