package scsusers import ( "bytes" "database/sql" "fmt" "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" "html/template" "log" "crypto/rand" "encoding/json" "encoding/base32" "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 } type UserData struct { Username string `json:"username"` UserPerms map[string]string `json:"perms"` UserSettings map[string]string `json:"settings"` } 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 { if len(username)==0 { return false } 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 { log.Println("Attempting login for "+ username) 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 LoadUser(username string) (UserData, error) { var u UserData q:=fmt.Sprintf("select data from %s_userdata where username=$1", c.TablePrefix) var d string err:=c.db.Get(d, q, username) if err != nil { log.Println("Error loading user: " + err.Error()) return u, err } err=json.Unmarshal([]byte(d), &u) if err != nil { log.Printf("Error decoding json on user %s. Unmarshal returned %s\n", err.Error()) } return u,err } func SaveUser(username string, d UserData) bool { q:=fmt.Sprintf("update %s_userdata set data=$1 where username=$2") j, err:=json.Marshal(d) if err != nil { log.Printf("SaveUser: json.Marshal failed for username %s : %s\n", username, err.Error()) return false } _, err=c.db.Exec(q, username, j) if err != nil { log.Printf("Saveuser: db.Exec failed for username %s : %s\n", username, err.Error()) return false } 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(16) 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(16) 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 { randomBytes := make([]byte, 32) _, err := rand.Read(randomBytes) if err != nil { panic(err) } return []byte(base32.StdEncoding.EncodeToString(randomBytes)[:n]) } 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.