github.com/decred/politeia@v1.4.0/politeiawww/cmd/politeiawww_dbutil/politeiawww_dbutil.go (about) 1 // Copyright (c) 2017-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bufio" 9 "bytes" 10 "crypto/tls" 11 "crypto/x509" 12 "encoding/hex" 13 "encoding/json" 14 "encoding/pem" 15 "errors" 16 "flag" 17 "fmt" 18 "io" 19 "net/url" 20 "os" 21 "path/filepath" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/davecgh/go-spew/spew" 27 "github.com/decred/dcrd/chaincfg/v3" 28 "github.com/decred/politeia/politeiad/api/v1/identity" 29 "github.com/decred/politeia/politeiad/backend/gitbe" 30 "github.com/decred/politeia/politeiad/backend/gitbe/decredplugin" 31 "github.com/decred/politeia/politeiawww/config" 32 "github.com/decred/politeia/politeiawww/legacy/user" 33 "github.com/decred/politeia/politeiawww/legacy/user/cockroachdb" 34 "github.com/decred/politeia/politeiawww/legacy/user/localdb" 35 mysqldb "github.com/decred/politeia/politeiawww/legacy/user/mysql" 36 "github.com/decred/politeia/util" 37 "github.com/google/uuid" 38 _ "github.com/jinzhu/gorm/dialects/postgres" 39 "github.com/marcopeereboom/sbox" 40 ) 41 42 const ( 43 defaultMySQLHost = "localhost:3306" 44 defaultCockroachDBHost = "localhost:26257" 45 // The following hardcoded CockroachDB paths are not ideal, instead they 46 // should use OS specific paths: 47 // `dcrutil.AppDataDir("cockroachdb", false)`, but since we use 48 // `~/.cockroachdb` in our script to generate the CockroachDB certs (see 49 // `/scripts/cockroachcerts.sh`) we are limited to use the same hardcoded 50 // paths here. 51 defaultRootCert = "~/.cockroachdb/certs/clients/politeiawww/ca.crt" 52 defaultClientCert = "~/.cockroachdb/certs/clients/politeiawww/client.politeiawww.crt" 53 defaultClientKey = "~/.cockroachdb/certs/clients/politeiawww/client.politeiawww.key" 54 55 // Politeia repo info 56 commentsJournalFilename = "comments.journal" 57 58 // Journal actions 59 journalActionAdd = "add" // Add entry 60 journalActionDel = "del" // Delete entry 61 ) 62 63 var ( 64 defaultHomeDir = config.DefaultHomeDir 65 defaultDataDir = config.DefaultDataDir 66 defaultEncryptionKey = filepath.Join(defaultHomeDir, "sbox.key") 67 68 // Database options 69 level = flag.Bool("leveldb", false, "") 70 cockroach = flag.Bool("cockroachdb", false, "") 71 mysql = flag.Bool("mysql", false, "") 72 73 // Application options 74 testnet = flag.Bool("testnet", false, "") 75 dataDir = flag.String("datadir", defaultDataDir, "") 76 cockroachdbhost = flag.String("cockroachdbhost", defaultCockroachDBHost, "") 77 mysqlhost = flag.String("mysqlhost", defaultMySQLHost, "") 78 79 rootCert = flag.String("rootcert", defaultRootCert, "") 80 clientCert = flag.String("clientcert", defaultClientCert, "") 81 clientKey = flag.String("clientkey", defaultClientKey, "") 82 encryptionKey = flag.String("encryptionkey", defaultEncryptionKey, "") 83 password = flag.String("password", "", "") 84 85 // Commands 86 addCredits = flag.Bool("addcredits", false, "") 87 dump = flag.Bool("dump", false, "") 88 setAdmin = flag.Bool("setadmin", false, "") 89 setEmail = flag.Bool("setemail", false, "") 90 stubUsers = flag.Bool("stubusers", false, "") 91 migrate = flag.Bool("migrate", false, "") 92 createKey = flag.Bool("createkey", false, "") 93 verifyIdentities = flag.Bool("verifyidentities", false, "") 94 resetTotp = flag.Bool("resettotp", false, "") 95 96 network string // Mainnet or testnet3 97 userDB user.Database 98 ) 99 100 const usageMsg = `politeiawww_dbutil usage: 101 Database options 102 -leveldb 103 Use LevelDB 104 -cockroachdb 105 Use CockroachDB 106 -mysql 107 Use MySQL 108 109 Application options 110 -testnet 111 Use testnet database 112 -datadir string 113 politeiawww data directory 114 (default osDataDir/politeiawww/data) 115 -cockroachdbhost string 116 CockroachDB ip:port 117 (default localhost:26257) 118 -rootcert string 119 File containing the CockroachDB SSL root cert 120 (default ~/.cockroachdb/certs/clients/politeiawww/ca.crt) 121 -clientcert string 122 File containing the CockroachDB SSL client cert 123 (default ~/.cockroachdb/certs/clients/politeiawww/client.politeiawww.crt) 124 -clientkey string 125 File containing the CockroachDB SSL client cert key 126 (default ~/.cockroachdb/certs/clients/politeiawww/client.politeiawww.key) 127 -encryptionkey string 128 File containing the CockroachDB/MySQL encryption key 129 (default osDataDir/politeiawww/sbox.key) 130 -password string 131 MySQL database password. 132 -mysqlhost string 133 MySQL ip:port 134 (default localhost:3306) 135 136 Commands 137 -addcredits 138 Add proposal credits to a user's account 139 Required DB flag : -leveldb, -cockroachdb or -mysql 140 LevelDB args : <email> <quantity> 141 CockroachDB args : <username> <quantity> 142 -setadmin 143 Set the admin flag for a user 144 Required DB flag : -leveldb, -cockroachdb or -mysql 145 LevelDB args : <email> <true/false> 146 CockroachDB args : <username> <true/false> 147 -setemail 148 Set a user's email to the provided email address 149 Required DB flag : -cockroachdb or -mysql 150 CockroachDB args : <username> <email> 151 -stubusers 152 Create user stubs for the public keys in a politeia repo 153 Required DB flag : -leveldb, -cockroachdb or -mysql 154 LevelDB args : <importDir> 155 CockroachDB args : <importDir> 156 -dump 157 Dump the entire database or the contents of a specific user 158 Required DB flag : -leveldb or -cockroachdb or -mysql 159 LevelDB args : <username> 160 -createkey 161 Create a new encryption key that can be used to encrypt data at rest 162 Required DB flag : None 163 Args : <destination (optional)> 164 (default osDataDir/politeiawww/sbox.key) 165 -migrate 166 Migrate from one user database to another 167 Required DB flag : None 168 Args : <fromDB> <toDB> 169 Valid DBs are mysql, cockroachdb, leveldb 170 -verifyidentities 171 Verify a user's identities do not violate any politeia rules. Invalid 172 identities are fixed. 173 Required DB flag : -cockroachdb or -mysql 174 -resettotp 175 Reset a user's totp settings in case they are locked out and 176 confirm identity. 177 Required DB flag : -leveldb, -cockroachdb or -mysql 178 LevelDB args : <email> 179 CockroachDB args : <username>` 180 181 func cmdDump() error { 182 args := flag.Args() 183 if len(args) == 0 { 184 return fmt.Errorf("username was not provided") 185 } 186 187 username := args[0] 188 u, err := userDB.UserGetByUsername(username) 189 if err != nil { 190 return err 191 } 192 193 fmt.Printf("%v", spew.Sdump(u)) 194 195 return nil 196 } 197 198 func cmdSetAdmin() error { 199 args := flag.Args() 200 if len(args) < 2 { 201 flag.Usage() 202 return nil 203 } 204 205 username := args[0] 206 isAdmin := (strings.ToLower(args[1]) == "true" || args[1] == "1") 207 208 u, err := userDB.UserGetByUsername(username) 209 if err != nil { 210 return err 211 } 212 213 u.Admin = isAdmin 214 215 err = userDB.UserUpdate(*u) 216 if err != nil { 217 return err 218 } 219 220 fmt.Printf("User with username '%v' admin status updated "+ 221 "to %v\n", username, isAdmin) 222 223 return nil 224 } 225 226 func cmdSetEmail() error { 227 args := flag.Args() 228 if len(args) < 2 { 229 flag.Usage() 230 return nil 231 } 232 233 if *level { 234 return fmt.Errorf("this cannot be used with the -leveldb flag") 235 } 236 237 username := strings.ToLower(args[0]) 238 newEmail := strings.ToLower(args[1]) 239 240 u, err := userDB.UserGetByUsername(username) 241 if err != nil { 242 return err 243 } 244 245 u.Email = newEmail 246 247 err = userDB.UserUpdate(*u) 248 if err != nil { 249 return err 250 } 251 252 fmt.Printf("User with username '%v' email successfully updated to '%v'\n", 253 username, newEmail) 254 fmt.Printf("politeiawww MUST BE restarted so the user email memory cache " + 255 "gets updated; politeiad is fine and does not need to be restarted\n") 256 257 return nil 258 } 259 260 func cmdAddCredits() error { 261 args := flag.Args() 262 if len(args) < 2 { 263 flag.Usage() 264 return nil 265 } 266 username := args[0] 267 268 quantity, err := strconv.Atoi(args[1]) 269 if err != nil { 270 return fmt.Errorf("parse int '%v' failed: %v", 271 args[1], err) 272 } 273 // Lookup user 274 u, err := userDB.UserGetByUsername(username) 275 if err != nil { 276 return err 277 } 278 279 // Create proposal credits 280 ts := time.Now().Unix() 281 c := make([]user.ProposalCredit, 0, quantity) 282 for i := 0; i < quantity; i++ { 283 c = append(c, user.ProposalCredit{ 284 PaywallID: 0, 285 Price: 0, 286 DatePurchased: ts, 287 TxID: "created_by_dbutil", 288 }) 289 } 290 u.UnspentProposalCredits = append(u.UnspentProposalCredits, c...) 291 292 // Update database 293 err = userDB.UserUpdate(*u) 294 if err != nil { 295 return fmt.Errorf("update user: %v", err) 296 } 297 298 fmt.Printf("%v proposal credits added to account %v\n", 299 quantity, username) 300 301 return nil 302 } 303 304 func replayCommentsJournal(path string, pubkeys map[string]struct{}) error { 305 b, err := os.ReadFile(path) 306 if err != nil { 307 return err 308 } 309 d := json.NewDecoder(bytes.NewReader(b)) 310 311 for { 312 var action gitbe.JournalAction 313 err = d.Decode(&action) 314 if errors.Is(err, io.EOF) { 315 break 316 } else if err != nil { 317 return fmt.Errorf("journal action: %v", err) 318 } 319 320 switch action.Action { 321 case journalActionAdd: 322 var c decredplugin.Comment 323 err = d.Decode(&c) 324 if err != nil { 325 return fmt.Errorf("journal add: %v", err) 326 } 327 pubkeys[c.PublicKey] = struct{}{} 328 329 case journalActionDel: 330 var cc decredplugin.CensorComment 331 err = d.Decode(&cc) 332 if err != nil { 333 return fmt.Errorf("journal censor: %v", err) 334 } 335 pubkeys[cc.PublicKey] = struct{}{} 336 337 default: 338 return fmt.Errorf("invalid action: %v", 339 action.Action) 340 } 341 } 342 343 return nil 344 } 345 346 func cmdStubUsers() error { 347 if len(flag.Args()) == 0 { 348 return fmt.Errorf("must provide import directory") 349 } 350 351 // Parse import directory 352 importDir := util.CleanAndExpandPath(flag.Arg(0)) 353 _, err := os.Stat(importDir) 354 if err != nil { 355 return err 356 } 357 358 // Walk import directory and compile all unique public 359 // keys that are found. 360 fmt.Printf("Walking import directory...\n") 361 pubkeys := make(map[string]struct{}) 362 err = filepath.Walk(importDir, 363 func(path string, info os.FileInfo, err error) error { 364 if err != nil { 365 return err 366 } 367 368 // Skip directories 369 if info.IsDir() { 370 return nil 371 } 372 373 switch info.Name() { 374 case commentsJournalFilename: 375 err := replayCommentsJournal(path, pubkeys) 376 if err != nil { 377 return err 378 } 379 } 380 381 return nil 382 }) 383 if err != nil { 384 return fmt.Errorf("walk import dir: %v", err) 385 } 386 387 fmt.Printf("Stubbing users...\n") 388 389 // update users on database 390 var i int 391 for k := range pubkeys { 392 username := fmt.Sprintf("dbutil_user%v", i) 393 email := username + "@example.com" 394 id, err := identity.PublicIdentityFromString(k) 395 if err != nil { 396 return err 397 } 398 399 err = userDB.UserNew(user.User{ 400 ID: uuid.New(), 401 Email: email, 402 Username: username, 403 HashedPassword: []byte("password"), 404 Admin: false, 405 Identities: []user.Identity{ 406 { 407 Key: id.Key, 408 Activated: time.Now().Unix(), 409 }, 410 }, 411 }) 412 if err != nil { 413 return err 414 } 415 416 i++ 417 } 418 419 fmt.Printf("Done!\n") 420 return nil 421 } 422 423 func connectLevelDB() (user.Database, error) { 424 dbDir := filepath.Join(*dataDir, network) 425 _, err := os.Stat(dbDir) 426 if err != nil { 427 if os.IsNotExist(err) { 428 err = fmt.Errorf("leveldb dir not found: %v", dbDir) 429 } 430 return nil, err 431 } 432 433 fmt.Printf("LevelDB : %v\n", dbDir) 434 return localdb.New(dbDir) 435 } 436 437 func connectCockroachDB() (user.Database, error) { 438 err := validateCockroachParams() 439 if err != nil { 440 return nil, fmt.Errorf("new cockroachdb: %v", err) 441 } 442 443 fmt.Printf("CockroachDB : %v %v", *cockroachdbhost, network) 444 445 return cockroachdb.New(*cockroachdbhost, network, *rootCert, 446 *clientCert, *clientKey, *encryptionKey) 447 } 448 449 func connectMySQL() (user.Database, error) { 450 err := validateMySQLParams() 451 if err != nil { 452 return nil, err 453 } 454 455 fmt.Printf("MySQL : %v %v\n", *mysqlhost, network) 456 457 return mysqldb.New(*mysqlhost, *password, network, *encryptionKey) 458 } 459 460 func connectDB(typeDB string) (user.Database, error) { 461 switch typeDB { 462 case "leveldb": 463 return connectLevelDB() 464 465 case "cockroachdb": 466 return connectCockroachDB() 467 468 case "mysql": 469 return connectMySQL() 470 471 default: 472 return nil, fmt.Errorf("invalid database type: %v", typeDB) 473 } 474 } 475 476 func cmdMigrate() error { 477 args := flag.Args() 478 if len(args) < 2 { 479 flag.Usage() 480 return nil 481 } 482 483 var ( 484 fromType = args[0] 485 toType = args[1] 486 ) 487 if fromType == toType { 488 return fmt.Errorf("origin and destination databases cannot be the same") 489 } 490 491 // Connect to the databases 492 fromDB, err := connectDB(fromType) 493 if err != nil { 494 return err 495 } 496 defer fromDB.Close() 497 498 toDB, err := connectDB(toType) 499 if err != nil { 500 return err 501 } 502 defer toDB.Close() 503 504 fmt.Printf("Migrating users from %v to %v...\n", fromType, toType) 505 506 // Migrate the users 507 var ( 508 paywallIndex uint64 509 userCount int 510 ) 511 err = fromDB.AllUsers(func(u *user.User) { 512 // Record the highest paywall address index found in the 513 // database. This will be saved in the new database once 514 // all of the users have been migrated. 515 if u.PaywallAddressIndex > paywallIndex { 516 paywallIndex = u.PaywallAddressIndex 517 } 518 519 // Check if username already exists. There was a ~2 520 // month period where a bug allowed for users to be 521 // created with duplicate usernames. 522 _, err = toDB.UserGetByUsername(u.Username) 523 switch err { 524 case user.ErrUserNotFound: 525 // Username doesn't exist; continue 526 527 case nil: 528 // The username already exists in the database. Allow the 529 // caller to update the username so that it's unique. 530 for !errors.Is(err, user.ErrUserNotFound) { 531 fmt.Printf("Username '%v' already exists. Username must be "+ 532 "updated for the following user before the migration can "+ 533 "continue.\n", u.Username) 534 535 fmt.Printf("ID : %v\n", u.ID.String()) 536 fmt.Printf("Email : %v\n", u.Email) 537 fmt.Printf("Username : %v\n", u.Username) 538 fmt.Printf("Input new username : ") 539 540 var input string 541 r := bufio.NewReader(os.Stdin) 542 input, err = r.ReadString('\n') 543 if err != nil { 544 panic(err) 545 } 546 username := strings.TrimSuffix(input, "\n") 547 username = strings.ToLower(strings.TrimSpace(username)) 548 549 u.Username = username 550 551 // Verify that the updated username is unique 552 _, err = toDB.UserGetByUsername(u.Username) 553 } 554 555 fmt.Printf("Username updated to '%v'\n", u.Username) 556 557 default: 558 panic(err) 559 } 560 561 err = toDB.InsertUser(*u) 562 if err != nil { 563 panic(fmt.Sprintf("InsertUser %v: %v", u.ID, err)) 564 } 565 userCount++ 566 }) 567 if err != nil { 568 return fmt.Errorf("AllUsers: %v", err) 569 } 570 if userCount == 0 { 571 fmt.Printf("No users found\n") 572 return nil 573 } 574 575 // Save the paywall address index to the new database. 576 // The index should be the same value as the number of 577 // users in the database. If it's not, update it and 578 // inform the caller. This can happen if the database 579 // has user stubs in it. 580 if int(paywallIndex) < userCount { 581 fmt.Printf("WARN: Paywall address index does not match the "+ 582 "user count; user count %v, paywall address index %v\n", 583 userCount, paywallIndex) 584 585 paywallIndex = uint64(userCount) 586 587 fmt.Printf("Updated paywall address index to %v\n", paywallIndex) 588 } 589 590 err = toDB.SetPaywallAddressIndex(paywallIndex) 591 if err != nil { 592 return fmt.Errorf("set paywall index '%v': %v", paywallIndex, err) 593 } 594 595 fmt.Printf("Users migrated : %v\n", userCount) 596 fmt.Printf("Paywall index : %v\n", paywallIndex) 597 fmt.Printf("Done!\n") 598 599 return nil 600 } 601 602 func cmdCreateKey() error { 603 path := defaultEncryptionKey 604 args := flag.Args() 605 if len(args) > 0 { 606 path = util.CleanAndExpandPath(args[0]) 607 } 608 609 // Don't allow overwriting an existing key 610 _, err := os.Stat(path) 611 if err == nil { 612 return fmt.Errorf("file already exists; cannot "+ 613 "overwrite %v", path) 614 } 615 616 // Create a new key 617 k, err := sbox.NewKey() 618 if err != nil { 619 return err 620 } 621 622 // Write hex encoded key to file 623 err = os.WriteFile(path, []byte(hex.EncodeToString(k[:])), 0644) 624 if err != nil { 625 return err 626 } 627 628 fmt.Printf("Encryption key saved to: %v\n", path) 629 630 // Zero out encryption key 631 util.Zero(k[:]) 632 k = nil 633 634 return nil 635 } 636 637 func validateCockroachParams() error { 638 // Validate host 639 _, err := url.Parse(*cockroachdbhost) 640 if err != nil { 641 return fmt.Errorf("parse host '%v': %v", 642 *cockroachdbhost, err) 643 } 644 645 // Validate root cert 646 b, err := os.ReadFile(*rootCert) 647 if err != nil { 648 return fmt.Errorf("read rootcert: %v", err) 649 } 650 651 block, _ := pem.Decode(b) 652 _, err = x509.ParseCertificate(block.Bytes) 653 if err != nil { 654 return fmt.Errorf("parse rootcert: %v", err) 655 } 656 657 // Validate client key pair 658 _, err = tls.LoadX509KeyPair(*clientCert, *clientKey) 659 if err != nil { 660 return fmt.Errorf("load key pair clientcert and "+ 661 "clientkey: %v", err) 662 } 663 664 // Ensure encryption key file exists 665 if !util.FileExists(*encryptionKey) { 666 return fmt.Errorf("file not found %v", *encryptionKey) 667 } 668 669 return nil 670 } 671 672 func validateMySQLParams() error { 673 // Validate host. 674 _, err := url.Parse(*mysqlhost) 675 if err != nil { 676 return fmt.Errorf("parse host '%v': %v", *mysqlhost, err) 677 } 678 679 // Validate password. 680 if *password == "" { 681 return fmt.Errorf("MySQL politeiawww user's password is missing;" + 682 " use -password flag to provide it") 683 } 684 685 // Ensure encryption key file exists. 686 if !util.FileExists(*encryptionKey) { 687 return fmt.Errorf("file not found %v", *encryptionKey) 688 } 689 690 return nil 691 } 692 693 func cmdVerifyIdentities() error { 694 args := flag.Args() 695 if len(args) != 1 { 696 return fmt.Errorf("invalid number of arguments; want <username>, got %v", 697 args) 698 } 699 700 u, err := userDB.UserGetByUsername(args[0]) 701 if err != nil { 702 return fmt.Errorf("UserGetByUsername(%v): %v", 703 args[0], err) 704 } 705 706 // Print all identities to help with debugging 707 fmt.Printf("\n") 708 for _, v := range u.Identities { 709 fmt.Printf("Status : %v\n", v.Status()) 710 fmt.Printf("Public Key : %v\n", v.String()) 711 fmt.Printf("Activated : %v\n", formatUnix(v.Activated)) 712 fmt.Printf("Deactivated: %v\n", formatUnix(v.Deactivated)) 713 fmt.Printf("\n") 714 } 715 716 // Verify inactive identities. There should only ever be one 717 // inactive identity at a time. If more than one inactive identity 718 // is found, deactivate all of them since it can't be determined 719 // which one is the most recent. 720 inactive := make(map[string]user.Identity, len(u.Identities)) // [pubkey]Identity 721 for _, v := range u.Identities { 722 if v.IsInactive() { 723 inactive[v.String()] = v 724 } 725 } 726 switch len(inactive) { 727 case 0: 728 fmt.Printf("0 inactive identities found; this is ok\n") 729 case 1: 730 fmt.Printf("1 inactive identity found; this is ok\n") 731 default: 732 fmt.Printf("%v inactive identities found\n", len(inactive)) 733 for _, v := range inactive { 734 fmt.Printf("%v\n", v.String()) 735 } 736 737 fmt.Printf("deactivating all inactive identities\n") 738 739 for i, v := range u.Identities { 740 if !v.IsInactive() { 741 // Not an inactive identity 742 continue 743 } 744 fmt.Printf("deactivating: %v\n", v.String()) 745 u.Identities[i].Deactivate() 746 } 747 } 748 749 // Verify active identities. There should only ever be one active 750 // identity at a time. 751 active := make(map[string]user.Identity, len(u.Identities)) // [pubkey]Identity 752 for _, v := range u.Identities { 753 if v.IsActive() { 754 active[v.String()] = v 755 } 756 } 757 switch len(active) { 758 case 0: 759 fmt.Printf("0 active identities found; this is ok\n") 760 case 1: 761 fmt.Printf("1 active identity found; this is ok\n") 762 default: 763 fmt.Printf("%v active identities found\n", len(active)) 764 for _, v := range active { 765 fmt.Printf("%v\n", v.String()) 766 } 767 768 fmt.Printf("deactivating all but the most recent active identity\n") 769 770 // Find most recent active identity 771 var pubkey string 772 var ts int64 773 for _, v := range active { 774 if v.Activated > ts { 775 pubkey = v.String() 776 ts = v.Activated 777 } 778 } 779 780 // Deactivate all but the most recent active identity 781 for i, v := range u.Identities { 782 if !v.IsActive() { 783 // Not an active identity 784 continue 785 } 786 if pubkey == v.String() { 787 // Most recent active identity 788 continue 789 } 790 fmt.Printf("deactivating: %v\n", v.String()) 791 u.Identities[i].Deactivate() 792 } 793 } 794 795 // Update user 796 err = userDB.UserUpdate(*u) 797 if err != nil { 798 return fmt.Errorf("UserUpdate: %v", err) 799 } 800 801 return nil 802 } 803 804 func cmdResetTOTP() error { 805 args := flag.Args() 806 if len(args) != 1 { 807 return fmt.Errorf("invalid number of arguments; want <username>, got %v", 808 args) 809 } 810 811 username := args[0] 812 u, err := userDB.UserGetByUsername(username) 813 if err != nil { 814 return err 815 } 816 817 u.TOTPLastUpdated = nil 818 u.TOTPSecret = "" 819 u.TOTPType = 0 820 u.TOTPVerified = false 821 822 err = userDB.UserUpdate(*u) 823 if err != nil { 824 return err 825 } 826 827 fmt.Printf("User with username '%v' reset totp\n", username) 828 829 return nil 830 } 831 832 func _main() error { 833 flag.Parse() 834 835 *dataDir = util.CleanAndExpandPath(*dataDir) 836 *rootCert = util.CleanAndExpandPath(*rootCert) 837 *clientCert = util.CleanAndExpandPath(*clientCert) 838 *clientKey = util.CleanAndExpandPath(*clientKey) 839 *encryptionKey = util.CleanAndExpandPath(*encryptionKey) 840 841 if *testnet { 842 network = chaincfg.TestNet3Params().Name 843 } else { 844 network = chaincfg.MainNetParams().Name 845 } 846 847 // Validate database selection. 848 switch { 849 case *mysql && *cockroach, *level && *mysql, *level && *cockroach, 850 *level && *cockroach && *mysql: 851 fmt.Println(mysql, cockroach) 852 return fmt.Errorf("multiple database flags; must use one of the " + 853 "following: -leveldb, -mysql or -cockroachdb") 854 } 855 856 switch { 857 case *addCredits || *setAdmin || *stubUsers || *resetTotp || *dump: 858 // These commands must be run with -cockroachdb, -mysql or -leveldb. 859 if !*level && !*cockroach && !*mysql { 860 return fmt.Errorf("missing database flag; must use " + 861 "-leveldb, -cockroachdb or -mysql") 862 } 863 case *verifyIdentities, *setEmail: 864 // These commands must be run with either -cockroachdb or -mysql. 865 if !*cockroach && !*mysql { 866 return fmt.Errorf("invalid database flag; must use " + 867 "either -mysql or -cockroachdb with this command") 868 } 869 case *migrate || *createKey: 870 // These commands must be run without a database flag. 871 if *level || *cockroach || *mysql { 872 return fmt.Errorf("unexpected database flag found; " + 873 "remove database flag -leveldb, -mysql and -cockroachdb") 874 } 875 } 876 877 // Connect to database 878 var err error 879 switch { 880 case *level: 881 userDB, err = connectLevelDB() 882 883 case *cockroach: 884 userDB, err = connectCockroachDB() 885 886 case *mysql: 887 userDB, err = connectMySQL() 888 889 } 890 if err != nil { 891 return err 892 } 893 if userDB != nil { 894 defer userDB.Close() 895 } 896 897 // Run command 898 switch { 899 case *addCredits: 900 return cmdAddCredits() 901 case *dump: 902 return cmdDump() 903 case *setAdmin: 904 return cmdSetAdmin() 905 case *setEmail: 906 return cmdSetEmail() 907 case *stubUsers: 908 return cmdStubUsers() 909 case *migrate: 910 return cmdMigrate() 911 case *createKey: 912 return cmdCreateKey() 913 case *verifyIdentities: 914 return cmdVerifyIdentities() 915 case *resetTotp: 916 return cmdResetTOTP() 917 default: 918 fmt.Printf("invalid command\n") 919 flag.Usage() 920 } 921 922 return nil 923 } 924 925 func main() { 926 // Custom usage message 927 flag.Usage = func() { 928 fmt.Fprintln(os.Stderr, usageMsg) 929 } 930 931 err := _main() 932 if err != nil { 933 fmt.Fprintf(os.Stderr, "%v\n", err) 934 os.Exit(1) 935 } 936 }