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 { var u string q := fmt.Sprintf("select username from %s_auth where username=$1", c.TablePrefix) err := c.db.Get(&u, q, username) if err == sql.ErrNoRows { return true } if err != nil { log.Printf("UsernameAvailable returned error: " + err.Error() + " Query was " + q) return false } return false } /* Check for username availability, add to database, send email */ func Register(username, email, ip string) bool { if !UsernameAvailable(username) { return false } log.Println("getting random bytes") pass := randBytes(16) log.Println("Generating hash") crypt, err := bcrypt.GenerateFromPassword(pass, 10) if err != nil { log.Printf("Bcrypt GenerateFromPassword failed? Pass is %s and error is %s\n", pass, err.Error()) return false } fmt.Println("db insert") q := fmt.Sprintf("insert into %s_auth (username, email, password, registration_date, registration_ip) values ($1, $2, $3, CURRENT_TIMESTAMP, $4)", c.TablePrefix) _, err = c.db.Exec(q, username, email, crypt, ip) if err != nil { log.Println("Register: insert failed: " + err.Error()) return false } if sendRegistrationEmail(email, username, string(pass)) { return true } log.Println("Failed to send registration email, deleting user.") q=fmt.Sprintf("delete from %s_auth where username=$1 AND password=$2", c.TablePrefix) _,err = c.db.Exec(q, username, string(crypt)) if err != nil { log.Println("Failed to delete new user " + username + " : " + err.Error()) } return false } func Login(username, password string) bool { q:=fmt.Sprintf("select password from %s_auth where username=$1",c.TablePrefix) var crypt string err:=c.db.Get(&crypt, q, username) if err != nil { log.Println("Failed login attempt for unknown username: " + username) return false } if bcrypt.CompareHashAndPassword([]byte(crypt), []byte(password)) == nil { log.Println("Failed password for " + username) return false } bump(username) return true } func bump(username string) { q:=fmt.Sprintf("update %s_auth set lastseen=CURRENT_TIMESTAMP where username=$1", c.TablePrefix) _, err :=c.db.Exec(q, username) if err != nil { log.Println("Error on user bump: " + err.Error()) } } func RecoverByUsername(u string) { var username, email string q:=fmt.Sprintf("select username, email from %s_auth where username=$1", c.TablePrefix) row:=c.db.QueryRow(q, u) err:=row.Scan(&username, &email) if err!=sql.ErrNoRows { recoverycode:=randBytes(10) qq:=fmt.Sprintf("update %s_auth set recoverycode=$1, recoverytime=NOW() where username=$2", c.TablePrefix) _,err:=c.db.Exec(qq, recoverycode, username) if err==nil { sendRecoveryEmail(email, username, string(recoverycode)) } } } func RecoverByEmail(e string) { var username, email string q:=fmt.Sprintf("select username, email from %s_auth where email=$1", c.TablePrefix) row:=c.db.QueryRow(q, e) err:=row.Scan(&username, &email) if err!=sql.ErrNoRows { recoverycode:=randBytes(10) qq:=fmt.Sprintf("update %s_auth set recoverycode=$1, recoverytime=NOW() where username=$2", c.TablePrefix) _,err:=c.db.Exec(qq, recoverycode, username) if err==nil { sendRecoveryEmail(email, username, string(recoverycode)) } } } func randBytes(n int) []byte { const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" 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 sendAlertEmail(username, recipient, message string) bool { data := struct { SiteName string FromEmail string UserName string Activity string }{ SiteName: c.SiteName, FromEmail: c.FromEmail, UserName: username, Activity: message, } var body bytes.Buffer err := c.Templates.Registration.Execute(&body, data) if err != nil { log.Printf("Alert template failed to execute: %v returned %s\n", data, err.Error()) return false } subject := fmt.Sprintf("New activity on %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 sendRecoveryEmail(recipient, username, code string) bool { data := struct { SiteName string FromEmail string UserName string RecoveryCode string }{ SiteName: c.SiteName, FromEmail: c.FromEmail, UserName: username, RecoveryCode: code, } 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 := `

Hey {{.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() }