forked from tangled.org/core
this repo has no description
at tracing 21 kB view raw
1package state 2 3import ( 4 "context" 5 "crypto/hmac" 6 "crypto/sha256" 7 "encoding/hex" 8 "fmt" 9 "log" 10 "log/slog" 11 "net/http" 12 "runtime/debug" 13 "strings" 14 "time" 15 16 comatproto "github.com/bluesky-social/indigo/api/atproto" 17 "github.com/bluesky-social/indigo/atproto/syntax" 18 lexutil "github.com/bluesky-social/indigo/lex/util" 19 securejoin "github.com/cyphar/filepath-securejoin" 20 "github.com/go-chi/chi/v5" 21 "go.opentelemetry.io/otel/attribute" 22 "tangled.sh/tangled.sh/core/api/tangled" 23 "tangled.sh/tangled.sh/core/appview" 24 "tangled.sh/tangled.sh/core/appview/auth" 25 "tangled.sh/tangled.sh/core/appview/db" 26 "tangled.sh/tangled.sh/core/appview/pages" 27 "tangled.sh/tangled.sh/core/jetstream" 28 "tangled.sh/tangled.sh/core/rbac" 29 "tangled.sh/tangled.sh/core/telemetry" 30) 31 32type State struct { 33 db *db.DB 34 auth *auth.Auth 35 enforcer *rbac.Enforcer 36 tidClock *syntax.TIDClock 37 pages *pages.Pages 38 resolver *appview.Resolver 39 jc *jetstream.JetstreamClient 40 t *telemetry.Telemetry 41 config *appview.Config 42} 43 44func Make(ctx context.Context, config *appview.Config) (*State, error) { 45 d, err := db.Make(config.DbPath) 46 if err != nil { 47 return nil, err 48 } 49 50 auth, err := auth.Make(config.CookieSecret) 51 if err != nil { 52 return nil, err 53 } 54 55 enforcer, err := rbac.NewEnforcer(config.DbPath) 56 if err != nil { 57 return nil, err 58 } 59 60 clock := syntax.NewTIDClock(0) 61 62 pgs := pages.NewPages(config) 63 64 resolver := appview.NewResolver() 65 66 bi, ok := debug.ReadBuildInfo() 67 var version string 68 if ok { 69 version = bi.Main.Version 70 } else { 71 version = "v0.0.0-unknown" 72 } 73 74 wrapper := db.DbWrapper{d} 75 jc, err := jetstream.NewJetstreamClient( 76 config.JetstreamEndpoint, 77 "appview", 78 []string{tangled.GraphFollowNSID, tangled.FeedStarNSID, tangled.PublicKeyNSID, tangled.RepoArtifactNSID}, 79 nil, 80 slog.Default(), 81 wrapper, 82 false, 83 ) 84 if err != nil { 85 return nil, fmt.Errorf("failed to create jetstream client: %w", err) 86 } 87 err = jc.StartJetstream(ctx, appview.Ingest(wrapper)) 88 if err != nil { 89 return nil, fmt.Errorf("failed to start jetstream watcher: %w", err) 90 } 91 92 var tele *telemetry.Telemetry 93 if config.EnableTelemetry { 94 tele, err = telemetry.NewTelemetry(ctx, "appview", version, config.Dev) 95 if err != nil { 96 return nil, fmt.Errorf("failed to setup telemetry: %w", err) 97 } 98 } 99 100 state := &State{ 101 d, 102 auth, 103 enforcer, 104 clock, 105 pgs, 106 resolver, 107 jc, 108 tele, 109 config, 110 } 111 112 return state, nil 113} 114 115func TID(c *syntax.TIDClock) string { 116 return c.Next().String() 117} 118 119func (s *State) Login(w http.ResponseWriter, r *http.Request) { 120 ctx := r.Context() 121 122 switch r.Method { 123 case http.MethodGet: 124 err := s.pages.Login(w, pages.LoginParams{}) 125 if err != nil { 126 log.Printf("rendering login page: %s", err) 127 } 128 129 return 130 case http.MethodPost: 131 handle := strings.TrimPrefix(r.FormValue("handle"), "@") 132 appPassword := r.FormValue("app_password") 133 134 resolved, err := s.resolver.ResolveIdent(ctx, handle) 135 if err != nil { 136 log.Println("failed to resolve handle:", err) 137 s.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle)) 138 return 139 } 140 141 atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword) 142 if err != nil { 143 s.pages.Notice(w, "login-msg", "Invalid handle or password.") 144 return 145 } 146 sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession} 147 148 err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint()) 149 if err != nil { 150 s.pages.Notice(w, "login-msg", "Failed to login, try again later.") 151 return 152 } 153 154 log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did) 155 156 did := resolved.DID.String() 157 defaultKnot := "knot1.tangled.sh" 158 159 go func() { 160 log.Printf("adding %s to default knot", did) 161 err = s.enforcer.AddMember(defaultKnot, did) 162 if err != nil { 163 log.Println("failed to add user to knot1.tangled.sh: ", err) 164 return 165 } 166 err = s.enforcer.E.SavePolicy() 167 if err != nil { 168 log.Println("failed to add user to knot1.tangled.sh: ", err) 169 return 170 } 171 172 secret, err := db.GetRegistrationKey(s.db, defaultKnot) 173 if err != nil { 174 log.Println("failed to get registration key for knot1.tangled.sh") 175 return 176 } 177 signedClient, err := NewSignedClient(defaultKnot, secret, s.config.Dev) 178 resp, err := signedClient.AddMember(did) 179 if err != nil { 180 log.Println("failed to add user to knot1.tangled.sh: ", err) 181 return 182 } 183 184 if resp.StatusCode != http.StatusNoContent { 185 log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode) 186 return 187 } 188 }() 189 190 s.pages.HxRedirect(w, "/") 191 return 192 } 193} 194 195func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 196 s.auth.ClearSession(r, w) 197 w.Header().Set("HX-Redirect", "/login") 198 w.WriteHeader(http.StatusSeeOther) 199} 200 201func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 202 ctx, span := s.t.TraceStart(r.Context(), "Timeline") 203 defer span.End() 204 205 user := s.auth.GetUser(r) 206 span.SetAttributes(attribute.String("user.did", user.Did)) 207 208 timeline, err := db.MakeTimeline(ctx, s.db) 209 if err != nil { 210 log.Println(err) 211 span.RecordError(err) 212 s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.") 213 } 214 215 var didsToResolve []string 216 for _, ev := range timeline { 217 if ev.Repo != nil { 218 didsToResolve = append(didsToResolve, ev.Repo.Did) 219 if ev.Source != nil { 220 didsToResolve = append(didsToResolve, ev.Source.Did) 221 } 222 } 223 if ev.Follow != nil { 224 didsToResolve = append(didsToResolve, ev.Follow.UserDid, ev.Follow.SubjectDid) 225 } 226 if ev.Star != nil { 227 didsToResolve = append(didsToResolve, ev.Star.StarredByDid, ev.Star.Repo.Did) 228 } 229 } 230 span.SetAttributes(attribute.Int("dids.to_resolve.count", len(didsToResolve))) 231 232 resolvedIds := s.resolver.ResolveIdents(ctx, didsToResolve) 233 didHandleMap := make(map[string]string) 234 for _, identity := range resolvedIds { 235 if !identity.Handle.IsInvalidHandle() { 236 didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 237 } else { 238 didHandleMap[identity.DID.String()] = identity.DID.String() 239 } 240 } 241 span.SetAttributes(attribute.Int("dids.resolved.count", len(resolvedIds))) 242 243 s.pages.Timeline(w, pages.TimelineParams{ 244 LoggedInUser: user, 245 Timeline: timeline, 246 DidHandleMap: didHandleMap, 247 }) 248 249 return 250} 251 252// requires auth 253func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) { 254 switch r.Method { 255 case http.MethodGet: 256 // list open registrations under this did 257 258 return 259 case http.MethodPost: 260 session, err := s.auth.Store.Get(r, appview.SessionName) 261 if err != nil || session.IsNew { 262 log.Println("unauthorized attempt to generate registration key") 263 http.Error(w, "Forbidden", http.StatusUnauthorized) 264 return 265 } 266 267 did := session.Values[appview.SessionDid].(string) 268 269 // check if domain is valid url, and strip extra bits down to just host 270 domain := r.FormValue("domain") 271 if domain == "" { 272 http.Error(w, "Invalid form", http.StatusBadRequest) 273 return 274 } 275 276 key, err := db.GenerateRegistrationKey(s.db, domain, did) 277 278 if err != nil { 279 log.Println(err) 280 http.Error(w, "unable to register this domain", http.StatusNotAcceptable) 281 return 282 } 283 284 w.Write([]byte(key)) 285 } 286} 287 288func (s *State) Keys(w http.ResponseWriter, r *http.Request) { 289 user := chi.URLParam(r, "user") 290 user = strings.TrimPrefix(user, "@") 291 292 if user == "" { 293 w.WriteHeader(http.StatusBadRequest) 294 return 295 } 296 297 id, err := s.resolver.ResolveIdent(r.Context(), user) 298 if err != nil { 299 w.WriteHeader(http.StatusInternalServerError) 300 return 301 } 302 303 pubKeys, err := db.GetPublicKeys(s.db, id.DID.String()) 304 if err != nil { 305 w.WriteHeader(http.StatusNotFound) 306 return 307 } 308 309 if len(pubKeys) == 0 { 310 w.WriteHeader(http.StatusNotFound) 311 return 312 } 313 314 for _, k := range pubKeys { 315 key := strings.TrimRight(k.Key, "\n") 316 w.Write([]byte(fmt.Sprintln(key))) 317 } 318} 319 320// create a signed request and check if a node responds to that 321func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { 322 user := s.auth.GetUser(r) 323 324 domain := chi.URLParam(r, "domain") 325 if domain == "" { 326 http.Error(w, "malformed url", http.StatusBadRequest) 327 return 328 } 329 log.Println("checking ", domain) 330 331 secret, err := db.GetRegistrationKey(s.db, domain) 332 if err != nil { 333 log.Printf("no key found for domain %s: %s\n", domain, err) 334 return 335 } 336 337 client, err := NewSignedClient(domain, secret, s.config.Dev) 338 if err != nil { 339 log.Println("failed to create client to ", domain) 340 } 341 342 resp, err := client.Init(user.Did) 343 if err != nil { 344 w.Write([]byte("no dice")) 345 log.Println("domain was unreachable after 5 seconds") 346 return 347 } 348 349 if resp.StatusCode == http.StatusConflict { 350 log.Println("status conflict", resp.StatusCode) 351 w.Write([]byte("already registered, sorry!")) 352 return 353 } 354 355 if resp.StatusCode != http.StatusNoContent { 356 log.Println("status nok", resp.StatusCode) 357 w.Write([]byte("no dice")) 358 return 359 } 360 361 // verify response mac 362 signature := resp.Header.Get("X-Signature") 363 signatureBytes, err := hex.DecodeString(signature) 364 if err != nil { 365 return 366 } 367 368 expectedMac := hmac.New(sha256.New, []byte(secret)) 369 expectedMac.Write([]byte("ok")) 370 371 if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 372 log.Printf("response body signature mismatch: %x\n", signatureBytes) 373 return 374 } 375 376 tx, err := s.db.BeginTx(r.Context(), nil) 377 if err != nil { 378 log.Println("failed to start tx", err) 379 http.Error(w, err.Error(), http.StatusInternalServerError) 380 return 381 } 382 defer func() { 383 tx.Rollback() 384 err = s.enforcer.E.LoadPolicy() 385 if err != nil { 386 log.Println("failed to rollback policies") 387 } 388 }() 389 390 // mark as registered 391 err = db.Register(tx, domain) 392 if err != nil { 393 log.Println("failed to register domain", err) 394 http.Error(w, err.Error(), http.StatusInternalServerError) 395 return 396 } 397 398 // set permissions for this did as owner 399 reg, err := db.RegistrationByDomain(tx, domain) 400 if err != nil { 401 log.Println("failed to register domain", err) 402 http.Error(w, err.Error(), http.StatusInternalServerError) 403 return 404 } 405 406 // add basic acls for this domain 407 err = s.enforcer.AddDomain(domain) 408 if err != nil { 409 log.Println("failed to setup owner of domain", err) 410 http.Error(w, err.Error(), http.StatusInternalServerError) 411 return 412 } 413 414 // add this did as owner of this domain 415 err = s.enforcer.AddOwner(domain, reg.ByDid) 416 if err != nil { 417 log.Println("failed to setup owner of domain", err) 418 http.Error(w, err.Error(), http.StatusInternalServerError) 419 return 420 } 421 422 err = tx.Commit() 423 if err != nil { 424 log.Println("failed to commit changes", err) 425 http.Error(w, err.Error(), http.StatusInternalServerError) 426 return 427 } 428 429 err = s.enforcer.E.SavePolicy() 430 if err != nil { 431 log.Println("failed to update ACLs", err) 432 http.Error(w, err.Error(), http.StatusInternalServerError) 433 return 434 } 435 436 w.Write([]byte("check success")) 437} 438 439func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) { 440 domain := chi.URLParam(r, "domain") 441 if domain == "" { 442 http.Error(w, "malformed url", http.StatusBadRequest) 443 return 444 } 445 446 user := s.auth.GetUser(r) 447 reg, err := db.RegistrationByDomain(s.db, domain) 448 if err != nil { 449 w.Write([]byte("failed to pull up registration info")) 450 return 451 } 452 453 var members []string 454 if reg.Registered != nil { 455 members, err = s.enforcer.GetUserByRole("server:member", domain) 456 if err != nil { 457 w.Write([]byte("failed to fetch member list")) 458 return 459 } 460 } 461 462 var didsToResolve []string 463 for _, m := range members { 464 didsToResolve = append(didsToResolve, m) 465 } 466 didsToResolve = append(didsToResolve, reg.ByDid) 467 resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve) 468 didHandleMap := make(map[string]string) 469 for _, identity := range resolvedIds { 470 if !identity.Handle.IsInvalidHandle() { 471 didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 472 } else { 473 didHandleMap[identity.DID.String()] = identity.DID.String() 474 } 475 } 476 477 ok, err := s.enforcer.IsServerOwner(user.Did, domain) 478 isOwner := err == nil && ok 479 480 p := pages.KnotParams{ 481 LoggedInUser: user, 482 DidHandleMap: didHandleMap, 483 Registration: reg, 484 Members: members, 485 IsOwner: isOwner, 486 } 487 488 s.pages.Knot(w, p) 489} 490 491// get knots registered by this user 492func (s *State) Knots(w http.ResponseWriter, r *http.Request) { 493 // for now, this is just pubkeys 494 user := s.auth.GetUser(r) 495 registrations, err := db.RegistrationsByDid(s.db, user.Did) 496 if err != nil { 497 log.Println(err) 498 } 499 500 s.pages.Knots(w, pages.KnotsParams{ 501 LoggedInUser: user, 502 Registrations: registrations, 503 }) 504} 505 506// list members of domain, requires auth and requires owner status 507func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) { 508 domain := chi.URLParam(r, "domain") 509 if domain == "" { 510 http.Error(w, "malformed url", http.StatusBadRequest) 511 return 512 } 513 514 // list all members for this domain 515 memberDids, err := s.enforcer.GetUserByRole("server:member", domain) 516 if err != nil { 517 w.Write([]byte("failed to fetch member list")) 518 return 519 } 520 521 w.Write([]byte(strings.Join(memberDids, "\n"))) 522 return 523} 524 525// add member to domain, requires auth and requires invite access 526func (s *State) AddMember(w http.ResponseWriter, r *http.Request) { 527 domain := chi.URLParam(r, "domain") 528 if domain == "" { 529 http.Error(w, "malformed url", http.StatusBadRequest) 530 return 531 } 532 533 subjectIdentifier := r.FormValue("subject") 534 if subjectIdentifier == "" { 535 http.Error(w, "malformed form", http.StatusBadRequest) 536 return 537 } 538 539 subjectIdentity, err := s.resolver.ResolveIdent(r.Context(), subjectIdentifier) 540 if err != nil { 541 w.Write([]byte("failed to resolve member did to a handle")) 542 return 543 } 544 log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain) 545 546 // announce this relation into the firehose, store into owners' pds 547 client, _ := s.auth.AuthorizedClient(r) 548 currentUser := s.auth.GetUser(r) 549 createdAt := time.Now().Format(time.RFC3339) 550 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 551 Collection: tangled.KnotMemberNSID, 552 Repo: currentUser.Did, 553 Rkey: appview.TID(), 554 Record: &lexutil.LexiconTypeDecoder{ 555 Val: &tangled.KnotMember{ 556 Subject: subjectIdentity.DID.String(), 557 Domain: domain, 558 CreatedAt: createdAt, 559 }}, 560 }) 561 562 // invalid record 563 if err != nil { 564 log.Printf("failed to create record: %s", err) 565 return 566 } 567 log.Println("created atproto record: ", resp.Uri) 568 569 secret, err := db.GetRegistrationKey(s.db, domain) 570 if err != nil { 571 log.Printf("no key found for domain %s: %s\n", domain, err) 572 return 573 } 574 575 ksClient, err := NewSignedClient(domain, secret, s.config.Dev) 576 if err != nil { 577 log.Println("failed to create client to ", domain) 578 return 579 } 580 581 ksResp, err := ksClient.AddMember(subjectIdentity.DID.String()) 582 if err != nil { 583 log.Printf("failed to make request to %s: %s", domain, err) 584 return 585 } 586 587 if ksResp.StatusCode != http.StatusNoContent { 588 w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err))) 589 return 590 } 591 592 err = s.enforcer.AddMember(domain, subjectIdentity.DID.String()) 593 if err != nil { 594 w.Write([]byte(fmt.Sprint("failed to add member: ", err))) 595 return 596 } 597 598 w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String()))) 599} 600 601func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) { 602} 603 604func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { 605 ctx, span := s.t.TraceStart(r.Context(), "NewRepo") 606 defer span.End() 607 608 switch r.Method { 609 case http.MethodGet: 610 user := s.auth.GetUser(r) 611 span.SetAttributes(attribute.String("user.did", user.Did)) 612 span.SetAttributes(attribute.String("request.method", "GET")) 613 614 knots, err := s.enforcer.GetDomainsForUser(user.Did) 615 if err != nil { 616 span.RecordError(err) 617 s.pages.Notice(w, "repo", "Invalid user account.") 618 return 619 } 620 span.SetAttributes(attribute.Int("knots.count", len(knots))) 621 622 s.pages.NewRepo(w, pages.NewRepoParams{ 623 LoggedInUser: user, 624 Knots: knots, 625 }) 626 627 case http.MethodPost: 628 user := s.auth.GetUser(r) 629 span.SetAttributes(attribute.String("user.did", user.Did)) 630 span.SetAttributes(attribute.String("request.method", "POST")) 631 632 domain := r.FormValue("domain") 633 if domain == "" { 634 s.pages.Notice(w, "repo", "Invalid form submission&mdash;missing knot domain.") 635 return 636 } 637 span.SetAttributes(attribute.String("domain", domain)) 638 639 repoName := r.FormValue("name") 640 if repoName == "" { 641 s.pages.Notice(w, "repo", "Repository name cannot be empty.") 642 return 643 } 644 span.SetAttributes(attribute.String("repo.name", repoName)) 645 646 // Check for valid repository name (GitHub-like rules) 647 // No spaces, only alphanumeric characters, dashes, and underscores 648 for _, char := range repoName { 649 if !((char >= 'a' && char <= 'z') || 650 (char >= 'A' && char <= 'Z') || 651 (char >= '0' && char <= '9') || 652 char == '-' || char == '_' || char == '.') { 653 s.pages.Notice(w, "repo", "Repository name can only contain alphanumeric characters, periods, hyphens, and underscores.") 654 return 655 } 656 } 657 658 defaultBranch := r.FormValue("branch") 659 if defaultBranch == "" { 660 defaultBranch = "main" 661 } 662 span.SetAttributes(attribute.String("repo.default_branch", defaultBranch)) 663 664 description := r.FormValue("description") 665 666 ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create") 667 if err != nil || !ok { 668 if err != nil { 669 span.RecordError(err) 670 } 671 span.SetAttributes(attribute.Bool("permission.granted", false)) 672 s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.") 673 return 674 } 675 span.SetAttributes(attribute.Bool("permission.granted", true)) 676 677 existingRepo, err := db.GetRepo(ctx, s.db, user.Did, repoName) 678 if err == nil && existingRepo != nil { 679 span.SetAttributes(attribute.Bool("repo.exists", true)) 680 s.pages.Notice(w, "repo", fmt.Sprintf("A repo by this name already exists on %s", existingRepo.Knot)) 681 return 682 } 683 span.SetAttributes(attribute.Bool("repo.exists", false)) 684 685 secret, err := db.GetRegistrationKey(s.db, domain) 686 if err != nil { 687 span.RecordError(err) 688 s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", domain)) 689 return 690 } 691 692 client, err := NewSignedClient(domain, secret, s.config.Dev) 693 if err != nil { 694 span.RecordError(err) 695 s.pages.Notice(w, "repo", "Failed to connect to knot server.") 696 return 697 } 698 699 rkey := appview.TID() 700 repo := &db.Repo{ 701 Did: user.Did, 702 Name: repoName, 703 Knot: domain, 704 Rkey: rkey, 705 Description: description, 706 } 707 708 rWithCtx := r.WithContext(ctx) 709 xrpcClient, _ := s.auth.AuthorizedClient(rWithCtx) 710 711 createdAt := time.Now().Format(time.RFC3339) 712 atresp, err := comatproto.RepoPutRecord(ctx, xrpcClient, &comatproto.RepoPutRecord_Input{ 713 Collection: tangled.RepoNSID, 714 Repo: user.Did, 715 Rkey: rkey, 716 Record: &lexutil.LexiconTypeDecoder{ 717 Val: &tangled.Repo{ 718 Knot: repo.Knot, 719 Name: repoName, 720 CreatedAt: createdAt, 721 Owner: user.Did, 722 }}, 723 }) 724 if err != nil { 725 span.RecordError(err) 726 log.Printf("failed to create record: %s", err) 727 s.pages.Notice(w, "repo", "Failed to announce repository creation.") 728 return 729 } 730 log.Println("created repo record: ", atresp.Uri) 731 span.SetAttributes(attribute.String("repo.uri", atresp.Uri)) 732 733 tx, err := s.db.BeginTx(ctx, nil) 734 if err != nil { 735 span.RecordError(err) 736 log.Println(err) 737 s.pages.Notice(w, "repo", "Failed to save repository information.") 738 return 739 } 740 defer func() { 741 tx.Rollback() 742 err = s.enforcer.E.LoadPolicy() 743 if err != nil { 744 log.Println("failed to rollback policies") 745 } 746 }() 747 748 resp, err := client.NewRepo(user.Did, repoName, defaultBranch) 749 if err != nil { 750 span.RecordError(err) 751 s.pages.Notice(w, "repo", "Failed to create repository on knot server.") 752 return 753 } 754 span.SetAttributes(attribute.Int("knot_response.status", resp.StatusCode)) 755 756 switch resp.StatusCode { 757 case http.StatusConflict: 758 s.pages.Notice(w, "repo", "A repository with that name already exists.") 759 return 760 case http.StatusInternalServerError: 761 s.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.") 762 case http.StatusNoContent: 763 // continue 764 } 765 766 repo.AtUri = atresp.Uri 767 err = db.AddRepo(ctx, tx, repo) 768 if err != nil { 769 span.RecordError(err) 770 log.Println(err) 771 s.pages.Notice(w, "repo", "Failed to save repository information.") 772 return 773 } 774 775 // acls 776 p, _ := securejoin.SecureJoin(user.Did, repoName) 777 err = s.enforcer.AddRepo(user.Did, domain, p) 778 if err != nil { 779 span.RecordError(err) 780 log.Println(err) 781 s.pages.Notice(w, "repo", "Failed to set up repository permissions.") 782 return 783 } 784 785 err = tx.Commit() 786 if err != nil { 787 span.RecordError(err) 788 log.Println("failed to commit changes", err) 789 http.Error(w, err.Error(), http.StatusInternalServerError) 790 return 791 } 792 793 err = s.enforcer.E.SavePolicy() 794 if err != nil { 795 span.RecordError(err) 796 log.Println("failed to update ACLs", err) 797 http.Error(w, err.Error(), http.StatusInternalServerError) 798 return 799 } 800 801 s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s", user.Handle, repoName)) 802 return 803 } 804}