decred.org/dcrdex@v1.0.5/client/webserver/api.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package webserver 5 6 import ( 7 "encoding/hex" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "net/http" 12 "time" 13 14 "decred.org/dcrdex/client/asset" 15 "decred.org/dcrdex/client/core" 16 "decred.org/dcrdex/client/db" 17 "decred.org/dcrdex/client/mm" 18 "decred.org/dcrdex/client/mm/libxc" 19 "decred.org/dcrdex/dex" 20 "decred.org/dcrdex/dex/config" 21 "decred.org/dcrdex/dex/encode" 22 ) 23 24 var zero = encode.ClearBytes 25 26 // apiAddDEX is the handler for the '/adddex' API request. 27 func (s *WebServer) apiAddDEX(w http.ResponseWriter, r *http.Request) { 28 form := new(addDexForm) 29 if !readPost(w, r, form) { 30 return 31 } 32 defer form.AppPW.Clear() 33 appPW, err := s.resolvePass(form.AppPW, r) 34 if err != nil { 35 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 36 return 37 } 38 cert := []byte(form.Cert) 39 40 if err = s.core.AddDEX(appPW, form.Addr, cert); err != nil { 41 s.writeAPIError(w, err) 42 return 43 } 44 writeJSON(w, simpleAck()) 45 } 46 47 // apiDiscoverAccount is the handler for the '/discoveracct' API request. 48 func (s *WebServer) apiDiscoverAccount(w http.ResponseWriter, r *http.Request) { 49 form := new(registrationForm) 50 defer form.Password.Clear() 51 if !readPost(w, r, form) { 52 return 53 } 54 cert := []byte(form.Cert) 55 pass, err := s.resolvePass(form.Password, r) 56 if err != nil { 57 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 58 return 59 } 60 defer zero(pass) 61 exchangeInfo, paid, err := s.core.DiscoverAccount(form.Addr, pass, cert) // TODO: update when paid return removed 62 if err != nil { 63 s.writeAPIError(w, err) 64 return 65 } 66 resp := struct { 67 OK bool `json:"ok"` 68 Exchange *core.Exchange `json:"xc,omitempty"` 69 Paid bool `json:"paid"` 70 }{ 71 OK: true, 72 Exchange: exchangeInfo, 73 Paid: paid, 74 } 75 writeJSON(w, resp) 76 } 77 78 // apiValidateAddress is the handlers for the '/validateaddress' API request. 79 func (s *WebServer) apiValidateAddress(w http.ResponseWriter, r *http.Request) { 80 form := &struct { 81 Addr string `json:"addr"` 82 AssetID *uint32 `json:"assetID"` 83 }{} 84 if !readPost(w, r, form) { 85 return 86 } 87 if form.AssetID == nil { 88 s.writeAPIError(w, errors.New("missing asset ID")) 89 return 90 } 91 valid, err := s.core.ValidateAddress(form.Addr, *form.AssetID) 92 if err != nil { 93 s.writeAPIError(w, err) 94 return 95 } 96 resp := struct { 97 OK bool `json:"ok"` 98 }{ 99 OK: valid, 100 } 101 writeJSON(w, resp) 102 } 103 104 // apiEstimateSendTxFee is the handler for the '/txfee' API request. 105 func (s *WebServer) apiEstimateSendTxFee(w http.ResponseWriter, r *http.Request) { 106 form := new(sendTxFeeForm) 107 if !readPost(w, r, form) { 108 return 109 } 110 if form.AssetID == nil { 111 s.writeAPIError(w, errors.New("missing asset ID")) 112 return 113 } 114 txFee, validAddress, err := s.core.EstimateSendTxFee(form.Addr, *form.AssetID, form.Value, form.Subtract, form.MaxWithdraw) 115 if err != nil { 116 s.writeAPIError(w, err) 117 return 118 } 119 resp := struct { 120 OK bool `json:"ok"` 121 TxFee uint64 `json:"txfee"` 122 ValidAddress bool `json:"validaddress"` 123 }{ 124 OK: true, 125 TxFee: txFee, 126 ValidAddress: validAddress, 127 } 128 writeJSON(w, resp) 129 } 130 131 // apiGetWalletPeers is the handler for the '/getwalletpeers' API request. 132 func (s *WebServer) apiGetWalletPeers(w http.ResponseWriter, r *http.Request) { 133 var form struct { 134 AssetID uint32 `json:"assetID"` 135 } 136 if !readPost(w, r, &form) { 137 return 138 } 139 peers, err := s.core.WalletPeers(form.AssetID) 140 if err != nil { 141 s.writeAPIError(w, err) 142 return 143 } 144 resp := struct { 145 OK bool `json:"ok"` 146 Peers []*asset.WalletPeer `json:"peers"` 147 }{ 148 OK: true, 149 Peers: peers, 150 } 151 writeJSON(w, resp) 152 } 153 154 // apiAddWalletPeer is the handler for the '/addwalletpeer' API request. 155 func (s *WebServer) apiAddWalletPeer(w http.ResponseWriter, r *http.Request) { 156 var form struct { 157 AssetID uint32 `json:"assetID"` 158 Address string `json:"addr"` 159 } 160 if !readPost(w, r, &form) { 161 return 162 } 163 err := s.core.AddWalletPeer(form.AssetID, form.Address) 164 if err != nil { 165 s.writeAPIError(w, err) 166 return 167 } 168 writeJSON(w, simpleAck()) 169 } 170 171 // apiRemoveWalletPeer is the handler for the '/removewalletpeer' API request. 172 func (s *WebServer) apiRemoveWalletPeer(w http.ResponseWriter, r *http.Request) { 173 var form struct { 174 AssetID uint32 `json:"assetID"` 175 Address string `json:"addr"` 176 } 177 if !readPost(w, r, &form) { 178 return 179 } 180 err := s.core.RemoveWalletPeer(form.AssetID, form.Address) 181 if err != nil { 182 s.writeAPIError(w, err) 183 return 184 } 185 writeJSON(w, simpleAck()) 186 } 187 188 func (s *WebServer) apiApproveTokenFee(w http.ResponseWriter, r *http.Request) { 189 var form struct { 190 AssetID uint32 `json:"assetID"` 191 Version uint32 `json:"version"` 192 Approval bool `json:"approval"` 193 } 194 if !readPost(w, r, &form) { 195 return 196 } 197 198 txFee, err := s.core.ApproveTokenFee(form.AssetID, form.Version, form.Approval) 199 if err != nil { 200 s.writeAPIError(w, err) 201 return 202 } 203 204 resp := struct { 205 OK bool `json:"ok"` 206 TxFee uint64 `json:"txFee"` 207 }{ 208 OK: true, 209 TxFee: txFee, 210 } 211 writeJSON(w, resp) 212 } 213 214 func (s *WebServer) apiApproveToken(w http.ResponseWriter, r *http.Request) { 215 var form struct { 216 AssetID uint32 `json:"assetID"` 217 DexAddr string `json:"dexAddr"` 218 Password encode.PassBytes `json:"pass"` 219 } 220 if !readPost(w, r, &form) { 221 return 222 } 223 pass, err := s.resolvePass(form.Password, r) 224 if err != nil { 225 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 226 return 227 } 228 defer zero(pass) 229 230 txID, err := s.core.ApproveToken(pass, form.AssetID, form.DexAddr, func() {}) 231 if err != nil { 232 s.writeAPIError(w, err) 233 return 234 } 235 resp := struct { 236 OK bool `json:"ok"` 237 TxID string `json:"txID"` 238 }{ 239 OK: true, 240 TxID: txID, 241 } 242 writeJSON(w, resp) 243 } 244 245 func (s *WebServer) apiUnapproveToken(w http.ResponseWriter, r *http.Request) { 246 var form struct { 247 AssetID uint32 `json:"assetID"` 248 Version uint32 `json:"version"` 249 Password encode.PassBytes `json:"pass"` 250 } 251 if !readPost(w, r, &form) { 252 return 253 } 254 pass, err := s.resolvePass(form.Password, r) 255 if err != nil { 256 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 257 return 258 } 259 defer zero(pass) 260 261 txID, err := s.core.UnapproveToken(pass, form.AssetID, form.Version) 262 if err != nil { 263 s.writeAPIError(w, err) 264 return 265 } 266 resp := struct { 267 OK bool `json:"ok"` 268 TxID string `json:"txID"` 269 }{ 270 OK: true, 271 TxID: txID, 272 } 273 writeJSON(w, resp) 274 } 275 276 // apiGetDEXInfo is the handler for the '/getdexinfo' API request. 277 func (s *WebServer) apiGetDEXInfo(w http.ResponseWriter, r *http.Request) { 278 form := new(registrationForm) 279 if !readPost(w, r, form) { 280 return 281 } 282 cert := []byte(form.Cert) 283 exchangeInfo, err := s.core.GetDEXConfig(form.Addr, cert) 284 if err != nil { 285 s.writeAPIError(w, err) 286 return 287 } 288 resp := struct { 289 OK bool `json:"ok"` 290 Exchange *core.Exchange `json:"xc,omitempty"` 291 }{ 292 OK: true, 293 Exchange: exchangeInfo, 294 } 295 writeJSON(w, resp) 296 } 297 298 // bondsFeeBuffer is a caching helper for the bonds fee buffer. Values for a 299 // given asset are cached for 45 minutes. These values are meant to provide a 300 // sensible but well-padded fee buffer for bond transactions now and well into 301 // the future, so a long expiry is appropriate. 302 func (s *WebServer) bondsFeeBuffer(assetID uint32) (feeBuffer uint64, err error) { 303 // (*Core).BondsFeeBuffer returns a fresh fee buffer based on a current (but 304 // padded) fee rate estimate. We assist the frontend by stabilizing this 305 // value for up to 45 minutes from the last request for a given asset. A web 306 // app could conceivably do the same, but we'll do this here between the 307 // backend (Core) and UI so that a webapp does not need to employ local 308 // storage/cookies and associated caching logic. 309 const expiry = 45 * time.Minute 310 s.bondBufMtx.Lock() 311 defer s.bondBufMtx.Unlock() 312 if buf, ok := s.bondBuf[assetID]; ok && time.Since(buf.stamp) < expiry { 313 feeBuffer = buf.val 314 log.Tracef("Using cached bond fee buffer (%v old): %d", 315 time.Since(buf.stamp), feeBuffer) 316 } else { 317 feeBuffer, err = s.core.BondsFeeBuffer(assetID) 318 if err != nil { 319 return 320 } 321 log.Tracef("Obtained fresh bond fee buffer: %d", feeBuffer) 322 s.bondBuf[assetID] = valStamp{feeBuffer, time.Now()} 323 } 324 return 325 } 326 327 // apiBondsFeeBuffer is the handler for the '/bondsfeebuffer' API request. 328 func (s *WebServer) apiBondsFeeBuffer(w http.ResponseWriter, r *http.Request) { 329 form := new(bondsFeeBufferForm) 330 if !readPost(w, r, form) { 331 return 332 } 333 feeBuffer, err := s.bondsFeeBuffer(form.AssetID) 334 if err != nil { 335 s.writeAPIError(w, err) 336 return 337 } 338 resp := struct { 339 OK bool `json:"ok"` 340 FeeBuffer uint64 `json:"feeBuffer"` 341 }{ 342 OK: true, 343 FeeBuffer: feeBuffer, 344 } 345 writeJSON(w, resp) 346 } 347 348 // apiPostBond is the handler for the '/postbond' API request. 349 func (s *WebServer) apiPostBond(w http.ResponseWriter, r *http.Request) { 350 post := new(postBondForm) 351 defer post.Password.Clear() 352 if !readPost(w, r, post) { 353 return 354 } 355 assetID := uint32(42) 356 if post.AssetID != nil { 357 assetID = *post.AssetID 358 } 359 wallet := s.core.WalletState(assetID) 360 if wallet == nil { 361 s.writeAPIError(w, errors.New("no wallet")) 362 return 363 } 364 pass, err := s.resolvePass(post.Password, r) 365 if err != nil { 366 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 367 return 368 } 369 defer zero(pass) 370 371 bondForm := &core.PostBondForm{ 372 Addr: post.Addr, 373 Cert: []byte(post.Cert), 374 AppPass: pass, 375 Bond: post.Bond, 376 Asset: &assetID, 377 LockTime: post.LockTime, 378 // Options valid only when creating an account with bond: 379 MaintainTier: post.Maintain, 380 MaxBondedAmt: post.MaxBondedAmt, 381 } 382 383 if post.FeeBuffer != nil { 384 bondForm.FeeBuffer = *post.FeeBuffer 385 } 386 387 _, err = s.core.PostBond(bondForm) 388 if err != nil { 389 s.writeAPIError(w, fmt.Errorf("add bond error: %w", err)) 390 return 391 } 392 // There was no error paying the fee, but we must wait on confirmations 393 // before informing the DEX of the fee payment. Those results will come 394 // through as a notification. 395 writeJSON(w, simpleAck()) 396 } 397 398 // apiUpdateBondOptions is the handler for the '/updatebondoptions' API request. 399 func (s *WebServer) apiUpdateBondOptions(w http.ResponseWriter, r *http.Request) { 400 form := new(core.BondOptionsForm) 401 if !readPost(w, r, form) { 402 return 403 } 404 405 err := s.core.UpdateBondOptions(form) 406 if err != nil { 407 s.writeAPIError(w, fmt.Errorf("update bond options error: %w", err)) 408 return 409 } 410 411 writeJSON(w, simpleAck()) 412 } 413 414 func (s *WebServer) apiRedeemPrepaidBond(w http.ResponseWriter, r *http.Request) { 415 var req struct { 416 Host string `json:"host"` 417 Code dex.Bytes `json:"code"` 418 AppPW encode.PassBytes `json:"appPW"` 419 Cert string `json:"cert"` 420 } 421 defer req.AppPW.Clear() 422 if !readPost(w, r, &req) { 423 return 424 } 425 appPW, err := s.resolvePass(req.AppPW, r) 426 if err != nil { 427 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 428 return 429 } 430 tier, err := s.core.RedeemPrepaidBond(appPW, req.Code, req.Host, []byte(req.Cert)) 431 if err != nil { 432 s.writeAPIError(w, err) 433 return 434 } 435 resp := &struct { 436 OK bool `json:"ok"` 437 Tier uint64 `json:"tier"` 438 }{ 439 OK: true, 440 Tier: tier, 441 } 442 writeJSON(w, resp) 443 } 444 445 // apiNewWallet is the handler for the '/newwallet' API request. 446 func (s *WebServer) apiNewWallet(w http.ResponseWriter, r *http.Request) { 447 form := new(newWalletForm) 448 defer form.AppPW.Clear() 449 defer form.Pass.Clear() 450 if !readPost(w, r, form) { 451 return 452 } 453 has := s.core.WalletState(form.AssetID) != nil 454 if has { 455 s.writeAPIError(w, fmt.Errorf("already have a wallet for %s", unbip(form.AssetID))) 456 return 457 } 458 pass, err := s.resolvePass(form.AppPW, r) 459 if err != nil { 460 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 461 return 462 } 463 defer zero(pass) 464 var parentForm *core.WalletForm 465 if f := form.ParentForm; f != nil { 466 parentForm = &core.WalletForm{ 467 AssetID: f.AssetID, 468 Config: f.Config, 469 Type: f.WalletType, 470 } 471 } 472 // Wallet does not exist yet. Try to create it. 473 err = s.core.CreateWallet(pass, form.Pass, &core.WalletForm{ 474 AssetID: form.AssetID, 475 Type: form.WalletType, 476 Config: form.Config, 477 ParentForm: parentForm, 478 }) 479 if err != nil { 480 s.writeAPIError(w, fmt.Errorf("error creating %s wallet: %w", unbip(form.AssetID), err)) 481 return 482 } 483 484 writeJSON(w, simpleAck()) 485 } 486 487 // apiRecoverWallet is the handler for the '/recoverwallet' API request. Commands 488 // a recovery of the specified wallet. 489 func (s *WebServer) apiRecoverWallet(w http.ResponseWriter, r *http.Request) { 490 var form struct { 491 AppPW encode.PassBytes `json:"appPW"` 492 AssetID uint32 `json:"assetID"` 493 Force bool `json:"force"` 494 } 495 if !readPost(w, r, &form) { 496 return 497 } 498 appPW, err := s.resolvePass(form.AppPW, r) 499 if err != nil { 500 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 501 return 502 } 503 status := s.core.WalletState(form.AssetID) 504 if status == nil { 505 s.writeAPIError(w, fmt.Errorf("no wallet for %d -> %s", form.AssetID, unbip(form.AssetID))) 506 return 507 } 508 err = s.core.RecoverWallet(form.AssetID, appPW, form.Force) 509 if err != nil { 510 // NOTE: client may check for code activeOrdersErr to prompt for 511 // override the active orders safety check. 512 s.writeAPIError(w, fmt.Errorf("error recovering %s wallet: %w", unbip(form.AssetID), err)) 513 return 514 } 515 516 writeJSON(w, simpleAck()) 517 } 518 519 // apiRescanWallet is the handler for the '/rescanwallet' API request. Commands 520 // a rescan of the specified wallet. 521 func (s *WebServer) apiRescanWallet(w http.ResponseWriter, r *http.Request) { 522 var form struct { 523 AssetID uint32 `json:"assetID"` 524 Force bool `json:"force"` 525 } 526 if !readPost(w, r, &form) { 527 return 528 } 529 status := s.core.WalletState(form.AssetID) 530 if status == nil { 531 s.writeAPIError(w, fmt.Errorf("No wallet for %d -> %s", form.AssetID, unbip(form.AssetID))) 532 return 533 } 534 err := s.core.RescanWallet(form.AssetID, form.Force) 535 if err != nil { 536 s.writeAPIError(w, fmt.Errorf("error rescanning %s wallet: %w", unbip(form.AssetID), err)) 537 return 538 } 539 540 writeJSON(w, simpleAck()) 541 } 542 543 // apiOpenWallet is the handler for the '/openwallet' API request. Unlocks the 544 // specified wallet. 545 func (s *WebServer) apiOpenWallet(w http.ResponseWriter, r *http.Request) { 546 form := new(openWalletForm) 547 defer form.Pass.Clear() 548 if !readPost(w, r, form) { 549 return 550 } 551 status := s.core.WalletState(form.AssetID) 552 if status == nil { 553 s.writeAPIError(w, fmt.Errorf("No wallet for %d -> %s", form.AssetID, unbip(form.AssetID))) 554 return 555 } 556 pass, err := s.resolvePass(form.Pass, r) 557 if err != nil { 558 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 559 return 560 } 561 defer zero(pass) 562 err = s.core.OpenWallet(form.AssetID, pass) 563 if err != nil { 564 s.writeAPIError(w, fmt.Errorf("error unlocking %s wallet: %w", unbip(form.AssetID), err)) 565 return 566 } 567 568 writeJSON(w, simpleAck()) 569 } 570 571 // apiNewDepositAddress gets a new deposit address from a wallet. 572 func (s *WebServer) apiNewDepositAddress(w http.ResponseWriter, r *http.Request) { 573 form := &struct { 574 AssetID *uint32 `json:"assetID"` 575 }{} 576 if !readPost(w, r, form) { 577 return 578 } 579 if form.AssetID == nil { 580 s.writeAPIError(w, errors.New("missing asset ID")) 581 return 582 } 583 assetID := *form.AssetID 584 585 addr, err := s.core.NewDepositAddress(assetID) 586 if err != nil { 587 s.writeAPIError(w, fmt.Errorf("error connecting to %s wallet: %w", unbip(assetID), err)) 588 return 589 } 590 591 writeJSON(w, &struct { 592 OK bool `json:"ok"` 593 Address string `json:"address"` 594 }{ 595 OK: true, 596 Address: addr, 597 }) 598 } 599 600 // apiAddressUsed checks whether an address has been used. 601 func (s *WebServer) apiAddressUsed(w http.ResponseWriter, r *http.Request) { 602 form := &struct { 603 AssetID *uint32 `json:"assetID"` 604 Addr string `json:"addr"` 605 }{} 606 if !readPost(w, r, form) { 607 return 608 } 609 if form.AssetID == nil { 610 s.writeAPIError(w, errors.New("missing asset ID")) 611 return 612 } 613 assetID := *form.AssetID 614 615 used, err := s.core.AddressUsed(assetID, form.Addr) 616 if err != nil { 617 s.writeAPIError(w, err) 618 return 619 } 620 621 writeJSON(w, &struct { 622 OK bool `json:"ok"` 623 Used bool `json:"used"` 624 }{ 625 OK: true, 626 Used: used, 627 }) 628 } 629 630 // apiConnectWallet is the handler for the '/connectwallet' API request. 631 // Connects to a specified wallet, but does not unlock it. 632 func (s *WebServer) apiConnectWallet(w http.ResponseWriter, r *http.Request) { 633 form := &struct { 634 AssetID uint32 `json:"assetID"` 635 }{} 636 if !readPost(w, r, form) { 637 return 638 } 639 err := s.core.ConnectWallet(form.AssetID) 640 if err != nil { 641 s.writeAPIError(w, fmt.Errorf("error connecting to %s wallet: %w", unbip(form.AssetID), err)) 642 return 643 } 644 645 writeJSON(w, simpleAck()) 646 } 647 648 // apiTrade is the handler for the '/trade' API request. 649 func (s *WebServer) apiTrade(w http.ResponseWriter, r *http.Request) { 650 form := new(tradeForm) 651 defer form.Pass.Clear() 652 if !readPost(w, r, form) { 653 return 654 } 655 r.Close = true 656 pass, err := s.resolvePass(form.Pass, r) 657 if err != nil { 658 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 659 return 660 } 661 defer zero(pass) 662 if form.Order == nil { 663 s.writeAPIError(w, errors.New("order missing")) 664 return 665 } 666 ord, err := s.core.Trade(pass, form.Order) 667 if err != nil { 668 s.writeAPIError(w, fmt.Errorf("error placing order: %w", err)) 669 return 670 } 671 resp := &struct { 672 OK bool `json:"ok"` 673 Order *core.Order `json:"order"` 674 }{ 675 OK: true, 676 Order: ord, 677 } 678 w.Header().Set("Connection", "close") 679 writeJSON(w, resp) 680 } 681 682 // apiTradeAsync is the handler for the '/tradeasync' API request. 683 func (s *WebServer) apiTradeAsync(w http.ResponseWriter, r *http.Request) { 684 form := new(tradeForm) 685 defer form.Pass.Clear() 686 if !readPost(w, r, form) { 687 return 688 } 689 r.Close = true 690 pass, err := s.resolvePass(form.Pass, r) 691 if err != nil { 692 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 693 return 694 } 695 defer zero(pass) 696 ord, err := s.core.TradeAsync(pass, form.Order) 697 if err != nil { 698 s.writeAPIError(w, fmt.Errorf("error placing order: %w", err)) 699 return 700 } 701 resp := &struct { 702 OK bool `json:"ok"` 703 Order *core.InFlightOrder `json:"order"` 704 }{ 705 OK: true, 706 Order: ord, 707 } 708 w.Header().Set("Connection", "close") 709 writeJSON(w, resp) 710 } 711 712 // apiAccountExport is the handler for the '/exportaccount' API request. 713 func (s *WebServer) apiAccountExport(w http.ResponseWriter, r *http.Request) { 714 form := new(accountExportForm) 715 defer form.Pass.Clear() 716 if !readPost(w, r, form) { 717 return 718 } 719 r.Close = true 720 pass, err := s.resolvePass(form.Pass, r) 721 if err != nil { 722 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 723 return 724 } 725 defer zero(pass) 726 account, bonds, err := s.core.AccountExport(pass, form.Host) 727 if err != nil { 728 s.writeAPIError(w, fmt.Errorf("error exporting account: %w", err)) 729 return 730 } 731 if bonds == nil { 732 bonds = make([]*db.Bond, 0) // marshal to [], not null 733 } 734 w.Header().Set("Connection", "close") 735 res := &struct { 736 OK bool `json:"ok"` 737 Account *core.Account `json:"account"` 738 Bonds []*db.Bond `json:"bonds"` 739 }{ 740 OK: true, 741 Account: account, 742 Bonds: bonds, 743 } 744 writeJSON(w, res) 745 } 746 747 // apiExportSeed is the handler for the '/exportseed' API request. 748 func (s *WebServer) apiExportSeed(w http.ResponseWriter, r *http.Request) { 749 form := &struct { 750 Pass encode.PassBytes `json:"pass"` 751 }{} 752 defer form.Pass.Clear() 753 if !readPost(w, r, form) { 754 return 755 } 756 r.Close = true 757 seed, err := s.core.ExportSeed(form.Pass) 758 if err != nil { 759 s.writeAPIError(w, fmt.Errorf("error exporting seed: %w", err)) 760 return 761 } 762 writeJSON(w, &struct { 763 OK bool `json:"ok"` 764 Seed string `json:"seed"` 765 }{ 766 OK: true, 767 Seed: seed, 768 }) 769 } 770 771 // apiAccountImport is the handler for the '/importaccount' API request. 772 func (s *WebServer) apiAccountImport(w http.ResponseWriter, r *http.Request) { 773 form := new(accountImportForm) 774 defer form.Pass.Clear() 775 if !readPost(w, r, form) { 776 return 777 } 778 r.Close = true 779 pass, err := s.resolvePass(form.Pass, r) 780 if err != nil { 781 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 782 return 783 } 784 defer zero(pass) 785 if form.Account == nil { 786 s.writeAPIError(w, errors.New("account missing")) 787 return 788 } 789 err = s.core.AccountImport(pass, form.Account, form.Bonds) 790 if err != nil { 791 s.writeAPIError(w, fmt.Errorf("error importing account: %w", err)) 792 return 793 } 794 w.Header().Set("Connection", "close") 795 writeJSON(w, simpleAck()) 796 } 797 798 func (s *WebServer) apiUpdateCert(w http.ResponseWriter, r *http.Request) { 799 form := &struct { 800 Host string `json:"host"` 801 Cert string `json:"cert"` 802 }{} 803 if !readPost(w, r, form) { 804 return 805 } 806 807 err := s.core.UpdateCert(form.Host, []byte(form.Cert)) 808 if err != nil { 809 s.writeAPIError(w, fmt.Errorf("error updating cert: %w", err)) 810 return 811 } 812 813 writeJSON(w, simpleAck()) 814 } 815 816 func (s *WebServer) apiUpdateDEXHost(w http.ResponseWriter, r *http.Request) { 817 form := &struct { 818 Pass encode.PassBytes `json:"pw"` 819 OldHost string `json:"oldHost"` 820 NewHost string `json:"newHost"` 821 Cert string `json:"cert"` 822 }{} 823 defer form.Pass.Clear() 824 if !readPost(w, r, form) { 825 return 826 } 827 pass, err := s.resolvePass(form.Pass, r) 828 if err != nil { 829 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 830 return 831 } 832 defer zero(pass) 833 cert := []byte(form.Cert) 834 exchange, err := s.core.UpdateDEXHost(form.OldHost, form.NewHost, pass, cert) 835 if err != nil { 836 s.writeAPIError(w, fmt.Errorf("error updating host: %w", err)) 837 return 838 } 839 840 resp := struct { 841 OK bool `json:"ok"` 842 Exchange *core.Exchange `json:"xc,omitempty"` 843 }{ 844 OK: true, 845 Exchange: exchange, 846 } 847 848 writeJSON(w, resp) 849 } 850 851 // apiRestoreWalletInfo is the handler for the '/restorewalletinfo' API 852 // request. 853 func (s *WebServer) apiRestoreWalletInfo(w http.ResponseWriter, r *http.Request) { 854 form := &struct { 855 AssetID uint32 856 Pass encode.PassBytes 857 }{} 858 defer form.Pass.Clear() 859 if !readPost(w, r, form) { 860 return 861 } 862 863 info, err := s.core.WalletRestorationInfo(form.Pass, form.AssetID) 864 if err != nil { 865 s.writeAPIError(w, fmt.Errorf("error updating cert: %w", err)) 866 return 867 } 868 869 resp := struct { 870 OK bool `json:"ok"` 871 RestorationInfo []*asset.WalletRestoration `json:"restorationinfo,omitempty"` 872 }{ 873 OK: true, 874 RestorationInfo: info, 875 } 876 writeJSON(w, resp) 877 } 878 879 // apiToggleAccountStatus is the handler for the '/toggleaccountstatus' API request. 880 func (s *WebServer) apiToggleAccountStatus(w http.ResponseWriter, r *http.Request) { 881 form := new(updateAccountStatusForm) 882 defer form.Pass.Clear() 883 if !readPost(w, r, form) { 884 return 885 } 886 defer form.Pass.Clear() 887 appPW, err := s.resolvePass(form.Pass, r) 888 if err != nil { 889 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 890 return 891 } 892 // Disable account. 893 err = s.core.ToggleAccountStatus(appPW, form.Host, form.Disable) 894 if err != nil { 895 s.writeAPIError(w, fmt.Errorf("error updating account status: %w", err)) 896 return 897 } 898 if form.Disable { 899 w.Header().Set("Connection", "close") 900 } 901 writeJSON(w, simpleAck()) 902 } 903 904 // apiCancel is the handler for the '/cancel' API request. 905 func (s *WebServer) apiCancel(w http.ResponseWriter, r *http.Request) { 906 form := new(cancelForm) 907 if !readPost(w, r, form) { 908 return 909 } 910 err := s.core.Cancel(form.OrderID) 911 if err != nil { 912 s.writeAPIError(w, fmt.Errorf("error cancelling order %s: %w", form.OrderID, err)) 913 return 914 } 915 writeJSON(w, simpleAck()) 916 } 917 918 // apiCloseWallet is the handler for the '/closewallet' API request. 919 func (s *WebServer) apiCloseWallet(w http.ResponseWriter, r *http.Request) { 920 form := &struct { 921 AssetID uint32 `json:"assetID"` 922 }{} 923 if !readPost(w, r, form) { 924 return 925 } 926 err := s.core.CloseWallet(form.AssetID) 927 if err != nil { 928 s.writeAPIError(w, fmt.Errorf("error locking %s wallet: %w", unbip(form.AssetID), err)) 929 return 930 } 931 932 writeJSON(w, simpleAck()) 933 } 934 935 // apiInit is the handler for the '/init' API request. 936 func (s *WebServer) apiInit(w http.ResponseWriter, r *http.Request) { 937 var init struct { 938 Pass encode.PassBytes `json:"pass"` 939 Seed string `json:"seed,omitempty"` 940 } 941 defer init.Pass.Clear() 942 if !readPost(w, r, &init) { 943 return 944 } 945 var seed *string 946 if len(init.Seed) > 0 { 947 seed = &init.Seed 948 } 949 mnemonicSeed, err := s.core.InitializeClient(init.Pass, seed) 950 if err != nil { 951 s.writeAPIError(w, fmt.Errorf("initialization error: %w", err)) 952 return 953 } 954 err = s.actuallyLogin(w, r, &loginForm{Pass: init.Pass}) 955 if err != nil { 956 s.writeAPIError(w, err) 957 return 958 } 959 960 writeJSON(w, struct { 961 OK bool `json:"ok"` 962 Hosts []string `json:"hosts"` 963 MnemonicSeed string `json:"mnemonic"` 964 }{ 965 OK: true, 966 Hosts: s.knownUnregisteredExchanges(map[string]*core.Exchange{}), 967 MnemonicSeed: mnemonicSeed, 968 }) 969 } 970 971 // apiIsInitialized is the handler for the '/isinitialized' request. 972 func (s *WebServer) apiIsInitialized(w http.ResponseWriter, r *http.Request) { 973 writeJSON(w, &struct { 974 OK bool `json:"ok"` 975 Initialized bool `json:"initialized"` 976 }{ 977 OK: true, 978 Initialized: s.core.IsInitialized(), 979 }) 980 } 981 982 func (s *WebServer) apiLocale(w http.ResponseWriter, r *http.Request) { 983 var lang string 984 if !readPost(w, r, &lang) { 985 return 986 } 987 m, found := localesMap[lang] 988 if !found { 989 s.writeAPIError(w, fmt.Errorf("no locale for language %q", lang)) 990 return 991 } 992 resp := make(map[string]string) 993 for translationID, defaultTranslation := range enUS { 994 t, found := m[translationID] 995 if !found { 996 t = defaultTranslation 997 } 998 resp[translationID] = t.T 999 } 1000 1001 writeJSON(w, resp) 1002 } 1003 1004 func (s *WebServer) apiSetLocale(w http.ResponseWriter, r *http.Request) { 1005 var lang string 1006 if !readPost(w, r, &lang) { 1007 return 1008 } 1009 if err := s.core.SetLanguage(lang); err != nil { 1010 s.writeAPIError(w, err) 1011 return 1012 } 1013 1014 // Get actual language after SetLanguage (in case of fallback) 1015 actualLang := s.core.Language() 1016 s.lang.Store(actualLang) 1017 if err := s.buildTemplates(actualLang); err != nil { 1018 s.writeAPIError(w, err) 1019 return 1020 } 1021 1022 writeJSON(w, simpleAck()) 1023 } 1024 1025 // apiBuildInfo is the handler for the '/buildinfo' API request. 1026 func (s *WebServer) apiBuildInfo(w http.ResponseWriter, r *http.Request) { 1027 resp := buildInfoResponse{ 1028 OK: true, 1029 Version: s.appVersion, 1030 Revision: commitHash, 1031 } 1032 writeJSON(w, resp) 1033 } 1034 1035 // apiLogin handles the 'login' API request. 1036 func (s *WebServer) apiLogin(w http.ResponseWriter, r *http.Request) { 1037 login := new(loginForm) 1038 defer login.Pass.Clear() 1039 if !readPost(w, r, login) { 1040 return 1041 } 1042 1043 err := s.actuallyLogin(w, r, login) 1044 if err != nil { 1045 s.writeAPIError(w, err) 1046 return 1047 } 1048 1049 notes, pokes, err := s.core.Notifications(100) 1050 if err != nil { 1051 log.Errorf("failed to get notifications: %v", err) 1052 } 1053 1054 writeJSON(w, &struct { 1055 OK bool `json:"ok"` 1056 Notes []*db.Notification `json:"notes"` 1057 Pokes []*db.Notification `json:"pokes"` 1058 }{ 1059 OK: true, 1060 Notes: notes, 1061 Pokes: pokes, 1062 }) 1063 } 1064 1065 func (s *WebServer) apiNotes(w http.ResponseWriter, r *http.Request) { 1066 notes, pokes, err := s.core.Notifications(100) 1067 if err != nil { 1068 s.writeAPIError(w, fmt.Errorf("failed to get notifications: %w", err)) 1069 return 1070 } 1071 1072 writeJSON(w, &struct { 1073 OK bool `json:"ok"` 1074 Notes []*db.Notification `json:"notes"` 1075 Pokes []*db.Notification `json:"pokes"` 1076 }{ 1077 OK: true, 1078 Notes: notes, 1079 Pokes: pokes, 1080 }) 1081 } 1082 1083 // apiLogout handles the 'logout' API request. 1084 func (s *WebServer) apiLogout(w http.ResponseWriter, r *http.Request) { 1085 err := s.core.Logout() 1086 if err != nil { 1087 s.writeAPIError(w, fmt.Errorf("logout error: %w", err)) 1088 return 1089 } 1090 1091 // With Core locked up, invalidate all known auth tokens and cached passwords 1092 // to force any other sessions to login again. 1093 s.deauth() 1094 1095 clearCookie(authCK, w) 1096 clearCookie(pwKeyCK, w) 1097 1098 response := struct { 1099 OK bool `json:"ok"` 1100 }{ 1101 OK: true, 1102 } 1103 writeJSON(w, response) 1104 } 1105 1106 // apiGetBalance handles the 'balance' API request. 1107 func (s *WebServer) apiGetBalance(w http.ResponseWriter, r *http.Request) { 1108 form := &struct { 1109 AssetID uint32 `json:"assetID"` 1110 }{} 1111 if !readPost(w, r, form) { 1112 return 1113 } 1114 bal, err := s.core.AssetBalance(form.AssetID) 1115 if err != nil { 1116 s.writeAPIError(w, fmt.Errorf("balance error: %w", err)) 1117 return 1118 } 1119 resp := &struct { 1120 OK bool `json:"ok"` 1121 Balance *core.WalletBalance `json:"balance"` 1122 }{ 1123 OK: true, 1124 Balance: bal, 1125 } 1126 writeJSON(w, resp) 1127 1128 } 1129 1130 // apiParseConfig parses an INI config file into a map[string]string. 1131 func (s *WebServer) apiParseConfig(w http.ResponseWriter, r *http.Request) { 1132 form := &struct { 1133 ConfigText string `json:"configtext"` 1134 }{} 1135 if !readPost(w, r, form) { 1136 return 1137 } 1138 configMap, err := config.Parse([]byte(form.ConfigText)) 1139 if err != nil { 1140 s.writeAPIError(w, fmt.Errorf("parse error: %w", err)) 1141 return 1142 } 1143 resp := &struct { 1144 OK bool `json:"ok"` 1145 Map map[string]string `json:"map"` 1146 }{ 1147 OK: true, 1148 Map: configMap, 1149 } 1150 writeJSON(w, resp) 1151 } 1152 1153 // apiWalletSettings fetches the currently stored wallet configuration settings. 1154 func (s *WebServer) apiWalletSettings(w http.ResponseWriter, r *http.Request) { 1155 form := &struct { 1156 AssetID uint32 `json:"assetID"` 1157 }{} 1158 if !readPost(w, r, form) { 1159 return 1160 } 1161 settings, err := s.core.WalletSettings(form.AssetID) 1162 if err != nil { 1163 s.writeAPIError(w, fmt.Errorf("error setting wallet settings: %w", err)) 1164 return 1165 } 1166 writeJSON(w, &struct { 1167 OK bool `json:"ok"` 1168 Map map[string]string `json:"map"` 1169 }{ 1170 OK: true, 1171 Map: settings, 1172 }) 1173 } 1174 1175 // apiToggleWalletStatus updates the wallet's status. 1176 func (s *WebServer) apiToggleWalletStatus(w http.ResponseWriter, r *http.Request) { 1177 form := new(walletStatusForm) 1178 if !readPost(w, r, form) { 1179 return 1180 } 1181 err := s.core.ToggleWalletStatus(form.AssetID, form.Disable) 1182 if err != nil { 1183 s.writeAPIError(w, fmt.Errorf("error setting wallet settings: %w", err)) 1184 return 1185 } 1186 response := struct { 1187 OK bool `json:"ok"` 1188 }{ 1189 OK: true, 1190 } 1191 writeJSON(w, response) 1192 } 1193 1194 // apiDefaultWalletCfg attempts to load configuration settings from the 1195 // asset's default path on the server. 1196 func (s *WebServer) apiDefaultWalletCfg(w http.ResponseWriter, r *http.Request) { 1197 form := &struct { 1198 AssetID uint32 `json:"assetID"` 1199 Type string `json:"type"` 1200 }{} 1201 if !readPost(w, r, form) { 1202 return 1203 } 1204 cfg, err := s.core.AutoWalletConfig(form.AssetID, form.Type) 1205 if err != nil { 1206 s.writeAPIError(w, fmt.Errorf("error getting wallet config: %w", err)) 1207 return 1208 } 1209 writeJSON(w, struct { 1210 OK bool `json:"ok"` 1211 Config map[string]string `json:"config"` 1212 }{ 1213 OK: true, 1214 Config: cfg, 1215 }) 1216 } 1217 1218 // apiOrders responds with a filtered list of user orders. 1219 func (s *WebServer) apiOrders(w http.ResponseWriter, r *http.Request) { 1220 filter := new(core.OrderFilter) 1221 if !readPost(w, r, filter) { 1222 return 1223 } 1224 1225 ords, err := s.core.Orders(filter) 1226 if err != nil { 1227 s.writeAPIError(w, fmt.Errorf("Orders error: %w", err)) 1228 return 1229 } 1230 writeJSON(w, &struct { 1231 OK bool `json:"ok"` 1232 Orders []*core.Order `json:"orders"` 1233 }{ 1234 OK: true, 1235 Orders: ords, 1236 }) 1237 } 1238 1239 // apiAccelerateOrder speeds up the mining of transactions in an order. 1240 func (s *WebServer) apiAccelerateOrder(w http.ResponseWriter, r *http.Request) { 1241 form := struct { 1242 Pass encode.PassBytes `json:"pw"` 1243 OrderID dex.Bytes `json:"orderID"` 1244 NewRate uint64 `json:"newRate"` 1245 }{} 1246 defer form.Pass.Clear() 1247 if !readPost(w, r, &form) { 1248 return 1249 } 1250 pass, err := s.resolvePass(form.Pass, r) 1251 if err != nil { 1252 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 1253 return 1254 } 1255 1256 txID, err := s.core.AccelerateOrder(pass, form.OrderID, form.NewRate) 1257 if err != nil { 1258 s.writeAPIError(w, fmt.Errorf("Accelerate Order error: %w", err)) 1259 return 1260 } 1261 1262 writeJSON(w, &struct { 1263 OK bool `json:"ok"` 1264 TxID string `json:"txID"` 1265 }{ 1266 OK: true, 1267 TxID: txID, 1268 }) 1269 } 1270 1271 // apiPreAccelerate responds with information about accelerating the mining of 1272 // swaps in an order 1273 func (s *WebServer) apiPreAccelerate(w http.ResponseWriter, r *http.Request) { 1274 var oid dex.Bytes 1275 if !readPost(w, r, &oid) { 1276 return 1277 } 1278 1279 preAccelerate, err := s.core.PreAccelerateOrder(oid) 1280 if err != nil { 1281 s.writeAPIError(w, fmt.Errorf("Pre accelerate error: %w", err)) 1282 return 1283 } 1284 1285 writeJSON(w, &struct { 1286 OK bool `json:"ok"` 1287 PreAccelerate *core.PreAccelerate `json:"preAccelerate"` 1288 }{ 1289 OK: true, 1290 PreAccelerate: preAccelerate, 1291 }) 1292 } 1293 1294 // apiAccelerationEstimate responds with how much it would cost to accelerate 1295 // an order to the requested fee rate. 1296 func (s *WebServer) apiAccelerationEstimate(w http.ResponseWriter, r *http.Request) { 1297 form := struct { 1298 OrderID dex.Bytes `json:"orderID"` 1299 NewRate uint64 `json:"newRate"` 1300 }{} 1301 1302 if !readPost(w, r, &form) { 1303 return 1304 } 1305 1306 fee, err := s.core.AccelerationEstimate(form.OrderID, form.NewRate) 1307 if err != nil { 1308 s.writeAPIError(w, fmt.Errorf("Accelerate Order error: %w", err)) 1309 return 1310 } 1311 1312 writeJSON(w, &struct { 1313 OK bool `json:"ok"` 1314 Fee uint64 `json:"fee"` 1315 }{ 1316 OK: true, 1317 Fee: fee, 1318 }) 1319 } 1320 1321 // apiOrder responds with data for an order. 1322 func (s *WebServer) apiOrder(w http.ResponseWriter, r *http.Request) { 1323 var oid dex.Bytes 1324 if !readPost(w, r, &oid) { 1325 return 1326 } 1327 1328 ord, err := s.core.Order(oid) 1329 if err != nil { 1330 s.writeAPIError(w, fmt.Errorf("Order error: %w", err)) 1331 return 1332 } 1333 writeJSON(w, &struct { 1334 OK bool `json:"ok"` 1335 Order *core.Order `json:"order"` 1336 }{ 1337 OK: true, 1338 Order: ord, 1339 }) 1340 } 1341 1342 // apiChangeAppPass updates the application password. 1343 func (s *WebServer) apiChangeAppPass(w http.ResponseWriter, r *http.Request) { 1344 form := &struct { 1345 AppPW encode.PassBytes `json:"appPW"` 1346 NewAppPW encode.PassBytes `json:"newAppPW"` 1347 }{} 1348 defer form.AppPW.Clear() 1349 defer form.NewAppPW.Clear() 1350 if !readPost(w, r, form) { 1351 return 1352 } 1353 1354 // Update application password. 1355 err := s.core.ChangeAppPass(form.AppPW, form.NewAppPW) 1356 if err != nil { 1357 s.writeAPIError(w, fmt.Errorf("change app pass error: %w", err)) 1358 return 1359 } 1360 1361 passwordIsCached := s.isPasswordCached(r) 1362 // Since the user changed the password, we clear all of the auth tokens 1363 // and cached passwords. However, we assign a new auth token and cache 1364 // the new password (if it was previously cached) for this session. 1365 s.deauth() 1366 authToken := s.authorize() 1367 setCookie(authCK, authToken, w) 1368 if passwordIsCached { 1369 key, err := s.cacheAppPassword(form.NewAppPW, authToken) 1370 if err != nil { 1371 log.Errorf("unable to cache password: %w", err) 1372 clearCookie(pwKeyCK, w) 1373 } else { 1374 setCookie(pwKeyCK, hex.EncodeToString(key), w) 1375 zero(key) 1376 } 1377 } 1378 1379 writeJSON(w, simpleAck()) 1380 } 1381 1382 // apiResetAppPassword resets the application password. 1383 func (s *WebServer) apiResetAppPassword(w http.ResponseWriter, r *http.Request) { 1384 form := new(struct { 1385 NewPass encode.PassBytes `json:"newPass"` 1386 Seed string `json:"seed"` 1387 }) 1388 defer form.NewPass.Clear() 1389 if !readPost(w, r, form) { 1390 return 1391 } 1392 1393 err := s.core.ResetAppPass(form.NewPass, form.Seed) 1394 if err != nil { 1395 s.writeAPIError(w, err) 1396 return 1397 } 1398 1399 writeJSON(w, simpleAck()) 1400 } 1401 1402 // apiReconfig sets new configuration details for the wallet. 1403 func (s *WebServer) apiReconfig(w http.ResponseWriter, r *http.Request) { 1404 form := &struct { 1405 AssetID uint32 `json:"assetID"` 1406 WalletType string `json:"walletType"` 1407 Config map[string]string `json:"config"` 1408 // newWalletPW json field should be omitted in case caller isn't interested 1409 // in setting new password, passing null JSON value will cause an unmarshal 1410 // error. 1411 NewWalletPW encode.PassBytes `json:"newWalletPW"` 1412 AppPW encode.PassBytes `json:"appPW"` 1413 }{} 1414 defer form.NewWalletPW.Clear() 1415 defer form.AppPW.Clear() 1416 if !readPost(w, r, form) { 1417 return 1418 } 1419 pass, err := s.resolvePass(form.AppPW, r) 1420 if err != nil { 1421 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 1422 return 1423 } 1424 defer zero(pass) 1425 // Update wallet settings. 1426 err = s.core.ReconfigureWallet(pass, form.NewWalletPW, &core.WalletForm{ 1427 AssetID: form.AssetID, 1428 Config: form.Config, 1429 Type: form.WalletType, 1430 }) 1431 if err != nil { 1432 s.writeAPIError(w, fmt.Errorf("reconfig error: %w", err)) 1433 return 1434 } 1435 1436 writeJSON(w, simpleAck()) 1437 } 1438 1439 // apiSend handles the 'send' API request. 1440 func (s *WebServer) apiSend(w http.ResponseWriter, r *http.Request) { 1441 form := new(sendForm) 1442 defer form.Pass.Clear() 1443 if !readPost(w, r, form) { 1444 return 1445 } 1446 state := s.core.WalletState(form.AssetID) 1447 if state == nil { 1448 s.writeAPIError(w, fmt.Errorf("no wallet found for %s", unbip(form.AssetID))) 1449 return 1450 } 1451 if len(form.Pass) == 0 { 1452 s.writeAPIError(w, fmt.Errorf("empty password")) 1453 return 1454 } 1455 coin, err := s.core.Send(form.Pass, form.AssetID, form.Value, form.Address, form.Subtract) 1456 if err != nil { 1457 s.writeAPIError(w, fmt.Errorf("send/withdraw error: %w", err)) 1458 return 1459 } 1460 resp := struct { 1461 OK bool `json:"ok"` 1462 Coin string `json:"coin"` 1463 }{ 1464 OK: true, 1465 Coin: coin.String(), 1466 } 1467 writeJSON(w, resp) 1468 } 1469 1470 // apiMaxBuy handles the 'maxbuy' API request. 1471 func (s *WebServer) apiMaxBuy(w http.ResponseWriter, r *http.Request) { 1472 form := &struct { 1473 Host string `json:"host"` 1474 Base uint32 `json:"base"` 1475 Quote uint32 `json:"quote"` 1476 Rate uint64 `json:"rate"` 1477 }{} 1478 if !readPost(w, r, form) { 1479 return 1480 } 1481 maxBuy, err := s.core.MaxBuy(form.Host, form.Base, form.Quote, form.Rate) 1482 if err != nil { 1483 s.writeAPIError(w, fmt.Errorf("max order estimation error: %w", err)) 1484 return 1485 } 1486 resp := struct { 1487 OK bool `json:"ok"` 1488 MaxBuy *core.MaxOrderEstimate `json:"maxBuy"` 1489 }{ 1490 OK: true, 1491 MaxBuy: maxBuy, 1492 } 1493 writeJSON(w, resp) 1494 } 1495 1496 // apiMaxSell handles the 'maxsell' API request. 1497 func (s *WebServer) apiMaxSell(w http.ResponseWriter, r *http.Request) { 1498 form := &struct { 1499 Host string `json:"host"` 1500 Base uint32 `json:"base"` 1501 Quote uint32 `json:"quote"` 1502 }{} 1503 if !readPost(w, r, form) { 1504 return 1505 } 1506 maxSell, err := s.core.MaxSell(form.Host, form.Base, form.Quote) 1507 if err != nil { 1508 s.writeAPIError(w, fmt.Errorf("max order estimation error: %w", err)) 1509 return 1510 } 1511 resp := struct { 1512 OK bool `json:"ok"` 1513 MaxSell *core.MaxOrderEstimate `json:"maxSell"` 1514 }{ 1515 OK: true, 1516 MaxSell: maxSell, 1517 } 1518 writeJSON(w, resp) 1519 } 1520 1521 // apiPreOrder handles the 'preorder' API request. 1522 func (s *WebServer) apiPreOrder(w http.ResponseWriter, r *http.Request) { 1523 form := new(core.TradeForm) 1524 if !readPost(w, r, form) { 1525 return 1526 } 1527 1528 est, err := s.core.PreOrder(form) 1529 if err != nil { 1530 s.writeAPIError(w, err) 1531 return 1532 } 1533 1534 resp := struct { 1535 OK bool `json:"ok"` 1536 Estimate *core.OrderEstimate `json:"estimate"` 1537 }{ 1538 OK: true, 1539 Estimate: est, 1540 } 1541 1542 writeJSON(w, resp) 1543 } 1544 1545 // apiActuallyLogin logs the user in. login form private data is expected to be 1546 // cleared by the caller. 1547 func (s *WebServer) actuallyLogin(w http.ResponseWriter, r *http.Request, login *loginForm) error { 1548 pass, err := s.resolvePass(login.Pass, r) 1549 defer zero(pass) 1550 if err != nil { 1551 return fmt.Errorf("password error: %w", err) 1552 } 1553 err = s.core.Login(pass) 1554 if err != nil { 1555 return fmt.Errorf("login error: %w", err) 1556 } 1557 1558 if !s.isAuthed(r) { 1559 authToken := s.authorize() 1560 setCookie(authCK, authToken, w) 1561 key, err := s.cacheAppPassword(pass, authToken) 1562 if err != nil { 1563 return fmt.Errorf("login error: %w", err) 1564 1565 } 1566 setCookie(pwKeyCK, hex.EncodeToString(key), w) 1567 zero(key) 1568 } 1569 1570 return nil 1571 } 1572 1573 // apiUser handles the 'user' API request. 1574 func (s *WebServer) apiUser(w http.ResponseWriter, r *http.Request) { 1575 var u *core.User 1576 if s.isAuthed(r) { 1577 u = s.core.User() 1578 } 1579 1580 var mmStatus *mm.Status 1581 if s.mm != nil { 1582 mmStatus = s.mm.Status() 1583 } 1584 1585 response := struct { 1586 User *core.User `json:"user"` 1587 Lang string `json:"lang"` 1588 Langs []string `json:"langs"` 1589 Inited bool `json:"inited"` 1590 OK bool `json:"ok"` 1591 MMStatus *mm.Status `json:"mmStatus"` 1592 }{ 1593 User: u, 1594 Lang: s.lang.Load().(string), 1595 Langs: s.langs, 1596 Inited: s.core.IsInitialized(), 1597 OK: true, 1598 MMStatus: mmStatus, 1599 } 1600 writeJSON(w, response) 1601 } 1602 1603 // apiToggleRateSource handles the /toggleratesource API request. 1604 func (s *WebServer) apiToggleRateSource(w http.ResponseWriter, r *http.Request) { 1605 form := &struct { 1606 Disable bool `json:"disable"` 1607 Source string `json:"source"` 1608 }{} 1609 if !readPost(w, r, form) { 1610 return 1611 } 1612 err := s.core.ToggleRateSourceStatus(form.Source, form.Disable) 1613 if err != nil { 1614 s.writeAPIError(w, fmt.Errorf("error disabling/enabling rate source: %w", err)) 1615 return 1616 } 1617 writeJSON(w, simpleAck()) 1618 } 1619 1620 // apiDeleteArchiveRecords handles the '/deletearchivedrecords' API request. 1621 func (s *WebServer) apiDeleteArchivedRecords(w http.ResponseWriter, r *http.Request) { 1622 form := new(deleteRecordsForm) 1623 if !readPost(w, r, form) { 1624 return 1625 } 1626 1627 var olderThan *time.Time 1628 if form.OlderThanMs > 0 { 1629 ot := time.UnixMilli(form.OlderThanMs) 1630 olderThan = &ot 1631 } 1632 1633 archivedRecordsPath, nRecordsDeleted, err := s.core.DeleteArchivedRecordsWithBackup(olderThan, form.SaveMatchesToFile, form.SaveOrdersToFile) 1634 if err != nil { 1635 s.writeAPIError(w, fmt.Errorf("error deleting archived records: %w", err)) 1636 return 1637 } 1638 resp := &struct { 1639 Ok bool `json:"ok"` 1640 ArchivedRecordsDeleted int `json:"archivedRecordsDeleted"` 1641 ArchivedRecordsPath string `json:"archivedRecordsPath"` 1642 }{ 1643 Ok: true, 1644 ArchivedRecordsDeleted: nRecordsDeleted, 1645 ArchivedRecordsPath: archivedRecordsPath, 1646 } 1647 writeJSON(w, resp) 1648 } 1649 1650 func (s *WebServer) apiMarketReport(w http.ResponseWriter, r *http.Request) { 1651 form := &struct { 1652 BaseID uint32 `json:"baseID"` 1653 QuoteID uint32 `json:"quoteID"` 1654 Host string `json:"host"` 1655 }{} 1656 if !readPost(w, r, form) { 1657 return 1658 } 1659 report, err := s.mm.MarketReport(form.Host, form.BaseID, form.QuoteID) 1660 if err != nil { 1661 s.writeAPIError(w, fmt.Errorf("error getting market report: %w", err)) 1662 return 1663 } 1664 writeJSON(w, &struct { 1665 OK bool `json:"ok"` 1666 Report *mm.MarketReport `json:"report"` 1667 }{ 1668 OK: true, 1669 Report: report, 1670 }) 1671 } 1672 1673 func (s *WebServer) apiCEXBalance(w http.ResponseWriter, r *http.Request) { 1674 var req struct { 1675 CEXName string `json:"cexName"` 1676 AssetID uint32 `json:"assetID"` 1677 } 1678 if !readPost(w, r, &req) { 1679 return 1680 } 1681 bal, err := s.mm.CEXBalance(req.CEXName, req.AssetID) 1682 if err != nil { 1683 s.writeAPIError(w, fmt.Errorf("error getting cex balance: %w", err)) 1684 return 1685 } 1686 writeJSON(w, &struct { 1687 OK bool `json:"ok"` 1688 CEXBalance *libxc.ExchangeBalance `json:"cexBalance"` 1689 }{ 1690 OK: true, 1691 CEXBalance: bal, 1692 }) 1693 } 1694 1695 func (s *WebServer) apiArchivedRuns(w http.ResponseWriter, r *http.Request) { 1696 runs, err := s.mm.ArchivedRuns() 1697 if err != nil { 1698 s.writeAPIError(w, fmt.Errorf("error getting archived runs: %w", err)) 1699 return 1700 } 1701 1702 writeJSON(w, &struct { 1703 OK bool `json:"ok"` 1704 Runs []*mm.MarketMakingRun `json:"runs"` 1705 }{ 1706 OK: true, 1707 Runs: runs, 1708 }) 1709 } 1710 1711 func (s *WebServer) apiRunLogs(w http.ResponseWriter, r *http.Request) { 1712 var req struct { 1713 StartTime int64 `json:"startTime"` 1714 Market *mm.MarketWithHost `json:"market"` 1715 N uint64 `json:"n"` 1716 RefID *uint64 `json:"refID,omitempty"` 1717 Filters *mm.RunLogFilters `json:"filters,omitempty"` 1718 } 1719 if !readPost(w, r, &req) { 1720 return 1721 } 1722 1723 if req.Market == nil { 1724 s.writeAPIError(w, errors.New("market missing")) 1725 return 1726 } 1727 1728 logs, updatedLogs, overview, err := s.mm.RunLogs(req.StartTime, req.Market, req.N, req.RefID, req.Filters) 1729 if err != nil { 1730 s.writeAPIError(w, fmt.Errorf("error getting run logs: %w", err)) 1731 return 1732 } 1733 1734 writeJSON(w, &struct { 1735 OK bool `json:"ok"` 1736 Overview *mm.MarketMakingRunOverview `json:"overview"` 1737 Logs []*mm.MarketMakingEvent `json:"logs"` 1738 UpdatedLogs []*mm.MarketMakingEvent `json:"updatedLogs"` 1739 }{ 1740 OK: true, 1741 Overview: overview, 1742 Logs: logs, 1743 UpdatedLogs: updatedLogs, 1744 }) 1745 } 1746 1747 func (s *WebServer) apiCEXBook(w http.ResponseWriter, r *http.Request) { 1748 var req struct { 1749 Host string `json:"host"` 1750 BaseID uint32 `json:"baseID"` 1751 QuoteID uint32 `json:"quoteID"` 1752 } 1753 if !readPost(w, r, &req) { 1754 return 1755 } 1756 buys, sells, err := s.mm.CEXBook(req.Host, req.BaseID, req.QuoteID) 1757 if err != nil { 1758 s.writeAPIError(w, fmt.Errorf("error CEX Book: %w", err)) 1759 return 1760 } 1761 1762 writeJSON(w, &struct { 1763 OK bool `json:"ok"` 1764 Book *core.OrderBook `json:"book"` 1765 }{ 1766 OK: true, 1767 Book: &core.OrderBook{ 1768 Buys: buys, 1769 Sells: sells, 1770 }, 1771 }) 1772 1773 } 1774 1775 func (s *WebServer) apiStakeStatus(w http.ResponseWriter, r *http.Request) { 1776 var assetID uint32 1777 if !readPost(w, r, &assetID) { 1778 return 1779 } 1780 status, err := s.core.StakeStatus(assetID) 1781 if err != nil { 1782 s.writeAPIError(w, fmt.Errorf("error fetching stake status for asset ID %d: %w", assetID, err)) 1783 return 1784 } 1785 writeJSON(w, &struct { 1786 OK bool `json:"ok"` 1787 Status *asset.TicketStakingStatus `json:"status"` 1788 }{ 1789 OK: true, 1790 Status: status, 1791 }) 1792 } 1793 1794 func (s *WebServer) apiSetVSP(w http.ResponseWriter, r *http.Request) { 1795 var req struct { 1796 AssetID uint32 `json:"assetID"` 1797 URL string `json:"url"` 1798 } 1799 if !readPost(w, r, &req) { 1800 return 1801 } 1802 if err := s.core.SetVSP(req.AssetID, req.URL); err != nil { 1803 s.writeAPIError(w, fmt.Errorf("error settings vsp to %q for asset ID %d: %w", req.URL, req.AssetID, err)) 1804 return 1805 } 1806 writeJSON(w, simpleAck()) 1807 } 1808 1809 func (s *WebServer) apiPurchaseTickets(w http.ResponseWriter, r *http.Request) { 1810 var req struct { 1811 AssetID uint32 `json:"assetID"` 1812 N int `json:"n"` 1813 AppPW encode.PassBytes `json:"appPW"` 1814 } 1815 if !readPost(w, r, &req) { 1816 return 1817 } 1818 appPW, err := s.resolvePass(req.AppPW, r) 1819 defer zero(appPW) 1820 if err != nil { 1821 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 1822 return 1823 } 1824 if err = s.core.PurchaseTickets(req.AssetID, appPW, req.N); err != nil { 1825 s.writeAPIError(w, fmt.Errorf("error purchasing tickets for asset ID %d: %w", req.AssetID, err)) 1826 return 1827 } 1828 writeJSON(w, simpleAck()) 1829 } 1830 1831 func (s *WebServer) apiSetVotingPreferences(w http.ResponseWriter, r *http.Request) { 1832 var req struct { 1833 AssetID uint32 `json:"assetID"` 1834 Choices map[string]string `json:"choices"` 1835 TSpendPolicy map[string]string `json:"tSpendPolicy"` 1836 TreasuryPolicy map[string]string `json:"treasuryPolicy"` 1837 } 1838 if !readPost(w, r, &req) { 1839 return 1840 } 1841 if err := s.core.SetVotingPreferences(req.AssetID, req.Choices, req.TSpendPolicy, req.TreasuryPolicy); err != nil { 1842 s.writeAPIError(w, fmt.Errorf("error setting voting preferences for asset ID %d: %w", req.AssetID, err)) 1843 return 1844 } 1845 writeJSON(w, simpleAck()) 1846 } 1847 1848 func (s *WebServer) apiListVSPs(w http.ResponseWriter, r *http.Request) { 1849 var assetID uint32 1850 if !readPost(w, r, &assetID) { 1851 return 1852 } 1853 vsps, err := s.core.ListVSPs(assetID) 1854 if err != nil { 1855 s.writeAPIError(w, fmt.Errorf("error listing VSPs for asset ID %d: %w", assetID, err)) 1856 return 1857 } 1858 writeJSON(w, &struct { 1859 OK bool `json:"ok"` 1860 VSPs []*asset.VotingServiceProvider `json:"vsps"` 1861 }{ 1862 OK: true, 1863 VSPs: vsps, 1864 }) 1865 } 1866 1867 func (s *WebServer) apiTicketPage(w http.ResponseWriter, r *http.Request) { 1868 var req struct { 1869 AssetID uint32 `json:"assetID"` 1870 ScanStart int32 `json:"scanStart"` 1871 N int `json:"n"` 1872 SkipN int `json:"skipN"` 1873 } 1874 if !readPost(w, r, &req) { 1875 return 1876 } 1877 tickets, err := s.core.TicketPage(req.AssetID, req.ScanStart, req.N, req.SkipN) 1878 if err != nil { 1879 s.writeAPIError(w, fmt.Errorf("error retrieving ticket page for %d: %w", req.AssetID, err)) 1880 return 1881 } 1882 writeJSON(w, &struct { 1883 OK bool `json:"ok"` 1884 Tickets []*asset.Ticket `json:"tickets"` 1885 }{ 1886 OK: true, 1887 Tickets: tickets, 1888 }) 1889 } 1890 1891 func (s *WebServer) apiMixingStats(w http.ResponseWriter, r *http.Request) { 1892 var req struct { 1893 AssetID uint32 `json:"assetID"` 1894 } 1895 if !readPost(w, r, &req) { 1896 return 1897 } 1898 stats, err := s.core.FundsMixingStats(req.AssetID) 1899 if err != nil { 1900 s.writeAPIError(w, fmt.Errorf("error reteiving mixing stats for %d: %w", req.AssetID, err)) 1901 return 1902 } 1903 writeJSON(w, &struct { 1904 OK bool `json:"ok"` 1905 Stats *asset.FundsMixingStats `json:"stats"` 1906 }{ 1907 OK: true, 1908 Stats: stats, 1909 }) 1910 } 1911 1912 func (s *WebServer) apiConfigureMixer(w http.ResponseWriter, r *http.Request) { 1913 var req struct { 1914 AssetID uint32 `json:"assetID"` 1915 Enabled bool `json:"enabled"` 1916 } 1917 if !readPost(w, r, &req) { 1918 return 1919 } 1920 pass, err := s.resolvePass(nil, r) 1921 if err != nil { 1922 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 1923 return 1924 } 1925 defer zero(pass) 1926 if err := s.core.ConfigureFundsMixer(pass, req.AssetID, req.Enabled); err != nil { 1927 s.writeAPIError(w, fmt.Errorf("error configuring mixing for %d: %w", req.AssetID, err)) 1928 return 1929 } 1930 writeJSON(w, simpleAck()) 1931 } 1932 1933 func (s *WebServer) apiStartMarketMakingBot(w http.ResponseWriter, r *http.Request) { 1934 var form struct { 1935 Config *mm.StartConfig `json:"config"` 1936 AppPW encode.PassBytes `json:"appPW"` 1937 } 1938 defer form.AppPW.Clear() 1939 if !readPost(w, r, &form) { 1940 s.writeAPIError(w, fmt.Errorf("failed to read form")) 1941 return 1942 } 1943 appPW, err := s.resolvePass(form.AppPW, r) 1944 if err != nil { 1945 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 1946 return 1947 } 1948 defer zero(appPW) 1949 if form.Config == nil { 1950 s.writeAPIError(w, errors.New("config missing")) 1951 return 1952 } 1953 if err = s.mm.StartBot(form.Config, nil, appPW, true); err != nil { 1954 s.writeAPIError(w, fmt.Errorf("error starting market making: %v", err)) 1955 return 1956 } 1957 1958 writeJSON(w, simpleAck()) 1959 } 1960 1961 func (s *WebServer) apiStopMarketMakingBot(w http.ResponseWriter, r *http.Request) { 1962 var form struct { 1963 Market *mm.MarketWithHost `json:"market"` 1964 } 1965 if !readPost(w, r, &form) { 1966 s.writeAPIError(w, fmt.Errorf("failed to read form")) 1967 return 1968 } 1969 if form.Market == nil { 1970 s.writeAPIError(w, errors.New("market missing")) 1971 return 1972 } 1973 if err := s.mm.StopBot(form.Market); err != nil { 1974 s.writeAPIError(w, fmt.Errorf("error stopping mm bot %q: %v", form.Market, err)) 1975 return 1976 } 1977 writeJSON(w, simpleAck()) 1978 } 1979 1980 func (s *WebServer) apiUpdateCEXConfig(w http.ResponseWriter, r *http.Request) { 1981 var updatedCfg *mm.CEXConfig 1982 if !readPost(w, r, &updatedCfg) { 1983 s.writeAPIError(w, fmt.Errorf("failed to read config")) 1984 return 1985 } 1986 1987 if err := s.mm.UpdateCEXConfig(updatedCfg); err != nil { 1988 s.writeAPIError(w, err) 1989 return 1990 } 1991 1992 writeJSON(w, simpleAck()) 1993 } 1994 1995 func (s *WebServer) apiUpdateBotConfig(w http.ResponseWriter, r *http.Request) { 1996 var updatedCfg *mm.BotConfig 1997 if !readPost(w, r, &updatedCfg) { 1998 s.writeAPIError(w, fmt.Errorf("failed to read config")) 1999 return 2000 } 2001 2002 if err := s.mm.UpdateBotConfig(updatedCfg); err != nil { 2003 s.writeAPIError(w, err) 2004 return 2005 } 2006 2007 writeJSON(w, simpleAck()) 2008 } 2009 2010 func (s *WebServer) apiRemoveBotConfig(w http.ResponseWriter, r *http.Request) { 2011 var form struct { 2012 Host string `json:"host"` 2013 BaseID uint32 `json:"baseID"` 2014 QuoteID uint32 `json:"quoteID"` 2015 } 2016 if !readPost(w, r, &form) { 2017 s.writeAPIError(w, fmt.Errorf("failed to read form")) 2018 return 2019 } 2020 2021 if err := s.mm.RemoveBotConfig(form.Host, form.BaseID, form.QuoteID); err != nil { 2022 s.writeAPIError(w, err) 2023 return 2024 } 2025 2026 writeJSON(w, simpleAck()) 2027 } 2028 2029 func (s *WebServer) apiMarketMakingStatus(w http.ResponseWriter, r *http.Request) { 2030 writeJSON(w, &struct { 2031 OK bool `json:"ok"` 2032 Status *mm.Status `json:"status"` 2033 }{ 2034 OK: true, 2035 Status: s.mm.Status(), 2036 }) 2037 } 2038 2039 func (s *WebServer) apiTxHistory(w http.ResponseWriter, r *http.Request) { 2040 var form struct { 2041 AssetID uint32 `json:"assetID"` 2042 N int `json:"n"` 2043 RefID string `json:"refID"` 2044 Past bool `json:"past"` 2045 } 2046 if !readPost(w, r, &form) { 2047 return 2048 } 2049 2050 var refID *string 2051 if len(form.RefID) > 0 { 2052 refID = &form.RefID 2053 } 2054 2055 txs, err := s.core.TxHistory(form.AssetID, form.N, refID, form.Past) 2056 if err != nil { 2057 s.writeAPIError(w, fmt.Errorf("error getting transaction history: %w", err)) 2058 return 2059 } 2060 writeJSON(w, &struct { 2061 OK bool `json:"ok"` 2062 Txs []*asset.WalletTransaction `json:"txs"` 2063 }{ 2064 OK: true, 2065 Txs: txs, 2066 }) 2067 } 2068 2069 func (s *WebServer) apiTakeAction(w http.ResponseWriter, r *http.Request) { 2070 var req struct { 2071 AssetID uint32 `json:"assetID"` 2072 ActionID string `json:"actionID"` 2073 Action json.RawMessage `json:"action"` 2074 } 2075 if !readPost(w, r, &req) { 2076 return 2077 } 2078 if err := s.core.TakeAction(req.AssetID, req.ActionID, req.Action); err != nil { 2079 s.writeAPIError(w, fmt.Errorf("error taking action: %w", err)) 2080 return 2081 } 2082 writeJSON(w, simpleAck()) 2083 } 2084 2085 func (s *WebServer) redeemGameCode(w http.ResponseWriter, r *http.Request) { 2086 var form struct { 2087 Code dex.Bytes `json:"code"` 2088 Msg string `json:"msg"` 2089 AppPW encode.PassBytes `json:"appPW"` 2090 } 2091 if !readPost(w, r, &form) { 2092 return 2093 } 2094 defer form.AppPW.Clear() 2095 appPW, err := s.resolvePass(form.AppPW, r) 2096 if err != nil { 2097 s.writeAPIError(w, fmt.Errorf("password error: %w", err)) 2098 return 2099 } 2100 coinID, win, err := s.core.RedeemGeocode(appPW, form.Code, form.Msg) 2101 if err != nil { 2102 s.writeAPIError(w, fmt.Errorf("redemption error: %w", err)) 2103 return 2104 } 2105 const dcrBipID = 42 2106 coinIDString, _ := asset.DecodeCoinID(dcrBipID, coinID) 2107 writeJSON(w, &struct { 2108 OK bool `json:"ok"` 2109 CoinID dex.Bytes `json:"coinID"` 2110 CoinString string `json:"coinString"` 2111 Win uint64 `json:"win"` 2112 }{ 2113 OK: true, 2114 CoinID: coinID, 2115 CoinString: coinIDString, 2116 Win: win, 2117 }) 2118 } 2119 2120 // writeAPIError logs the formatted error and sends a standardResponse with the 2121 // error message. 2122 func (s *WebServer) writeAPIError(w http.ResponseWriter, err error) { 2123 var cErr *core.Error 2124 var code *int 2125 if errors.As(err, &cErr) { 2126 code = cErr.Code() 2127 } 2128 2129 innerErr := core.UnwrapErr(err) 2130 resp := &standardResponse{ 2131 OK: false, 2132 Msg: innerErr.Error(), 2133 Code: code, 2134 } 2135 log.Error(err.Error()) 2136 writeJSON(w, resp) 2137 } 2138 2139 // setCookie sets the value of a cookie in the http response. 2140 func setCookie(name, value string, w http.ResponseWriter) { 2141 http.SetCookie(w, &http.Cookie{ 2142 Name: name, 2143 Path: "/", 2144 Value: value, 2145 SameSite: http.SameSiteStrictMode, 2146 }) 2147 } 2148 2149 // clearCookie removes a cookie in the http response. 2150 func clearCookie(name string, w http.ResponseWriter) { 2151 http.SetCookie(w, &http.Cookie{ 2152 Name: name, 2153 Path: "/", 2154 Value: "", 2155 Expires: time.Unix(0, 0), 2156 SameSite: http.SameSiteStrictMode, 2157 }) 2158 } 2159 2160 // resolvePass returns the appPW if it has a value, but if not, it attempts 2161 // to retrieve the cached password using the information in cookies. 2162 func (s *WebServer) resolvePass(appPW []byte, r *http.Request) ([]byte, error) { 2163 if len(appPW) > 0 { 2164 return appPW, nil 2165 } 2166 cachedPass, err := s.getCachedPasswordUsingRequest(r) 2167 if err != nil { 2168 if errors.Is(err, errNoCachedPW) { 2169 return nil, fmt.Errorf("app pass cannot be empty") 2170 } 2171 return nil, fmt.Errorf("error retrieving cached pw: %w", err) 2172 } 2173 return cachedPass, nil 2174 }