+1
-1
appview/config/config.go
+1
-1
appview/config/config.go
+98
-82
appview/oauth/handler/handler.go
+98
-82
appview/oauth/handler/handler.go
···
8
8
"log"
9
9
"net/http"
10
10
"net/url"
11
+
"slices"
11
12
"strings"
12
13
"time"
13
14
···
25
26
"tangled.sh/tangled.sh/core/appview/oauth/client"
26
27
"tangled.sh/tangled.sh/core/appview/pages"
27
28
"tangled.sh/tangled.sh/core/idresolver"
28
-
"tangled.sh/tangled.sh/core/knotclient"
29
29
"tangled.sh/tangled.sh/core/rbac"
30
30
"tangled.sh/tangled.sh/core/tid"
31
31
)
···
353
353
return pubKey, nil
354
354
}
355
355
356
+
var (
357
+
tangledHandle = "tangled.sh"
358
+
tangledDid = "did:plc:wshs7t2adsemcrrd4snkeqli"
359
+
defaultSpindle = "spindle.tangled.sh"
360
+
defaultKnot = "knot1.tangled.sh"
361
+
)
362
+
356
363
func (o *OAuthHandler) addToDefaultSpindle(did string) {
357
364
// use the tangled.sh app password to get an accessJwt
358
365
// and create an sh.tangled.spindle.member record with that
359
-
360
-
defaultSpindle := "spindle.tangled.sh"
361
-
appPassword := o.config.Core.AppPassword
362
-
363
366
spindleMembers, err := db.GetSpindleMembers(
364
367
o.db,
365
368
db.FilterEq("instance", "spindle.tangled.sh"),
···
375
378
return
376
379
}
377
380
378
-
// TODO: hardcoded tangled handle and did for now
379
-
tangledHandle := "tangled.sh"
380
-
tangledDid := "did:plc:wshs7t2adsemcrrd4snkeqli"
381
+
log.Printf("adding %s to default spindle", did)
382
+
session, err := o.createAppPasswordSession()
383
+
if err != nil {
384
+
log.Printf("failed to create session: %s", err)
385
+
return
386
+
}
387
+
388
+
record := tangled.SpindleMember{
389
+
LexiconTypeID: "sh.tangled.spindle.member",
390
+
Subject: did,
391
+
Instance: defaultSpindle,
392
+
CreatedAt: time.Now().Format(time.RFC3339),
393
+
}
394
+
395
+
if err := session.putRecord(record); err != nil {
396
+
log.Printf("failed to add member to default knot: %s", err)
397
+
return
398
+
}
399
+
400
+
log.Printf("successfully added %s to default spindle", did)
401
+
}
402
+
403
+
func (o *OAuthHandler) addToDefaultKnot(did string) {
404
+
// use the tangled.sh app password to get an accessJwt
405
+
// and create an sh.tangled.spindle.member record with that
406
+
407
+
allKnots, err := o.enforcer.GetKnotsForUser(did)
408
+
if err != nil {
409
+
log.Printf("failed to get knot members for did %s: %v", did, err)
410
+
return
411
+
}
412
+
413
+
if slices.Contains(allKnots, defaultKnot) {
414
+
log.Printf("did %s is already a member of the default knot", did)
415
+
return
416
+
}
381
417
382
-
if appPassword == "" {
383
-
log.Println("no app password configured, skipping spindle member addition")
418
+
log.Printf("adding %s to default knot", did)
419
+
session, err := o.createAppPasswordSession()
420
+
if err != nil {
421
+
log.Printf("failed to create session: %s", err)
384
422
return
385
423
}
386
424
387
-
log.Printf("adding %s to default spindle", did)
425
+
record := tangled.KnotMember{
426
+
LexiconTypeID: "sh.tangled.knot.member",
427
+
Subject: did,
428
+
Domain: defaultKnot,
429
+
CreatedAt: time.Now().Format(time.RFC3339),
430
+
}
431
+
432
+
if err := session.putRecord(record); err != nil {
433
+
log.Printf("failed to add member to default knot: %s", err)
434
+
return
435
+
}
436
+
437
+
log.Printf("successfully added %s to default Knot", did)
438
+
}
439
+
440
+
// create a session using apppasswords
441
+
type session struct {
442
+
AccessJwt string `json:"accessJwt"`
443
+
PdsEndpoint string
444
+
}
445
+
446
+
func (o *OAuthHandler) createAppPasswordSession() (*session, error) {
447
+
appPassword := o.config.Core.AppPassword
448
+
if appPassword == "" {
449
+
return nil, fmt.Errorf("no app password configured, skipping member addition")
450
+
}
388
451
389
452
resolved, err := o.idResolver.ResolveIdent(context.Background(), tangledDid)
390
453
if err != nil {
391
-
log.Printf("failed to resolve tangled.sh DID %s: %v", tangledDid, err)
392
-
return
454
+
return nil, fmt.Errorf("failed to resolve tangled.sh DID %s: %v", tangledDid, err)
393
455
}
394
456
395
457
pdsEndpoint := resolved.PDSEndpoint()
396
458
if pdsEndpoint == "" {
397
-
log.Printf("no PDS endpoint found for tangled.sh DID %s", tangledDid)
398
-
return
459
+
return nil, fmt.Errorf("no PDS endpoint found for tangled.sh DID %s", tangledDid)
399
460
}
400
461
401
462
sessionPayload := map[string]string{
···
404
465
}
405
466
sessionBytes, err := json.Marshal(sessionPayload)
406
467
if err != nil {
407
-
log.Printf("failed to marshal session payload: %v", err)
408
-
return
468
+
return nil, fmt.Errorf("failed to marshal session payload: %v", err)
409
469
}
410
470
411
471
sessionURL := pdsEndpoint + "/xrpc/com.atproto.server.createSession"
412
472
sessionReq, err := http.NewRequestWithContext(context.Background(), "POST", sessionURL, bytes.NewBuffer(sessionBytes))
413
473
if err != nil {
414
-
log.Printf("failed to create session request: %v", err)
415
-
return
474
+
return nil, fmt.Errorf("failed to create session request: %v", err)
416
475
}
417
476
sessionReq.Header.Set("Content-Type", "application/json")
418
477
419
478
client := &http.Client{Timeout: 30 * time.Second}
420
479
sessionResp, err := client.Do(sessionReq)
421
480
if err != nil {
422
-
log.Printf("failed to create session: %v", err)
423
-
return
481
+
return nil, fmt.Errorf("failed to create session: %v", err)
424
482
}
425
483
defer sessionResp.Body.Close()
426
484
427
485
if sessionResp.StatusCode != http.StatusOK {
428
-
log.Printf("failed to create session: HTTP %d", sessionResp.StatusCode)
429
-
return
486
+
return nil, fmt.Errorf("failed to create session: HTTP %d", sessionResp.StatusCode)
430
487
}
431
488
432
-
var session struct {
433
-
AccessJwt string `json:"accessJwt"`
434
-
}
489
+
var session session
435
490
if err := json.NewDecoder(sessionResp.Body).Decode(&session); err != nil {
436
-
log.Printf("failed to decode session response: %v", err)
437
-
return
491
+
return nil, fmt.Errorf("failed to decode session response: %v", err)
438
492
}
439
493
440
-
record := tangled.SpindleMember{
441
-
LexiconTypeID: "sh.tangled.spindle.member",
442
-
Subject: did,
443
-
Instance: defaultSpindle,
444
-
CreatedAt: time.Now().Format(time.RFC3339),
445
-
}
494
+
session.PdsEndpoint = pdsEndpoint
495
+
496
+
return &session, nil
497
+
}
446
498
499
+
func (s *session) putRecord(record any) error {
447
500
recordBytes, err := json.Marshal(record)
448
501
if err != nil {
449
-
log.Printf("failed to marshal spindle member record: %v", err)
450
-
return
502
+
return fmt.Errorf("failed to marshal knot member record: %w", err)
451
503
}
452
504
453
-
payload := map[string]interface{}{
505
+
payload := map[string]any{
454
506
"repo": tangledDid,
455
-
"collection": tangled.SpindleMemberNSID,
507
+
"collection": tangled.KnotMemberNSID,
456
508
"rkey": tid.TID(),
457
509
"record": json.RawMessage(recordBytes),
458
510
}
459
511
460
512
payloadBytes, err := json.Marshal(payload)
461
513
if err != nil {
462
-
log.Printf("failed to marshal request payload: %v", err)
463
-
return
514
+
return fmt.Errorf("failed to marshal request payload: %w", err)
464
515
}
465
516
466
-
url := pdsEndpoint + "/xrpc/com.atproto.repo.putRecord"
517
+
url := s.PdsEndpoint + "/xrpc/com.atproto.repo.putRecord"
467
518
req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(payloadBytes))
468
519
if err != nil {
469
-
log.Printf("failed to create HTTP request: %v", err)
470
-
return
520
+
return fmt.Errorf("failed to create HTTP request: %w", err)
471
521
}
472
522
473
523
req.Header.Set("Content-Type", "application/json")
474
-
req.Header.Set("Authorization", "Bearer "+session.AccessJwt)
524
+
req.Header.Set("Authorization", "Bearer "+s.AccessJwt)
475
525
526
+
client := &http.Client{Timeout: 30 * time.Second}
476
527
resp, err := client.Do(req)
477
528
if err != nil {
478
-
log.Printf("failed to add user to default spindle: %v", err)
479
-
return
529
+
return fmt.Errorf("failed to add user to default Knot: %w", err)
480
530
}
481
531
defer resp.Body.Close()
482
532
483
533
if resp.StatusCode != http.StatusOK {
484
-
log.Printf("failed to add user to default spindle: HTTP %d", resp.StatusCode)
485
-
return
486
-
}
487
-
488
-
log.Printf("successfully added %s to default spindle", did)
489
-
}
490
-
491
-
func (o *OAuthHandler) addToDefaultKnot(did string) {
492
-
defaultKnot := "knot1.tangled.sh"
493
-
494
-
log.Printf("adding %s to default knot", did)
495
-
err := o.enforcer.AddKnotMember(defaultKnot, did)
496
-
if err != nil {
497
-
log.Println("failed to add user to knot1.tangled.sh: ", err)
498
-
return
499
-
}
500
-
err = o.enforcer.E.SavePolicy()
501
-
if err != nil {
502
-
log.Println("failed to add user to knot1.tangled.sh: ", err)
503
-
return
504
-
}
505
-
506
-
secret, err := db.GetRegistrationKey(o.db, defaultKnot)
507
-
if err != nil {
508
-
log.Println("failed to get registration key for knot1.tangled.sh")
509
-
return
510
-
}
511
-
signedClient, err := knotclient.NewSignedClient(defaultKnot, secret, o.config.Core.Dev)
512
-
resp, err := signedClient.AddMember(did)
513
-
if err != nil {
514
-
log.Println("failed to add user to knot1.tangled.sh: ", err)
515
-
return
534
+
return fmt.Errorf("failed to add user to default Knot: HTTP %d", resp.StatusCode)
516
535
}
517
536
518
-
if resp.StatusCode != http.StatusNoContent {
519
-
log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode)
520
-
return
521
-
}
537
+
return nil
522
538
}
-157
appview/repo/index.go
-157
appview/repo/index.go
···
101
101
user := rp.oauth.GetUser(r)
102
102
repoInfo := f.RepoInfo(user)
103
103
104
-
// secret, err := db.GetRegistrationKey(rp.db, f.Knot)
105
-
// if err != nil {
106
-
// log.Printf("failed to get registration key for %s: %s", f.Knot, err)
107
-
// rp.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
108
-
// }
109
-
110
-
// signedClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
111
-
// if err != nil {
112
-
// log.Printf("failed to create signed client for %s: %s", f.Knot, err)
113
-
// return
114
-
// }
115
-
116
-
// var forkInfo *types.ForkInfo
117
-
// if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) {
118
-
// forkInfo, err = getForkInfo(r, repoInfo, rp, f, result.Ref, user, signedClient)
119
-
// if err != nil {
120
-
// log.Printf("Failed to fetch fork information: %v", err)
121
-
// return
122
-
// }
123
-
// }
124
-
125
104
// TODO: a bit dirty
126
105
languageInfo, err := rp.getLanguageInfo(f, us, result.Ref, ref == "")
127
106
if err != nil {
···
227
206
228
207
return languageStats, nil
229
208
}
230
-
231
-
// func getForkInfo(
232
-
// r *http.Request,
233
-
// repoInfo repoinfo.RepoInfo,
234
-
// rp *Repo,
235
-
// f *reporesolver.ResolvedRepo,
236
-
// currentRef string,
237
-
// user *oauth.User,
238
-
// signedClient *knotclient.SignedClient,
239
-
// ) (*types.ForkInfo, error) {
240
-
// if user == nil {
241
-
// return nil, nil
242
-
// }
243
-
//
244
-
// forkInfo := types.ForkInfo{
245
-
// IsFork: repoInfo.Source != nil,
246
-
// Status: types.UpToDate,
247
-
// }
248
-
//
249
-
// if !forkInfo.IsFork {
250
-
// forkInfo.IsFork = false
251
-
// return &forkInfo, nil
252
-
// }
253
-
//
254
-
// us, err := knotclient.NewUnsignedClient(repoInfo.Source.Knot, rp.config.Core.Dev)
255
-
// if err != nil {
256
-
// log.Printf("failed to create unsigned client for %s", repoInfo.Source.Knot)
257
-
// return nil, err
258
-
// }
259
-
//
260
-
// result, err := us.Branches(repoInfo.Source.Did, repoInfo.Source.Name)
261
-
// if err != nil {
262
-
// log.Println("failed to reach knotserver", err)
263
-
// return nil, err
264
-
// }
265
-
//
266
-
// if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool {
267
-
// return branch.Name == currentRef
268
-
// }) {
269
-
// forkInfo.Status = types.MissingBranch
270
-
// return &forkInfo, nil
271
-
// }
272
-
//
273
-
// <<<<<<< Conflict 1 of 2
274
-
// %%%%%%% Changes from base #1 to side #1
275
-
// client, err := rp.oauth.ServiceClient(
276
-
// r,
277
-
// oauth.WithService(f.Knot),
278
-
// oauth.WithLxm(tangled.RepoHiddenRefNSID),
279
-
// oauth.WithDev(rp.config.Core.Dev),
280
-
// )
281
-
// if err != nil {
282
-
// log.Printf("failed to connect to knot server: %v", err)
283
-
// %%%%%%% Changes from base #2 to side #2
284
-
// - newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, currentRef, currentRef)
285
-
// + newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, f.Ref, f.Ref)
286
-
// if err != nil || newHiddenRefResp.StatusCode != http.StatusNoContent {
287
-
// log.Printf("failed to update tracking branch: %s", err)
288
-
// +++++++ Contents of side #3
289
-
// client, err := rp.oauth.ServiceClient(
290
-
// r,
291
-
// oauth.WithService(f.Knot),
292
-
// oauth.WithLxm(tangled.RepoHiddenRefNSID),
293
-
// oauth.WithDev(rp.config.Core.Dev),
294
-
// )
295
-
// if err != nil {
296
-
// log.Printf("failed to connect to knot server: %v", err)
297
-
// >>>>>>> Conflict 1 of 2 ends
298
-
// return nil, err
299
-
// }
300
-
//
301
-
// <<<<<<< Conflict 2 of 2
302
-
// %%%%%%% Changes from base #1 to side #1
303
-
// resp, err := tangled.RepoHiddenRef(
304
-
// r.Context(),
305
-
// client,
306
-
// &tangled.RepoHiddenRef_Input{
307
-
// - ForkRef: f.Ref,
308
-
// - RemoteRef: f.Ref,
309
-
// + ForkRef: currentRef,
310
-
// + RemoteRef: currentRef,
311
-
// Repo: f.RepoAt().String(),
312
-
// },
313
-
// )
314
-
// if err != nil || !resp.Success {
315
-
// if err != nil {
316
-
// log.Printf("failed to update tracking branch: %s", err)
317
-
// } else {
318
-
// log.Printf("failed to update tracking branch: success=false")
319
-
// }
320
-
// return nil, fmt.Errorf("failed to update tracking branch")
321
-
// }
322
-
//
323
-
// - hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
324
-
// + hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
325
-
//
326
-
// %%%%%%% Changes from base #2 to side #2
327
-
// - hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
328
-
// + hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
329
-
//
330
-
// +++++++ Contents of side #3
331
-
// resp, err := tangled.RepoHiddenRef(
332
-
// r.Context(),
333
-
// client,
334
-
// &tangled.RepoHiddenRef_Input{
335
-
// ForkRef: currentRef,
336
-
// RemoteRef: currentRef,
337
-
// Repo: f.RepoAt().String(),
338
-
// },
339
-
// )
340
-
// if err != nil || !resp.Success {
341
-
// if err != nil {
342
-
// log.Printf("failed to update tracking branch: %s", err)
343
-
// } else {
344
-
// log.Printf("failed to update tracking branch: success=false")
345
-
// }
346
-
// return nil, fmt.Errorf("failed to update tracking branch")
347
-
// }
348
-
//
349
-
// hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
350
-
// >>>>>>> Conflict 2 of 2 ends
351
-
// var status types.AncestorCheckResponse
352
-
// forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt()), repoInfo.Name, currentRef, hiddenRef)
353
-
// if err != nil {
354
-
// log.Printf("failed to check if fork is ahead/behind: %s", err)
355
-
// return nil, err
356
-
// }
357
-
//
358
-
// if err := json.NewDecoder(forkSyncableResp.Body).Decode(&status); err != nil {
359
-
// log.Printf("failed to decode fork status: %s", err)
360
-
// return nil, err
361
-
// }
362
-
//
363
-
// forkInfo.Status = status.Status
364
-
// return &forkInfo, nil
365
-
// }
+21
-90
appview/repo/repo.go
+21
-90
appview/repo/repo.go
···
863
863
fail("Failed to write record to PDS.", err)
864
864
return
865
865
}
866
-
l = l.With("at-uri", resp.Uri)
866
+
867
+
aturi := resp.Uri
868
+
l = l.With("at-uri", aturi)
867
869
l.Info("wrote record to PDS")
868
870
869
-
l.Info("adding to knot")
870
-
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
871
+
tx, err := rp.db.BeginTx(r.Context(), nil)
871
872
if err != nil {
872
-
fail("Failed to add to knot.", err)
873
+
fail("Failed to add collaborator.", err)
873
874
return
874
875
}
875
876
876
-
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
877
-
if err != nil {
878
-
fail("Failed to add to knot.", err)
879
-
return
880
-
}
877
+
rollback := func() {
878
+
err1 := tx.Rollback()
879
+
err2 := rp.enforcer.E.LoadPolicy()
880
+
err3 := rollbackRecord(context.Background(), aturi, client)
881
881
882
-
ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.Name, collaboratorIdent.DID.String())
883
-
if err != nil {
884
-
fail("Knot was unreachable.", err)
885
-
return
886
-
}
882
+
// ignore txn complete errors, this is okay
883
+
if errors.Is(err1, sql.ErrTxDone) {
884
+
err1 = nil
885
+
}
887
886
888
-
if ksResp.StatusCode != http.StatusNoContent {
889
-
fail(fmt.Sprintf("Knot returned unexpected status code: %d.", ksResp.StatusCode), nil)
890
-
return
891
-
}
892
-
893
-
tx, err := rp.db.BeginTx(r.Context(), nil)
894
-
if err != nil {
895
-
fail("Failed to add collaborator.", err)
896
-
return
897
-
}
898
-
defer func() {
899
-
tx.Rollback()
900
-
err = rp.enforcer.E.LoadPolicy()
901
-
if err != nil {
902
-
fail("Failed to add collaborator.", err)
887
+
if errs := errors.Join(err1, err2, err3); errs != nil {
888
+
l.Error("failed to rollback changes", "errs", errs)
889
+
return
903
890
}
904
-
}()
891
+
}
892
+
defer rollback()
905
893
906
894
err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo())
907
895
if err != nil {
···
932
920
fail("Failed to update collaborator permissions.", err)
933
921
return
934
922
}
923
+
924
+
// clear aturi to when everything is successful
925
+
aturi = ""
935
926
936
927
rp.pages.HxRefresh(w)
937
928
}
···
1207
1198
case "pipelines":
1208
1199
rp.pipelineSettings(w, r)
1209
1200
}
1210
-
1211
-
// user := rp.oauth.GetUser(r)
1212
-
// repoCollaborators, err := f.Collaborators(r.Context())
1213
-
// if err != nil {
1214
-
// log.Println("failed to get collaborators", err)
1215
-
// }
1216
-
1217
-
// isCollaboratorInviteAllowed := false
1218
-
// if user != nil {
1219
-
// ok, err := rp.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.DidSlashRepo())
1220
-
// if err == nil && ok {
1221
-
// isCollaboratorInviteAllowed = true
1222
-
// }
1223
-
// }
1224
-
1225
-
// us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
1226
-
// if err != nil {
1227
-
// log.Println("failed to create unsigned client", err)
1228
-
// return
1229
-
// }
1230
-
1231
-
// result, err := us.Branches(f.OwnerDid(), f.Name)
1232
-
// if err != nil {
1233
-
// log.Println("failed to reach knotserver", err)
1234
-
// return
1235
-
// }
1236
-
1237
-
// // all spindles that this user is a member of
1238
-
// spindles, err := rp.enforcer.GetSpindlesForUser(user.Did)
1239
-
// if err != nil {
1240
-
// log.Println("failed to fetch spindles", err)
1241
-
// return
1242
-
// }
1243
-
1244
-
// var secrets []*tangled.RepoListSecrets_Secret
1245
-
// if f.Spindle != "" {
1246
-
// if spindleClient, err := rp.oauth.ServiceClient(
1247
-
// r,
1248
-
// oauth.WithService(f.Spindle),
1249
-
// oauth.WithLxm(tangled.RepoListSecretsNSID),
1250
-
// oauth.WithDev(rp.config.Core.Dev),
1251
-
// ); err != nil {
1252
-
// log.Println("failed to create spindle client", err)
1253
-
// } else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt().String()); err != nil {
1254
-
// log.Println("failed to fetch secrets", err)
1255
-
// } else {
1256
-
// secrets = resp.Secrets
1257
-
// }
1258
-
// }
1259
-
1260
-
// rp.pages.RepoSettings(w, pages.RepoSettingsParams{
1261
-
// LoggedInUser: user,
1262
-
// RepoInfo: f.RepoInfo(user),
1263
-
// Collaborators: repoCollaborators,
1264
-
// IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed,
1265
-
// Branches: result.Branches,
1266
-
// Spindles: spindles,
1267
-
// CurrentSpindle: f.Spindle,
1268
-
// Secrets: secrets,
1269
-
// })
1270
1201
}
1271
1202
1272
1203
func (rp *Repo) generalSettings(w http.ResponseWriter, r *http.Request) {
-299
knotclient/signer.go
-299
knotclient/signer.go
···
1
-
package knotclient
2
-
3
-
import (
4
-
"bytes"
5
-
"crypto/hmac"
6
-
"crypto/sha256"
7
-
"encoding/hex"
8
-
"encoding/json"
9
-
"fmt"
10
-
"net/http"
11
-
"net/url"
12
-
"time"
13
-
14
-
"tangled.sh/tangled.sh/core/types"
15
-
)
16
-
17
-
type SignerTransport struct {
18
-
Secret string
19
-
}
20
-
21
-
func (s SignerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
22
-
timestamp := time.Now().Format(time.RFC3339)
23
-
mac := hmac.New(sha256.New, []byte(s.Secret))
24
-
message := req.Method + req.URL.Path + timestamp
25
-
mac.Write([]byte(message))
26
-
signature := hex.EncodeToString(mac.Sum(nil))
27
-
req.Header.Set("X-Signature", signature)
28
-
req.Header.Set("X-Timestamp", timestamp)
29
-
return http.DefaultTransport.RoundTrip(req)
30
-
}
31
-
32
-
type SignedClient struct {
33
-
Secret string
34
-
Url *url.URL
35
-
client *http.Client
36
-
}
37
-
38
-
func NewSignedClient(domain, secret string, dev bool) (*SignedClient, error) {
39
-
client := &http.Client{
40
-
Timeout: 5 * time.Second,
41
-
Transport: SignerTransport{
42
-
Secret: secret,
43
-
},
44
-
}
45
-
46
-
scheme := "https"
47
-
if dev {
48
-
scheme = "http"
49
-
}
50
-
url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain))
51
-
if err != nil {
52
-
return nil, err
53
-
}
54
-
55
-
signedClient := &SignedClient{
56
-
Secret: secret,
57
-
client: client,
58
-
Url: url,
59
-
}
60
-
61
-
return signedClient, nil
62
-
}
63
-
64
-
func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {
65
-
return http.NewRequest(method, s.Url.JoinPath(endpoint).String(), bytes.NewReader(body))
66
-
}
67
-
68
-
func (s *SignedClient) Init(did string) (*http.Response, error) {
69
-
const (
70
-
Method = "POST"
71
-
Endpoint = "/init"
72
-
)
73
-
74
-
body, _ := json.Marshal(map[string]any{
75
-
"did": did,
76
-
})
77
-
78
-
req, err := s.newRequest(Method, Endpoint, body)
79
-
if err != nil {
80
-
return nil, err
81
-
}
82
-
83
-
return s.client.Do(req)
84
-
}
85
-
86
-
func (s *SignedClient) NewRepo(did, repoName, defaultBranch string) (*http.Response, error) {
87
-
const (
88
-
Method = "PUT"
89
-
Endpoint = "/repo/new"
90
-
)
91
-
92
-
body, _ := json.Marshal(map[string]any{
93
-
"did": did,
94
-
"name": repoName,
95
-
"default_branch": defaultBranch,
96
-
})
97
-
98
-
req, err := s.newRequest(Method, Endpoint, body)
99
-
if err != nil {
100
-
return nil, err
101
-
}
102
-
103
-
return s.client.Do(req)
104
-
}
105
-
106
-
func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {
107
-
const (
108
-
Method = "GET"
109
-
)
110
-
endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
111
-
112
-
body, _ := json.Marshal(map[string]any{
113
-
"did": ownerDid,
114
-
"source": source,
115
-
"name": name,
116
-
"hiddenref": hiddenRef,
117
-
})
118
-
119
-
req, err := s.newRequest(Method, endpoint, body)
120
-
if err != nil {
121
-
return nil, err
122
-
}
123
-
124
-
return s.client.Do(req)
125
-
}
126
-
127
-
func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {
128
-
const (
129
-
Method = "POST"
130
-
)
131
-
endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
132
-
133
-
body, _ := json.Marshal(map[string]any{
134
-
"did": ownerDid,
135
-
"source": source,
136
-
"name": name,
137
-
})
138
-
139
-
req, err := s.newRequest(Method, endpoint, body)
140
-
if err != nil {
141
-
return nil, err
142
-
}
143
-
144
-
return s.client.Do(req)
145
-
}
146
-
147
-
func (s *SignedClient) ForkRepo(ownerDid, source, name string) (*http.Response, error) {
148
-
const (
149
-
Method = "POST"
150
-
Endpoint = "/repo/fork"
151
-
)
152
-
153
-
body, _ := json.Marshal(map[string]any{
154
-
"did": ownerDid,
155
-
"source": source,
156
-
"name": name,
157
-
})
158
-
159
-
req, err := s.newRequest(Method, Endpoint, body)
160
-
if err != nil {
161
-
return nil, err
162
-
}
163
-
164
-
return s.client.Do(req)
165
-
}
166
-
167
-
func (s *SignedClient) RemoveRepo(did, repoName string) (*http.Response, error) {
168
-
const (
169
-
Method = "DELETE"
170
-
Endpoint = "/repo"
171
-
)
172
-
173
-
body, _ := json.Marshal(map[string]any{
174
-
"did": did,
175
-
"name": repoName,
176
-
})
177
-
178
-
req, err := s.newRequest(Method, Endpoint, body)
179
-
if err != nil {
180
-
return nil, err
181
-
}
182
-
183
-
return s.client.Do(req)
184
-
}
185
-
186
-
func (s *SignedClient) AddMember(did string) (*http.Response, error) {
187
-
const (
188
-
Method = "PUT"
189
-
Endpoint = "/member/add"
190
-
)
191
-
192
-
body, _ := json.Marshal(map[string]any{
193
-
"did": did,
194
-
})
195
-
196
-
req, err := s.newRequest(Method, Endpoint, body)
197
-
if err != nil {
198
-
return nil, err
199
-
}
200
-
201
-
return s.client.Do(req)
202
-
}
203
-
204
-
func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
205
-
const (
206
-
Method = "PUT"
207
-
)
208
-
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
209
-
210
-
body, _ := json.Marshal(map[string]any{
211
-
"branch": branch,
212
-
})
213
-
214
-
req, err := s.newRequest(Method, endpoint, body)
215
-
if err != nil {
216
-
return nil, err
217
-
}
218
-
219
-
return s.client.Do(req)
220
-
}
221
-
222
-
func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
223
-
const (
224
-
Method = "POST"
225
-
)
226
-
endpoint := fmt.Sprintf("/%s/%s/collaborator/add", ownerDid, repoName)
227
-
228
-
body, _ := json.Marshal(map[string]any{
229
-
"did": memberDid,
230
-
})
231
-
232
-
req, err := s.newRequest(Method, endpoint, body)
233
-
if err != nil {
234
-
return nil, err
235
-
}
236
-
237
-
return s.client.Do(req)
238
-
}
239
-
240
-
func (s *SignedClient) Merge(
241
-
patch []byte,
242
-
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
243
-
) (*http.Response, error) {
244
-
const (
245
-
Method = "POST"
246
-
)
247
-
endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
248
-
249
-
mr := types.MergeRequest{
250
-
Branch: branch,
251
-
CommitMessage: commitMessage,
252
-
CommitBody: commitBody,
253
-
AuthorName: authorName,
254
-
AuthorEmail: authorEmail,
255
-
Patch: string(patch),
256
-
}
257
-
258
-
body, _ := json.Marshal(mr)
259
-
260
-
req, err := s.newRequest(Method, endpoint, body)
261
-
if err != nil {
262
-
return nil, err
263
-
}
264
-
265
-
return s.client.Do(req)
266
-
}
267
-
268
-
func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
269
-
const (
270
-
Method = "POST"
271
-
)
272
-
endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo)
273
-
274
-
body, _ := json.Marshal(map[string]any{
275
-
"patch": string(patch),
276
-
"branch": branch,
277
-
})
278
-
279
-
req, err := s.newRequest(Method, endpoint, body)
280
-
if err != nil {
281
-
return nil, err
282
-
}
283
-
284
-
return s.client.Do(req)
285
-
}
286
-
287
-
func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {
288
-
const (
289
-
Method = "POST"
290
-
)
291
-
endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch))
292
-
293
-
req, err := s.newRequest(Method, endpoint, nil)
294
-
if err != nil {
295
-
return nil, err
296
-
}
297
-
298
-
return s.client.Do(req)
299
-
}
-10
knotserver/http_util.go
-10
knotserver/http_util.go
···
20
20
func notFound(w http.ResponseWriter) {
21
21
writeError(w, "not found", http.StatusNotFound)
22
22
}
23
-
24
-
func writeMsg(w http.ResponseWriter, msg string) {
25
-
writeJSON(w, map[string]string{"msg": msg})
26
-
}
27
-
28
-
func writeConflict(w http.ResponseWriter, data interface{}) {
29
-
w.Header().Set("Content-Type", "application/json")
30
-
w.WriteHeader(http.StatusConflict)
31
-
json.NewEncoder(w).Encode(data)
32
-
}
+7
-3
knotserver/ingester.go
+7
-3
knotserver/ingester.go
···
255
255
didSlashRepo, _ := securejoin.SecureJoin(owner.DID.String(), repo.Name)
256
256
257
257
// check perms for this user
258
-
if ok, err := h.e.IsCollaboratorInviteAllowed(did, rbac.ThisServer, didSlashRepo); !ok || err != nil {
259
-
return fmt.Errorf("insufficient permissions: %w", err)
258
+
ok, err := h.e.IsCollaboratorInviteAllowed(did, rbac.ThisServer, didSlashRepo)
259
+
if err != nil {
260
+
return fmt.Errorf("failed to check permissions: %w", err)
261
+
}
262
+
if !ok {
263
+
return fmt.Errorf("insufficient permissions: %s, %s, %s", did, "IsCollaboratorInviteAllowed", didSlashRepo)
260
264
}
261
265
262
266
if err := h.db.AddDid(subjectId.DID.String()); err != nil {
···
298
302
return fmt.Errorf("error reading response body: %w", err)
299
303
}
300
304
301
-
for _, key := range strings.Split(string(plaintext), "\n") {
305
+
for key := range strings.SplitSeq(string(plaintext), "\n") {
302
306
if key == "" {
303
307
continue
304
308
}
-2
knotserver/internal.go
-2
knotserver/internal.go
-7
nix/modules/knot.nix
-7
nix/modules/knot.nix
···
99
99
description = "DID of owner (required)";
100
100
};
101
101
102
-
secretFile = mkOption {
103
-
type = lib.types.path;
104
-
example = "KNOT_SERVER_SECRET=<hash>";
105
-
description = "File containing secret key provided by appview (required)";
106
-
};
107
-
108
102
dbPath = mkOption {
109
103
type = types.path;
110
104
default = "${cfg.stateDir}/knotserver.db";
···
207
201
"KNOT_SERVER_HOSTNAME=${cfg.server.hostname}"
208
202
"KNOT_SERVER_OWNER=${cfg.server.owner}"
209
203
];
210
-
EnvironmentFile = cfg.server.secretFile;
211
204
ExecStart = "${cfg.package}/bin/knot server";
212
205
Restart = "always";
213
206
};