Browse Source

Add send register confirm mail

Unknown 11 years ago
parent
commit
de087c7b4a
10 changed files with 204 additions and 28 deletions
  1. 1 0
      README.md
  2. 4 1
      conf/app.ini
  3. 10 15
      models/user.go
  4. 5 6
      modules/auth/mail.go
  5. 2 2
      modules/base/conf.go
  6. 54 1
      modules/base/tool.go
  7. 7 0
      modules/mailer/mail.go
  8. 112 0
      modules/mailer/mailer.go
  9. 1 1
      routers/repo/repo.go
  10. 8 2
      routers/user/user.go

+ 1 - 0
README.md

@@ -39,6 +39,7 @@ There are two ways to install Gogs:
 
 ## Acknowledgments
 
+- Mail service is based on [WeTalk](https://github.com/beego/wetalk).
 - Logo inspired by [martini](https://github.com/martini-contrib).
 
 ## Contributors

+ 4 - 1
conf/app.ini

@@ -35,14 +35,17 @@ SECRET_KEY = !#@FDEWREWR&*(
 ACTIVE_CODE_LIVE_MINUTES = 180
 RESET_PASSWD_CODE_LIVE_MINUTES = 180
 ; User need to confirm e-mail for registration
-REGISTER_EMAIL_CONFIRM = true
+REGISTER_EMAIL_CONFIRM = false
 
 [mailer]
 ENABLED = false
 ; Name displayed in mail title
 SUBJECT = %(APP_NAME)s
 ; Mail server
+; Gmail: smtp.gmail.com:587
 HOST = 
+; Mail from address
+FROM = 
 ; Mailer user name and password
 USER = 
 PASSWD = 

+ 10 - 15
models/user.go

@@ -105,19 +105,19 @@ func GetUserSalt() string {
 }
 
 // RegisterUser creates record of a new user.
-func RegisterUser(user *User) (err error) {
+func RegisterUser(user *User) (*User, error) {
 	isExist, err := IsUserExist(user.Name)
 	if err != nil {
-		return err
+		return nil, err
 	} else if isExist {
-		return ErrUserAlreadyExist
+		return nil, ErrUserAlreadyExist
 	}
 
 	isExist, err = IsEmailUsed(user.Email)
 	if err != nil {
-		return err
+		return nil, err
 	} else if isExist {
-		return ErrEmailAlreadyUsed
+		return nil, ErrEmailAlreadyUsed
 	}
 
 	user.LowerName = strings.ToLower(user.Name)
@@ -126,22 +126,17 @@ func RegisterUser(user *User) (err error) {
 	user.Expired = time.Now().Add(3 * 24 * time.Hour)
 	user.Rands = GetUserSalt()
 	if err = user.EncodePasswd(); err != nil {
-		return err
+		return nil, err
 	} else if _, err = orm.Insert(user); err != nil {
-		return err
+		return nil, err
 	} else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil {
 		if _, err := orm.Id(user.Id).Delete(&User{}); err != nil {
-			return errors.New(fmt.Sprintf(
+			return nil, errors.New(fmt.Sprintf(
 				"both create userpath %s and delete table record faild: %v", user.Name, err))
 		}
-		return err
-	}
-
-	// Send confirmation e-mail.
-	if base.Service.RegisterEmailConfitm {
-
+		return nil, err
 	}
-	return nil
+	return user, nil
 }
 
 // UpdateUser updates user's information.

+ 5 - 6
modules/auth/mail.go

@@ -16,7 +16,7 @@ import (
 // create a time limit code for user active
 func CreateUserActiveCode(user *models.User, startInf interface{}) string {
 	hours := base.Service.ActiveCodeLives / 60
-	data := fmt.Sprintf("%d", user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
+	data := base.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
 	code := base.CreateTimeLimitCode(data, hours, startInf)
 
 	// add tail hex username
@@ -32,11 +32,10 @@ func SendRegisterMail(user *models.User) {
 	data := mailer.GetMailTmplData(user)
 	data["Code"] = code
 	body := base.RenderTemplate("mail/auth/register_success.html", data)
-	_, _, _ = code, subject, body
 
-	// msg := mailer.NewMailMessage([]string{user.Email}, subject, body)
-	// msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
+	msg := mailer.NewMailMessage([]string{user.Email}, subject, body)
+	msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
 
-	// // async send mail
-	// mailer.SendAsync(msg)
+	// async send mail
+	mailer.SendAsync(msg)
 }

+ 2 - 2
modules/base/conf.go

@@ -37,7 +37,7 @@ var (
 )
 
 var Service struct {
-	RegisterEmailConfitm bool
+	RegisterEmailConfirm bool
 	ActiveCodeLives      int
 	ResetPwdCodeLives    int
 }
@@ -138,7 +138,7 @@ func newRegisterService() {
 		log.Warn("Register Service: Mail Service is not enabled")
 		return
 	}
-	Service.RegisterEmailConfitm = true
+	Service.RegisterEmailConfirm = true
 	log.Info("Register Service Enabled")
 }
 

+ 54 - 1
modules/base/tool.go

@@ -13,6 +13,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"math"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -59,13 +60,14 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
 
 	// create sha1 encode string
 	sh := sha1.New()
-	sh.Write([]byte(data + SecretKey + startStr + endStr + fmt.Sprintf("%d", minutes)))
+	sh.Write([]byte(data + SecretKey + startStr + endStr + ToStr(minutes)))
 	encoded := hex.EncodeToString(sh.Sum(nil))
 
 	code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
 	return code
 }
 
+// TODO:
 func RenderTemplate(TplNames string, Data map[interface{}]interface{}) string {
 	// if beego.RunMode == "dev" {
 	// 	beego.BuildTemplate(beego.ViewsPath)
@@ -300,6 +302,57 @@ func DateFormat(t time.Time, format string) string {
 	return t.Format(format)
 }
 
+type argInt []int
+
+func (a argInt) Get(i int, args ...int) (r int) {
+	if i >= 0 && i < len(a) {
+		r = a[i]
+	}
+	if len(args) > 0 {
+		r = args[0]
+	}
+	return
+}
+
+// convert any type to string
+func ToStr(value interface{}, args ...int) (s string) {
+	switch v := value.(type) {
+	case bool:
+		s = strconv.FormatBool(v)
+	case float32:
+		s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
+	case float64:
+		s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
+	case int:
+		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+	case int8:
+		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+	case int16:
+		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+	case int32:
+		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+	case int64:
+		s = strconv.FormatInt(v, argInt(args).Get(0, 10))
+	case uint:
+		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+	case uint8:
+		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+	case uint16:
+		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+	case uint32:
+		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+	case uint64:
+		s = strconv.FormatUint(v, argInt(args).Get(0, 10))
+	case string:
+		s = v
+	case []byte:
+		s = string(v)
+	default:
+		s = fmt.Sprintf("%v", v)
+	}
+	return s
+}
+
 type Actioner interface {
 	GetOpType() int
 	GetActUserName() string

+ 7 - 0
modules/mailer/mail.go

@@ -9,6 +9,13 @@ import (
 	"github.com/gogits/gogs/modules/base"
 )
 
+// Create New mail message use MailFrom and MailUser
+func NewMailMessage(To []string, subject, body string) Message {
+	msg := NewHtmlMessage(To, base.MailService.User, subject, body)
+	msg.User = base.MailService.User
+	return msg
+}
+
 func GetMailTmplData(user *models.User) map[interface{}]interface{} {
 	data := make(map[interface{}]interface{}, 10)
 	data["AppName"] = base.AppName

+ 112 - 0
modules/mailer/mailer.go

@@ -0,0 +1,112 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package mailer
+
+import (
+	"fmt"
+	"net/smtp"
+	"strings"
+
+	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/log"
+)
+
+type Message struct {
+	To      []string
+	From    string
+	Subject string
+	Body    string
+	User    string
+	Type    string
+	Massive bool
+	Info    string
+}
+
+// create mail content
+func (m Message) Content() string {
+	// set mail type
+	contentType := "text/plain; charset=UTF-8"
+	if m.Type == "html" {
+		contentType = "text/html; charset=UTF-8"
+	}
+
+	// create mail content
+	content := "From: " + m.User + "<" + m.From +
+		">\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
+	return content
+}
+
+// Direct Send mail message
+func Send(msg Message) (int, error) {
+	log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
+	host := strings.Split(base.MailService.Host, ":")
+
+	// get message body
+	content := msg.Content()
+
+	auth := smtp.PlainAuth("", base.MailService.User, base.MailService.Passwd, host[0])
+
+	if len(msg.To) == 0 {
+		return 0, fmt.Errorf("empty receive emails")
+	}
+
+	if len(msg.Body) == 0 {
+		return 0, fmt.Errorf("empty email body")
+	}
+
+	if msg.Massive {
+		// send mail to multiple emails one by one
+		num := 0
+		for _, to := range msg.To {
+			body := []byte("To: " + to + "\r\n" + content)
+			err := smtp.SendMail(base.MailService.Host, auth, msg.From, []string{to}, body)
+			if err != nil {
+				return num, err
+			}
+			num++
+		}
+		return num, nil
+	} else {
+		body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
+
+		// send to multiple emails in one message
+		err := smtp.SendMail(base.MailService.Host, auth, msg.From, msg.To, body)
+		if err != nil {
+			return 0, err
+		} else {
+			return 1, nil
+		}
+	}
+}
+
+// Async Send mail message
+func SendAsync(msg Message) {
+	// TODO may be need pools limit concurrent nums
+	go func() {
+		num, err := Send(msg)
+		tos := strings.Join(msg.To, "; ")
+		info := ""
+		if err != nil {
+			if len(msg.Info) > 0 {
+				info = ", info: " + msg.Info
+			}
+			// log failed
+			log.Error(fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
+			return
+		}
+		log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
+	}()
+}
+
+// Create html mail message
+func NewHtmlMessage(To []string, From, Subject, Body string) Message {
+	return Message{
+		To:      To,
+		From:    From,
+		Subject: Subject,
+		Body:    Body,
+		Type:    "html",
+	}
+}

+ 1 - 1
routers/repo/repo.go

@@ -50,10 +50,10 @@ func SettingPost(ctx *middleware.Context) {
 
 		if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
 			ctx.Handle(200, "repo.Delete", err)
-			log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
 			return
 		}
 	}
 
+	log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
 	ctx.Render.Redirect("/", 302)
 }

+ 8 - 2
routers/user/user.go

@@ -134,10 +134,11 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
 		Name:     form.UserName,
 		Email:    form.Email,
 		Passwd:   form.Password,
-		IsActive: !base.Service.RegisterEmailConfitm,
+		IsActive: !base.Service.RegisterEmailConfirm,
 	}
 
-	if err := models.RegisterUser(u); err != nil {
+	var err error
+	if u, err = models.RegisterUser(u); err != nil {
 		switch err.Error() {
 		case models.ErrUserAlreadyExist.Error():
 			ctx.RenderWithErr("Username has been already taken", "user/signup", &form)
@@ -150,6 +151,11 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
 	}
 
 	log.Trace("%s User created: %s", ctx.Req.RequestURI, strings.ToLower(form.UserName))
+
+	// Send confirmation e-mail.
+	if base.Service.RegisterEmailConfirm {
+		auth.SendRegisterMail(u)
+	}
 	ctx.Render.Redirect("/user/login")
 }