From df4eeaba13008fca1bec3e4a7fc71ce3b721fd0b Mon Sep 17 00:00:00 2001 From: vilmibm Date: Sat, 4 Mar 2023 01:15:09 +0000 Subject: [PATCH] stuff, but also things --- .gitignore | 1 + cmd/registeruser/main.go | 10 +++ cmd/welcome/form.go | 159 +++++++++++++++++++++++++++++++++++++++ cmd/welcome/main.go | 2 +- sshkey/sshkey.go | 23 ++++++ 5 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 cmd/registeruser/main.go create mode 100644 cmd/welcome/form.go create mode 100644 sshkey/sshkey.go diff --git a/.gitignore b/.gitignore index 9929bb2..87c956d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ cmd/visit/visit cmd/signup/signup cmd/review/review cmd/welcome/welcome +cmd/createkeyfile/createkeyfile diff --git a/cmd/registeruser/main.go b/cmd/registeruser/main.go new file mode 100644 index 0000000..dd27c8d --- /dev/null +++ b/cmd/registeruser/main.go @@ -0,0 +1,10 @@ +package main + +import "fmt" + +// TODO this command adds a new user to /town/var/town.db +// it's meant to be invoked by the welcome binary upon successfully creating a new user account + +func main() { + fmt.Println("lol") +} diff --git a/cmd/welcome/form.go b/cmd/welcome/form.go new file mode 100644 index 0000000..b080748 --- /dev/null +++ b/cmd/welcome/form.go @@ -0,0 +1,159 @@ +package main + +import ( + "errors" + "fmt" + "net/mail" + "regexp" + "strings" + + "git.tilde.town/tildetown/town/invites" + "git.tilde.town/tildetown/town/sshkey" + "git.tilde.town/tildetown/town/stats" + "github.com/AlecAivazis/survey/v2" + "github.com/charmbracelet/lipgloss" +) + +func surveyIconSet(icons *survey.IconSet) { + icons.Question.Text = "~" + icons.Question.Format = "magenta:b" +} + +func promptCode() (code string, err error) { + err = survey.AskOne(&survey.Input{ + Message: "invite code?", + }, &code, + survey.WithValidator(survey.Required), + survey.WithIcons(surveyIconSet)) + code = strings.TrimSpace(code) + return +} + +func confirmContinue() (conf bool, err error) { + err = survey.AskOne( + &survey.Confirm{ + Message: "Does the above look ok?", + }, &conf, + survey.WithValidator(survey.Required), + survey.WithIcons(surveyIconSet)) + return +} + +type asker struct { + UserData *newUserData + Style lipgloss.Style + Invite invites.Invite + TownData stats.TildeData +} + +func (a *asker) Ask() (err error) { + // TODO somehow un and email getting set to "" but pubkey works fine? + + if err = a.promptUsername(); err != nil { + return err + } + + if err = a.promptEmail(); err != nil { + return err + } + + if err = a.promptKey(); err != nil { + return err + } + + s := a.Style.SetString( + fmt.Sprintf(`ok! your account is about to be created with the following details: + +username: %s +email: %s +pubkey: %s`, a.UserData.Username, a.UserData.Email, a.UserData.PubKey)).Bold(true).MaxWidth(80) + + fmt.Println(s) + + return nil +} + +func (a *asker) promptUsername() (err error) { + // copied from /etc/adduser.conf + usernameRE := regexp.MustCompile(`^[a-z][-a-z0-9_]*$`) + err = survey.AskOne( + &survey.Input{ + Message: "desired username?", + Default: a.UserData.Username, + }, &a.UserData.Username, + survey.WithValidator(survey.Required), + survey.WithIcons(surveyIconSet), + survey.WithValidator(func(val interface{}) error { + un := val.(string) + if len(un) > 32 { + return fmt.Errorf("username '%s' is too long", un) + } + return nil + }), + survey.WithValidator(func(val interface{}) error { + un := val.(string) + if !usernameRE.MatchString(un) { + return errors.New("usernames must start with a letter and only contain letters, nubers, - or _") + } + return nil + }), + survey.WithValidator(func(val interface{}) error { + un := val.(string) + for _, v := range a.TownData.Users { + if v.Username == un { + return fmt.Errorf("username '%s' is already in use", un) + } + } + return nil + })) + + return +} + +func (a *asker) promptEmail() (err error) { + err = survey.AskOne( + &survey.Input{ + Message: "e-mail (for account recovery only)?", + Default: a.UserData.Email, + }, &a.UserData.Email, + survey.WithValidator(survey.Required), + survey.WithIcons(surveyIconSet), + survey.WithValidator(func(val interface{}) error { + email := val.(string) + _, err := mail.ParseAddress(email) + if err != nil { + return fmt.Errorf("'%s' doesn't look like an email: %w", email, err) + } + + if !strings.Contains(email, ".") { + return fmt.Errorf("'%s' doesn't look like an email: domain not fully qualified", email) + } + + return nil + })) + + return +} + +func (a *asker) promptKey() (err error) { + err = survey.AskOne( + &survey.Input{ + Message: "SSH public key?", + Default: a.UserData.PubKey, + }, &a.UserData.PubKey, + survey.WithValidator(survey.Required), + survey.WithIcons(surveyIconSet), + survey.WithValidator(func(v interface{}) error { + key := v.(string) + valid, err := sshkey.ValidKey(key) + if err != nil { + return fmt.Errorf("failed to validate key: %w", err) + } + if !valid { + return errors.New("that doesn't seem like a valid SSH key. try another public key?") + } + return nil + })) + + return +} diff --git a/cmd/welcome/main.go b/cmd/welcome/main.go index ab8dc88..c8fa9e2 100644 --- a/cmd/welcome/main.go +++ b/cmd/welcome/main.go @@ -131,7 +131,7 @@ if you end up very stuck, you can email root@tilde.town .`, data.Username)) fmt.Println(s) // TODO mark invite as used - // TODO add user to town.db + // TODO invoke helper to add user to town.db return nil } diff --git a/sshkey/sshkey.go b/sshkey/sshkey.go new file mode 100644 index 0000000..ad719ed --- /dev/null +++ b/sshkey/sshkey.go @@ -0,0 +1,23 @@ +package sshkey + +import ( + "bytes" + "errors" + "os/exec" +) + +const skgpath = "/usr/bin/ssh-keygen" + +func ValidKey(key string) (valid bool, err error) { + cmd := exec.Command(skgpath, "-l", "-f", "-") + cmd.Stdin = bytes.NewBuffer([]byte(key)) + err = cmd.Run() + + if err == nil { + valid = true + } else if errors.Is(&exec.ExitError{}, err) { + err = nil + } + + return +}