form.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package login
  2. import (
  3. "errors"
  4. "log/slog"
  5. "net/http"
  6. "github.com/ayn2op/discordo/internal/config"
  7. "github.com/ayn2op/discordo/internal/consts"
  8. "github.com/diamondburned/arikawa/v3/api"
  9. "github.com/diamondburned/arikawa/v3/utils/httputil"
  10. "github.com/rivo/tview"
  11. "github.com/zalando/go-keyring"
  12. )
  13. type DoneFn = func(token string)
  14. type Form struct {
  15. *tview.Pages
  16. cfg *config.Config
  17. app *tview.Application
  18. form *tview.Form
  19. done DoneFn
  20. }
  21. func NewForm(cfg *config.Config, app *tview.Application, done DoneFn) *Form {
  22. f := &Form{
  23. Pages: tview.NewPages(),
  24. cfg: cfg,
  25. app: app,
  26. form: tview.NewForm(),
  27. done: done,
  28. }
  29. f.form.
  30. AddInputField("Email", "", 0, nil, nil).
  31. AddPasswordField("Password", "", 0, 0, nil).
  32. AddPasswordField("Code (optional)", "", 0, 0, nil).
  33. AddButton("Login", f.login)
  34. f.AddAndSwitchToPage("form", f.form, true)
  35. return f
  36. }
  37. func (f *Form) login() {
  38. email := f.form.GetFormItem(0).(*tview.InputField).GetText()
  39. password := f.form.GetFormItem(1).(*tview.InputField).GetText()
  40. if email == "" || password == "" {
  41. return
  42. }
  43. // Create an API client without an authentication token.
  44. client := api.NewClient("")
  45. // Spoof the user agent of a web browser.
  46. client.UserAgent = f.cfg.Identify.UserAgent
  47. body := httputil.WithJSONBody(struct {
  48. Email string `json:"login"`
  49. Password string `json:"password"`
  50. }{email, password})
  51. var (
  52. resp *api.LoginResponse
  53. err error
  54. )
  55. err = client.RequestJSON(&resp, http.MethodPost, api.EndpointLogin, body)
  56. if err != nil {
  57. f.onError(err)
  58. return
  59. }
  60. if resp.Token == "" && resp.MFA {
  61. code := f.form.GetFormItem(2).(*tview.InputField).GetText()
  62. if code == "" {
  63. f.onError(errors.New("code required"))
  64. return
  65. }
  66. // Attempt to login using the code.
  67. resp, err = client.TOTP(code, resp.Ticket)
  68. if err != nil {
  69. f.onError(err)
  70. return
  71. }
  72. }
  73. if resp.Token == "" {
  74. f.onError(errors.New("missing token"))
  75. return
  76. }
  77. go keyring.Set(consts.Name, "token", resp.Token)
  78. if f.done != nil {
  79. f.done(resp.Token)
  80. }
  81. }
  82. func (f *Form) onError(err error) {
  83. slog.Error("failed to login", "err", err)
  84. modal := tview.NewModal().
  85. SetText(err.Error()).
  86. AddButtons([]string{"Close"}).
  87. SetDoneFunc(func(_ int, _ string) {
  88. f.RemovePage("modal").SwitchToPage("form")
  89. })
  90. f.
  91. AddAndSwitchToPage("modal", centered(modal, 0, 0), true).
  92. ShowPage("form")
  93. }
  94. func centered(p tview.Primitive, width, height int) tview.Primitive {
  95. return tview.NewGrid().
  96. SetColumns(0, width, 0).
  97. SetRows(0, height, 0).
  98. AddItem(p, 1, 1, 1, 1, 0, 0, true)
  99. }