account.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package pkg
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/ente-io/cli/internal"
  7. "github.com/ente-io/cli/internal/api"
  8. "github.com/ente-io/cli/pkg/model"
  9. "github.com/ente-io/cli/utils/encoding"
  10. "log"
  11. bolt "go.etcd.io/bbolt"
  12. )
  13. const AccBucket = "accounts"
  14. func (c *ClICtrl) AddAccount(cxt context.Context) {
  15. var flowErr error
  16. defer func() {
  17. if flowErr != nil {
  18. log.Fatal(flowErr)
  19. }
  20. }()
  21. app := internal.GetAppType()
  22. cxt = context.WithValue(cxt, "app", string(app))
  23. dir := internal.GetExportDir()
  24. if dir == "" {
  25. flowErr = fmt.Errorf("export directory not set")
  26. return
  27. }
  28. email, flowErr := internal.GetUserInput("Enter email address")
  29. if flowErr != nil {
  30. return
  31. }
  32. var verifyEmail bool
  33. srpAttr, flowErr := c.Client.GetSRPAttributes(cxt, email)
  34. if flowErr != nil {
  35. // if flowErr type is ApiError and status code is 404, then set verifyEmail to true and continue
  36. // else return
  37. if apiErr, ok := flowErr.(*api.ApiError); ok && apiErr.StatusCode == 404 {
  38. verifyEmail = true
  39. } else {
  40. return
  41. }
  42. }
  43. var authResponse *api.AuthorizationResponse
  44. var keyEncKey []byte
  45. if verifyEmail || srpAttr.IsEmailMFAEnabled {
  46. authResponse, flowErr = c.validateEmail(cxt, email)
  47. } else {
  48. authResponse, keyEncKey, flowErr = c.signInViaPassword(cxt, srpAttr)
  49. }
  50. if flowErr != nil {
  51. return
  52. }
  53. if authResponse.IsMFARequired() {
  54. authResponse, flowErr = c.validateTOTP(cxt, authResponse)
  55. }
  56. if authResponse.EncryptedToken == "" || authResponse.KeyAttributes == nil {
  57. panic("no encrypted token or keyAttributes")
  58. }
  59. secretInfo, decErr := c.decryptAccSecretInfo(cxt, authResponse, keyEncKey)
  60. if decErr != nil {
  61. flowErr = decErr
  62. return
  63. }
  64. err := c.storeAccount(cxt, email, authResponse.ID, app, secretInfo, dir)
  65. if err != nil {
  66. flowErr = err
  67. return
  68. } else {
  69. fmt.Println("Account added successfully")
  70. fmt.Println("run `ente export` to initiate export of your account data")
  71. }
  72. }
  73. func (c *ClICtrl) storeAccount(_ context.Context, email string, userID int64, app api.App, secretInfo *model.AccSecretInfo, exportDir string) error {
  74. // get password
  75. err := c.DB.Update(func(tx *bolt.Tx) error {
  76. b, err := tx.CreateBucketIfNotExists([]byte(AccBucket))
  77. if err != nil {
  78. return err
  79. }
  80. accInfo := model.Account{
  81. Email: email,
  82. UserID: userID,
  83. MasterKey: *model.MakeEncString(secretInfo.MasterKey, c.KeyHolder.DeviceKey),
  84. SecretKey: *model.MakeEncString(secretInfo.SecretKey, c.KeyHolder.DeviceKey),
  85. Token: *model.MakeEncString(secretInfo.Token, c.KeyHolder.DeviceKey),
  86. App: app,
  87. PublicKey: encoding.EncodeBase64(secretInfo.PublicKey),
  88. ExportDir: exportDir,
  89. }
  90. accInfoBytes, err := json.Marshal(accInfo)
  91. if err != nil {
  92. return err
  93. }
  94. accountKey := accInfo.AccountKey()
  95. return b.Put([]byte(accountKey), accInfoBytes)
  96. })
  97. return err
  98. }
  99. func (c *ClICtrl) GetAccounts(cxt context.Context) ([]model.Account, error) {
  100. var accounts []model.Account
  101. err := c.DB.View(func(tx *bolt.Tx) error {
  102. b := tx.Bucket([]byte(AccBucket))
  103. err := b.ForEach(func(k, v []byte) error {
  104. var info model.Account
  105. err := json.Unmarshal(v, &info)
  106. if err != nil {
  107. return err
  108. }
  109. accounts = append(accounts, info)
  110. return nil
  111. })
  112. if err != nil {
  113. return err
  114. }
  115. return nil
  116. })
  117. return accounts, err
  118. }
  119. func (c *ClICtrl) ListAccounts(cxt context.Context) error {
  120. accounts, err := c.GetAccounts(cxt)
  121. if err != nil {
  122. return err
  123. }
  124. fmt.Printf("Configured accounts: %d\n", len(accounts))
  125. for _, acc := range accounts {
  126. fmt.Println("====================================")
  127. fmt.Println("Email: ", acc.Email)
  128. fmt.Println("ID: ", acc.UserID)
  129. fmt.Println("App: ", acc.App)
  130. fmt.Println("ExportDir:", acc.ExportDir)
  131. fmt.Println("====================================")
  132. }
  133. return nil
  134. }
  135. func (c *ClICtrl) UpdateAccount(ctx context.Context, params model.UpdateAccountParams) error {
  136. accounts, err := c.GetAccounts(ctx)
  137. if err != nil {
  138. return err
  139. }
  140. var acc *model.Account
  141. for _, a := range accounts {
  142. if a.Email == params.Email && a.App == params.App {
  143. acc = &a
  144. break
  145. }
  146. }
  147. if acc == nil {
  148. return fmt.Errorf("account not found, use `account list` to list accounts")
  149. }
  150. if params.ExportDir != nil && *params.ExportDir != "" {
  151. _, err := internal.ValidateDirForWrite(*params.ExportDir)
  152. if err != nil {
  153. return err
  154. }
  155. acc.ExportDir = *params.ExportDir
  156. }
  157. err = c.DB.Update(func(tx *bolt.Tx) error {
  158. b, err := tx.CreateBucketIfNotExists([]byte(AccBucket))
  159. if err != nil {
  160. return err
  161. }
  162. accInfoBytes, err := json.Marshal(acc)
  163. if err != nil {
  164. return err
  165. }
  166. accountKey := acc.AccountKey()
  167. return b.Put([]byte(accountKey), accInfoBytes)
  168. })
  169. return err
  170. }