diff --git a/conf/app.ini b/conf/app.ini index debfcf93b..82d78d233 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -1,5 +1,6 @@ ; App name that shows on every page title APP_NAME = Gogs: Go Git Service +APP_LOGO = img/favicon.png ; !!MUST CHANGE TO YOUR USER NAME!! RUN_USER = lunny ; Either "dev", "prod" or "test", default is "dev" @@ -11,7 +12,8 @@ LANG_IGNS = Google Go|C|Python|Ruby|C Sharp LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|BSD (3-Clause) License [server] -DOMAIN = gogits.org +DOMAIN = localhost +ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/ HTTP_ADDR = HTTP_PORT = 3000 @@ -27,7 +29,13 @@ SSL_MODE = disable [security] ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! -USER_PASSWD_SALT = !#@FDEWREWR&*( +SECRET_KEY = !#@FDEWREWR&*( + +[service] +ACTIVE_CODE_LIVE_MINUTES = 180 +RESET_PASSWD_CODE_LIVE_MINUTES = 180 +; User need to confirm e-mail for registration +REGISTER_EMAIL_CONFIRM = true [mailer] ENABLED = false diff --git a/models/user.go b/models/user.go index 80af9bd4b..579f6a748 100644 --- a/models/user.go +++ b/models/user.go @@ -19,14 +19,6 @@ import ( "github.com/gogits/gogs/modules/base" ) -var ( - UserPasswdSalt string -) - -func init() { - UserPasswdSalt = base.Cfg.MustValue("security", "USER_PASSWD_SALT") -} - // User types. const ( UT_INDIVIDUAL = iota + 1 @@ -56,6 +48,9 @@ type User struct { AvatarEmail string `xorm:"not null"` Location string Website string + IsActive bool + Rands string `xorm:"VARCHAR(10)"` + Expired time.Time Created time.Time `xorm:"created"` Updated time.Time `xorm:"updated"` } @@ -104,6 +99,11 @@ func (user *User) NewGitSig() *git.Signature { } } +// return a user salt token +func GetUserSalt() string { + return base.GetRandomString(10) +} + // RegisterUser creates record of a new user. func RegisterUser(user *User) (err error) { isExist, err := IsUserExist(user.Name) @@ -123,6 +123,8 @@ func RegisterUser(user *User) (err error) { user.LowerName = strings.ToLower(user.Name) user.Avatar = base.EncodeMd5(user.Email) user.AvatarEmail = user.Email + user.Expired = time.Now().Add(3 * 24 * time.Hour) + user.Rands = GetUserSalt() if err = user.EncodePasswd(); err != nil { return err } else if _, err = orm.Insert(user); err != nil { @@ -134,6 +136,11 @@ func RegisterUser(user *User) (err error) { } return err } + + // Send confirmation e-mail. + if base.Service.RegisterEmailConfitm { + + } return nil } @@ -183,7 +190,7 @@ func DeleteUser(user *User) error { // EncodePasswd encodes password to safe format. func (user *User) EncodePasswd() error { - newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(UserPasswdSalt), 16384, 8, 1, 64) + newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64) user.Passwd = fmt.Sprintf("%x", newPasswd) return err } diff --git a/modules/auth/mail.go b/modules/auth/mail.go new file mode 100644 index 000000000..6f6bf20a0 --- /dev/null +++ b/modules/auth/mail.go @@ -0,0 +1,42 @@ +// 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 auth + +import ( + "encoding/hex" + "fmt" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/mailer" +) + +// 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 + code := base.CreateTimeLimitCode(data, hours, startInf) + + // add tail hex username + code += hex.EncodeToString([]byte(user.LowerName)) + return code +} + +// Send user register mail with active code +func SendRegisterMail(user *models.User) { + code := CreateUserActiveCode(user, nil) + subject := "Register success, Welcome" + + 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) + + // // async send mail + // mailer.SendAsync(msg) +} diff --git a/modules/base/conf.go b/modules/base/conf.go index 9f6de56b9..ee5638ed6 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -28,11 +28,20 @@ type Mailer struct { var ( AppVer string AppName string + AppLogo string + AppUrl string Domain string + SecretKey string Cfg *goconfig.ConfigFile MailService *Mailer ) +var Service struct { + RegisterEmailConfitm bool + ActiveCodeLives int + ResetPwdCodeLives int +} + func exeDir() (string, error) { file, err := exec.LookPath(os.Args[0]) if err != nil { @@ -54,6 +63,11 @@ var logLevels = map[string]string{ "Critical": "5", } +func newService() { + Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180) + Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180) +} + func newLogService() { // Get and check log mode. mode := Cfg.MustValue("log", "MODE", "console") @@ -117,6 +131,17 @@ func newMailService() { } } +func newRegisterService() { + if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") { + return + } else if MailService == nil { + log.Warn("Register Service: Mail Service is not enabled") + return + } + Service.RegisterEmailConfitm = true + log.Info("Register Service Enabled") +} + func init() { var err error workDir, err := exeDir() @@ -143,9 +168,13 @@ func init() { Cfg.BlockMode = false AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service") + AppLogo = Cfg.MustValue("", "APP_LOGO", "img/favicon.png") + AppUrl = Cfg.MustValue("server", "ROOT_URL") Domain = Cfg.MustValue("server", "DOMAIN") + SecretKey = Cfg.MustValue("security", "SECRET_KEY") // Extensions. newLogService() newMailService() + newRegisterService() } diff --git a/modules/base/tool.go b/modules/base/tool.go index 046b2c517..2a989b377 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -7,6 +7,8 @@ package base import ( "bytes" "crypto/md5" + "crypto/rand" + "crypto/sha1" "encoding/hex" "encoding/json" "fmt" @@ -22,6 +24,66 @@ func EncodeMd5(str string) string { return hex.EncodeToString(m.Sum(nil)) } +// Random generate string +func GetRandomString(n int) string { + const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + var bytes = make([]byte, n) + rand.Read(bytes) + for i, b := range bytes { + bytes[i] = alphanum[b%byte(len(alphanum))] + } + return string(bytes) +} + +// create a time limit code +// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string +func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string { + format := "YmdHi" + + var start, end time.Time + var startStr, endStr string + + if startInf == nil { + // Use now time create code + start = time.Now() + startStr = DateFormat(start, format) + } else { + // use start string create code + startStr = startInf.(string) + start, _ = DateParse(startStr, format) + startStr = DateFormat(start, format) + } + + end = start.Add(time.Minute * time.Duration(minutes)) + endStr = DateFormat(end, format) + + // create sha1 encode string + sh := sha1.New() + sh.Write([]byte(data + SecretKey + startStr + endStr + fmt.Sprintf("%d", minutes))) + encoded := hex.EncodeToString(sh.Sum(nil)) + + code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) + return code +} + +func RenderTemplate(TplNames string, Data map[interface{}]interface{}) string { + // if beego.RunMode == "dev" { + // beego.BuildTemplate(beego.ViewsPath) + // } + + // ibytes := bytes.NewBufferString("") + // if _, ok := beego.BeeTemplates[TplNames]; !ok { + // panic("can't find templatefile in the path:" + TplNames) + // } + // err := beego.BeeTemplates[TplNames].ExecuteTemplate(ibytes, TplNames, Data) + // if err != nil { + // beego.Trace("template Execute err:", err) + // } + // icontent, _ := ioutil.ReadAll(ibytes) + // return string(icontent) + return "not implement yet" +} + // AvatarLink returns avatar link by given e-mail. func AvatarLink(email string) string { return "http://1.gravatar.com/avatar/" + EncodeMd5(email) diff --git a/modules/mailer/mail.go b/modules/mailer/mail.go new file mode 100644 index 000000000..fe74af9ee --- /dev/null +++ b/modules/mailer/mail.go @@ -0,0 +1,24 @@ +// 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 ( + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/base" +) + +func GetMailTmplData(user *models.User) map[interface{}]interface{} { + data := make(map[interface{}]interface{}, 10) + data["AppName"] = base.AppName + data["AppVer"] = base.AppVer + data["AppUrl"] = base.AppUrl + data["AppLogo"] = base.AppLogo + data["ActiveCodeLives"] = base.Service.ActiveCodeLives + data["ResetPwdCodeLives"] = base.Service.ResetPwdCodeLives + if user != nil { + data["User"] = user + } + return data +} diff --git a/modules/middleware/logger.go b/modules/middleware/logger.go index 9ae620eba..dcf852460 100644 --- a/modules/middleware/logger.go +++ b/modules/middleware/logger.go @@ -37,6 +37,8 @@ func Logger() martini.Handler { content = fmt.Sprintf("\033[1;33m%s\033[0m", content) case 404: content = fmt.Sprintf("\033[1;31m%s\033[0m", content) + case 500: + content = fmt.Sprintf("\033[1;36m%s\033[0m", content) } } log.Println(content) diff --git a/routers/user/user.go b/routers/user/user.go index fc56997b5..05aeac60e 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -131,9 +131,10 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) { } u := &models.User{ - Name: form.UserName, - Email: form.Email, - Passwd: form.Password, + Name: form.UserName, + Email: form.Email, + Passwd: form.Password, + IsActive: !base.Service.RegisterEmailConfitm, } if err := models.RegisterUser(u); err != nil {