github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/main/server/helpers.go (about) 1 package server 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "flag" 8 "fmt" 9 "net/http" 10 "os" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/DapperCollectives/CAST/backend/main/models" 16 "github.com/DapperCollectives/CAST/backend/main/shared" 17 "github.com/go-playground/validator/v10" 18 "github.com/jackc/pgx/v4" 19 "github.com/rs/zerolog/log" 20 "github.com/thoas/go-funk" 21 ) 22 23 var allowedFileTypes = []string{"image/jpg", "image/jpeg", "image/png", "image/gif"} 24 25 const ( 26 maxFileSize = 5 * 1024 * 1024 // 5MB 27 ) 28 29 type Helpers struct { 30 A *App 31 } 32 33 func (h *Helpers) Initialize(app *App) { 34 h.A = app 35 } 36 37 func (h *Helpers) useStrategyTally( 38 p models.Proposal, 39 v []*models.VoteWithBalance, 40 ) (models.ProposalResults, error) { 41 42 s := h.initStrategy(*p.Strategy) 43 if s == nil { 44 return models.ProposalResults{}, errors.New("Strategy not found.") 45 } 46 47 proposalInitialized := models.NewProposalResults(p.ID, p.Choices) 48 results, err := s.TallyVotes(v, proposalInitialized, &p) 49 if err != nil { 50 return models.ProposalResults{}, err 51 } 52 53 return results, nil 54 } 55 56 func (h *Helpers) useStrategyGetVotes( 57 p models.Proposal, 58 v []*models.VoteWithBalance, 59 ) ([]*models.VoteWithBalance, error) { 60 61 s := h.initStrategy(*p.Strategy) 62 if s == nil { 63 return nil, errors.New("Strategy not found.") 64 } 65 66 votesWithWeights, err := s.GetVotes(v, &p) 67 if err != nil { 68 return nil, err 69 } 70 71 return votesWithWeights, nil 72 } 73 74 func (h *Helpers) useStrategyGetVoteWeight( 75 p models.Proposal, 76 v *models.VoteWithBalance, 77 ) (float64, error) { 78 s := h.initStrategy(*p.Strategy) 79 if s == nil { 80 return 0, errors.New("Strategy not found.") 81 } 82 83 weight, err := s.GetVoteWeightForBalance(v, &p) 84 if err != nil { 85 return 0, err 86 } 87 88 return weight, nil 89 } 90 91 func (h *Helpers) useStrategyFetchBalance( 92 v models.Vote, 93 p models.Proposal, 94 s Strategy, 95 ) (models.VoteWithBalance, errorResponse) { 96 97 emptyBalance := &models.Balance{ 98 Addr: v.Addr, 99 Proposal_id: p.ID, 100 } 101 102 if p.Block_height != nil { 103 emptyBalance.BlockHeight = *p.Block_height 104 } 105 106 c := models.Community{ID: p.Community_id} 107 if err := c.GetCommunityByProposalId(h.A.DB, p.ID); err != nil { 108 return models.VoteWithBalance{}, errGetCommunity 109 } 110 111 strategy, err := c.GetStrategy(*p.Strategy) 112 if err != nil { 113 return models.VoteWithBalance{}, errStrategyNotFound 114 } 115 116 balance, err := s.FetchBalance(emptyBalance, &p) 117 if err != nil { 118 log.Error().Err(err).Msgf("User does not have the required balance %v.", v.Addr) 119 errResponse := errInsufficientBalance 120 errResponse.Details = fmt.Sprintf(errResponse.Details, *strategy.Threshold, *strategy.Contract.Name) 121 return models.VoteWithBalance{}, errResponse 122 } 123 124 vb := models.VoteWithBalance{ 125 Vote: v, 126 PrimaryAccountBalance: &balance.PrimaryAccountBalance, 127 SecondaryAccountBalance: &balance.SecondaryAccountBalance, 128 StakingBalance: &balance.StakingBalance, 129 } 130 131 return vb, nilErr 132 } 133 134 func (h *Helpers) fetchProposal(vars map[string]string, query string) (models.Proposal, error) { 135 proposalId, err := strconv.Atoi(vars[query]) 136 if err != nil { 137 msg := fmt.Sprintf("Invalid proposalId: %s", vars["proposalId"]) 138 log.Error().Err(err).Msg(msg) 139 return models.Proposal{}, errors.New(msg) 140 } 141 142 p := models.Proposal{ID: proposalId} 143 144 if err := p.GetProposalById(h.A.DB); err != nil { 145 switch err.Error() { 146 case pgx.ErrNoRows.Error(): 147 msg := fmt.Sprintf("Proposal with ID %d not found.", proposalId) 148 return models.Proposal{}, errors.New(msg) 149 default: 150 return models.Proposal{}, err 151 } 152 } 153 154 return p, nil 155 } 156 157 func (h *Helpers) uploadFile(r *http.Request) (interface{}, error) { 158 file, handler, err := r.FormFile("file") 159 if err != nil { 160 log.Error().Err(err).Msg("FormFile Retrieval Error.") 161 return nil, err 162 } 163 defer file.Close() 164 165 // ensure mime type is allowed 166 mime := handler.Header.Get("Content-Type") 167 if !funk.Contains(allowedFileTypes, mime) { 168 msg := fmt.Sprintf("Uploaded file type of '%s' is not allowed.", mime) 169 log.Error().Msg(msg) 170 return nil, errors.New(msg) 171 } 172 173 pin, err := h.A.IpfsClient.PinFile(file, handler.Filename) 174 if err != nil { 175 log.Error().Err(err).Msg("Error pinning file to IPFS.") 176 return nil, err 177 } 178 179 resp := struct { 180 Cid string `json:"cid"` 181 }{ 182 Cid: pin.IpfsHash, 183 } 184 185 return resp, nil 186 } 187 188 func (h *Helpers) getPaginatedVotes( 189 r *http.Request, 190 p models.Proposal, 191 ) ( 192 []*models.VoteWithBalance, 193 shared.PageParams, 194 error, 195 ) { 196 197 pageParams := getPageParams(*r, 25) 198 199 votes, totalRecords, err := models.GetVotesForProposal( 200 h.A.DB, 201 p.ID, 202 *p.Strategy, 203 pageParams, 204 ) 205 if err != nil { 206 return nil, shared.PageParams{}, err 207 } 208 209 pageParams.TotalRecords = totalRecords 210 211 return votes, pageParams, nil 212 } 213 214 func (h *Helpers) processVote(addr string, p models.Proposal) (*models.VoteWithBalance, error) { 215 vote, err := h.fetchVote(addr, p.ID) 216 if err != nil { 217 return nil, err 218 } 219 220 weight, err := h.useStrategyGetVoteWeight(p, vote) 221 if err != nil { 222 return nil, err 223 } 224 225 vote.Weight = &weight 226 return vote, err 227 } 228 229 func (h *Helpers) fetchVote(addr string, id int) (*models.VoteWithBalance, error) { 230 voteWithBalance := &models.VoteWithBalance{ 231 Vote: models.Vote{ 232 Addr: addr, 233 Proposal_id: id, 234 }} 235 236 if err := voteWithBalance.GetVote(h.A.DB); err != nil { 237 switch err.Error() { 238 case pgx.ErrNoRows.Error(): 239 msg := fmt.Sprintf("Vote not found.") 240 return nil, errors.New(msg) 241 default: 242 return nil, err 243 } 244 } 245 246 return voteWithBalance, nil 247 } 248 249 func (h *Helpers) processVotes( 250 addr string, 251 ids []int, 252 pageParams shared.PageParams, 253 ) ( 254 []*models.VoteWithBalance, 255 shared.PageParams, 256 error, 257 ) { 258 votes, totalRecords, err := models.GetVotesForAddress( 259 h.A.DB, 260 addr, 261 &ids, 262 pageParams, 263 ) 264 if err != nil { 265 log.Error().Err(err).Msg("Error getting votes for address.") 266 return nil, pageParams, err 267 } 268 269 var votesWithBalances []*models.VoteWithBalance 270 271 for _, vote := range votes { 272 proposal := models.Proposal{ID: vote.Proposal_id} 273 if err := proposal.GetProposalById(h.A.DB); err != nil { 274 switch err.Error() { 275 case pgx.ErrNoRows.Error(): 276 msg := fmt.Sprintf("Proposal with ID %d not found.", vote.Proposal_id) 277 return nil, pageParams, errors.New(msg) 278 default: 279 return nil, pageParams, err 280 } 281 } 282 283 s := h.initStrategy(*proposal.Strategy) 284 if s == nil { 285 return nil, pageParams, errors.New("Strategy not found.") 286 } 287 288 weight, err := s.GetVoteWeightForBalance(vote, &proposal) 289 if err != nil { 290 return nil, pageParams, err 291 } 292 293 vote.Weight = &weight 294 votesWithBalances = append(votesWithBalances, vote) 295 } 296 297 pageParams.TotalRecords = totalRecords 298 299 return votesWithBalances, pageParams, nil 300 } 301 302 func (h *Helpers) createVote(r *http.Request, p models.Proposal) (*models.VoteWithBalance, errorResponse) { 303 var v models.Vote 304 if err := validatePayload(r.Body, &v); err != nil { 305 log.Error().Err(err).Msg("Invalid request payload.") 306 return nil, errIncompleteRequest 307 } 308 309 v.Proposal_id = p.ID 310 311 // validate user hasn't already voted 312 existingVote := models.Vote{Proposal_id: v.Proposal_id, Addr: v.Addr} 313 if err := existingVote.GetVote(h.A.DB); err == nil { 314 errResponse := errAlreadyVoted 315 errResponse.Details = fmt.Sprintf(errResponse.Details, v.Addr, v.Proposal_id) 316 log.Error().Msgf(errResponse.Details) 317 return nil, errResponse 318 } 319 320 // check that proposal is live 321 if os.Getenv("APP_ENV") != "DEV" { 322 if !p.IsLive() { 323 return nil, errInactiveProposal 324 } 325 } 326 327 if errResponse := h.validateVote(p, v); errResponse != nilErr { 328 return nil, errResponse 329 } 330 331 v.Proposal_id = p.ID 332 333 s := h.initStrategy(*p.Strategy) 334 if s == nil { 335 return nil, errStrategyNotFound 336 } 337 338 voteWithBalance, errResponse := h.useStrategyFetchBalance(v, p, s) 339 if errResponse != nilErr { 340 return nil, errResponse 341 } 342 343 if errResponse := h.insertVote(voteWithBalance, p); errResponse != nilErr { 344 return nil, errResponse 345 } 346 347 return &voteWithBalance, nilErr 348 } 349 350 func (h *Helpers) insertVote(v models.VoteWithBalance, p models.Proposal) errorResponse { 351 weight, err := h.useStrategyGetVoteWeight(p, &v) 352 if err != nil { 353 log.Error().Err(err).Msgf("Error getting vote weight for address %s.", v.Addr) 354 return errIncompleteRequest 355 } 356 357 c := models.Community{ID: p.Community_id} 358 if err := c.GetCommunityByProposalId(h.A.DB, p.ID); err != nil { 359 return errGetCommunity 360 } 361 362 strategy, err := c.GetStrategy(*p.Strategy) 363 if err != nil { 364 return errStrategyNotFound 365 } 366 367 if err = p.ValidateBalance(weight); err != nil { 368 log.Error().Err(err).Msg("Account balance is too low to vote on this proposal.") 369 errResponse := errInsufficientBalance 370 errResponse.Details = fmt.Sprintf(errResponse.Details, *strategy.Threshold, *strategy.Contract.Name) 371 return errResponse 372 } 373 374 // Include voucher in vote data when pinning 375 ipfsVote := map[string]interface{}{ 376 "vote": v, 377 } 378 v.Cid, err = h.pinJSONToIpfs(ipfsVote) 379 if err != nil { 380 log.Error().Err(err).Msg("Error pinning proposal to IPFS.") 381 return errCreateVote 382 } 383 384 if err := v.CreateVote(h.A.DB); err != nil { 385 msg := fmt.Sprintf("Error creating vote for address %s.", v.Addr) 386 log.Error().Err(err).Msg(msg) 387 return errCreateVote 388 } 389 390 return nilErr 391 } 392 393 func (h *Helpers) validateVote(p models.Proposal, v models.Vote) errorResponse { 394 395 // validate the user is not on community's blocklist 396 if err := h.validateBlocklist(v.Addr, p.Community_id); err != nil { 397 log.Error().Err(err).Msgf(fmt.Sprintf("Address %v is on blocklist for community id %v.\n", v.Addr, p.Community_id)) 398 return errForbidden 399 } 400 401 // validate choice exists on proposal 402 if err := v.ValidateChoice(p); err != nil { 403 log.Error().Err(err) 404 return errIncompleteRequest 405 } 406 407 // If voucher is present 408 if v.Voucher != nil { 409 // Transaction Signature validation 410 voucher := v.Voucher 411 authorizer := voucher.Authorizers[0] 412 413 v.Composite_signatures = shared.GetUserCompositeSignatureFromVoucher(voucher) 414 415 // Validate authorizer 416 if authorizer != v.Addr || authorizer != (*v.Composite_signatures)[0].Addr { 417 err := errors.New("authorizer address must match voter address and envelope signer") 418 log.Error().Err(err) 419 return errIncompleteRequest 420 } 421 422 message := voucher.Arguments[0]["value"] 423 424 messageBytes, err := hex.DecodeString(message) 425 if err != nil { 426 log.Error().Err(err) 427 return errIncompleteRequest 428 } 429 430 // validate proper message format 431 //<proposalId>:<choice>:<timestamp> 432 if err := models.ValidateVoteMessage(string(messageBytes), p); err != nil { 433 log.Error().Err(err) 434 return errIncompleteRequest 435 } 436 437 // re-build message & composite signatures for validation 438 // set v.Message as the encoded message, rather than the colon(:) delimited message above. 439 // we can do this because we can always recover the tx arguments that make up the 440 // colon delimited message by decoding this rlp encoded message 441 v.Message = shared.EncodeMessageFromVoucher(voucher) 442 443 if err := h.validateTxSignature(v.Addr, v.Message, v.Composite_signatures); err != nil { 444 return errIncompleteRequest 445 } 446 } else { 447 // validate proper message format 448 // hex decode before validating 449 if err := models.ValidateVoteMessage(v.Message, p); err != nil { 450 log.Error().Err(err) 451 return errIncompleteRequest 452 } 453 454 if err := h.validateUserSignature(v.Addr, v.Message, v.Composite_signatures); err != nil { 455 return errIncompleteRequest 456 } 457 } 458 459 return nilErr 460 } 461 462 func (h *Helpers) fetchCommunity(id int) (models.Community, error) { 463 community := models.Community{ID: id} 464 465 if err := community.GetCommunity(h.A.DB); err != nil { 466 log.Error().Err(err) 467 return models.Community{}, err 468 } 469 470 return community, nil 471 } 472 473 func (h *Helpers) searchCommunities( 474 searchText string, 475 filters string, 476 pageParams shared.PageParams, 477 ) ( 478 []*models.Community, 479 int, 480 map[string]int, 481 error, 482 ) { 483 filtersSlice := strings.Split(filters, ",") 484 if searchText == "" { 485 isSearch := true 486 487 results, totalRecords, err := models.GetDefaultCommunities( 488 h.A.DB, 489 pageParams, 490 filtersSlice, 491 isSearch, 492 ) 493 if err != nil { 494 log.Error().Err(err) 495 return nil, 0, nil, err 496 } 497 498 categoryCount, err := models.GetCategoryCount(h.A.DB, searchText) 499 if err != nil { 500 return []*models.Community{}, 0, nil, err 501 } 502 503 return results, totalRecords, categoryCount, nil 504 } else { 505 results, totalRecords, err := models.SearchForCommunity( 506 h.A.DB, 507 searchText, 508 filtersSlice, 509 pageParams, 510 ) 511 if err != nil { 512 return []*models.Community{}, 0, nil, err 513 } 514 515 categoryCount, err := models.GetCategoryCount(h.A.DB, searchText) 516 if err != nil { 517 return []*models.Community{}, 0, nil, err 518 } 519 520 return results, totalRecords, categoryCount, nil 521 } 522 } 523 524 func (h *Helpers) createProposal(p models.Proposal) (models.Proposal, errorResponse) { 525 if err := h.validateStrategyName(*p.Strategy); err != nil { 526 fmt.Printf("Error validating strategy name: %v \n", err) 527 return models.Proposal{}, errStrategyNotFound 528 } 529 530 if p.Voucher != nil { 531 if err := h.validateUserViaVoucher(p.Creator_addr, p.Voucher); err != nil { 532 return models.Proposal{}, errForbidden 533 } 534 } else { 535 if err := h.validateUser(p.Creator_addr, p.Timestamp, p.Composite_signatures); err != nil { 536 return models.Proposal{}, errForbidden 537 } 538 } 539 540 community, err := h.fetchCommunity(p.Community_id) 541 if err != nil { 542 return models.Proposal{}, errIncompleteRequest 543 } 544 545 strategy, err := models.MatchStrategyByProposal(*community.Strategies, *p.Strategy) 546 if err != nil { 547 log.Error().Err(err).Msg("Community does not have this strategy available.") 548 return models.Proposal{}, errIncompleteRequest 549 } 550 551 // Set Min Balance/Max Weight to community defaults if not provided 552 if p.Min_balance == nil { 553 p.Min_balance = strategy.Contract.Threshold 554 } 555 if p.Max_weight == nil { 556 p.Max_weight = strategy.Contract.MaxWeight 557 } 558 559 header, err := h.A.FlowAdapter.LiveClient.GetLatestBlockHeader(context.Background(), true) 560 if err != nil { 561 log.Error().Err(err).Msg("Couldn't get block header") 562 return models.Proposal{}, errIncompleteRequest 563 } 564 p.Block_height = &header.Height 565 566 if err := h.enforceCommunityRestrictions(community, p, strategy); err != nil { 567 return models.Proposal{}, errIncompleteRequest 568 } 569 570 p.Cid, err = h.pinJSONToIpfs(p) 571 if err != nil { 572 log.Error().Err(err).Msg("IPFS error: " + err.Error()) 573 return models.Proposal{}, errIncompleteRequest 574 } 575 576 validate := validator.New() 577 vErr := validate.Struct(p) 578 if vErr != nil { 579 log.Error().Err(vErr) 580 return models.Proposal{}, errIncompleteRequest 581 } 582 583 if os.Getenv("APP_ENV") == "PRODUCTION" { 584 if strategy.Contract.Name != nil && p.Start_time.Before(time.Now().UTC().Add(time.Hour)) { 585 p.Start_time = time.Now().UTC().Add(time.Hour) 586 } 587 } 588 589 if err := p.CreateProposal(h.A.DB); err != nil { 590 return models.Proposal{}, errIncompleteRequest 591 } 592 593 return p, nilErr 594 } 595 596 func (h *Helpers) validateStrategyName(name string) error { 597 if name == "" { 598 return errors.New("Strategy name is required.") 599 } 600 601 for k, _ := range strategyMap { 602 if name == k { 603 return nil 604 } else { 605 continue 606 } 607 } 608 609 return errors.New("Strategy not found.") 610 } 611 612 func (h *Helpers) enforceCommunityRestrictions( 613 c models.Community, 614 p models.Proposal, 615 s models.Strategy, 616 ) error { 617 618 if *c.Only_authors_to_submit { 619 if err := models.EnsureRoleForCommunity(h.A.DB, p.Creator_addr, c.ID, "author"); err != nil { 620 errMsg := fmt.Sprintf("Account %s is not an author for community %d.", p.Creator_addr, p.Community_id) 621 log.Error().Err(err).Msg(errMsg) 622 return errors.New(errMsg) 623 } 624 } else { 625 fmt.Println("Community does not require authors to submit proposals") 626 627 threshold, err := strconv.ParseFloat(*c.Proposal_threshold, 64) 628 if err != nil { 629 log.Error().Err(err).Msg("Invalid proposal threshold") 630 return errors.New("Invalid proposal threshold") 631 } 632 633 contract := shared.Contract{ 634 Name: c.Contract_name, 635 Addr: c.Contract_addr, 636 Public_path: c.Public_path, 637 Threshold: &threshold, 638 } 639 hasBalance, err := h.processTokenThreshold(p.Creator_addr, contract, *c.Contract_type) 640 if err != nil { 641 errMsg := "Error processing Token Threshold." 642 log.Error().Err(err).Msg(errMsg) 643 return errors.New(errMsg) 644 } 645 646 if !hasBalance { 647 errMsg := "Insufficient token balance to create proposal." 648 log.Error().Err(err).Msg(errMsg) 649 return errors.New(errMsg) 650 } 651 } 652 653 return nil 654 } 655 656 func (h *Helpers) createCommunity(payload models.CreateCommunityRequestPayload) (models.Community, error) { 657 c := payload.Community 658 659 if c.Voucher != nil { 660 log.Info().Msgf("validate user via voucher %v \n", c.Voucher) 661 if err := h.validateUserViaVoucher(c.Creator_addr, c.Voucher); err != nil { 662 return models.Community{}, err 663 } 664 } else { 665 if err := h.validateUser(c.Creator_addr, c.Timestamp, c.Composite_signatures); err != nil { 666 return models.Community{}, err 667 } 668 } 669 670 cid, err := h.pinJSONToIpfs(c) 671 if err != nil { 672 log.Error().Err(err).Msg("Error pinning JSON to IPFS.") 673 return models.Community{}, err 674 } 675 c.Cid = cid 676 677 validate := validator.New() 678 vErr := validate.Struct(c) 679 if vErr != nil { 680 log.Error().Err(vErr).Msg("Invalid community.") 681 return models.Community{}, err 682 } 683 684 if err := c.CreateCommunity(h.A.DB); err != nil { 685 log.Error().Err(err).Msg("Database error creating community.") 686 return models.Community{}, err 687 } 688 689 if err := h.processCommunityRoles(&c, &payload); err != nil { 690 log.Error().Err(err).Msg("Error processing community roles.") 691 return models.Community{}, err 692 } 693 694 return c, nil 695 } 696 697 func (h *Helpers) processCommunityRoles( 698 c *models.Community, 699 p *models.CreateCommunityRequestPayload, 700 ) error { 701 if err := models.GrantRolesToCommunityCreator(h.A.DB, c.Creator_addr, c.ID); err != nil { 702 errMsg := "Database error adding community creator roles." 703 log.Error().Err(err).Msg(errMsg) 704 return errors.New(errMsg) 705 } 706 707 if p.Additional_admins != nil { 708 for _, addr := range *p.Additional_admins { 709 if err := models.GrantAdminRolesToAddress(h.A.DB, c.ID, addr); err != nil { 710 log.Error().Err(err) 711 return err 712 } 713 } 714 } 715 716 if p.Additional_authors != nil { 717 for _, addr := range *p.Additional_authors { 718 if err := models.GrantAuthorRolesToAddress(h.A.DB, c.ID, addr); err != nil { 719 log.Error().Err(err) 720 return err 721 } 722 } 723 } 724 725 return nil 726 } 727 728 func (h *Helpers) updateCommunity(id int, payload models.UpdateCommunityRequestPayload) (models.Community, error) { 729 c, err := h.fetchCommunity(id) 730 if err != nil { 731 return models.Community{}, err 732 } 733 734 // validate is community creator 735 // TODO: update to validating address is admin 736 if err := c.CanUpdateCommunity(h.A.DB, payload.Signing_addr); err != nil { 737 log.Error().Err(err) 738 return models.Community{}, err 739 } 740 741 if payload.Voucher != nil { 742 if err := h.validateUserViaVoucher(payload.Signing_addr, payload.Voucher); err != nil { 743 log.Error().Err(err) 744 return models.Community{}, err 745 } 746 } else { 747 if err := h.validateUser(payload.Signing_addr, payload.Timestamp, payload.Composite_signatures); err != nil { 748 log.Error().Err(err) 749 return models.Community{}, err 750 } 751 } 752 753 if err := c.UpdateCommunity(h.A.DB, &payload); err != nil { 754 log.Error().Err(err) 755 return models.Community{}, err 756 } 757 758 c, err = h.fetchCommunity(id) 759 if err != nil { 760 return models.Community{}, err 761 } 762 763 return c, nil 764 } 765 766 func (h *Helpers) removeUserRole(payload models.CommunityUserPayload) (int, error) { 767 if payload.Voucher != nil { 768 if err := h.validateUserViaVoucher(payload.Signing_addr, payload.Voucher); err != nil { 769 log.Error().Err(err) 770 return http.StatusForbidden, err 771 } 772 } else { 773 if err := h.validateUser(payload.Signing_addr, payload.Timestamp, payload.Composite_signatures); err != nil { 774 log.Error().Err(err) 775 return http.StatusForbidden, err 776 } 777 } 778 779 if payload.User_type == "member" { 780 if payload.Addr == payload.Signing_addr { 781 // If a member is removing themselves, remove all their other roles as well 782 userRoles, err := models.GetAllRolesForUserInCommunity(h.A.DB, payload.Addr, payload.Community_id) 783 if err != nil { 784 log.Error().Err(err) 785 return http.StatusInternalServerError, err 786 } 787 for _, userRole := range userRoles { 788 if err := userRole.Remove(h.A.DB); err != nil { 789 log.Error().Err(err) 790 return http.StatusInternalServerError, err 791 } 792 } 793 } else { 794 // validate someone else is not removing a "member" role 795 CANNOT_REMOVE_MEMBER_ERR := errors.New("Cannot remove another member from a community.") 796 log.Error().Err(CANNOT_REMOVE_MEMBER_ERR) 797 return http.StatusForbidden, CANNOT_REMOVE_MEMBER_ERR 798 } 799 } 800 801 u := payload.CommunityUser 802 803 if payload.User_type == "admin" { 804 // validate signer is admin 805 var adminUser = models.CommunityUser{Addr: payload.Signing_addr, Community_id: payload.Community_id, User_type: "admin"} 806 if err := adminUser.GetCommunityUser(h.A.DB); err != nil { 807 USER_MUST_BE_ADMIN_ERR := errors.New("User must be community admin.") 808 log.Error().Err(err).Msg("Database error.") 809 log.Error().Err(USER_MUST_BE_ADMIN_ERR) 810 return http.StatusForbidden, USER_MUST_BE_ADMIN_ERR 811 } 812 // If the admin role is being removed, remove author role as well 813 author := models.CommunityUser{Addr: u.Addr, Community_id: u.Community_id, User_type: "author"} 814 if err := author.Remove(h.A.DB); err != nil { 815 return http.StatusInternalServerError, err 816 } 817 // remove admin role 818 if err := u.Remove(h.A.DB); err != nil { 819 return http.StatusInternalServerError, err 820 } 821 // Otherwise, just remove the specified user role 822 } else if err := u.Remove(h.A.DB); err != nil { 823 return http.StatusInternalServerError, err 824 } 825 826 return http.StatusOK, nil 827 } 828 829 func (h *Helpers) createCommunityUser(payload models.CommunityUserPayload) (int, error) { 830 // validate community_user payload fields 831 validate := validator.New() 832 vErr := validate.Struct(payload) 833 if vErr != nil { 834 errMsg := "Invalid community user." 835 log.Error().Err(vErr).Msg(errMsg) 836 return http.StatusBadRequest, errors.New(errMsg) 837 } 838 // validate user is allowed to create this user 839 if payload.User_type != "member" { 840 if payload.Signing_addr == payload.Addr { 841 CANNOT_GRANT_SELF_ERR := errors.New("Users cannot grant themselves a privileged user_type.") 842 log.Error().Err(CANNOT_GRANT_SELF_ERR) 843 return http.StatusForbidden, CANNOT_GRANT_SELF_ERR 844 } 845 // If signing address is not user address, verify they have admin status in this community 846 var communityAdmin = models.CommunityUser{Community_id: payload.Community_id, Addr: payload.Signing_addr, User_type: "admin"} 847 if err := communityAdmin.GetCommunityUser(h.A.DB); err != nil { 848 USER_MUST_BE_ADMIN_ERR := errors.New("User must be community admin to grant privileges.") 849 log.Error().Err(err).Msg("Database error.") 850 log.Error().Err(USER_MUST_BE_ADMIN_ERR) 851 return http.StatusForbidden, USER_MUST_BE_ADMIN_ERR 852 } 853 } 854 // only an account can add itself as a "member", unless an admin is granting 855 // an address a privileged role 856 if payload.User_type == "member" && payload.Addr != payload.Signing_addr { 857 CANNOT_ADD_MEMBER_ERR := errors.New( 858 "An account can only add itself as a community member, unless an admin is granting privileged role.", 859 ) 860 log.Error().Err(CANNOT_ADD_MEMBER_ERR) 861 return http.StatusForbidden, CANNOT_ADD_MEMBER_ERR 862 } 863 864 if payload.Voucher != nil { 865 if err := h.validateUserViaVoucher(payload.Signing_addr, payload.Voucher); err != nil { 866 log.Error().Err(err) 867 return http.StatusForbidden, err 868 } 869 } else { 870 if err := h.validateUser(payload.Signing_addr, payload.Timestamp, payload.Composite_signatures); err != nil { 871 log.Error().Err(err) 872 return http.StatusForbidden, err 873 } 874 } 875 876 // check that community user doesnt already exist 877 // should throw a "ErrNoRows" error 878 u := payload.CommunityUser 879 if err := u.GetCommunityUser(h.A.DB); err == nil { 880 errMsg := fmt.Sprintf("Error: Address %s is already a %s of community %d.\n", u.Addr, u.User_type, u.Community_id) 881 log.Error().Err(err).Msg(errMsg) 882 return http.StatusBadRequest, errors.New(errMsg) 883 } 884 885 // Grant appropriate roles 886 if u.User_type == "admin" { 887 if err := models.GrantAdminRolesToAddress(h.A.DB, u.Community_id, u.Addr); err != nil { 888 log.Error().Err(err) 889 return http.StatusInternalServerError, err 890 } 891 } else if u.User_type == "author" { 892 if err := models.GrantAuthorRolesToAddress(h.A.DB, u.Community_id, u.Addr); err != nil { 893 return http.StatusInternalServerError, err 894 } 895 } else { 896 // grant member role 897 if err := u.CreateCommunityUser(h.A.DB); err != nil { 898 log.Error().Err(err) 899 return http.StatusInternalServerError, err 900 } 901 } 902 903 return http.StatusCreated, nil 904 } 905 906 func (h *Helpers) updateAddressesInList(id int, payload models.ListUpdatePayload, action string) (int, error) { 907 l := models.List{ID: id} 908 909 // get current proposal from DB 910 if err := l.GetListById(h.A.DB); err != nil { 911 errMsg := fmt.Sprintf("Error querying list with id %v.", id) 912 log.Error().Err(err).Msg(errMsg) 913 return http.StatusInternalServerError, err 914 } 915 916 validate := validator.New() 917 if vErr := validate.Struct(payload); vErr != nil { 918 errMsg := "Remove from list validation error." 919 if action == "add" { 920 errMsg = "Add to list validation error." 921 } 922 log.Error().Err(vErr).Msg(errMsg) 923 return http.StatusBadRequest, errors.New(errMsg) 924 } 925 926 if err := h.validateUserWithRole(payload.Signing_addr, payload.Timestamp, payload.Composite_signatures, l.Community_id, "admin"); err != nil { 927 log.Error().Err(err) 928 return http.StatusForbidden, err 929 } 930 931 if action == "remove" { 932 l.RemoveAddresses(payload.Addresses) 933 } else { 934 l.AddAddresses(payload.Addresses) 935 } 936 937 cid, err := h.pinJSONToIpfs(l) 938 if err != nil { 939 log.Error().Err(err).Msg("IPFS error: " + err.Error()) 940 return http.StatusInternalServerError, errors.New("Error pinning JSON to IPFS.") 941 } 942 l.Cid = cid 943 944 if err := l.UpdateList(h.A.DB); err != nil { 945 errMsg := "Database error updating list." 946 log.Error().Err(err).Msg(errMsg) 947 return http.StatusInternalServerError, err 948 } 949 950 return http.StatusOK, nil 951 } 952 953 func (h *Helpers) createListForCommunity(payload models.ListPayload) (models.List, int, error) { 954 if existingList, _ := models.GetListForCommunityByType(h.A.DB, payload.Community_id, *payload.List_type); existingList.ID > 0 { 955 errMsg := fmt.Sprintf("List of type %s already exists for community %d.", *payload.List_type, payload.Community_id) 956 return models.List{}, http.StatusBadRequest, errors.New(errMsg) 957 } 958 959 // validate payload fields 960 validate := validator.New() 961 if vErr := validate.Struct(payload); vErr != nil { 962 errMsg := "Validation error in list payload." 963 log.Error().Err(vErr).Msg(errMsg) 964 return models.List{}, http.StatusBadRequest, errors.New(errMsg) 965 } 966 967 if err := h.validateUserWithRole(payload.Signing_addr, payload.Timestamp, payload.Composite_signatures, payload.Community_id, "admin"); err != nil { 968 log.Error().Err(err) 969 return models.List{}, http.StatusForbidden, err 970 } 971 972 l := payload.List 973 974 cid, err := h.pinJSONToIpfs(l) 975 if err != nil { 976 log.Error().Err(err).Msg("IPFS error: " + err.Error()) 977 return models.List{}, http.StatusInternalServerError, errors.New("Error pinning JSON to IPFS.") 978 } 979 l.Cid = cid 980 981 // create list 982 if err := l.CreateList(h.A.DB); err != nil { 983 return models.List{}, http.StatusInternalServerError, err 984 } 985 986 return l, http.StatusCreated, nil 987 } 988 989 func (h *Helpers) validateUserSignature(addr string, message string, sigs *[]shared.CompositeSignature) error { 990 shouldValidateSignature := h.A.Config.Features["validateSigs"] 991 992 if !shouldValidateSignature { 993 return nil 994 } 995 996 hexMessage := hex.EncodeToString([]byte(message)) 997 if err := h.A.FlowAdapter.ValidateSignature(addr, hexMessage, sigs, "USER"); err != nil { 998 return err 999 } 1000 1001 return nil 1002 } 1003 1004 func (h *Helpers) validateTxSignature(addr string, message string, sigs *[]shared.CompositeSignature) error { 1005 shouldValidateSignature := h.A.Config.Features["validateSigs"] 1006 1007 if !shouldValidateSignature { 1008 return nil 1009 } 1010 1011 if err := h.A.FlowAdapter.ValidateSignature(addr, message, sigs, "TRANSACTION"); err != nil { 1012 return err 1013 } 1014 return nil 1015 } 1016 1017 func (h *Helpers) validateBlocklist(addr string, communityId int) error { 1018 if !h.A.Config.Features["validateBlocklist"] { 1019 return nil 1020 } 1021 1022 blockList, _ := models.GetListForCommunityByType(h.A.DB, communityId, "block") 1023 isBlocked := funk.Contains(blockList.Addresses, addr) 1024 1025 isTest := flag.Lookup("test.v") != nil 1026 1027 if isBlocked && !isTest { 1028 return errors.New("User does not have permission.") 1029 } 1030 return nil 1031 } 1032 1033 // Need to move this to conditional middleware 1034 func (h *Helpers) validateTimestamp(timestamp string, expiry int) error { 1035 if !h.A.Config.Features["validateTimestamps"] { 1036 return nil 1037 } 1038 // check timestamp and ensure no longer than expiry seconds has passed 1039 stamp, _ := strconv.ParseInt(timestamp, 10, 64) 1040 uxTime := time.Unix(stamp/1000, (stamp%1000)*1000*1000) 1041 diff := time.Now().UTC().Sub(uxTime).Seconds() 1042 if diff > float64(expiry) { 1043 err := errors.New("Timestamp on request has expired.") 1044 log.Error().Err(err).Msgf("expiry error: %v", diff) 1045 return err 1046 } 1047 return nil 1048 } 1049 1050 func (h *Helpers) validateUser(addr, timestamp string, compositeSignatures *[]shared.CompositeSignature) error { 1051 1052 if err := h.validateTimestamp(timestamp, 60); err != nil { 1053 return err 1054 } 1055 1056 if err := h.validateUserSignature(addr, timestamp, compositeSignatures); err != nil { 1057 return err 1058 } 1059 1060 return nil 1061 } 1062 1063 func (h *Helpers) validateUserViaVoucher(addr string, voucher *shared.Voucher) error { 1064 timestamp := voucher.Arguments[0]["value"] 1065 if err := h.validateTimestamp(timestamp, 60); err != nil { 1066 return err 1067 } 1068 1069 compositeSignatures := shared.GetUserCompositeSignatureFromVoucher(voucher) 1070 // Validate authorizer 1071 authorizer := voucher.Authorizers[0] 1072 if authorizer != addr || authorizer != (*compositeSignatures)[0].Addr { 1073 err := errors.New("authorizer address must match voter address and envelope signer") 1074 log.Error().Err(err) 1075 return err 1076 } 1077 // validate signature using encoded transaction payload as message 1078 message := shared.EncodeMessageFromVoucher(voucher) 1079 if err := h.validateTxSignature(addr, message, compositeSignatures); err != nil { 1080 return err 1081 } 1082 1083 return nil 1084 } 1085 1086 func (h *Helpers) validateUserWithRole(addr, timestamp string, compositeSignatures *[]shared.CompositeSignature, communityId int, role string) error { 1087 //print out all the params 1088 if err := h.validateTimestamp(timestamp, 60); err != nil { 1089 return err 1090 } 1091 if err := h.validateUserSignature(addr, timestamp, compositeSignatures); err != nil { 1092 return err 1093 } 1094 if err := models.EnsureRoleForCommunity(h.A.DB, addr, communityId, role); err != nil { 1095 errMsg := fmt.Sprintf("Account %s is not an author for community %d.", addr, communityId) 1096 log.Error().Err(err).Msg(errMsg) 1097 return err 1098 } 1099 1100 return nil 1101 } 1102 1103 func (h *Helpers) validateUserWithRoleViaVoucher(addr string, voucher *shared.Voucher, communityId int, role string) error { 1104 timestamp := voucher.Arguments[0]["value"] 1105 if err := h.validateTimestamp(timestamp, 60); err != nil { 1106 return err 1107 } 1108 1109 compositeSignatures := shared.GetUserCompositeSignatureFromVoucher(voucher) 1110 // Validate authorizer 1111 authorizer := voucher.Authorizers[0] 1112 if authorizer != addr || authorizer != (*compositeSignatures)[0].Addr { 1113 err := errors.New("authorizer address must match voter address and envelope signer") 1114 log.Error().Err(err) 1115 return err 1116 } 1117 1118 // validate signature using encoded transaction payload as message 1119 message := shared.EncodeMessageFromVoucher(voucher) 1120 if err := h.validateTxSignature(addr, message, compositeSignatures); err != nil { 1121 return err 1122 } 1123 if err := models.EnsureRoleForCommunity(h.A.DB, addr, communityId, role); err != nil { 1124 errMsg := fmt.Sprintf("Account %s is not an author for community %d.", addr, communityId) 1125 log.Error().Err(err).Msg(errMsg) 1126 return err 1127 } 1128 1129 return nil 1130 } 1131 1132 func (h *Helpers) processTokenThreshold(address string, c shared.Contract, contractType string) (bool, error) { 1133 var scriptPath string 1134 1135 if contractType == "nft" { 1136 scriptPath = "./main/cadence/scripts/get_nfts_ids.cdc" 1137 } else { 1138 scriptPath = "./main/cadence/scripts/get_balance.cdc" 1139 } 1140 1141 hasBalance, err := h.A.FlowAdapter.EnforceTokenThreshold(scriptPath, address, &c) 1142 if err != nil { 1143 return false, err 1144 } 1145 1146 return hasBalance, nil 1147 } 1148 1149 func (h *Helpers) initStrategy(name string) Strategy { 1150 s := strategyMap[name] 1151 if s == nil { 1152 return nil 1153 } 1154 1155 s.InitStrategy(h.A.FlowAdapter, h.A.DB) 1156 1157 return s 1158 } 1159 1160 func (h *Helpers) pinJSONToIpfs(data interface{}) (*string, error) { 1161 shouldOverride := flag.Lookup("ipfs-override").Value.(flag.Getter).Get().(bool) 1162 if shouldOverride { 1163 dummyHash := "dummy-hash" 1164 return &dummyHash, nil 1165 } 1166 1167 pin, err := h.A.IpfsClient.PinJson(data) 1168 if err != nil { 1169 return nil, err 1170 } 1171 return &pin.IpfsHash, nil 1172 } 1173 1174 func (h *Helpers) appendFiltersToResponse( 1175 results []*models.Community, 1176 pageParams shared.PageParams, 1177 count map[string]int, 1178 ) (interface{}, error) { 1179 var filters []shared.SearchFilter 1180 var CATEGORIES = []string{ 1181 "all", 1182 "dao", 1183 "social", 1184 "protocol", 1185 "creator", 1186 "nft", 1187 "collector", 1188 } 1189 1190 var totalCount int 1191 for _, category := range CATEGORIES { 1192 if category == "all" { 1193 continue 1194 } 1195 filters = append(filters, shared.SearchFilter{ 1196 Text: category, 1197 Amount: count[category], 1198 }) 1199 totalCount += count[category] 1200 } 1201 1202 filters = append(filters, shared.SearchFilter{ 1203 Text: "all", 1204 Amount: totalCount, 1205 }) 1206 1207 paginatedResults := shared.GetPaginatedResponseWithPayload(results, pageParams) 1208 appendedResponse := struct { 1209 Filters []shared.SearchFilter `json:"filters"` 1210 Results shared.PaginatedResponse `json:"results"` 1211 }{ 1212 Filters: filters, 1213 Results: *paginatedResults, 1214 } 1215 1216 return appendedResponse, nil 1217 } 1218 1219 func validateContractThreshold(s []models.Strategy) error { 1220 for _, s := range s { 1221 if s.Threshold != nil { 1222 if *s.Threshold < 1 { 1223 return errors.New("Contract Threshold cannot be less than 1.") 1224 } 1225 } 1226 } 1227 return nil 1228 } 1229 1230 func validateProposalThreshold(threshold string, onlyAuthorsToSubmit bool) error { 1231 propThreshold, err := strconv.ParseFloat(threshold, 64) 1232 if err != nil { 1233 return errors.New("Error Converting Proposal Threshold to Float.") 1234 } 1235 if !onlyAuthorsToSubmit && propThreshold < 1 { 1236 return errors.New("Proposal Threshold cannot be less than 1.") 1237 } 1238 return nil 1239 }