summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2024-11-09 12:16:36 +0200
committerKyren223 <Kyren223@proton.me>2024-11-09 12:16:36 +0200
commit8d97e7000c6579180d37c977edf5d071e4ff1219 (patch)
tree59639552d8111752f0fc10356221a12544e43afa
parent8118d9265a5d35c84dcf00a454ecf0209a0351ef (diff)
feat: finish authentication UI
-rw-r--r--internal/client/client.go12
-rw-r--r--internal/client/ui/auth/auth.go122
-rw-r--r--internal/client/ui/auth/field/field.go26
3 files changed, 115 insertions, 45 deletions
diff --git a/internal/client/client.go b/internal/client/client.go
index e050afc..22c9d30 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -1,13 +1,13 @@
package client
import (
- "context"
- "crypto/ed25519"
+ // "context"
+ // "crypto/ed25519"
"log"
tea "github.com/charmbracelet/bubbletea"
- "github.com/kyren223/eko/internal/client/gateway"
+ // "github.com/kyren223/eko/internal/client/gateway"
"github.com/kyren223/eko/internal/client/ui"
"github.com/kyren223/eko/internal/client/ui/auth"
"github.com/kyren223/eko/pkg/assert"
@@ -27,10 +27,10 @@ func Run() {
program := tea.NewProgram(initialModel(), tea.WithAltScreen())
assert.AddFlush(BubbleTeaCloser{program})
- _, privKey, err := ed25519.GenerateKey(nil)
- assert.NoError(err, "private key gen should not error")
+ // _, privKey, err := ed25519.GenerateKey(nil)
+ // assert.NoError(err, "private key gen should not error")
- gateway.Connect(context.Background(), program, privKey)
+ // gateway.Connect(context.Background(), program, privKey)
if _, err := program.Run(); err != nil {
log.Println(err)
}
diff --git a/internal/client/ui/auth/auth.go b/internal/client/ui/auth/auth.go
index fa37d4e..0cabe38 100644
--- a/internal/client/ui/auth/auth.go
+++ b/internal/client/ui/auth/auth.go
@@ -1,15 +1,20 @@
package auth
import (
+ "crypto/ed25519"
+ "crypto/x509"
"errors"
"fmt"
+ "log"
"os"
"path/filepath"
+ "reflect"
"strings"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
+ "golang.org/x/crypto/ssh"
"github.com/kyren223/eko/internal/client/ui"
authfield "github.com/kyren223/eko/internal/client/ui/auth/field"
@@ -26,29 +31,30 @@ const (
var (
grayStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
- focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("198"))
- cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("198"))
+ focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#5874FF"))
+ cursorStyle = focusedStyle
noStyle = lipgloss.NewStyle()
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F16265"))
- fieldStyle = lipgloss.NewStyle().
- PaddingLeft(1).
- Border(lipgloss.RoundedBorder()).
- BorderForeground(lipgloss.Color("#007E8A"))
- focusedSignupButton = focusedStyle.Render("[ sign-up ]")
- focusedSigninButton = focusedStyle.Render("[ sign-in ]")
+ fieldBlurredStyle = lipgloss.NewStyle().
+ PaddingLeft(1).
+ Border(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("#007E8A"))
+ fieldFocusedStyle = fieldBlurredStyle.BorderForeground(focusedStyle.GetForeground()).Border(lipgloss.ThickBorder())
+
+ focusedSignupButton = focusedStyle.Bold(true).Render("[ SIGN-UP ]")
+ focusedSigninButton = focusedStyle.Bold(true).Render("[ SIGN-IN ]")
blurredSignupButton = fmt.Sprintf("[ %s ]", grayStyle.Render("sign-up"))
blurredSigninButton = fmt.Sprintf("[ %s ]", grayStyle.Render("sign-in"))
headerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#54D7A9"))
- titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#5874FF"))
+ titleStyle = focusedStyle.Bold(true)
signupTitle = titleStyle.Render(`
____ _ ____ _ _ _ _ ___
[__ | | __ |\ | __ | | |__]
___] | |__] | \| |__| |
`)
-
signinTitle = titleStyle.Render(`
____ _ ____ _ _ _ _ _
[__ | | __ |\ | __ | |\ |
@@ -59,7 +65,7 @@ ___] | |__] | \| | | \|
concealIcon = lipgloss.NewStyle().PaddingLeft(1).Render("󰈉 ")
popupStyle = lipgloss.NewStyle().Border(lipgloss.ThickBorder())
- choiceSelectedStyle = lipgloss.NewStyle().Background(focusedStyle.GetForeground()).Padding(0, 1).Margin(0, 1)
+ choiceSelectedStyle = lipgloss.NewStyle().Background(lipgloss.Color("#0029f5")).Padding(0, 1).Margin(0, 1)
choiceUnselectedStyle = lipgloss.NewStyle().Background(grayStyle.GetForeground()).Padding(0, 1).Margin(0, 1)
)
@@ -83,10 +89,11 @@ func New() Model {
for i := range m.fields {
field := authfield.New(48)
field.Input.Cursor.Style = cursorStyle
- field.Style = fieldStyle
+ field.BlurredStyle = fieldBlurredStyle
+ field.FocusedStyle = fieldFocusedStyle
field.ErrorStyle = errorStyle
- field.FocusedStyle = focusedStyle
- field.BlurredStyle = noStyle
+ field.FocusedTextStyle = focusedStyle
+ field.BlurredTextStyle = noStyle
switch i {
case usernameField:
@@ -233,16 +240,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.popup = nil
return m, m.SetSignup(false)
}
- if choice == "overwrite" {
- // TODO: how should I handle this
- // I need to somehow use this to notify that the file
- // should be overwritten, or maybe I should remove this option?
- // And make sure the user manually deletes/renames/moves the file
- // So nobody can claim that this deleted their SSH keys
- // (or more likely: I won't accidentally delete my SSH keys)
- m.popup = nil
- return m, nil
- }
if choice == "cancel" {
m.popup = nil
return m, nil
@@ -367,6 +364,22 @@ func (m *Model) ButtonPressed(msg tea.Msg) tea.Cmd {
}
func (m *Model) Signup() tea.Cmd {
+ passphrase := m.fields[passphraseField].Input.Value()
+ confirmation := m.fields[passphraseConfirmField].Input.Value()
+
+ hasPassphrase := len(passphrase) != 0
+ hasConfirmation := len(confirmation) != 0
+ if hasPassphrase && !hasConfirmation {
+ m.fields[passphraseConfirmField].Input.Err = errors.New("Confirmation required")
+ return nil
+ } else if !hasPassphrase && hasConfirmation {
+ m.fields[passphraseField].Input.Err = errors.New("Empty passphrase")
+ return nil
+ } else if hasPassphrase && hasConfirmation && passphrase != confirmation {
+ m.fields[passphraseConfirmField].Input.Err = errors.New("Passphrase mismatch")
+ return nil
+ }
+
privateKeyFilepath := expandPath(m.fields[privateKeyField].Input.Value())
err := os.MkdirAll(filepath.Dir(privateKeyFilepath), 0o755)
if err != nil {
@@ -379,27 +392,33 @@ func (m *Model) Signup() tea.Cmd {
info, e := os.Stat(privateKeyFilepath)
assert.NoError(e, "if file exists it should be fine to stat it")
if info.IsDir() {
- m.fields[privateKeyField].Input.Err = errors.New("directory exists")
+ m.fields[privateKeyField].Input.Err = errors.New("File is a directory")
return nil
}
- content := fmt.Sprintf("File '%s' exists.\nDo you want to overwrite or sign-in instead?", privateKeyFilepath)
- m.popup = createPopup(content, []string{"sign-in", "overwrite"}, []string{"cancel"})
+ content := fmt.Sprintf("File '%s' exists.\nDo you want to sign-in instead?", privateKeyFilepath)
+ m.popup = createPopup(content, []string{"sign-in"}, []string{"cancel"})
return nil
}
if err != nil {
m.fields[privateKeyField].Input.Err = errors.Unwrap(err)
+ if errors.Unwrap(err).Error() == "is a directory" {
+ m.fields[privateKeyField].Input.Err = errors.New("File is a directory")
+ }
+ log.Println("signup open file error:", err)
assert.NotNil(errors.Unwrap(err), "there should always be an error to unwrap", "err", err)
return nil
}
+ // file.Write()
file.Close()
os.Remove(privateKeyFilepath)
+
return tea.Quit
}
func (m *Model) signin() tea.Cmd {
privateKeyFilepath := expandPath(m.fields[privateKeyField].Input.Value())
- _, err := os.ReadFile(privateKeyFilepath)
+ file, err := os.ReadFile(privateKeyFilepath)
if errors.Is(err, os.ErrNotExist) {
content := fmt.Sprintf("File '%s' doesn't exist.\nDo you want to sign-up instead?", privateKeyFilepath)
m.popup = createPopup(content, []string{"sign-up"}, []string{"cancel"})
@@ -407,9 +426,54 @@ func (m *Model) signin() tea.Cmd {
}
if err != nil {
m.fields[privateKeyField].Input.Err = errors.Unwrap(err)
+ if errors.Unwrap(err).Error() == "is a directory" {
+ m.fields[privateKeyField].Input.Err = errors.New("File is a directory")
+ }
assert.NotNil(errors.Unwrap(err), "there should always be an error to unwrap", "err", err)
return nil
}
+
+ var privateKey any
+ passphrase := m.fields[passphraseField].Input.Value()
+
+ if len(passphrase) == 0 {
+ privateKey, err = ssh.ParseRawPrivateKey(file)
+ if err, ok := err.(*ssh.PassphraseMissingError); ok {
+ m.fields[passphraseField].Input.Err = errors.New("Missing passphrase")
+ log.Println("passphrase missing:", err)
+ return nil
+ }
+ if err != nil {
+ m.fields[privateKeyField].Input.Err = errors.New("Invalid private key file format")
+ log.Println("passphrase error:", err)
+ return nil
+ }
+ } else {
+ privateKey, err = ssh.ParseRawPrivateKeyWithPassphrase(file, []byte(passphrase))
+ if err == x509.IncorrectPasswordError {
+ m.fields[passphraseField].Input.Err = errors.New("Incorrect Passphrase")
+ return nil
+ }
+ if err != nil && (err.Error() == "ssh: not an encrypted key" || err.Error() == "ssh: key is not password protected") {
+ privateKey, err = ssh.ParseRawPrivateKey(file)
+ }
+ if err != nil {
+ m.fields[privateKeyField].Input.Err = errors.New("Invalid private key file format")
+ log.Println("passphrase error:", err)
+ return nil
+ }
+ }
+
+ privKey, ok := privateKey.(*ed25519.PrivateKey)
+ if !ok {
+ m.fields[privateKeyField].Input.Err = errors.New("Must be ed25519")
+ keyType := reflect.TypeOf(privateKey)
+ log.Println("incorrect private key type, got:", keyType.String(), reflect.ValueOf(privateKey).String())
+ return nil
+ }
+
+ _ = privKey
+
return tea.Quit
}
@@ -447,7 +511,7 @@ func test() {
// pubKey, privKey, err := ed25519.GenerateKey(nil)
// sshPrivKey, err := ssh.NewSignerFromSigner(privKey)
// ssh.MarshalPrivateKey()
- // ssh.MarshalPrivateKey()
+ // ssh.MarshalAuthorizedKey()
// ssh.ParseRawPrivateKey()
// ssh.ParseRawPrivateKeyWithPassphrase()
}
diff --git a/internal/client/ui/auth/field/field.go b/internal/client/ui/auth/field/field.go
index 4c6ca12..dca4203 100644
--- a/internal/client/ui/auth/field/field.go
+++ b/internal/client/ui/auth/field/field.go
@@ -19,11 +19,12 @@ type Model struct {
concealIcon string
icon string
- Style lipgloss.Style
- FocusedStyle lipgloss.Style
- BlurredStyle lipgloss.Style
- ErrorStyle lipgloss.Style
- HeaderStyle lipgloss.Style
+ FocusedStyle lipgloss.Style
+ BlurredStyle lipgloss.Style
+ FocusedTextStyle lipgloss.Style
+ BlurredTextStyle lipgloss.Style
+ ErrorStyle lipgloss.Style
+ HeaderStyle lipgloss.Style
}
func New(width int) Model {
@@ -52,7 +53,12 @@ func (m Model) View() string {
return ""
}
- style := m.Style
+
+ style := m.BlurredStyle
+ if m.Input.Focused() {
+ style = m.FocusedStyle
+ }
+
header := m.HeaderStyle.Render(m.Header)
if m.Input.Err != nil {
error := m.Input.Err.Error()
@@ -77,15 +83,15 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
func (m *Model) Focus() tea.Cmd {
- m.Input.PromptStyle = m.FocusedStyle
- m.Input.TextStyle = m.FocusedStyle
+ m.Input.PromptStyle = m.FocusedTextStyle
+ m.Input.TextStyle = m.FocusedTextStyle
return m.Input.Focus()
}
func (m *Model) Blur() {
m.Input.Blur()
- m.Input.PromptStyle = m.BlurredStyle
- m.Input.TextStyle = m.BlurredStyle
+ m.Input.PromptStyle = m.BlurredTextStyle
+ m.Input.TextStyle = m.BlurredTextStyle
}
func (m *Model) SetWidth(width int) {