diff --git a/main.go b/main.go new file mode 100644 index 0000000..de8d81d --- /dev/null +++ b/main.go @@ -0,0 +1,201 @@ +package scsusers + +import ( + "bytes" + "database/sql" + "fmt" + "github.com/jmoiron/sqlx" + "golang.org/x/crypto/bcrypt" + "html/template" + "log" + "math/rand" + "net/smtp" + "strings" +) + +type templates struct { + Registration *template.Template + Alert *template.Template + Recovery *template.Template +} + +type config struct { + SiteName string + FromEmail string + Templates templates + db *sqlx.DB + TablePrefix string +} + +var c config + +func Init(dbin *sqlx.DB, tp, sitename, fromaddr string) { + c.db = dbin + c.TablePrefix = tp + c.SiteName = sitename + c.FromEmail = fromaddr + + SetRegistrationTemplate("") + SetAlertTemplate("") + SetRecoveryTemplate("") + +} + +func UsernameAvailable(username string) bool { + + q := fmt.Sprintf("select username from %s_auth where username=$1", c.TablePrefix) + err := c.db.Get(q, username) + if err == sql.ErrNoRows { + return true + } + return false +} + +/* Check for username availability, add to database, send email */ + +func Register(username, email, ip string) bool { + if !UsernameAvailable(username) { + return false + } + pass := randBytes(16) + crypt, err := bcrypt.GenerateFromPassword(pass, 20) + if err != nil { + log.Printf("Bcrypt GenerateFromPassword failed? Pass is %s and error is %s\n", pass, err.Error()) + return false + } + q := fmt.Sprintf("insert into %s_auth (username, email, password, registration_date, registration_ip) values ($1, $2, $3, NOW(), $4)", c.TablePrefix) + _, err = c.db.Exec(q, username, email, crypt, ip) + if err != nil { + return false + } + + return true +} + +func randBytes(n int) []byte { + const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return b +} + +func sendRegistrationEmail(recipient, username, password string) bool { + data := struct { + SiteName string + FromEmail string + UserName string + Pass string + }{ + SiteName: c.SiteName, + FromEmail: c.FromEmail, + UserName: username, + Pass: password, + } + var body bytes.Buffer + err := c.Templates.Registration.Execute(&body, data) + if err != nil { + log.Printf("Registration template failed to execute: %v returned %s\n", data, err.Error()) + return false + } + subject := fmt.Sprintf("Welcome to %s", c.SiteName) + err = SendMail("localhost:25", c.FromEmail, subject, body.String(), recipient) + if err != nil { + log.Printf("Error sending mail to %s: %s\n", recipient, err.Error()) + return false + } + return true +} + +func SetRegistrationTemplate(t string) bool { + if len(t) != 0 { + r, err := template.New("reg").Parse(t) + if err != nil { + c.Templates.Registration = r + return true + } + } + df := `

Hello {{.UserName}}! Welcome to {{.SiteName}}! We've created your account with the username you selected and the following password: {{.Pass}}
You can change your password to whatever you want once you log in.

` + + r, err := template.New("reg").Parse(df) + if err != nil { + log.Fatal("Default registration template MUST compile. Error: " + err.Error()) + } + c.Templates.Registration = r + + return false +} + +func SetAlertTemplate(t string) bool { + if len(t) != 0 { + r, err := template.New("alert").Parse(t) + if err != nil { + c.Templates.Alert = r + return true + } + } + df := `

Hello {{.UserName}}! Just letting you know that {{.Activity}}.
You can disable future notifications in your user settings.

` + r, err := template.New("alert").Parse(df) + if err != nil { + log.Fatal("Default alert template MUST compile. Error: " + err.Error()) + } + c.Templates.Alert = r + return false +} + +func SetRecoveryTemplate(t string) bool { + if len(t) != 0 { + r, err := template.New("recovery").Parse(t) + if err != nil { + c.Templates.Recovery = r + return true + } + } + df := `

Hello {{.UserName}}! Someone (hopefully you) has attempted an account recovery agt {{.SiteName}}. If this was you, enter the following code to regain access: {{.RecoveryCode}}
If this was not you, you can ignore this email.

` + r, err := template.New("recovery").Parse(df) + if err != nil { + log.Fatal("Default alert template MUST compile. Error: " + err.Error()) + } + c.Templates.Recovery = r + + return false +} + +func SendMail(addr, from, subject, body string, to string) error { + r := strings.NewReplacer("\r\n", "", "\r", "", "\n", "", "%0a", "", "%0d", "") + + c, err := smtp.Dial(addr) + if err != nil { + return err + } + defer c.Close() + if err = c.Mail(r.Replace(from)); err != nil { + return err + } + to = r.Replace(to) + if err = c.Rcpt(to); err != nil { + return err + } + + w, err := c.Data() + if err != nil { + return err + } + + msg := "To: " + to + "\r\n" + + "From: " + from + "\r\n" + + "Subject: " + subject + "\r\n" + + "Content-Type: text/html; charset=\"UTF-8\"\r\n" + + "\r\n" + body + + _, err = w.Write([]byte(msg)) + if err != nil { + return err + } + err = w.Close() + if err != nil { + return err + } + return c.Quit() +} diff --git a/templates/registration.html b/templates/registration.html new file mode 100644 index 0000000..5d942f9 --- /dev/null +++ b/templates/registration.html @@ -0,0 +1,15 @@ + + + + + + +

+ Hello {{.UserName}}! Welcome to {{.SiteName}}! We've created your account with the username you selected and the following password: {{.Pass}}
+ You can change your password to whatever you want once you log in. +

+ + + +