Signed-off-by: Lewis lewis@tangled.org
+227
-115
Diff
round #13
+127
-26
knotserver/xrpc/create_repo.go
+127
-26
knotserver/xrpc/create_repo.go
···
6
6
"errors"
7
7
"fmt"
8
8
"net/http"
9
-
"path/filepath"
9
+
"os"
10
10
"strings"
11
11
"time"
12
12
13
-
comatproto "github.com/bluesky-social/indigo/api/atproto"
14
13
"github.com/bluesky-social/indigo/atproto/syntax"
15
-
"github.com/bluesky-social/indigo/xrpc"
14
+
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
16
15
securejoin "github.com/cyphar/filepath-securejoin"
17
16
gogit "github.com/go-git/go-git/v5"
18
17
"tangled.org/core/api/tangled"
19
18
"tangled.org/core/hook"
20
19
"tangled.org/core/knotserver/git"
20
+
"tangled.org/core/knotserver/repodid"
21
21
"tangled.org/core/rbac"
22
22
xrpcerr "tangled.org/core/xrpc/errors"
23
23
)
···
51
51
return
52
52
}
53
53
54
-
rkey := data.Rkey
54
+
repoName := data.Name
55
55
56
-
ident, err := h.Resolver.ResolveIdent(r.Context(), actorDid.String())
57
-
if err != nil || ident.Handle.IsInvalidHandle() {
58
-
fail(xrpcerr.GenericError(err))
56
+
if repoName == "" {
57
+
fail(xrpcerr.GenericError(fmt.Errorf("repository name is required")))
59
58
return
60
59
}
61
60
62
-
xrpcc := xrpc.Client{
63
-
Host: ident.PDSEndpoint(),
61
+
defaultBranch := h.Config.Repo.MainBranch
62
+
if data.DefaultBranch != nil && *data.DefaultBranch != "" {
63
+
defaultBranch = *data.DefaultBranch
64
64
}
65
65
66
-
resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey)
67
-
if err != nil {
66
+
if err := validateRepoName(repoName); err != nil {
67
+
l.Error("creating repo", "error", err.Error())
68
68
fail(xrpcerr.GenericError(err))
69
69
return
70
70
}
71
71
72
-
repo := resp.Value.Val.(*tangled.Repo)
72
+
var repoDid string
73
+
var prepared *repodid.PreparedDID
73
74
74
-
defaultBranch := h.Config.Repo.MainBranch
75
-
if data.DefaultBranch != nil && *data.DefaultBranch != "" {
76
-
defaultBranch = *data.DefaultBranch
75
+
knotServiceUrl := "https://" + h.Config.Server.Hostname
76
+
if h.Config.Server.Dev {
77
+
knotServiceUrl = "http://" + h.Config.Server.Hostname
77
78
}
78
79
79
-
if err := validateRepoName(repo.Name); err != nil {
80
-
l.Error("creating repo", "error", err.Error())
81
-
fail(xrpcerr.GenericError(err))
80
+
switch {
81
+
case data.RepoDid != nil && strings.HasPrefix(*data.RepoDid, "did:web:"):
82
+
if err := repodid.VerifyRepoDIDWeb(r.Context(), h.Resolver, *data.RepoDid, knotServiceUrl); err != nil {
83
+
l.Error("verifying did:web", "error", err.Error())
84
+
writeError(w, xrpcerr.GenericError(err), http.StatusBadRequest)
85
+
return
86
+
}
87
+
88
+
exists, err := h.Db.RepoDidExists(*data.RepoDid)
89
+
if err != nil {
90
+
l.Error("checking did:web uniqueness", "error", err.Error())
91
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
92
+
return
93
+
}
94
+
if exists {
95
+
writeError(w, xrpcerr.GenericError(fmt.Errorf("did:web %s is already in use on this knot", *data.RepoDid)), http.StatusConflict)
96
+
return
97
+
}
98
+
99
+
repoDid = *data.RepoDid
100
+
101
+
case data.RepoDid != nil && *data.RepoDid != "":
102
+
writeError(w, xrpcerr.GenericError(fmt.Errorf("only did:web is accepted as a user-provided repo DID; did:plc is auto-generated")), http.StatusBadRequest)
82
103
return
104
+
105
+
default:
106
+
existingDid, dbErr := h.Db.GetRepoDid(actorDid.String(), repoName)
107
+
if dbErr == nil && existingDid != "" {
108
+
didRepoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, existingDid)
109
+
if _, statErr := os.Stat(didRepoPath); statErr == nil {
110
+
l.Info("repo already exists from previous attempt", "repoDid", existingDid)
111
+
output := tangled.RepoCreate_Output{RepoDid: &existingDid}
112
+
writeJson(w, &output)
113
+
return
114
+
}
115
+
l.Warn("stale repo key found without directory, cleaning up", "repoDid", existingDid)
116
+
if delErr := h.Db.DeleteRepoKey(existingDid); delErr != nil {
117
+
l.Error("failed to clean up stale repo key", "repoDid", existingDid, "error", delErr.Error())
118
+
writeError(w, xrpcerr.GenericError(fmt.Errorf("failed to clean up stale state, retry later")), http.StatusInternalServerError)
119
+
return
120
+
}
121
+
}
122
+
123
+
var prepErr error
124
+
prepared, prepErr = repodid.PrepareRepoDID(h.Config.Server.PlcUrl, knotServiceUrl)
125
+
if prepErr != nil {
126
+
l.Error("preparing repo DID", "error", prepErr.Error())
127
+
writeError(w, xrpcerr.GenericError(prepErr), http.StatusInternalServerError)
128
+
return
129
+
}
130
+
repoDid = prepared.RepoDid
131
+
132
+
atUri := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, data.Rkey)
133
+
if err := h.Db.StoreRepoKey(repoDid, prepared.SigningKeyRaw, actorDid.String(), repoName, atUri); err != nil {
134
+
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
135
+
writeError(w, xrpcerr.GenericError(fmt.Errorf("repository %s already being created", repoName)), http.StatusConflict)
136
+
return
137
+
}
138
+
l.Error("claiming repo key slot", "error", err.Error())
139
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
140
+
return
141
+
}
142
+
}
143
+
144
+
l = l.With("repoDid", repoDid)
145
+
146
+
repoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, repoDid)
147
+
rbacPath := repoDid
148
+
149
+
cleanup := func() {
150
+
if rmErr := os.RemoveAll(repoPath); rmErr != nil {
151
+
l.Error("failed to clean up repo directory", "path", repoPath, "error", rmErr.Error())
152
+
}
83
153
}
84
154
85
-
relativeRepoPath := filepath.Join(actorDid.String(), repo.Name)
86
-
repoPath, _ := securejoin.SecureJoin(h.Config.Repo.ScanPath, relativeRepoPath)
155
+
cleanupAll := func() {
156
+
cleanup()
157
+
if delErr := h.Db.DeleteRepoKey(repoDid); delErr != nil {
158
+
l.Error("failed to clean up repo key", "error", delErr.Error())
159
+
}
160
+
}
87
161
88
162
if data.Source != nil && *data.Source != "" {
89
163
err = git.Fork(repoPath, *data.Source, h.Config)
90
164
if err != nil {
91
165
l.Error("forking repo", "error", err.Error())
166
+
cleanupAll()
92
167
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
93
168
return
94
169
}
···
96
171
err = git.InitBare(repoPath, defaultBranch)
97
172
if err != nil {
98
173
l.Error("initializing bare repo", "error", err.Error())
174
+
cleanupAll()
99
175
if errors.Is(err, gogit.ErrRepositoryAlreadyExists) {
100
176
fail(xrpcerr.RepoExistsError("repository already exists"))
101
177
return
102
-
} else {
103
-
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
178
+
}
179
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
180
+
return
181
+
}
182
+
}
183
+
184
+
if data.RepoDid != nil && strings.HasPrefix(*data.RepoDid, "did:web:") {
185
+
webAtUri := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, data.Rkey)
186
+
if err := h.Db.StoreRepoDidWeb(repoDid, actorDid.String(), repoName, webAtUri); err != nil {
187
+
cleanupAll()
188
+
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
189
+
writeError(w, xrpcerr.GenericError(fmt.Errorf("did:web %s is already in use", repoDid)), http.StatusConflict)
104
190
return
105
191
}
192
+
l.Error("storing did:web repo entry", "error", err.Error())
193
+
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
194
+
return
195
+
}
196
+
}
197
+
198
+
if prepared != nil {
199
+
plcCtx, plcCancel := context.WithTimeout(context.Background(), 30*time.Second)
200
+
defer plcCancel()
201
+
if err := prepared.Submit(plcCtx); err != nil {
202
+
l.Error("submitting to PLC directory", "error", err.Error())
203
+
cleanupAll()
204
+
writeError(w, xrpcerr.GenericError(fmt.Errorf("PLC directory submission failed: %w", err)), http.StatusInternalServerError)
205
+
return
106
206
}
107
207
}
108
208
109
209
// add perms for this user to access the repo
110
-
err = h.Enforcer.AddRepo(actorDid.String(), rbac.ThisServer, relativeRepoPath)
210
+
err = h.Enforcer.AddRepo(actorDid.String(), rbac.ThisServer, rbacPath)
111
211
if err != nil {
112
212
l.Error("adding repo permissions", "error", err.Error())
213
+
cleanupAll()
113
214
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
114
215
return
115
216
}
···
127
228
// Therefore, to bypass the local tap, requestCrawl directly to the knotmirror.
128
229
go func() {
129
230
if h.Config.Server.Dev {
130
-
repoAt := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, rkey)
231
+
repoAt := fmt.Sprintf("at://%s/%s/%s", actorDid, tangled.RepoNSID, data.Rkey)
131
232
rCtx, rCancel := context.WithTimeout(context.Background(), 10*time.Second)
132
233
defer rCancel()
133
234
h.requestCrawl(rCtx, &tangled.SyncRequestCrawl_Input{
···
137
238
}
138
239
}()
139
240
140
-
w.WriteHeader(http.StatusOK)
241
+
writeJson(w, &tangled.RepoCreate_Output{RepoDid: &repoDid})
141
242
}
142
243
143
244
func (h *Xrpc) requestCrawl(ctx context.Context, input *tangled.SyncRequestCrawl_Input) error {
144
245
h.Logger.Info("requesting crawl", "mirrors", h.Config.KnotMirrors)
145
246
for _, knotmirror := range h.Config.KnotMirrors {
146
-
xrpcc := xrpc.Client{Host: knotmirror}
247
+
xrpcc := indigoxrpc.Client{Host: knotmirror}
147
248
if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil {
148
249
h.Logger.Error("error requesting crawl", "err", err)
149
250
} else {
+10
-7
knotserver/xrpc/delete_branch.go
+10
-7
knotserver/xrpc/delete_branch.go
···
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
10
"github.com/bluesky-social/indigo/xrpc"
11
-
securejoin "github.com/cyphar/filepath-securejoin"
12
11
"tangled.org/core/api/tangled"
13
12
"tangled.org/core/knotserver/git"
14
13
"tangled.org/core/rbac"
···
57
56
}
58
57
59
58
repo := resp.Value.Val.(*tangled.Repo)
60
-
didPath, err := securejoin.SecureJoin(ident.DID.String(), repo.Name)
59
+
repoDid, err := x.Db.GetRepoDid(ident.DID.String(), repo.Name)
61
60
if err != nil {
62
-
fail(xrpcerr.GenericError(err))
61
+
fail(xrpcerr.RepoNotFoundError)
62
+
return
63
+
}
64
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
65
+
if err != nil {
66
+
fail(xrpcerr.RepoNotFoundError)
63
67
return
64
68
}
65
69
66
-
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {
67
-
l.Error("insufficent permissions", "did", actorDid.String(), "repo", didPath)
70
+
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil {
71
+
l.Error("insufficent permissions", "did", actorDid.String(), "repo", repoDid)
68
72
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
69
73
return
70
74
}
71
75
72
-
path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath)
73
-
gr, err := git.PlainOpen(path)
76
+
gr, err := git.PlainOpen(repoPath)
74
77
if err != nil {
75
78
fail(xrpcerr.GenericError(err))
76
79
return
+15
-9
knotserver/xrpc/delete_repo.go
+15
-9
knotserver/xrpc/delete_repo.go
···
5
5
"fmt"
6
6
"net/http"
7
7
"os"
8
-
"path/filepath"
9
8
10
9
comatproto "github.com/bluesky-social/indigo/api/atproto"
11
10
"github.com/bluesky-social/indigo/atproto/syntax"
12
11
"github.com/bluesky-social/indigo/xrpc"
13
-
securejoin "github.com/cyphar/filepath-securejoin"
14
12
"tangled.org/core/api/tangled"
15
13
"tangled.org/core/rbac"
16
14
xrpcerr "tangled.org/core/xrpc/errors"
···
61
59
return
62
60
}
63
61
64
-
relativeRepoPath := filepath.Join(did, name)
65
-
isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath)
62
+
repoDid, err := x.Db.GetRepoDid(did, name)
66
63
if err != nil {
67
-
fail(xrpcerr.GenericError(err))
64
+
fail(xrpcerr.RepoNotFoundError)
68
65
return
69
66
}
70
-
if !isDeleteAllowed {
71
-
fail(xrpcerr.AccessControlError(actorDid.String()))
67
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
68
+
if err != nil {
69
+
fail(xrpcerr.RepoNotFoundError)
72
70
return
73
71
}
74
72
75
-
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
73
+
isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, repoDid)
76
74
if err != nil {
77
75
fail(xrpcerr.GenericError(err))
78
76
return
79
77
}
78
+
if !isDeleteAllowed {
79
+
fail(xrpcerr.AccessControlError(actorDid.String()))
80
+
return
81
+
}
80
82
81
83
err = os.RemoveAll(repoPath)
82
84
if err != nil {
···
85
87
return
86
88
}
87
89
88
-
err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, relativeRepoPath)
90
+
err = x.Enforcer.RemoveRepo(did, rbac.ThisServer, repoDid)
89
91
if err != nil {
90
92
l.Error("failed to delete repo from enforcer", "error", err.Error())
91
93
writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError)
92
94
return
93
95
}
94
96
97
+
if err := x.Db.DeleteRepoKey(repoDid); err != nil {
98
+
l.Error("failed to delete repo key", "error", err.Error())
99
+
}
100
+
95
101
w.WriteHeader(http.StatusOK)
96
102
}
+11
-9
knotserver/xrpc/fork_status.go
+11
-9
knotserver/xrpc/fork_status.go
···
7
7
"path/filepath"
8
8
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
-
securejoin "github.com/cyphar/filepath-securejoin"
11
10
"tangled.org/core/api/tangled"
12
11
"tangled.org/core/knotserver/git"
13
12
"tangled.org/core/rbac"
···
51
50
name = filepath.Base(source)
52
51
}
53
52
54
-
relativeRepoPath := filepath.Join(did, name)
55
-
56
-
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
57
-
l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath)
58
-
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
53
+
repoDid, err := x.Db.GetRepoDid(did, name)
54
+
if err != nil {
55
+
fail(xrpcerr.RepoNotFoundError)
59
56
return
60
57
}
61
-
62
-
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
58
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
63
59
if err != nil {
64
-
fail(xrpcerr.GenericError(err))
60
+
fail(xrpcerr.RepoNotFoundError)
61
+
return
62
+
}
63
+
64
+
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil {
65
+
l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid)
66
+
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
65
67
return
66
68
}
67
69
+11
-10
knotserver/xrpc/fork_sync.go
+11
-10
knotserver/xrpc/fork_sync.go
···
4
4
"encoding/json"
5
5
"fmt"
6
6
"net/http"
7
-
"path/filepath"
8
7
9
8
"github.com/bluesky-social/indigo/atproto/syntax"
10
-
securejoin "github.com/cyphar/filepath-securejoin"
11
9
"tangled.org/core/api/tangled"
12
10
"tangled.org/core/knotserver/git"
13
11
"tangled.org/core/rbac"
···
42
40
return
43
41
}
44
42
45
-
relativeRepoPath := filepath.Join(did, name)
46
-
47
-
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
48
-
l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath)
49
-
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
43
+
repoDid, err := x.Db.GetRepoDid(did, name)
44
+
if err != nil {
45
+
fail(xrpcerr.RepoNotFoundError)
50
46
return
51
47
}
52
-
53
-
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
48
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
54
49
if err != nil {
55
-
fail(xrpcerr.GenericError(err))
50
+
fail(xrpcerr.RepoNotFoundError)
51
+
return
52
+
}
53
+
54
+
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil {
55
+
l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid)
56
+
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
56
57
return
57
58
}
58
59
+8
-10
knotserver/xrpc/merge.go
+8
-10
knotserver/xrpc/merge.go
···
7
7
"net/http"
8
8
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
-
securejoin "github.com/cyphar/filepath-securejoin"
11
10
"tangled.org/core/api/tangled"
12
11
"tangled.org/core/knotserver/git"
13
12
"tangled.org/core/patchutil"
···
43
42
return
44
43
}
45
44
46
-
relativeRepoPath, err := securejoin.SecureJoin(did, name)
45
+
repoDid, err := x.Db.GetRepoDid(did, name)
47
46
if err != nil {
48
-
fail(xrpcerr.GenericError(err))
47
+
fail(xrpcerr.RepoNotFoundError)
49
48
return
50
49
}
51
-
52
-
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
53
-
l.Error("insufficient permissions", "did", actorDid.String(), "repo", relativeRepoPath)
54
-
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
50
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
51
+
if err != nil {
52
+
fail(xrpcerr.RepoNotFoundError)
55
53
return
56
54
}
57
55
58
-
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
59
-
if err != nil {
60
-
fail(xrpcerr.GenericError(err))
56
+
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil {
57
+
l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid)
58
+
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
61
59
return
62
60
}
63
61
+4
-6
knotserver/xrpc/merge_check.go
+4
-6
knotserver/xrpc/merge_check.go
···
6
6
"fmt"
7
7
"net/http"
8
8
9
-
securejoin "github.com/cyphar/filepath-securejoin"
10
9
"tangled.org/core/api/tangled"
11
10
"tangled.org/core/knotserver/git"
12
11
"tangled.org/core/patchutil"
···
34
33
return
35
34
}
36
35
37
-
relativeRepoPath, err := securejoin.SecureJoin(did, name)
36
+
repoDid, err := x.Db.GetRepoDid(did, name)
38
37
if err != nil {
39
-
fail(xrpcerr.GenericError(err))
38
+
fail(xrpcerr.RepoNotFoundError)
40
39
return
41
40
}
42
-
43
-
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, relativeRepoPath)
41
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
44
42
if err != nil {
45
-
fail(xrpcerr.GenericError(err))
43
+
fail(xrpcerr.RepoNotFoundError)
46
44
return
47
45
}
48
46
+9
-6
knotserver/xrpc/set_default_branch.go
+9
-6
knotserver/xrpc/set_default_branch.go
···
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
10
"github.com/bluesky-social/indigo/xrpc"
11
-
securejoin "github.com/cyphar/filepath-securejoin"
12
11
"tangled.org/core/api/tangled"
13
12
"tangled.org/core/knotserver/git"
14
13
"tangled.org/core/rbac"
···
59
58
}
60
59
61
60
repo := resp.Value.Val.(*tangled.Repo)
62
-
didPath, err := securejoin.SecureJoin(actorDid.String(), repo.Name)
61
+
repoDid, err := x.Db.GetRepoDid(actorDid.String(), repo.Name)
63
62
if err != nil {
64
-
fail(xrpcerr.GenericError(err))
63
+
fail(xrpcerr.RepoNotFoundError)
64
+
return
65
+
}
66
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
67
+
if err != nil {
68
+
fail(xrpcerr.RepoNotFoundError)
65
69
return
66
70
}
67
71
68
-
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {
72
+
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil {
69
73
l.Error("insufficent permissions", "did", actorDid.String())
70
74
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
71
75
return
72
76
}
73
77
74
-
path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath)
75
-
gr, err := git.PlainOpen(path)
78
+
gr, err := git.PlainOpen(repoPath)
76
79
if err != nil {
77
80
fail(xrpcerr.GenericError(err))
78
81
return
+24
-22
knotserver/xrpc/xrpc.go
+24
-22
knotserver/xrpc/xrpc.go
···
4
4
"encoding/json"
5
5
"log/slog"
6
6
"net/http"
7
+
"os"
8
+
"path/filepath"
7
9
"strings"
8
10
9
11
securejoin "github.com/cyphar/filepath-securejoin"
12
+
"github.com/go-chi/chi/v5"
10
13
"tangled.org/core/api/tangled"
11
14
"tangled.org/core/idresolver"
12
15
"tangled.org/core/jetstream"
···
16
19
"tangled.org/core/rbac"
17
20
xrpcerr "tangled.org/core/xrpc/errors"
18
21
"tangled.org/core/xrpc/serviceauth"
19
-
20
-
"github.com/go-chi/chi/v5"
21
22
)
22
23
23
24
type Xrpc struct {
···
78
79
return r
79
80
}
80
81
81
-
// parseRepoParam parses a repo parameter in 'did/repoName' format and returns
82
-
// the full repository path on disk
83
82
func (x *Xrpc) parseRepoParam(repo string) (string, error) {
84
-
if repo == "" {
83
+
if repo == "" || !strings.HasPrefix(repo, "did:") {
85
84
return "", xrpcerr.NewXrpcError(
86
85
xrpcerr.WithTag("InvalidRequest"),
87
-
xrpcerr.WithMessage("missing repo parameter"),
86
+
xrpcerr.WithMessage("missing or invalid repo parameter, expected a repo DID"),
88
87
)
89
88
}
90
89
91
-
// Parse repo string (did/repoName format)
92
-
parts := strings.SplitN(repo, "/", 2)
93
-
if len(parts) != 2 {
94
-
return "", xrpcerr.NewXrpcError(
95
-
xrpcerr.WithTag("InvalidRequest"),
96
-
xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"),
97
-
)
90
+
if !strings.Contains(repo, "/") {
91
+
repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repo)
92
+
if err != nil {
93
+
return "", xrpcerr.RepoNotFoundError
94
+
}
95
+
return repoPath, nil
98
96
}
99
97
100
-
did := parts[0]
101
-
repoName := parts[1]
98
+
parts := strings.SplitN(repo, "/", 2)
99
+
ownerDid, repoName := parts[0], parts[1]
100
+
101
+
repoDid, err := x.Db.GetRepoDid(ownerDid, repoName)
102
+
if err == nil {
103
+
repoPath, _, _, resolveErr := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
104
+
if resolveErr == nil {
105
+
return repoPath, nil
106
+
}
107
+
}
102
108
103
-
// Construct repository path using the same logic as didPath
104
-
didRepoPath, err := securejoin.SecureJoin(did, repoName)
105
-
if err != nil {
109
+
repoPath, joinErr := securejoin.SecureJoin(x.Config.Repo.ScanPath, filepath.Join(ownerDid, repoName))
110
+
if joinErr != nil {
106
111
return "", xrpcerr.RepoNotFoundError
107
112
}
108
-
109
-
repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath)
110
-
if err != nil {
113
+
if _, statErr := os.Stat(repoPath); statErr != nil {
111
114
return "", xrpcerr.RepoNotFoundError
112
115
}
113
-
114
116
return repoPath, nil
115
117
}
116
118
History
14 rounds
0 comments
oyster.cafe
submitted
#13
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
Lewis: May this revision serve well! <lewis@tangled.org>
no conflicts, ready to merge
expand 0 comments
oyster.cafe
submitted
#12
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#11
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#10
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#9
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#8
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#7
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#6
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#5
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#4
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#3
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#2
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#1
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>
expand 0 comments
oyster.cafe
submitted
#0
1 commit
expand
collapse
knotserver/xrpc: use repo DID resolution in all handlers
Signed-off-by: Lewis <lewis@tangled.org>