forked from tildetown/town
stuff, but also things
parent
015b28ba6a
commit
df4eeaba13
|
@ -6,3 +6,4 @@ cmd/visit/visit
|
||||||
cmd/signup/signup
|
cmd/signup/signup
|
||||||
cmd/review/review
|
cmd/review/review
|
||||||
cmd/welcome/welcome
|
cmd/welcome/welcome
|
||||||
|
cmd/createkeyfile/createkeyfile
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -131,7 +131,7 @@ if you end up very stuck, you can email root@tilde.town .`, data.Username))
|
||||||
fmt.Println(s)
|
fmt.Println(s)
|
||||||
|
|
||||||
// TODO mark invite as used
|
// TODO mark invite as used
|
||||||
// TODO add user to town.db
|
// TODO invoke helper to add user to town.db
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue