forked from tangled.org/core
this repo has no description

appview: remove all mentions of SignedClient

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 9b88f828 161b5275

verified
Changed files
+127 -651
appview
config
oauth
handler
repo
knotclient
knotserver
nix
modules
+1 -1
appview/config/config.go
··· 17 17 Dev bool `env:"DEV, default=false"` 18 18 DisallowedNicknamesFile string `env:"DISALLOWED_NICKNAMES_FILE"` 19 19 20 - // temporarily, to add users to default spindle 20 + // temporarily, to add users to default knot and spindle 21 21 AppPassword string `env:"APP_PASSWORD"` 22 22 } 23 23
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 47 47 } 48 48 49 49 w.WriteHeader(http.StatusNoContent) 50 - return 51 50 } 52 51 53 52 func (h *InternalHandle) InternalKeys(w http.ResponseWriter, r *http.Request) { ··· 63 62 data = append(data, j) 64 63 } 65 64 writeJSON(w, data) 66 - return 67 65 } 68 66 69 67 type PushOptions struct {
-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 };