Skip to content

Commit 0e1ed9d

Browse files
committed
fix
1 parent 6595241 commit 0e1ed9d

File tree

7 files changed

+91
-39
lines changed

7 files changed

+91
-39
lines changed

models/user/email_address.go

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -313,27 +313,27 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
313313
return UpdateUserCols(ctx, user, "rands")
314314
}
315315

316-
// MakeEmailPrimary sets primary email address of given user.
317-
func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
318-
has, err := db.GetEngine(ctx).Get(email)
319-
if err != nil {
316+
func MakeActiveEmailPrimary(ctx context.Context, emailID int64) error {
317+
return makeEmailPrimaryInternal(ctx, emailID, true)
318+
}
319+
320+
func MakeInactiveEmailPrimary(ctx context.Context, emailID int64) error {
321+
return makeEmailPrimaryInternal(ctx, emailID, false)
322+
}
323+
324+
func makeEmailPrimaryInternal(ctx context.Context, emailID int64, isActive bool) error {
325+
email := &EmailAddress{}
326+
if has, err := db.GetEngine(ctx).ID(emailID).Where(builder.Eq{"is_activated": isActive}).Get(email); err != nil {
320327
return err
321328
} else if !has {
322-
return ErrEmailAddressNotExist{Email: email.Email}
323-
}
324-
325-
if !email.IsActivated {
326-
return ErrEmailNotActivated
329+
return ErrEmailAddressNotExist{}
327330
}
328331

329332
user := &User{}
330-
has, err = db.GetEngine(ctx).ID(email.UID).Get(user)
331-
if err != nil {
333+
if has, err := db.GetEngine(ctx).ID(email.UID).Get(user); err != nil {
332334
return err
333335
} else if !has {
334-
return ErrUserNotExist{
335-
UID: email.UID,
336-
}
336+
return ErrUserNotExist{UID: email.UID}
337337
}
338338

339339
ctx, committer, err := db.TxContext(ctx)
@@ -365,6 +365,21 @@ func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
365365
return committer.Commit()
366366
}
367367

368+
// ChangeInactivePrimaryEmail replaces the inactive primary email of a given user
369+
func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, newEmailAddr string) error {
370+
return db.WithTx(ctx, func(ctx context.Context) error {
371+
_, err := db.GetEngine(ctx).Where(builder.Eq{"uid": uid, "lower_email": strings.ToLower(oldEmailAddr)}).Delete(&EmailAddress{})
372+
if err != nil {
373+
return err
374+
}
375+
newEmail, err := InsertEmailAddress(ctx, &EmailAddress{UID: uid, Email: newEmailAddr})
376+
if err != nil {
377+
return err
378+
}
379+
return MakeInactiveEmailPrimary(ctx, newEmail.ID)
380+
})
381+
}
382+
368383
// VerifyActiveEmailCode verifies active email code when active account
369384
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
370385
minutes := setting.Service.ActiveCodeLives

models/user/email_address_test.go

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,22 @@ func TestIsEmailUsed(t *testing.T) {
4545
func TestMakeEmailPrimary(t *testing.T) {
4646
assert.NoError(t, unittest.PrepareTestDatabase())
4747

48-
email := &user_model.EmailAddress{
49-
50-
}
51-
err := user_model.MakeEmailPrimary(db.DefaultContext, email)
48+
err := user_model.MakeActiveEmailPrimary(db.DefaultContext, 9999999)
5249
assert.Error(t, err)
53-
assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error())
50+
assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{})
5451

55-
email = &user_model.EmailAddress{
56-
57-
}
58-
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
52+
email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "[email protected]"})
53+
err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
5954
assert.Error(t, err)
6055
assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error())
6156

62-
email = &user_model.EmailAddress{
63-
64-
}
65-
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
57+
email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "[email protected]"})
58+
err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
6659
assert.Error(t, err)
6760
assert.True(t, user_model.IsErrUserNotExist(err))
6861

69-
email = &user_model.EmailAddress{
70-
71-
}
72-
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
62+
email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "[email protected]"})
63+
err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
7364
assert.NoError(t, err)
7465

7566
user, _ := user_model.GetUserByID(db.DefaultContext, int64(10))

options/locale/locale_en-US.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ forgot_password_title= Forgot Password
368368
forgot_password = Forgot password?
369369
sign_up_now = Need an account? Register now.
370370
sign_up_successful = Account was successfully created. Welcome!
371-
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process.
371+
confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
372372
must_change_password = Update your password
373373
allow_password_change = Require user to change password (recommended)
374374
reset_password_mail_sent_prompt = A confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the account recovery process.
@@ -378,6 +378,7 @@ prohibit_login = Sign In Prohibited
378378
prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator.
379379
resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again.
380380
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
381+
change_unconfirmed_mail_address = If your registration email address is incorrect, you can change it here and resend a new confirmation email.
381382
resend_mail = Click here to resend your activation email
382383
email_not_associate = The email address is not associated with any account.
383384
send_reset_mail = Send Account Recovery Email

routers/web/auth/auth.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ func sendActivateEmail(ctx *context.Context, u *user_model.User) {
646646
mailer.SendActivateAccountMail(ctx.Locale, u)
647647

648648
activeCodeLives := timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
649-
msgHTML := ctx.Locale.Tr("auth.confirmation_mail_sent_prompt", u.Email, activeCodeLives)
649+
msgHTML := ctx.Locale.Tr("auth.confirmation_mail_sent_prompt_ex", u.Email, activeCodeLives)
650650
renderActivationPromptMessage(ctx, msgHTML)
651651
}
652652

@@ -656,6 +656,10 @@ func renderActivationVerifyPassword(ctx *context.Context, code string) {
656656
ctx.HTML(http.StatusOK, TplActivate)
657657
}
658658

