|
@@ -0,0 +1,152 @@
|
|
|
|
|
+package login
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "errors"
|
|
|
|
|
+
|
|
|
|
|
+ "github.com/ayn2op/discordo/internal/config"
|
|
|
|
|
+ "github.com/diamondburned/arikawa/v3/api"
|
|
|
|
|
+ "github.com/gdamore/tcell/v2"
|
|
|
|
|
+ "github.com/rivo/tview"
|
|
|
|
|
+ "github.com/zalando/go-keyring"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+type DoneFn = func(token string, err error)
|
|
|
|
|
+
|
|
|
|
|
+type Form struct {
|
|
|
|
|
+ *tview.Flex
|
|
|
|
|
+ app *tview.Application
|
|
|
|
|
+ errorTextView *tview.TextView
|
|
|
|
|
+
|
|
|
|
|
+ inputs []*tview.InputField
|
|
|
|
|
+ active int
|
|
|
|
|
+ done DoneFn
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func NewForm(app *tview.Application, done DoneFn) *Form {
|
|
|
|
|
+ self := &Form{
|
|
|
|
|
+ Flex: tview.NewFlex().SetDirection(tview.FlexRow),
|
|
|
|
|
+ app: app,
|
|
|
|
|
+ errorTextView: tview.NewTextView(),
|
|
|
|
|
+
|
|
|
|
|
+ done: done,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ emailInput := tview.NewInputField()
|
|
|
|
|
+ emailInput.
|
|
|
|
|
+ SetBorder(true).
|
|
|
|
|
+ SetTitle("Email").
|
|
|
|
|
+ SetTitleAlign(tview.AlignLeft)
|
|
|
|
|
+
|
|
|
|
|
+ passwordInput := tview.NewInputField()
|
|
|
|
|
+ passwordInput.
|
|
|
|
|
+ SetMaskCharacter('*').
|
|
|
|
|
+ SetBorder(true).
|
|
|
|
|
+ SetTitle("Password").
|
|
|
|
|
+ SetTitleAlign(tview.AlignLeft)
|
|
|
|
|
+
|
|
|
|
|
+ codeInput := tview.NewInputField()
|
|
|
|
|
+ codeInput.
|
|
|
|
|
+ SetMaskCharacter('*').
|
|
|
|
|
+ SetBorder(true).
|
|
|
|
|
+ SetTitle("Code (optional)").
|
|
|
|
|
+ SetTitleAlign(tview.AlignLeft)
|
|
|
|
|
+
|
|
|
|
|
+ self.inputs = []*tview.InputField{emailInput, passwordInput, codeInput}
|
|
|
|
|
+ for i, input := range self.inputs {
|
|
|
|
|
+ var focus bool
|
|
|
|
|
+ if i == 0 {
|
|
|
|
|
+ focus = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.AddItem(input, 3, 1, focus)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.
|
|
|
|
|
+ AddItem(self.errorTextView, 0, 1, false).
|
|
|
|
|
+ SetBorderPadding(0, 0, 1, 1).
|
|
|
|
|
+ SetInputCapture(self.onInputCapture)
|
|
|
|
|
+ return self
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (self *Form) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|
|
|
|
+ switch event.Key() {
|
|
|
|
|
+ case tcell.KeyBacktab:
|
|
|
|
|
+ if self.active == 0 {
|
|
|
|
|
+ self.active = len(self.inputs) - 1
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.active--
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.app.SetFocus(self.inputs[self.active])
|
|
|
|
|
+ return nil
|
|
|
|
|
+ case tcell.KeyTab:
|
|
|
|
|
+ if self.active == len(self.inputs)-1 {
|
|
|
|
|
+ self.active = 0
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.active++
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.app.SetFocus(self.inputs[self.active])
|
|
|
|
|
+ return nil
|
|
|
|
|
+
|
|
|
|
|
+ case tcell.KeyEnter:
|
|
|
|
|
+ // If the currently active input is not the email input, proceed to login with the provided details.
|
|
|
|
|
+ if self.active != 0 {
|
|
|
|
|
+ self.login()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return event
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (self *Form) login() {
|
|
|
|
|
+ email := self.inputs[0].GetText()
|
|
|
|
|
+ password := self.inputs[1].GetText()
|
|
|
|
|
+ if email == "" || password == "" {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Create an API client without an authentication token.
|
|
|
|
|
+ client := api.NewClient("")
|
|
|
|
|
+
|
|
|
|
|
+ // Attempt to login using the email and password.
|
|
|
|
|
+ resp, err := client.Login(email, password)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ self.onError(err)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if resp.Token == "" && resp.MFA {
|
|
|
|
|
+ code := self.inputs[2].GetText()
|
|
|
|
|
+ if code == "" {
|
|
|
|
|
+ self.onError(errors.New("code required"))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Attempt to login using the code.
|
|
|
|
|
+ resp, err = client.TOTP(code, resp.Ticket)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ self.onError(err)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if resp.Token == "" {
|
|
|
|
|
+ self.onError(errors.New("missing token"))
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ go keyring.Set(config.Name, "token", resp.Token)
|
|
|
|
|
+
|
|
|
|
|
+ if self.done != nil {
|
|
|
|
|
+ self.done(resp.Token, nil)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (self *Form) onError(err error) {
|
|
|
|
|
+ self.errorTextView.SetText(err.Error())
|
|
|
|
|
+
|
|
|
|
|
+ if self.done != nil {
|
|
|
|
|
+ self.done("", err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|