add welcome message

trunk
vilmibm 2023-03-04 00:51:40 +00:00
parent 9444954bc6
commit 6a1fcbcf32
1 changed files with 31 additions and 158 deletions

View File

@ -4,26 +4,22 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"net/mail"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strings"
"git.tilde.town/tildetown/town/invites" "git.tilde.town/tildetown/town/invites"
"git.tilde.town/tildetown/town/sshkey"
"git.tilde.town/tildetown/town/stats" "git.tilde.town/tildetown/town/stats"
"github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
_ "embed" _ "embed"
) )
// TODO move magic key machine to static page
// TODO link to code of conduct as part of the form and ask if they agree
//go:embed welcome.txt //go:embed welcome.txt
var welcomeArt string var welcomeArt string
// TODO move magic key machine to static page
type newUserData struct { type newUserData struct {
Username string Username string
DisplayName string DisplayName string
@ -31,148 +27,12 @@ type newUserData struct {
PubKey string PubKey string
} }
func surveyIconSet(icons *survey.IconSet) { func defaultStyle() lipgloss.Style {
icons.Question.Text = "~" return lipgloss.NewStyle().
icons.Question.Format = "magenta:b" Foreground(lipgloss.AdaptiveColor{
} Light: "#7D19BD",
Dark: "#E0B0FF",
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
} }
func _main() error { func _main() error {
@ -181,13 +41,7 @@ func _main() error {
return err return err
} }
s := lipgloss.NewStyle(). s := defaultStyle().SetString(welcomeArt)
Foreground(lipgloss.AdaptiveColor{
Light: "#7D19BD",
Dark: "#E0B0FF",
})
s = s.SetString(welcomeArt)
fmt.Println(s) fmt.Println(s)
code, err := promptCode() code, err := promptCode()
@ -220,7 +74,7 @@ func _main() error {
UserData: data, UserData: data,
Invite: *invite, Invite: *invite,
TownData: townData, TownData: townData,
Style: s, Style: defaultStyle(),
} }
if err = a.Ask(); err != nil { if err = a.Ask(); err != nil {
@ -252,11 +106,30 @@ Please email root@tilde.town and paste the following error:
%s %s
Your invite code has not been marked as used and you're welcome to try again, though if there is a system issue you might need to wait for word from an admin.`, err.Error())) Your invite code has not been marked as used and you're welcome to try again,
though if there is a system issue you might need to wait for word from an admin.`, err.Error()))
fmt.Println(s) fmt.Println(s)
return nil return nil
} }
s = s.SetString(fmt.Sprintf(`OK! your user account has been created.
welcome, ~%[1]s <3
This program is going to exit and you are now free to ssh to town as yourself:
ssh %[1]s@tilde.town
if your public key isn't found by ssh, you'll need to explain to ssh how to find it with:
ssh -i "replace with path to public key file" %[1]s@tilde.town
for help with ssh, see: https://tilde.town/wiki/getting-started/ssh.html
if you end up very stuck, you can email root@tilde.town .`, data.Username))
fmt.Println(s)
// TODO mark invite as used // TODO mark invite as used
// TODO add user to town.db // TODO add user to town.db