Command line client for Write.as https://write.as/apps/cli
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 line
8.3 KiB

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "text/tabwriter"
  9. "github.com/hashicorp/go-multierror"
  10. "github.com/writeas/writeas-cli/api"
  11. "github.com/writeas/writeas-cli/commands"
  12. "github.com/writeas/writeas-cli/config"
  13. "github.com/writeas/writeas-cli/executable"
  14. "github.com/writeas/writeas-cli/log"
  15. cli "gopkg.in/urfave/cli.v1"
  16. )
  17. func requireAuth(f cli.ActionFunc, action string) cli.ActionFunc {
  18. return func(c *cli.Context) error {
  19. // check for logged in users when host is provided without user
  20. if c.GlobalIsSet("host") && !c.GlobalIsSet("user") {
  21. // multiple users should display a list
  22. if num, users, err := usersLoggedIn(c); num > 1 && err == nil {
  23. return cli.NewExitError(fmt.Sprintf("Multiple logged in users, please use '-u' or '-user' to specify one of:\n%s", strings.Join(users, ", ")), 1)
  24. } else if num == 1 && err == nil {
  25. // single user found for host should be set as user flag so LoadUser can
  26. // succeed, and notify the client
  27. if err := c.GlobalSet("user", users[0]); err != nil {
  28. return cli.NewExitError(fmt.Sprintf("Failed to set user flag for only logged in user at host %s: %v", users[0], err), 1)
  29. }
  30. log.Info(c, "Host specified without user flag, using logged in user: %s\n", users[0])
  31. } else if err != nil {
  32. return cli.NewExitError(fmt.Sprintf("Failed to check for logged in users: %v", err), 1)
  33. }
  34. } else if !c.GlobalIsSet("host") && !c.GlobalIsSet("user") {
  35. // check for global configured pair host/user
  36. cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]))
  37. if err != nil {
  38. return cli.NewExitError(fmt.Sprintf("Failed to load config from file: %v", err), 1)
  39. // set flags if found
  40. }
  41. // set flags if both were found in config
  42. if cfg.Default.Host != "" && cfg.Default.User != "" {
  43. err = c.GlobalSet("host", cfg.Default.Host)
  44. if err != nil {
  45. return cli.NewExitError(fmt.Sprintf("Failed to set host from global config: %v", err), 1)
  46. }
  47. err = c.GlobalSet("user", cfg.Default.User)
  48. if err != nil {
  49. return cli.NewExitError(fmt.Sprintf("Failed to set user from global config: %v", err), 1)
  50. }
  51. } else {
  52. num, err := totalUsersLoggedIn(c)
  53. if err != nil {
  54. return cli.NewExitError(fmt.Sprintf("Failed to check for logged in users: %v", err), 1)
  55. } else if num > 0 {
  56. return cli.NewExitError("You are authenticated, but have no default user/host set. Supply -user and -host flags.", 1)
  57. }
  58. }
  59. }
  60. u, err := config.LoadUser(c)
  61. if err != nil {
  62. return cli.NewExitError(fmt.Sprintf("Couldn't load user: %v", err), 1)
  63. }
  64. if u == nil {
  65. return cli.NewExitError("You must be authenticated to "+action+".\nLog in first with: "+executable.Name()+" auth <username>", 1)
  66. }
  67. return f(c)
  68. }
  69. }
  70. // usersLoggedIn checks for logged in users for the set host flag
  71. // it returns the number of users and a slice of usernames
  72. func usersLoggedIn(c *cli.Context) (int, []string, error) {
  73. path, err := config.UserHostDir(c)
  74. if err != nil {
  75. return 0, nil, err
  76. }
  77. dir, err := os.Open(path)
  78. if err != nil {
  79. return 0, nil, err
  80. }
  81. contents, err := dir.Readdir(0)
  82. if err != nil {
  83. return 0, nil, err
  84. }
  85. var names []string
  86. for _, file := range contents {
  87. if file.IsDir() {
  88. // stat user.json
  89. if _, err := os.Stat(filepath.Join(path, file.Name(), "user.json")); err == nil {
  90. names = append(names, file.Name())
  91. }
  92. }
  93. }
  94. return len(names), names, nil
  95. }
  96. // totalUsersLoggedIn checks for logged in users for any host
  97. // it returns the number of users and an error if any
  98. func totalUsersLoggedIn(c *cli.Context) (int, error) {
  99. path := config.UserDataDir(c.App.ExtraInfo()["configDir"])
  100. dir, err := os.Open(path)
  101. if err != nil {
  102. return 0, err
  103. }
  104. contents, err := dir.Readdir(0)
  105. if err != nil {
  106. return 0, err
  107. }
  108. count := 0
  109. for _, file := range contents {
  110. if file.IsDir() {
  111. subDir, err := os.Open(filepath.Join(path, file.Name()))
  112. if err != nil {
  113. return 0, err
  114. }
  115. subContents, err := subDir.Readdir(0)
  116. if err != nil {
  117. return 0, err
  118. }
  119. for _, subFile := range subContents {
  120. if subFile.IsDir() {
  121. if _, err := os.Stat(filepath.Join(path, file.Name(), subFile.Name(), "user.json")); err == nil {
  122. count++
  123. }
  124. }
  125. }
  126. }
  127. }
  128. return count, nil
  129. }
  130. func cmdAuth(c *cli.Context) error {
  131. err := commands.CmdAuth(c)
  132. if err != nil {
  133. return err
  134. }
  135. // Get the username from the command, just like commands.CmdAuth does
  136. username := c.Args().Get(0)
  137. // Update config if this is user's first auth
  138. cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]))
  139. if err != nil {
  140. log.Errorln("Not saving config. Unable to load config: %s", err)
  141. return err
  142. }
  143. if cfg.Default.Host == "" && cfg.Default.User == "" {
  144. // This is user's first auth, so save defaults
  145. cfg.Default.Host = api.HostURL(c)
  146. cfg.Default.User = username
  147. err = config.SaveConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]), cfg)
  148. if err != nil {
  149. log.Errorln("Not saving config. Unable to save config: %s", err)
  150. return err
  151. }
  152. fmt.Printf("Set %s on %s as default account.\n", username, c.GlobalString("host"))
  153. }
  154. return nil
  155. }
  156. func cmdLogOut(c *cli.Context) error {
  157. err := commands.CmdLogOut(c)
  158. if err != nil {
  159. return err
  160. }
  161. // Remove this from config if it's the default account
  162. cfg, err := config.LoadConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]))
  163. if err != nil {
  164. log.Errorln("Not updating config. Unable to load: %s", err)
  165. return err
  166. }
  167. username, err := config.CurrentUser(c)
  168. if err != nil {
  169. log.Errorln("Not updating config. Unable to load current user: %s", err)
  170. return err
  171. }
  172. reqHost := api.HostURL(c)
  173. if reqHost == "" {
  174. // No --host given, so we're using the default host
  175. reqHost = cfg.Default.Host
  176. }
  177. if cfg.Default.Host == reqHost && cfg.Default.User == username {
  178. // We're logging out of default username + host, so remove from config file
  179. cfg.Default.Host = ""
  180. cfg.Default.User = ""
  181. err = config.SaveConfig(config.UserDataDir(c.App.ExtraInfo()["configDir"]), cfg)
  182. if err != nil {
  183. log.Errorln("Not updating config. Unable to save config: %s", err)
  184. return err
  185. }
  186. }
  187. return nil
  188. }
  189. func cmdAccounts(c *cli.Context) error {
  190. // get user config dir
  191. userDir := config.UserDataDir(c.App.ExtraInfo()["configDir"])
  192. // load defaults
  193. cfg, err := config.LoadConfig(userDir)
  194. if err != nil {
  195. return cli.NewExitError("Could not load default user configuration", 1)
  196. }
  197. defaultUser := cfg.Default.User
  198. defaultHost := cfg.Default.Host
  199. if parts := strings.Split(defaultHost, "://"); len(parts) > 1 {
  200. defaultHost = parts[1]
  201. }
  202. // get each host dir
  203. files, err := ioutil.ReadDir(userDir)
  204. if err != nil {
  205. return cli.NewExitError("Could not read user configuration directory", 1)
  206. }
  207. // accounts will be a slice of slices of string. the first string in
  208. // a subslice should always be the hostname
  209. accounts := [][]string{}
  210. for _, file := range files {
  211. if file.IsDir() {
  212. dirName := file.Name()
  213. // get each user in host dir
  214. users, err := usersFromDir(filepath.Join(userDir, dirName))
  215. if err != nil {
  216. log.Info(c, "Failed to get users from %s: %v", dirName, err)
  217. continue
  218. }
  219. if len(users) != 0 {
  220. // append the slice of users as a new slice in accounts w/ the host prepended
  221. accounts = append(accounts, append([]string{dirName}, users...))
  222. }
  223. }
  224. }
  225. // print out all logged in accounts
  226. tw := tabwriter.NewWriter(os.Stdout, 10, 2, 2, ' ', tabwriter.TabIndent)
  227. if len(accounts) == 0 && (c.Bool("v") || c.Bool("verbose") || c.GlobalBool("v") || c.GlobalBool("verbose")) {
  228. fmt.Fprintf(tw, "%s\t", "No authenticated accounts found.\n")
  229. }
  230. for _, userList := range accounts {
  231. host := userList[0]
  232. for _, username := range userList[1:] {
  233. if host == defaultHost && username == defaultUser {
  234. fmt.Fprintf(tw, "[%s]\t%s (default)\n", host, username)
  235. continue
  236. }
  237. fmt.Fprintf(tw, "[%s]\t%s\n", host, username)
  238. }
  239. }
  240. return tw.Flush()
  241. }
  242. func usersFromDir(path string) ([]string, error) {
  243. users := make([]string, 0, 4)
  244. files, err := ioutil.ReadDir(path)
  245. if err != nil {
  246. return nil, err
  247. }
  248. var errs error
  249. for _, file := range files {
  250. if file.IsDir() {
  251. _, err := os.Stat(filepath.Join(path, file.Name(), "user.json"))
  252. if err != nil {
  253. err = multierror.Append(errs, err)
  254. continue
  255. }
  256. users = append(users, file.Name())
  257. }
  258. }
  259. return users, errs
  260. }