diff --git a/pkg/server/controllers/routes.go b/pkg/server/controllers/routes.go index a54e6df2..f3b93fe0 100644 --- a/pkg/server/controllers/routes.go +++ b/pkg/server/controllers/routes.go @@ -38,8 +38,9 @@ func NewWebRoutes(a *app.App, c *Controllers) []Route { {"GET", "/password-reset", c.Users.PasswordResetView.ServeHTTP, true}, {"PATCH", "/password-reset", c.Users.PasswordReset, true}, {"GET", "/password-reset/{token}", c.Users.PasswordResetConfirm, true}, - {"GET", "/verify-email", mw.Auth(a, c.Users.VerifyEmail, redirectGuest), true}, {"POST", "/reset-token", c.Users.CreateResetToken, true}, + {"POST", "/verification-token", mw.Auth(a, c.Users.CreateEmailVerificationToken, redirectGuest), true}, + {"GET", "/verify-email", mw.Auth(a, c.Users.VerifyEmail, redirectGuest), true}, {"PATCH", "/account/profile", mw.Auth(a, c.Users.ProfileUpdate, nil), true}, {"PATCH", "/account/password", mw.Auth(a, c.Users.PasswordUpdate, nil), true}, } diff --git a/pkg/server/controllers/users.go b/pkg/server/controllers/users.go index 87f658fa..790bf22e 100644 --- a/pkg/server/controllers/users.go +++ b/pkg/server/controllers/users.go @@ -11,6 +11,7 @@ import ( "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/log" + "github.com/dnote/dnote/pkg/server/mailer" "github.com/dnote/dnote/pkg/server/token" "github.com/dnote/dnote/pkg/server/views" "github.com/gorilla/mux" @@ -666,3 +667,51 @@ func (u *Users) VerifyEmail(w http.ResponseWriter, r *http.Request) { setSessionCookie(w, session.Key, session.ExpiresAt) http.Redirect(w, r, "/", http.StatusFound) } + +func (u *Users) CreateEmailVerificationToken(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + user := context.User(r.Context()) + if user == nil { + handleHTMLError(w, r, app.ErrLoginRequired, "No authenticated user found", u.EmailVerificationView, vd) + return + } + + var account database.Account + err := u.app.DB.Where("user_id = ?", user.ID).First(&account).Error + if err != nil { + handleHTMLError(w, r, err, "finding account", u.EmailVerificationView, vd) + return + } + + if account.EmailVerified { + handleHTMLError(w, r, app.ErrEmailAlreadyVerified, "email is already verified.", u.EmailVerificationView, vd) + return + } + if account.Email.String == "" { + handleHTMLError(w, r, app.ErrEmailRequired, "email is empty.", u.EmailVerificationView, vd) + return + } + + tok, err := token.Create(u.app.DB, account.UserID, database.TokenTypeEmailVerification) + if err != nil { + handleHTMLError(w, r, err, "saving token", u.EmailVerificationView, vd) + return + } + + if err := u.app.SendVerificationEmail(account.Email.String, tok.Value); err != nil { + if errors.Cause(err) == mailer.ErrSMTPNotConfigured { + handleHTMLError(w, r, app.ErrInvalidSMTPConfig, "SMTP config is not configured correctly.", u.EmailVerificationView, vd) + } else { + handleHTMLError(w, r, err, "sending verification email", u.EmailVerificationView, vd) + } + + return + } + + alert := views.Alert{ + Level: views.AlertLvlSuccess, + Message: "Please check your email for the verification", + } + views.RedirectAlert(w, r, "/", http.StatusFound, alert) +} diff --git a/pkg/server/controllers/users_test.go b/pkg/server/controllers/users_test.go index 4d6bf26a..b59b63cd 100644 --- a/pkg/server/controllers/users_test.go +++ b/pkg/server/controllers/users_test.go @@ -1314,3 +1314,75 @@ func TestVerifyEmail(t *testing.T) { assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token should have not been used") }) } + +func TestCreateVerificationToken(t *testing.T) { + t.Run("success", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + // Setup + emailBackend := testutils.MockEmailbackendImplementation{} + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + EmailBackend: &emailBackend, + }) + defer server.Close() + + user := testutils.SetupUserData() + testutils.SetupAccountData(user, "alice@example.com", "pass1234") + + // Execute + req := testutils.MakeReq(server.URL, "POST", "/verification-token", "") + res := testutils.HTTPAuthDo(t, req, user) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "status code mismatch") + + var account database.Account + var token database.Token + var tokenCount int + testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account") + testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token") + testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token") + + assert.Equal(t, account.EmailVerified, false, "email_verified should not have been updated") + assert.NotEqual(t, token.Value, "", "token Value mismatch") + assert.Equal(t, tokenCount, 1, "token count mismatch") + assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token UsedAt mismatch") + assert.Equal(t, len(emailBackend.Emails), 1, "email queue count mismatch") + }) + + t.Run("already verified", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + user := testutils.SetupUserData() + a := testutils.SetupAccountData(user, "alice@example.com", "pass1234") + a.EmailVerified = true + testutils.MustExec(t, testutils.DB.Save(&a), "preparing account") + + // Execute + req := testutils.MakeReq(server.URL, "POST", "/verification-token", "") + res := testutils.HTTPAuthDo(t, req, user) + + // Test + assert.StatusCodeEquals(t, res, http.StatusConflict, "Status code mismatch") + + var account database.Account + var tokenCount int + testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account") + testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token") + + assert.Equal(t, account.EmailVerified, true, "email_verified should not have been updated") + assert.Equal(t, tokenCount, 0, "token count mismatch") + }) +} diff --git a/pkg/server/views/users/settings.gohtml b/pkg/server/views/users/settings.gohtml index f56b537f..e7cbefaa 100644 --- a/pkg/server/views/users/settings.gohtml +++ b/pkg/server/views/users/settings.gohtml @@ -34,6 +34,8 @@