A vibe coded tangled fork which supports pijul.
1package xrpc
2
3import (
4 "net/http"
5 "os"
6 "slices"
7 "strings"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 securejoin "github.com/cyphar/filepath-securejoin"
11 "tangled.org/core/api/tangled"
12 "tangled.org/core/rbac"
13 xrpcerr "tangled.org/core/xrpc/errors"
14)
15
16const (
17 permPush int64 = 1 << iota
18 permSettings
19 permCreateDiscussion
20 permEditDiscussion
21 permTagDiscussion
22 permOwner
23 permInvite
24 permDelete
25)
26
27// RepoPermissions returns the permissions the authenticated user has on a repository.
28// This endpoint is VCS-agnostic — it works for both git and pijul repos.
29func (x *Xrpc) RepoPermissions(w http.ResponseWriter, r *http.Request) {
30 l := x.Logger.With("handler", "RepoPermissions")
31 fail := func(e xrpcerr.XrpcError, status int) {
32 l.Error("failed", "kind", e.Tag, "error", e.Message)
33 writeError(w, e, status)
34 }
35
36 actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
37 if !ok {
38 fail(xrpcerr.MissingActorDidError, http.StatusBadRequest)
39 return
40 }
41
42 repo := r.URL.Query().Get("repo")
43 if repo == "" {
44 fail(xrpcerr.NewXrpcError(
45 xrpcerr.WithTag("InvalidRequest"),
46 xrpcerr.WithMessage("missing repo parameter"),
47 ), http.StatusBadRequest)
48 return
49 }
50
51 repoPath, err := x.parseRepoParam(repo)
52 if err != nil {
53 fail(err.(xrpcerr.XrpcError), http.StatusBadRequest)
54 return
55 }
56
57 if _, err := os.Stat(repoPath); err != nil {
58 if os.IsNotExist(err) {
59 writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent)
60 return
61 }
62 fail(xrpcerr.GenericError(err), http.StatusInternalServerError)
63 return
64 }
65
66 repoParts := strings.SplitN(repo, "/", 2)
67 if len(repoParts) != 2 {
68 fail(xrpcerr.NewXrpcError(
69 xrpcerr.WithTag("InvalidRequest"),
70 xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"),
71 ), http.StatusBadRequest)
72 return
73 }
74
75 didSlashRepo, err := securejoin.SecureJoin(repoParts[0], repoParts[1])
76 if err != nil {
77 fail(xrpcerr.InvalidRepoError(repo), http.StatusBadRequest)
78 return
79 }
80
81 roles := x.Enforcer.GetPermissionsInRepo(actorDid.String(), rbac.ThisServer, didSlashRepo)
82
83 var mask int64
84 var permissions []string
85 add := func(name string, bit int64, ok bool) {
86 if !ok {
87 return
88 }
89 mask |= bit
90 permissions = append(permissions, name)
91 }
92
93 has := func(perm string) bool {
94 return slices.Contains(roles, perm)
95 }
96
97 add("push", permPush, has("repo:push"))
98 add("settings", permSettings, has("repo:settings"))
99 add("create_discussion", permCreateDiscussion, has("repo:create_discussion"))
100 add("edit_discussion", permEditDiscussion, has("repo:edit_discussion"))
101 add("tag_discussion", permTagDiscussion, has("repo:tag_discussion"))
102 add("owner", permOwner, has("repo:owner"))
103 add("invite", permInvite, has("repo:invite"))
104 add("delete", permDelete, has("repo:delete"))
105
106 writeJson(w, tangled.RepoPermissions_Output{
107 Mask: mask,
108 Permissions: permissions,
109 })
110}