659+
func renderActivationChangeEmail(ctx *context.Context) {
660+
ctx.HTML(http.StatusOK, TplActivate)
661+
}
662+
659663
// Activate render activate user page
660664
func Activate(ctx *context.Context) {
661665
code := ctx.FormString("code")
@@ -674,7 +678,7 @@ func Activate(ctx *context.Context) {
674678
return
675679
}
676680

677-
// Resend confirmation email.
681+
// Resend confirmation email. FIXME: ideally this should be in a POST request
678682
sendActivateEmail(ctx, ctx.Doer)
679683
return
680684
}
@@ -698,7 +702,28 @@ func Activate(ctx *context.Context) {
698702
// ActivatePost handles account activation with password check
699703
func ActivatePost(ctx *context.Context) {
700704
code := ctx.FormString("code")
701-
if code == "" || (ctx.Doer != nil && ctx.Doer.IsActive) {
705+
if ctx.Doer != nil && ctx.Doer.IsActive {
706+
ctx.Redirect(setting.AppSubURL + "/user/activate") // it will redirect again to the correct page
707+
return
708+
}
709+
710+
if code == "" {
711+
newEmail := strings.TrimSpace(ctx.FormString("change_email"))
712+
if ctx.Doer != nil && newEmail != "" && !strings.EqualFold(ctx.Doer.Email, newEmail) {
713+
if user_model.ValidateEmail(newEmail) != nil {
714+
ctx.Flash.Error(ctx.Locale.Tr("form.email_invalid"), true)
715+
renderActivationChangeEmail(ctx)
716+
return
717+
}
718+
err := user_model.ChangeInactivePrimaryEmail(ctx, ctx.Doer.ID, ctx.Doer.Email, newEmail)
719+
if err != nil {
720+
ctx.Flash.Error(ctx.Locale.Tr("admin.emails.not_updated", newEmail), true)
721+
renderActivationChangeEmail(ctx)
722+
return
723+
}
724+
ctx.Doer.Email = newEmail
725+
}
726+
// FIXME: at the moment, GET request handles the "send confirmation email" action. But the old code does this redirect and then send a confirmation email.
702727
ctx.Redirect(setting.AppSubURL + "/user/activate")
703728
return
704729
}

routers/web/user/setting/account.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ func EmailPost(ctx *context.Context) {
9292
ctx.Data["Title"] = ctx.Tr("settings")
9393
ctx.Data["PageIsSettingsAccount"] = true
9494

95-
// Make emailaddress primary.
95+
// Make email address primary.
9696
if ctx.FormString("_method") == "PRIMARY" {
97-
if err := user_model.MakeEmailPrimary(ctx, &user_model.EmailAddress{ID: ctx.FormInt64("id")}); err != nil {
97+
if err := user_model.MakeActiveEmailPrimary(ctx, ctx.FormInt64("id")); err != nil {
9898
ctx.ServerError("MakeEmailPrimary", err)
9999
return
100100
}

templates/user/auth/activate.tmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
<input name="code" type="hidden" value="{{.ActivationCode}}">
2222
{{else}}
2323
<p>{{ctx.Locale.Tr "auth.has_unconfirmed_mail" .SignedUser.Name .SignedUser.Email}}</p>
24+
<details>
25+
<summary>{{ctx.Locale.Tr "auth.change_unconfirmed_mail_address"}}</summary>
26+
<div class="tw-py-2">
27+
<label for="change-email">{{ctx.Locale.Tr "email"}}</label>
28+
<input id="change-email" name="change_email" type="email" value="{{.SignedUser.Email}}">
29+
</div>
30+
</details>
2431
<div class="divider"></div>
2532
<div class="text right">
2633
<button class="ui primary button">{{ctx.Locale.Tr "auth.resend_mail"}}</button>

tests/integration/signup_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,26 @@ func TestSignupEmailActive(t *testing.T) {
107107
resp := MakeRequest(t, req, http.StatusOK)
108108
assert.Contains(t, resp.Body.String(), `A new confirmation email has been sent to <b>[email protected]</b>.`)
109109

110-
// access "user/active" means trying to re-send the activation email
110+
// access "user/activate" means trying to re-send the activation email
111111
session := loginUserWithPassword(t, "test-user-1", "password1")
112112
resp = session.MakeRequest(t, NewRequest(t, "GET", "/user/activate"), http.StatusOK)
113113
assert.Contains(t, resp.Body.String(), "You have already requested an activation email recently")
114114

115-
// access "user/active" with a valid activation code, then get the "verify password" page
115+
// access anywhere else will see a "Activate Your Account" prompt, and there is a chance to change email
116+
resp = session.MakeRequest(t, NewRequest(t, "GET", "/user/issues"), http.StatusOK)
117+
assert.Contains(t, resp.Body.String(), `<input id="change-email" name="change_email" `)
118+
119+
// post to "user/activate" with a new email
116120
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "test-user-1"})
121+
resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/activate", map[string]string{"change_email": "[email protected]"}), http.StatusSeeOther)
122+
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "test-user-1"})
123+
assert.Equal(t, "[email protected]", user.Email)
124+
email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "[email protected]"})
125+
assert.False(t, email.IsActivated)
126+
assert.True(t, email.IsPrimary)
127+
128+
// access "user/activate" with a valid activation code, then get the "verify password" page
129+
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "test-user-1"})
117130
activationCode := user.GenerateEmailActivateCode(user.Email)
118131
resp = session.MakeRequest(t, NewRequest(t, "GET", "/user/activate?code="+activationCode), http.StatusOK)
119132
assert.Contains(t, resp.Body.String(), `<input id="verify-password"`)

0 commit comments

Comments
 (0)