| @@ -241,6 +241,8 @@ func runWeb(*cli.Context) { | |||||
| m.Get("", user.Settings) | m.Get("", user.Settings) | ||||
| m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) | m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) | ||||
| m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar) | m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar) | ||||
| m.Get("/email", user.SettingsEmails) | |||||
| m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) | |||||
| m.Get("/password", user.SettingsPassword) | m.Get("/password", user.SettingsPassword) | ||||
| m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost) | m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost) | ||||
| m.Get("/ssh", user.SettingsSSHKeys) | m.Get("/ssh", user.SettingsSSHKeys) | ||||
| @@ -252,6 +254,7 @@ func runWeb(*cli.Context) { | |||||
| m.Group("/user", func() { | m.Group("/user", func() { | ||||
| // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) | // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) | ||||
| m.Any("/activate", user.Activate) | m.Any("/activate", user.Activate) | ||||
| m.Any("/activate_email", user.ActivateEmail) | |||||
| m.Get("/email2user", user.Email2User) | m.Get("/email2user", user.Email2User) | ||||
| m.Get("/forget_password", user.ForgotPasswd) | m.Get("/forget_password", user.ForgotPasswd) | ||||
| m.Post("/forget_password", user.ForgotPasswdPost) | m.Post("/forget_password", user.ForgotPasswdPost) | ||||
| @@ -45,7 +45,7 @@ func init() { | |||||
| new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone), | new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone), | ||||
| new(Mirror), new(Release), new(LoginSource), new(Webhook), | new(Mirror), new(Release), new(LoginSource), new(Webhook), | ||||
| new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser), | new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser), | ||||
| new(Notice)) | |||||
| new(Notice), new(EmailAddress)) | |||||
| } | } | ||||
| func LoadModelsConfig() { | func LoadModelsConfig() { | ||||
| @@ -42,6 +42,8 @@ var ( | |||||
| ErrUserNotExist = errors.New("User does not exist") | ErrUserNotExist = errors.New("User does not exist") | ||||
| ErrUserNotKeyOwner = errors.New("User does not the owner of public key") | ErrUserNotKeyOwner = errors.New("User does not the owner of public key") | ||||
| ErrEmailAlreadyUsed = errors.New("E-mail already used") | ErrEmailAlreadyUsed = errors.New("E-mail already used") | ||||
| ErrEmailNotExist = errors.New("E-mail does not exist") | |||||
| ErrEmailNotActivated = errors.New("E-mail address has not been activated") | |||||
| ErrUserNameIllegal = errors.New("User name contains illegal characters") | ErrUserNameIllegal = errors.New("User name contains illegal characters") | ||||
| ErrLoginSourceNotExist = errors.New("Login source does not exist") | ErrLoginSourceNotExist = errors.New("Login source does not exist") | ||||
| ErrLoginSourceNotActived = errors.New("Login source is not actived") | ErrLoginSourceNotActived = errors.New("Login source is not actived") | ||||
| @@ -50,10 +52,11 @@ var ( | |||||
| // User represents the object of individual and member of organization. | // User represents the object of individual and member of organization. | ||||
| type User struct { | type User struct { | ||||
| Id int64 | |||||
| LowerName string `xorm:"UNIQUE NOT NULL"` | |||||
| Name string `xorm:"UNIQUE NOT NULL"` | |||||
| FullName string | |||||
| Id int64 | |||||
| LowerName string `xorm:"UNIQUE NOT NULL"` | |||||
| Name string `xorm:"UNIQUE NOT NULL"` | |||||
| FullName string | |||||
| // Email is the primary email address (to be used for communication). | |||||
| Email string `xorm:"UNIQUE(s) NOT NULL"` | Email string `xorm:"UNIQUE(s) NOT NULL"` | ||||
| Passwd string `xorm:"NOT NULL"` | Passwd string `xorm:"NOT NULL"` | ||||
| LoginType LoginType | LoginType LoginType | ||||
| @@ -93,6 +96,16 @@ type User struct { | |||||
| Members []*User `xorm:"-"` | Members []*User `xorm:"-"` | ||||
| } | } | ||||
| // EmailAdresses is the list of all email addresses of a user. Can contain the | |||||
| // primary email address, but is not obligatory | |||||
| type EmailAddress struct { | |||||
| Id int64 | |||||
| Uid int64 `xorm:"INDEX NOT NULL"` | |||||
| Email string `xorm:"UNIQUE NOT NULL"` | |||||
| IsActivated bool | |||||
| IsPrimary bool `xorm:"-"` | |||||
| } | |||||
| // DashboardLink returns the user dashboard page link. | // DashboardLink returns the user dashboard page link. | ||||
| func (u *User) DashboardLink() string { | func (u *User) DashboardLink() string { | ||||
| if u.IsOrganization() { | if u.IsOrganization() { | ||||
| @@ -248,6 +261,9 @@ func IsEmailUsed(email string) (bool, error) { | |||||
| if len(email) == 0 { | if len(email) == 0 { | ||||
| return false, nil | return false, nil | ||||
| } | } | ||||
| if has, err := x.Get(&EmailAddress{Email: email}); has || err != nil { | |||||
| return has, err | |||||
| } | |||||
| return x.Get(&User{Email: email}) | return x.Get(&User{Email: email}) | ||||
| } | } | ||||
| @@ -355,6 +371,25 @@ func VerifyUserActiveCode(code string) (user *User) { | |||||
| return nil | return nil | ||||
| } | } | ||||
| // verify active code when active account | |||||
| func VerifyActiveEmailCode(code, email string) *EmailAddress { | |||||
| minutes := setting.Service.ActiveCodeLives | |||||
| if user := getVerifyUser(code); user != nil { | |||||
| // time limit code | |||||
| prefix := code[:base.TimeLimitCodeLength] | |||||
| data := com.ToStr(user.Id) + email + user.LowerName + user.Passwd + user.Rands | |||||
| if base.VerifyTimeLimitCode(data, minutes, prefix) { | |||||
| emailAddress := &EmailAddress{Email: email} | |||||
| if has, _ := x.Get(emailAddress); has { | |||||
| return emailAddress | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // ChangeUserName changes all corresponding setting from old user name to new one. | // ChangeUserName changes all corresponding setting from old user name to new one. | ||||
| func ChangeUserName(u *User, newUserName string) (err error) { | func ChangeUserName(u *User, newUserName string) (err error) { | ||||
| if !IsLegalName(newUserName) { | if !IsLegalName(newUserName) { | ||||
| @@ -488,6 +523,10 @@ func DeleteUser(u *User) error { | |||||
| if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil { | if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| // Delete all alternative email addresses | |||||
| if _, err = x.Delete(&EmailAddress{Uid: u.Id}); err != nil { | |||||
| return err | |||||
| } | |||||
| // Delete all SSH keys. | // Delete all SSH keys. | ||||
| keys := make([]*PublicKey, 0, 10) | keys := make([]*PublicKey, 0, 10) | ||||
| if err = x.Find(&keys, &PublicKey{OwnerId: u.Id}); err != nil { | if err = x.Find(&keys, &PublicKey{OwnerId: u.Id}); err != nil { | ||||
| @@ -508,9 +547,12 @@ func DeleteUser(u *User) error { | |||||
| return err | return err | ||||
| } | } | ||||
| // DeleteInactivateUsers deletes all inactivate users. | |||||
| // DeleteInactivateUsers deletes all inactivate users and email addresses. | |||||
| func DeleteInactivateUsers() error { | func DeleteInactivateUsers() error { | ||||
| _, err := x.Where("is_active=?", false).Delete(new(User)) | _, err := x.Where("is_active=?", false).Delete(new(User)) | ||||
| if err == nil { | |||||
| _, err = x.Where("is_activated=?", false).Delete(new(EmailAddress)) | |||||
| } | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -584,6 +626,117 @@ func GetUserIdsByNames(names []string) []int64 { | |||||
| return ids | return ids | ||||
| } | } | ||||
| // Get all email addresses | |||||
| func GetEmailAddresses(uid int64) ([]*EmailAddress, error) { | |||||
| emails := make([]*EmailAddress, 0, 5) | |||||
| err := x.Where("owner_id=?", uid).Find(&emails) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| u, err := GetUserById(uid) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| isPrimaryFound := false | |||||
| for _, email := range emails { | |||||
| if email.Email == u.Email { | |||||
| isPrimaryFound = true | |||||
| email.IsPrimary = true | |||||
| } else { | |||||
| email.IsPrimary = false | |||||
| } | |||||
| } | |||||
| // We alway want the primary email address displayed, even if it's not in | |||||
| // the emailaddress table (yet) | |||||
| if !isPrimaryFound { | |||||
| emails = append(emails, &EmailAddress{Email: u.Email, IsActivated: true, IsPrimary: true}) | |||||
| } | |||||
| return emails, nil | |||||
| } | |||||
| func AddEmailAddress(email *EmailAddress) error { | |||||
| used, err := IsEmailUsed(email.Email) | |||||
| if err != nil { | |||||
| return err | |||||
| } else if used { | |||||
| return ErrEmailAlreadyUsed | |||||
| } | |||||
| _, err = x.Insert(email) | |||||
| return err | |||||
| } | |||||
| func (email *EmailAddress) Activate() error { | |||||
| email.IsActivated = true | |||||
| if _, err := x.Id(email.Id).AllCols().Update(email); err != nil { | |||||
| return err | |||||
| } | |||||
| if user, err := GetUserById(email.Uid); err != nil { | |||||
| return err | |||||
| } else { | |||||
| user.Rands = GetUserSalt() | |||||
| return UpdateUser(user) | |||||
| } | |||||
| } | |||||
| func DeleteEmailAddress(email *EmailAddress) error { | |||||
| has, err := x.Get(email) | |||||
| if err != nil { | |||||
| return err | |||||
| } else if !has { | |||||
| return ErrEmailNotExist | |||||
| } | |||||
| if _, err = x.Delete(email); err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func MakeEmailPrimary(email *EmailAddress) error { | |||||
| has, err := x.Get(email) | |||||
| if err != nil { | |||||
| return err | |||||
| } else if !has { | |||||
| return ErrEmailNotExist | |||||
| } | |||||
| if !email.IsActivated { | |||||
| return ErrEmailNotActivated | |||||
| } | |||||
| user := &User{Id: email.Uid} | |||||
| has, err = x.Get(user) | |||||
| if err != nil { | |||||
| return err | |||||
| } else if !has { | |||||
| return ErrUserNotExist | |||||
| } | |||||
| // Make sure the former primary email doesn't disappear | |||||
| former_primary_email := &EmailAddress{Email: user.Email} | |||||
| has, err = x.Get(former_primary_email) | |||||
| if err != nil { | |||||
| return err | |||||
| } else if !has { | |||||
| former_primary_email.Uid = user.Id | |||||
| former_primary_email.IsActivated = user.IsActive | |||||
| x.Insert(former_primary_email) | |||||
| } | |||||
| user.Email = email.Email | |||||
| _, err = x.Id(user.Id).AllCols().Update(user) | |||||
| return err | |||||
| } | |||||
| // UserCommit represents a commit with validation of user. | // UserCommit represents a commit with validation of user. | ||||
| type UserCommit struct { | type UserCommit struct { | ||||
| User *User | User *User | ||||
| @@ -629,14 +782,27 @@ func GetUserByEmail(email string) (*User, error) { | |||||
| if len(email) == 0 { | if len(email) == 0 { | ||||
| return nil, ErrUserNotExist | return nil, ErrUserNotExist | ||||
| } | } | ||||
| // First try to find the user by primary email | |||||
| user := &User{Email: strings.ToLower(email)} | user := &User{Email: strings.ToLower(email)} | ||||
| has, err := x.Get(user) | has, err := x.Get(user) | ||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| } else if !has { | |||||
| return nil, ErrUserNotExist | |||||
| } | } | ||||
| return user, nil | |||||
| if has { | |||||
| return user, nil | |||||
| } | |||||
| // Otherwise, check in alternative list for activated email addresses | |||||
| emailAddress := &EmailAddress{Email: strings.ToLower(email), IsActivated: true} | |||||
| has, err = x.Get(emailAddress) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| if has { | |||||
| return GetUserById(emailAddress.Uid) | |||||
| } | |||||
| return nil, ErrUserNotExist | |||||
| } | } | ||||
| // SearchUserByName returns given number of users whose name contains keyword. | // SearchUserByName returns given number of users whose name contains keyword. | ||||
| @@ -97,6 +97,14 @@ func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) b | |||||
| return validate(errs, ctx.Data, f, ctx.Locale) | return validate(errs, ctx.Data, f, ctx.Locale) | ||||
| } | } | ||||
| type AddEmailForm struct { | |||||
| Email string `form:"email" binding:"Required;Email;MaxSize(50)"` | |||||
| } | |||||
| func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||||
| } | |||||
| type ChangePasswordForm struct { | type ChangePasswordForm struct { | ||||
| OldPassword string `form:"old_password" binding:"Required;MinSize(6);MaxSize(255)"` | OldPassword string `form:"old_password" binding:"Required;MinSize(6);MaxSize(255)"` | ||||
| Password string `form:"password" binding:"Required;MinSize(6);MaxSize(255)"` | Password string `form:"password" binding:"Required;MinSize(6);MaxSize(255)"` | ||||
| @@ -21,6 +21,7 @@ import ( | |||||
| const ( | const ( | ||||
| AUTH_ACTIVE base.TplName = "mail/auth/active" | AUTH_ACTIVE base.TplName = "mail/auth/active" | ||||
| AUTH_ACTIVATE_EMAIL base.TplName = "mail/auth/activate_email" | |||||
| AUTH_REGISTER_SUCCESS base.TplName = "mail/auth/register_success" | AUTH_REGISTER_SUCCESS base.TplName = "mail/auth/register_success" | ||||
| AUTH_RESET_PASSWORD base.TplName = "mail/auth/reset_passwd" | AUTH_RESET_PASSWORD base.TplName = "mail/auth/reset_passwd" | ||||
| @@ -64,6 +65,17 @@ func CreateUserActiveCode(u *models.User, startInf interface{}) string { | |||||
| return code | return code | ||||
| } | } | ||||
| // create a time limit code for user active | |||||
| func CreateUserEmailActivateCode(u *models.User, e *models.EmailAddress, startInf interface{}) string { | |||||
| minutes := setting.Service.ActiveCodeLives | |||||
| data := com.ToStr(u.Id) + e.Email + u.LowerName + u.Passwd + u.Rands | |||||
| code := base.CreateTimeLimitCode(data, minutes, startInf) | |||||
| // add tail hex username | |||||
| code += hex.EncodeToString([]byte(u.LowerName)) | |||||
| return code | |||||
| } | |||||
| // Send user register mail with active code | // Send user register mail with active code | ||||
| func SendRegisterMail(r macaron.Render, u *models.User) { | func SendRegisterMail(r macaron.Render, u *models.User) { | ||||
| code := CreateUserActiveCode(u, nil) | code := CreateUserActiveCode(u, nil) | ||||
| @@ -103,6 +115,27 @@ func SendActiveMail(r macaron.Render, u *models.User) { | |||||
| SendAsync(&msg) | SendAsync(&msg) | ||||
| } | } | ||||
| // Send email to verify secondary email. | |||||
| func SendActivateEmail(r macaron.Render, user *models.User, email *models.EmailAddress) { | |||||
| code := CreateUserEmailActivateCode(user, email, nil) | |||||
| subject := "Verify your e-mail address" | |||||
| data := GetMailTmplData(user) | |||||
| data["Code"] = code | |||||
| data["Email"] = email.Email | |||||
| body, err := r.HTMLString(string(AUTH_ACTIVATE_EMAIL), data) | |||||
| if err != nil { | |||||
| log.Error(4, "mail.SendActiveMail(fail to render): %v", err) | |||||
| return | |||||
| } | |||||
| msg := NewMailMessage([]string{email.Email}, subject, body) | |||||
| msg.Info = fmt.Sprintf("UID: %d, send activate email to %s", user.Id, email.Email) | |||||
| SendAsync(&msg) | |||||
| } | |||||
| // Send reset password email. | // Send reset password email. | ||||
| func SendResetPasswdMail(r macaron.Render, u *models.User) { | func SendResetPasswdMail(r macaron.Render, u *models.User) { | ||||
| code := CreateUserActiveCode(u, nil) | code := CreateUserActiveCode(u, nil) | ||||
| @@ -345,6 +345,27 @@ func Activate(ctx *middleware.Context) { | |||||
| ctx.HTML(200, ACTIVATE) | ctx.HTML(200, ACTIVATE) | ||||
| } | } | ||||
| func ActivateEmail(ctx *middleware.Context) { | |||||
| code := ctx.Query("code") | |||||
| email_string := ctx.Query("email") | |||||
| // Verify code. | |||||
| if email := models.VerifyActiveEmailCode(code, email_string); email != nil { | |||||
| err := email.Activate() | |||||
| if err != nil { | |||||
| ctx.Handle(500, "ActivateEmail", err) | |||||
| } | |||||
| log.Trace("Email activated: %s", email.Email) | |||||
| ctx.Flash.Success(ctx.Tr("settings.activate_email_success")) | |||||
| } | |||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings/email") | |||||
| return | |||||
| } | |||||
| func ForgotPasswd(ctx *middleware.Context) { | func ForgotPasswd(ctx *middleware.Context) { | ||||
| ctx.Data["Title"] = ctx.Tr("auth.forgot_password") | ctx.Data["Title"] = ctx.Tr("auth.forgot_password") | ||||
| @@ -14,6 +14,7 @@ import ( | |||||
| "github.com/gogits/gogs/modules/auth" | "github.com/gogits/gogs/modules/auth" | ||||
| "github.com/gogits/gogs/modules/base" | "github.com/gogits/gogs/modules/base" | ||||
| "github.com/gogits/gogs/modules/log" | "github.com/gogits/gogs/modules/log" | ||||
| "github.com/gogits/gogs/modules/mailer" | |||||
| "github.com/gogits/gogs/modules/middleware" | "github.com/gogits/gogs/modules/middleware" | ||||
| "github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
| ) | ) | ||||
| @@ -21,6 +22,7 @@ import ( | |||||
| const ( | const ( | ||||
| SETTINGS_PROFILE base.TplName = "user/settings/profile" | SETTINGS_PROFILE base.TplName = "user/settings/profile" | ||||
| SETTINGS_PASSWORD base.TplName = "user/settings/password" | SETTINGS_PASSWORD base.TplName = "user/settings/password" | ||||
| SETTINGS_EMAILS base.TplName = "user/settings/email" | |||||
| SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys" | SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys" | ||||
| SETTINGS_SOCIAL base.TplName = "user/settings/social" | SETTINGS_SOCIAL base.TplName = "user/settings/social" | ||||
| SETTINGS_APPLICATIONS base.TplName = "user/settings/applications" | SETTINGS_APPLICATIONS base.TplName = "user/settings/applications" | ||||
| @@ -126,6 +128,112 @@ func SettingsAvatar(ctx *middleware.Context, form auth.UploadAvatarForm) { | |||||
| ctx.Flash.Success(ctx.Tr("settings.update_avatar_success")) | ctx.Flash.Success(ctx.Tr("settings.update_avatar_success")) | ||||
| } | } | ||||
| func SettingsEmails(ctx *middleware.Context) { | |||||
| ctx.Data["Title"] = ctx.Tr("settings") | |||||
| ctx.Data["PageIsUserSettings"] = true | |||||
| ctx.Data["PageIsSettingsEmails"] = true | |||||
| var err error | |||||
| ctx.Data["Emails"], err = models.GetEmailAddresses(ctx.User.Id) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "email.GetEmailAddresses", err) | |||||
| return | |||||
| } | |||||
| ctx.HTML(200, SETTINGS_EMAILS) | |||||
| } | |||||
| func SettingsEmailPost(ctx *middleware.Context, form auth.AddEmailForm) { | |||||
| ctx.Data["Title"] = ctx.Tr("settings") | |||||
| ctx.Data["PageIsUserSettings"] = true | |||||
| ctx.Data["PageIsSettingsEmails"] = true | |||||
| var err error | |||||
| ctx.Data["Emails"], err = models.GetEmailAddresses(ctx.User.Id) | |||||
| if err != nil { | |||||
| ctx.Handle(500, "email.GetEmailAddresses", err) | |||||
| return | |||||
| } | |||||
| // Delete Email address. | |||||
| if ctx.Query("_method") == "DELETE" { | |||||
| id := com.StrTo(ctx.Query("id")).MustInt64() | |||||
| if id <= 0 { | |||||
| return | |||||
| } | |||||
| if err = models.DeleteEmailAddress(&models.EmailAddress{Id: id}); err != nil { | |||||
| ctx.Handle(500, "DeleteEmail", err) | |||||
| } else { | |||||
| log.Trace("Email address deleted: %s", ctx.User.Name) | |||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings/email") | |||||
| } | |||||
| return | |||||
| } | |||||
| // Make emailaddress primary. | |||||
| if ctx.Query("_method") == "PRIMARY" { | |||||
| id := com.StrTo(ctx.Query("id")).MustInt64() | |||||
| if id <= 0 { | |||||
| return | |||||
| } | |||||
| if err = models.MakeEmailPrimary(&models.EmailAddress{Id: id}); err != nil { | |||||
| ctx.Handle(500, "MakeEmailPrimary", err) | |||||
| } else { | |||||
| log.Trace("Email made primary: %s", ctx.User.Name) | |||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings/email") | |||||
| } | |||||
| return | |||||
| } | |||||
| // Add Email address. | |||||
| if ctx.Req.Method == "POST" { | |||||
| if ctx.HasError() { | |||||
| ctx.HTML(200, SETTINGS_EMAILS) | |||||
| return | |||||
| } | |||||
| cleanEmail := strings.Replace(form.Email, "\n", "", -1) | |||||
| e := &models.EmailAddress{ | |||||
| Uid: ctx.User.Id, | |||||
| Email: cleanEmail, | |||||
| IsActivated: !setting.Service.RegisterEmailConfirm, | |||||
| } | |||||
| if err := models.AddEmailAddress(e); err != nil { | |||||
| if err == models.ErrEmailAlreadyUsed { | |||||
| ctx.RenderWithErr(ctx.Tr("form.email_has_been_used"), SETTINGS_EMAILS, &form) | |||||
| return | |||||
| } | |||||
| ctx.Handle(500, "email.AddEmailAddress", err) | |||||
| return | |||||
| } else { | |||||
| // Send confirmation e-mail | |||||
| if setting.Service.RegisterEmailConfirm { | |||||
| mailer.SendActivateEmail(ctx.Render, ctx.User, e) | |||||
| if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil { | |||||
| log.Error(4, "Set cache(MailResendLimit) fail: %v", err) | |||||
| } | |||||
| ctx.Flash.Success(ctx.Tr("settings.add_email_success_confirmation_email_sent")) | |||||
| } else { | |||||
| ctx.Flash.Success(ctx.Tr("settings.add_email_success")) | |||||
| } | |||||
| log.Trace("Email address added: %s", e.Email) | |||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings/email") | |||||
| return | |||||
| } | |||||
| } | |||||
| ctx.HTML(200, SETTINGS_EMAILS) | |||||
| } | |||||
| func SettingsPassword(ctx *middleware.Context) { | func SettingsPassword(ctx *middleware.Context) { | ||||
| ctx.Data["Title"] = ctx.Tr("settings") | ctx.Data["Title"] = ctx.Tr("settings") | ||||
| ctx.Data["PageIsUserSettings"] = true | ctx.Data["PageIsUserSettings"] = true | ||||
| @@ -0,0 +1,30 @@ | |||||
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |||||
| <title>{{.User.Name}}, please activate your e-mail address</title> | |||||
| </head> | |||||
| <body style="background:#eee;"> | |||||
| <div style="color:#333; font:12px/1.5 Tahoma,Arial,sans-serif;; text-shadow:1px 1px #fff; padding:0; margin:0;"> | |||||
| <div style="width:600px;margin:0 auto; padding:40px 0 20px;"> | |||||
| <div style="border:1px solid #d9d9d9;border-radius:3px; background:#fff; box-shadow: 0px 2px 5px rgba(0, 0, 0,.05); -webkit-box-shadow: 0px 2px 5px rgba(0, 0, 0,.05);"> | |||||
| <div style="padding: 20px 15px;"> | |||||
| <h1 style="font-size:20px; padding:10px 0 20px; margin:0; border-bottom:1px solid #ddd;"><img src="{{.AppUrl}}/img/favicon.png" style="height: 32px; margin-bottom: -10px;"> <a style="color:#333;text-decoration:none;" target="_blank" href="{{.AppUrl}}">{{.AppName}}</a></h1> | |||||
| <div style="padding:40px 15px;"> | |||||
| <div style="font-size:16px; padding-bottom:30px; font-weight:bold;"> | |||||
| Hi <span style="color: #00BFFF;">{{.User.Name}}</span>, | |||||
| </div> | |||||
| <div style="font-size:14px; padding:0 15px;"> | |||||
| <p style="margin:0;padding:0 0 9px 0;">Please click the following link to verify your e-mail address within <b>{{.ActiveCodeLives}} hours</b>.</p> | |||||
| <p style="margin:0;padding:0 0 9px 0;"> | |||||
| <a href="{{.AppUrl}}user/activate_email?code={{.Code}}&email={{.Email}}">{{.AppUrl}}user/activate_email?code={{.Code}}&email={{.Email}}</a> | |||||
| </p> | |||||
| <p style="margin:0;padding:0 0 9px 0;">Not working? Try copying and pasting it to your browser.</p> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div style="color:#aaa;padding:10px;text-align:center;"> | |||||
| © 2014 <a style="color:#888;text-decoration:none;" target="_blank" href="http://gogits.org">Gogs: Go Git Service</a> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,58 @@ | |||||
| {{template "ng/base/head" .}} | |||||
| {{template "ng/base/header" .}} | |||||
| <div id="setting-wrapper" class="main-wrapper"> | |||||
| <div id="user-profile-setting" class="container clear"> | |||||
| {{template "user/settings/nav" .}} | |||||
| <div class="grid-4-5 left"> | |||||
| <div class="setting-content"> | |||||
| {{template "ng/base/alert" .}} | |||||
| <div id="user-email-setting-content"> | |||||
| <div id="user-email-panel" class="panel panel-radius"> | |||||
| <div class="panel-header"> | |||||
| <strong>{{.i18n.Tr "settings.manage_emails"}}</strong> | |||||
| </div> | |||||
| <ul class="panel-body setting-list"> | |||||
| <li>{{.i18n.Tr "settings.email_desc"}}</li> | |||||
| {{range .Emails}} | |||||
| <li class="email clear"> | |||||
| <div class="email-content left"> | |||||
| <p><strong>{{.Email}}</strong></p> | |||||
| </div> | |||||
| {{if not .IsPrimary}} | |||||
| {{if .IsActivated}} | |||||
| <form action="{{AppSubUrl}}/user/settings/email" method="post"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <input name="_method" type="hidden" value="PRIMARY"> | |||||
| <input name="id" type="hidden" value="{{.Id}}"> | |||||
| <button class="right email-btn btn btn-green btn-radius btn-small">{{$.i18n.Tr "settings.primary_email"}}</button> | |||||
| </form> | |||||
| {{end}} | |||||
| <form action="{{AppSubUrl}}/user/settings/email" method="post"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <input name="_method" type="hidden" value="DELETE"> | |||||
| <input name="id" type="hidden" value="{{.Id}}"> | |||||
| <button class="right email-btn btn btn-red btn-radius btn-small">{{$.i18n.Tr "settings.delete_email"}}</button> | |||||
| </form> | |||||
| {{end}} | |||||
| </li> | |||||
| {{end}} | |||||
| <form action="{{AppSubUrl}}/user/settings/email" method="post"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <p class="panel-header"><strong>{{.i18n.Tr "settings.add_new_email"}}</strong></p> | |||||
| <p class="field"> | |||||
| <label class="req" for="email">{{.i18n.Tr "settings.email"}}</label> | |||||
| <input class="ipt ipt-radius" id="email" name="email" type="text" required /> | |||||
| </p> | |||||
| <p class="field"> | |||||
| <label></label> | |||||
| <button class="btn btn-green btn-radius" id="email-add-btn">{{.i18n.Tr "settings.add_email"}}</button> | |||||
| </p> | |||||
| </form> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "ng/base/footer" .}} | |||||
| @@ -4,6 +4,7 @@ | |||||
| <ul class="menu menu-vertical switching-list grid-1-5 left"> | <ul class="menu menu-vertical switching-list grid-1-5 left"> | ||||
| <li {{if .PageIsSettingsProfile}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings">{{.i18n.Tr "settings.profile"}}</a></li> | <li {{if .PageIsSettingsProfile}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings">{{.i18n.Tr "settings.profile"}}</a></li> | ||||
| <li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li> | <li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li> | ||||
| <li {{if .PageIsSettingsEmail}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/email">{{.i18n.Tr "settings.emails"}}</a></li> | |||||
| <li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li> | <li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li> | ||||
| <li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li> | <li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li> | ||||
| <li {{if .PageIsSettingsApplications}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/applications">{{.i18n.Tr "settings.applications"}}</a></li> | <li {{if .PageIsSettingsApplications}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/applications">{{.i18n.Tr "settings.applications"}}</a></li> | ||||