diff --git a/2fa.go b/2fa.go new file mode 100644 index 0000000..f027edb --- /dev/null +++ b/2fa.go @@ -0,0 +1,44 @@ +package scsusers + +import ( + "crypto/rand" + "errors" + "fmt" + "math/big" + "time" +) + +func generate2fa() string { + num, err := rand.Int(rand.Reader, big.NewInt(999999)) + if err != nil { + return "918273" + } + return fmt.Sprintf("%06d", num) + +} +func Validate2FA(u *UserData, challenge string) bool { + v, ok := u.Get("2fa") + return ok && v == challenge +} + +func Send2FA(u *UserData) error { + code := generate2fa() + u.Set("2fa", code) + u.Set("2faexpires", fmt.Sprintf("%d", time.Now().Add(15*time.Minute).Unix())) + email,ok:=u.Get("email") + if !ok { + return errors.New("send2fa: no email") + } + firstname,ok:=u.Get("firstname") + if !ok { + return errors.New("send2fa: no firstname") + } + lastname,ok:=u.Get("lastname") + if !ok { + return errors.New("send2fa: no lastname") + } + fullname:=fmt.Sprintf("%s %s", firstname, lastname) + return Send2faEmail(email, fullname, code) + +} + diff --git a/emails.go b/emails.go new file mode 100644 index 0000000..c252b0d --- /dev/null +++ b/emails.go @@ -0,0 +1,196 @@ +package scsusers + +import ( + "bytes" + "crypto/rand" + "database/sql" + "encoding/base32" + "fmt" + "log" + "net/smtp" + "strings" + "time" +) + +func RecoverByUsername(username string) { + var email string + username = strings.ToLower(username) + + q := fmt.Sprintf("select email from %s_auth where username = ?", c.TablePrefix) + err := c.db.Get(&email, q, username) + if err != sql.ErrNoRows { + recoverycode := randBytes(16) + qq := fmt.Sprintf("update %s_auth set recoverycode=?, recoverytime=NOW() where username = ?", 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 from %s_auth where email = ?", c.TablePrefix) + err := c.db.Get(&username, q, e) + if err != sql.ErrNoRows { + recoverycode := randBytes(16) + qq := fmt.Sprintf("update %s_auth set recoverycode=?, recoverytime=NOW() where username = ?", 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("scsusers.sendRegistrationEmail: Registration template failed to execute: %v returned %s\n", data, err.Error()) + return false + } + subject := fmt.Sprintf("Welcome to %s", c.SiteName) + err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) + if err != nil { + log.Printf("scsusers.SendRegistrationEmail: 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.Alert.Execute(&body, data) + if err != nil { + log.Printf("scsusers.sendAlertEmail: Alert template failed to execute: %v returned %s\n", data, err.Error()) + return false + } + + subject := fmt.Sprintf("new Activity Notification on %s", c.SiteName) + err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) + if err != nil { + log.Printf("scsusers.sendAlertEmail: 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("scsusers.sendRecoveryEmail: Recovery template failed to execute: %v returned %s\n", data, err.Error()) + return false + } + subject := fmt.Sprintf("Account recovery at %s", c.SiteName) + err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) + if err != nil { + log.Printf("scsusers.sendRecoveryEmail: Error sending mail to %s: %s\n", recipient, err.Error()) + return false + } + return true +} + +func Send2faEmail(recipient, fullname, code string) error { + data := struct { + SiteName string + FromEmail string + FullName string + Code string + }{ + SiteName: c.SiteName, + FromEmail: c.FromEmail, + FullName: fullname, + Code: code, + } + var body bytes.Buffer + err := c.Templates.TwoFA.Execute(&body, data) + if err != nil { + return fmt.Errorf("scsusers.send2fayEmail: 2fa template failed to execute: %v returned %s", data, err.Error()) + } + subject := fmt.Sprintf("Two Factor Authentication Code at %s", c.SiteName) + err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) + return err +} + +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 + } + + date := time.Now() + msg := "To: " + to + "\r\n" + + "From: " + from + "\r\n" + + "Subject: " + subject + "\r\n" + + "Content-Type: text/html; charset=\"UTF-8\"\r\n" + + "Date: " + date.Format(time.RFC1123Z) + "\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/main.go b/main.go index eead13e..a6c685d 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,12 @@ package scsusers import ( - "bytes" - "crypto/rand" "database/sql" - "encoding/base32" "encoding/json" "fmt" "html/template" "log" - "net/smtp" "strings" - "time" "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" @@ -21,6 +16,7 @@ type templates struct { Registration *template.Template Alert *template.Template Recovery *template.Template + TwoFA *template.Template } type config struct { @@ -59,6 +55,7 @@ func Init(dbin *sqlx.DB, tp, sitename, fromaddr, smtpserver string) { SetRegistrationTemplate("") SetAlertTemplate("") SetRecoveryTemplate("") + Set2faTemplate("") } func UsernameAvailable(username string) bool { @@ -134,16 +131,16 @@ func Get(username string) (*UserData, bool) { return nil, false } q = fmt.Sprintf("select meta_key, meta_value, id from %s_meta where user=?", c.TablePrefix) - rows,err:=c.db.Query(q,u.UserID ) + rows, err := c.db.Query(q, u.UserID) if err != nil && err != sql.ErrNoRows { log.Printf("scsuser.Get: select: %s", err.Error()) - return u,false + return u, false } for rows.Next() { var m metadata - rows.Scan(m.Key,m.Value,m.ID) - u.Meta[m.Key]=m + rows.Scan(m.Key, m.Value, m.ID) + u.Meta[m.Key] = m } return u, true } @@ -307,218 +304,3 @@ func SetMeta(username string, metakey string, metavalue string) { log.Printf("scsusers.SetMeta: %s %s %s %s\n", username, metakey, metavalue, err.Error()) } } - -func RecoverByUsername(username string) { - var email string - username = strings.ToLower(username) - - q := fmt.Sprintf("select email from %s_auth where username = ?", c.TablePrefix) - err := c.db.Get(&email, q, username) - if err != sql.ErrNoRows { - recoverycode := randBytes(16) - qq := fmt.Sprintf("update %s_auth set recoverycode=?, recoverytime=NOW() where username = ?", 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 from %s_auth where email ILIKE ?", c.TablePrefix) - err := c.db.Get(&username, q, e) - if err != sql.ErrNoRows { - recoverycode := randBytes(16) - qq := fmt.Sprintf("update %s_auth set recoverycode=?, recoverytime=NOW() where username = ?", 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("scsusers.sendRegistrationEmail: Registration template failed to execute: %v returned %s\n", data, err.Error()) - return false - } - subject := fmt.Sprintf("Welcome to %s", c.SiteName) - err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) - if err != nil { - log.Printf("scsusers.SendRegistrationEmail: 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.Alert.Execute(&body, data) - if err != nil { - log.Printf("scsusers.sendAlertEmail: Alert template failed to execute: %v returned %s\n", data, err.Error()) - return false - } - - subject := fmt.Sprintf("new Activity Notification on %s", c.SiteName) - err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) - if err != nil { - log.Printf("scsusers.sendAlertEmail: 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("scsusers.sendRecoveryEmail: Recovery template failed to execute: %v returned %s\n", data, err.Error()) - return false - } - subject := fmt.Sprintf("Account recovery at %s", c.SiteName) - err = SendMail(c.SMTPServer, c.FromEmail, subject, body.String(), recipient) - if err != nil { - log.Printf("scsusers.sendRecoveryEmail: 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.