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 confirmCoC() (conf bool, err error) { err = survey.AskOne( &survey.Confirm{ Message: "do you agree to the above CoC?", Default: true, }, &conf, survey.WithValidator(survey.Required), survey.WithIcons(surveyIconSet)) 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) { 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 }