mirror of
https://github.com/dnote/dnote
synced 2026-03-16 15:35:52 +01:00
Remove email verified flag
This commit is contained in:
parent
ca5af5e34a
commit
c82f41a489
20 changed files with 17 additions and 568 deletions
|
|
@ -65,28 +65,6 @@ func getNoreplySender(webURL string) (string, error) {
|
|||
return addr, nil
|
||||
}
|
||||
|
||||
// SendVerificationEmail sends verification email
|
||||
func (a *App) SendVerificationEmail(email, tokenValue string) error {
|
||||
body, err := a.EmailTemplates.Execute(mailer.EmailTypeEmailVerification, mailer.EmailKindText, mailer.EmailVerificationTmplData{
|
||||
Token: tokenValue,
|
||||
WebURL: a.WebURL,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "executing reset verification template for %s", email)
|
||||
}
|
||||
|
||||
from, err := GetSenderEmail(a.WebURL, defaultSender)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting the sender email")
|
||||
}
|
||||
|
||||
if err := a.EmailBackend.Queue("Verify your Dnote email address", from, []string{email}, mailer.EmailKindText, body); err != nil {
|
||||
return errors.Wrapf(err, "queueing email for %s", email)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendWelcomeEmail sends welcome email
|
||||
func (a *App) SendWelcomeEmail(email string) error {
|
||||
body, err := a.EmailTemplates.Execute(mailer.EmailTypeWelcome, mailer.EmailKindText, mailer.WelcomeTmplData{
|
||||
|
|
|
|||
|
|
@ -26,22 +26,6 @@ import (
|
|||
"github.com/dnote/dnote/pkg/server/testutils"
|
||||
)
|
||||
|
||||
func TestSendVerificationEmail(t *testing.T) {
|
||||
emailBackend := testutils.MockEmailbackendImplementation{}
|
||||
a := NewTest()
|
||||
a.EmailBackend = &emailBackend
|
||||
a.WebURL = "http://example.com"
|
||||
|
||||
if err := a.SendVerificationEmail("alice@example.com", "mockTokenValue"); err != nil {
|
||||
t.Fatal(err, "failed to perform")
|
||||
}
|
||||
|
||||
assert.Equalf(t, len(emailBackend.Emails), 1, "email queue count mismatch")
|
||||
assert.Equal(t, emailBackend.Emails[0].From, "noreply@example.com", "email sender mismatch")
|
||||
assert.DeepEqual(t, emailBackend.Emails[0].To, []string{"alice@example.com"}, "email sender mismatch")
|
||||
|
||||
}
|
||||
|
||||
func TestSendWelcomeEmail(t *testing.T) {
|
||||
emailBackend := testutils.MockEmailbackendImplementation{}
|
||||
a := NewTest()
|
||||
|
|
|
|||
|
|
@ -79,7 +79,4 @@ var (
|
|||
ErrInvalidPassword appError = "Invalid currnet password."
|
||||
// ErrEmailTooLong is an error for email length exceeding the limit
|
||||
ErrEmailTooLong appError = "Email is too long."
|
||||
|
||||
// ErrEmailAlreadyVerified is an error for trying to verify email that is already verified
|
||||
ErrEmailAlreadyVerified appError = "Email is already verified."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -232,8 +232,6 @@ func getStatusCode(err error) int {
|
|||
return http.StatusUnauthorized
|
||||
case app.ErrEmailTooLong:
|
||||
return http.StatusBadRequest
|
||||
case app.ErrEmailAlreadyVerified:
|
||||
return http.StatusConflict
|
||||
case app.ErrMissingToken:
|
||||
return http.StatusBadRequest
|
||||
case app.ErrExpiredToken:
|
||||
|
|
|
|||
|
|
@ -58,8 +58,6 @@ func NewWebRoutes(a *app.App, c *Controllers) []Route {
|
|||
{"PATCH", "/password-reset", c.Users.PasswordReset, true},
|
||||
{"GET", "/password-reset/{token}", c.Users.PasswordResetConfirm, true},
|
||||
{"POST", "/reset-token", c.Users.CreateResetToken, true},
|
||||
{"POST", "/verification-token", mw.Auth(a.DB, c.Users.CreateEmailVerificationToken, redirectGuest), true},
|
||||
{"GET", "/verify-email/{token}", mw.Auth(a.DB, c.Users.VerifyEmail, redirectGuest), true},
|
||||
{"PATCH", "/account/profile", mw.Auth(a.DB, c.Users.ProfileUpdate, nil), true},
|
||||
{"PATCH", "/account/password", mw.Auth(a.DB, c.Users.PasswordUpdate, nil), true},
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ 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"
|
||||
|
|
@ -80,10 +79,6 @@ func NewUsers(app *app.App, viewEngine *views.Engine) *Users {
|
|||
views.Config{Title: "About", Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"},
|
||||
"users/settings_about",
|
||||
),
|
||||
EmailVerificationView: viewEngine.NewView(app,
|
||||
views.Config{Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"},
|
||||
"users/email_verification",
|
||||
),
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +91,6 @@ type Users struct {
|
|||
AboutView *views.View
|
||||
PasswordResetView *views.View
|
||||
PasswordResetConfirmView *views.View
|
||||
EmailVerificationView *views.View
|
||||
app *app.App
|
||||
}
|
||||
|
||||
|
|
@ -599,10 +593,6 @@ func (u *Users) ProfileUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// check if email was changed
|
||||
if form.Email != account.Email.String {
|
||||
account.EmailVerified = false
|
||||
}
|
||||
account.Email.String = form.Email
|
||||
|
||||
if err := tx.Save(&account).Error; err != nil {
|
||||
|
|
@ -620,119 +610,3 @@ func (u *Users) ProfileUpdate(w http.ResponseWriter, r *http.Request) {
|
|||
views.RedirectAlert(w, r, "/", http.StatusFound, alert)
|
||||
}
|
||||
|
||||
func (u *Users) VerifyEmail(w http.ResponseWriter, r *http.Request) {
|
||||
vd := views.Data{}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
tokenValue := vars["token"]
|
||||
|
||||
if tokenValue == "" {
|
||||
handleHTMLError(w, r, app.ErrMissingToken, "Missing email verification token", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
var token database.Token
|
||||
if err := u.app.DB.
|
||||
Where("value = ? AND type = ?", tokenValue, database.TokenTypeEmailVerification).
|
||||
First(&token).Error; err != nil {
|
||||
handleHTMLError(w, r, app.ErrInvalidToken, "Finding token", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
if token.UsedAt != nil {
|
||||
handleHTMLError(w, r, app.ErrInvalidToken, "Token has already been used.", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
// Expire after ttl
|
||||
if time.Since(token.CreatedAt).Minutes() > 30 {
|
||||
handleHTMLError(w, r, app.ErrExpiredToken, "Token has expired.", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
var account database.Account
|
||||
if err := u.app.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil {
|
||||
handleHTMLError(w, r, err, "finding account", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
if account.EmailVerified {
|
||||
handleHTMLError(w, r, app.ErrEmailAlreadyVerified, "Already verified", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
tx := u.app.DB.Begin()
|
||||
account.EmailVerified = true
|
||||
if err := tx.Save(&account).Error; err != nil {
|
||||
tx.Rollback()
|
||||
handleHTMLError(w, r, err, "updating email_verified", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil {
|
||||
tx.Rollback()
|
||||
handleHTMLError(w, r, err, "updating reset token", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
var user database.User
|
||||
if err := u.app.DB.Where("id = ?", token.UserID).First(&user).Error; err != nil {
|
||||
handleHTMLError(w, r, err, "finding user", u.EmailVerificationView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := u.app.SignIn(&user)
|
||||
if err != nil {
|
||||
handleHTMLError(w, r, err, "Creating session", u.EmailVerificationView, vd)
|
||||
}
|
||||
|
||||
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.SettingView, 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.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
if account.EmailVerified {
|
||||
handleHTMLError(w, r, app.ErrEmailAlreadyVerified, "email is already verified.", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
if account.Email.String == "" {
|
||||
handleHTMLError(w, r, app.ErrEmailRequired, "email is empty.", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
tok, err := token.Create(u.app.DB, account.UserID, database.TokenTypeEmailVerification)
|
||||
if err != nil {
|
||||
handleHTMLError(w, r, err, "saving token", u.SettingView, vd)
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.app.SendVerificationEmail(account.Email.String, tok.Value); err != nil {
|
||||
if pkgErrors.Cause(err) == mailer.ErrSMTPNotConfigured {
|
||||
handleHTMLError(w, r, app.ErrInvalidSMTPConfig, "SMTP config is not configured correctly.", u.SettingView, vd)
|
||||
} else {
|
||||
handleHTMLError(w, r, err, "sending verification email", u.SettingView, vd)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
alert := views.Alert{
|
||||
Level: views.AlertLvlSuccess,
|
||||
Message: "Please check your email for the verification",
|
||||
}
|
||||
views.RedirectAlert(w, r, "/", http.StatusFound, alert)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -578,12 +578,6 @@ func TestResetPassword(t *testing.T) {
|
|||
Type: database.TokenTypeResetPassword,
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "preparing token")
|
||||
otherTok := database.Token{
|
||||
UserID: u.ID,
|
||||
Value: "somerandomvalue",
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&otherTok), "preparing another token")
|
||||
|
||||
s1 := database.Session{
|
||||
Key: "some-session-key-1",
|
||||
|
|
@ -618,16 +612,14 @@ func TestResetPassword(t *testing.T) {
|
|||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusFound, "Status code mismatch")
|
||||
|
||||
var resetToken, verificationToken database.Token
|
||||
var resetToken database.Token
|
||||
var account database.Account
|
||||
testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "finding reset token")
|
||||
testutils.MustExec(t, db.Where("value = ?", "somerandomvalue").First(&verificationToken), "finding reset token")
|
||||
testutils.MustExec(t, db.Where("id = ?", acc.ID).First(&account), "finding account")
|
||||
|
||||
assert.NotEqual(t, resetToken.UsedAt, nil, "reset_token UsedAt mismatch")
|
||||
passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte("newpassword"))
|
||||
assert.Equal(t, passwordErr, nil, "Password mismatch")
|
||||
assert.Equal(t, verificationToken.UsedAt, (*time.Time)(nil), "verificationToken UsedAt mismatch")
|
||||
|
||||
var s1Count, s2Count int64
|
||||
testutils.MustExec(t, db.Model(&database.Session{}).Where("id = ?", s1.ID).Count(&s1Count), "counting s1")
|
||||
|
|
@ -777,46 +769,6 @@ func TestResetPassword(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("using wrong type token: email_verification", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
// Setup
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
u := testutils.SetupUserData(db)
|
||||
acc := testutils.SetupAccountData(db, u, "alice@example.com", "somepassword")
|
||||
tok := database.Token{
|
||||
UserID: u.ID,
|
||||
Value: "MivFxYiSMMA4An9dP24DNQ==",
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "Failed to prepare reset_token")
|
||||
testutils.MustExec(t, db.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
|
||||
|
||||
dat := url.Values{}
|
||||
dat.Set("token", "MivFxYiSMMA4An9dP24DNQ==")
|
||||
dat.Set("password", "oldpassword")
|
||||
dat.Set("password_confirmation", "oldpassword")
|
||||
req := testutils.MakeFormReq(server.URL, "PATCH", "/password-reset", dat)
|
||||
|
||||
// Execute
|
||||
res := testutils.HTTPDo(t, req)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
|
||||
|
||||
var resetToken database.Token
|
||||
var account database.Account
|
||||
testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
|
||||
testutils.MustExec(t, db.Where("id = ?", acc.ID).First(&account), "failed to find account")
|
||||
|
||||
assert.Equal(t, acc.Password, account.Password, "password should not have been updated")
|
||||
assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateResetToken(t *testing.T) {
|
||||
|
|
@ -1018,9 +970,7 @@ func TestUpdateEmail(t *testing.T) {
|
|||
defer server.Close()
|
||||
|
||||
u := testutils.SetupUserData(db)
|
||||
acc := testutils.SetupAccountData(db, u, "alice@example.com", "pass1234")
|
||||
acc.EmailVerified = true
|
||||
testutils.MustExec(t, db.Save(&acc), "updating email_verified")
|
||||
testutils.SetupAccountData(db, u, "alice@example.com", "pass1234")
|
||||
|
||||
// Execute
|
||||
dat := url.Values{}
|
||||
|
|
@ -1039,7 +989,6 @@ func TestUpdateEmail(t *testing.T) {
|
|||
testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&account), "finding account")
|
||||
|
||||
assert.Equal(t, account.Email.String, "alice-new@example.com", "email mismatch")
|
||||
assert.Equal(t, account.EmailVerified, false, "EmailVerified mismatch")
|
||||
})
|
||||
|
||||
t.Run("password mismatch", func(t *testing.T) {
|
||||
|
|
@ -1053,9 +1002,7 @@ func TestUpdateEmail(t *testing.T) {
|
|||
defer server.Close()
|
||||
|
||||
u := testutils.SetupUserData(db)
|
||||
acc := testutils.SetupAccountData(db, u, "alice@example.com", "pass1234")
|
||||
acc.EmailVerified = true
|
||||
testutils.MustExec(t, db.Save(&acc), "updating email_verified")
|
||||
testutils.SetupAccountData(db, u, "alice@example.com", "pass1234")
|
||||
|
||||
// Execute
|
||||
dat := url.Values{}
|
||||
|
|
@ -1074,238 +1021,6 @@ func TestUpdateEmail(t *testing.T) {
|
|||
testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&account), "finding account")
|
||||
|
||||
assert.Equal(t, account.Email.String, "alice@example.com", "email mismatch")
|
||||
assert.Equal(t, account.EmailVerified, true, "EmailVerified mismatch")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyEmail(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
// Setup
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
user := testutils.SetupUserData(db)
|
||||
testutils.SetupAccountData(db, user, "alice@example.com", "pass1234")
|
||||
tok := database.Token{
|
||||
UserID: user.ID,
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
Value: "someTokenValue",
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "preparing token")
|
||||
|
||||
// Execute
|
||||
req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "")
|
||||
res := testutils.HTTPAuthDo(t, db, req, user)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusFound, "Status code mismatch")
|
||||
|
||||
var account database.Account
|
||||
var token database.Token
|
||||
var tokenCount int64
|
||||
testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
|
||||
testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
|
||||
testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
|
||||
|
||||
assert.Equal(t, account.EmailVerified, true, "email_verified mismatch")
|
||||
assert.NotEqual(t, token.Value, "", "token value should not have been updated")
|
||||
assert.Equal(t, tokenCount, int64(1), "token count mismatch")
|
||||
assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
|
||||
})
|
||||
|
||||
t.Run("used token", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
// Setup
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
user := testutils.SetupUserData(db)
|
||||
testutils.SetupAccountData(db, user, "alice@example.com", "pass1234")
|
||||
|
||||
usedAt := time.Now().Add(time.Hour * -11).UTC()
|
||||
tok := database.Token{
|
||||
UserID: user.ID,
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
Value: "someTokenValue",
|
||||
UsedAt: &usedAt,
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "preparing token")
|
||||
|
||||
// Execute
|
||||
req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "")
|
||||
res := testutils.HTTPAuthDo(t, db, req, user)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusBadRequest, "")
|
||||
|
||||
var account database.Account
|
||||
var token database.Token
|
||||
var tokenCount int64
|
||||
testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
|
||||
testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
|
||||
testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
|
||||
|
||||
assert.Equal(t, account.EmailVerified, false, "email_verified mismatch")
|
||||
assert.NotEqual(t, token.UsedAt, nil, "token used_at mismatch")
|
||||
assert.Equal(t, tokenCount, int64(1), "token count mismatch")
|
||||
assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
|
||||
})
|
||||
|
||||
t.Run("expired token", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
// Setup
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
user := testutils.SetupUserData(db)
|
||||
testutils.SetupAccountData(db, user, "alice@example.com", "pass1234")
|
||||
|
||||
tok := database.Token{
|
||||
UserID: user.ID,
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
Value: "someTokenValue",
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "preparing token")
|
||||
testutils.MustExec(t, db.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-31)), "Failed to prepare token created_at")
|
||||
|
||||
// Execute
|
||||
req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "")
|
||||
res := testutils.HTTPAuthDo(t, db, req, user)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusGone, "")
|
||||
|
||||
var account database.Account
|
||||
var token database.Token
|
||||
var tokenCount int64
|
||||
testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
|
||||
testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
|
||||
testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
|
||||
|
||||
assert.Equal(t, account.EmailVerified, false, "email_verified mismatch")
|
||||
assert.Equal(t, tokenCount, int64(1), "token count mismatch")
|
||||
assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token should have not been used")
|
||||
})
|
||||
|
||||
t.Run("already verified", func(t *testing.T) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
// Setup
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
user := testutils.SetupUserData(db)
|
||||
acc := testutils.SetupAccountData(db, user, "alice@example.com", "oldpass1234")
|
||||
acc.EmailVerified = true
|
||||
testutils.MustExec(t, db.Save(&acc), "preparing account")
|
||||
|
||||
tok := database.Token{
|
||||
UserID: user.ID,
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
Value: "someTokenValue",
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "preparing token")
|
||||
|
||||
// Execute
|
||||
req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "")
|
||||
res := testutils.HTTPAuthDo(t, db, req, user)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusConflict, "")
|
||||
|
||||
var account database.Account
|
||||
var token database.Token
|
||||
var tokenCount int64
|
||||
testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
|
||||
testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
|
||||
testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
|
||||
|
||||
assert.Equal(t, account.EmailVerified, true, "email_verified mismatch")
|
||||
assert.Equal(t, tokenCount, int64(1), "token count mismatch")
|
||||
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) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
|
||||
// Setup
|
||||
emailBackend := testutils.MockEmailbackendImplementation{}
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
a.EmailBackend = &emailBackend
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
user := testutils.SetupUserData(db)
|
||||
testutils.SetupAccountData(db, user, "alice@example.com", "pass1234")
|
||||
|
||||
// Execute
|
||||
req := testutils.MakeReq(server.URL, "POST", "/verification-token", "")
|
||||
res := testutils.HTTPAuthDo(t, db, req, user)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusFound, "status code mismatch")
|
||||
|
||||
var account database.Account
|
||||
var token database.Token
|
||||
var tokenCount int64
|
||||
testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
|
||||
testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
|
||||
testutils.MustExec(t, 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, int64(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) {
|
||||
db := testutils.InitMemoryDB(t)
|
||||
// Setup
|
||||
a := app.NewTest()
|
||||
a.Clock = clock.NewMock()
|
||||
a.DB = db
|
||||
server := MustNewServer(t, &a)
|
||||
defer server.Close()
|
||||
|
||||
user := testutils.SetupUserData(db)
|
||||
acc := testutils.SetupAccountData(db, user, "alice@example.com", "pass1234")
|
||||
acc.EmailVerified = true
|
||||
testutils.MustExec(t, db.Save(&acc), "preparing account")
|
||||
|
||||
// Execute
|
||||
req := testutils.MakeReq(server.URL, "POST", "/verification-token", "")
|
||||
res := testutils.HTTPAuthDo(t, db, req, user)
|
||||
|
||||
// Test
|
||||
assert.StatusCodeEquals(t, res, http.StatusConflict, "Status code mismatch")
|
||||
|
||||
var account database.Account
|
||||
var tokenCount int64
|
||||
testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
|
||||
testutils.MustExec(t, 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, int64(0), "token count mismatch")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ package database
|
|||
const (
|
||||
// TokenTypeResetPassword is a type of a token for reseting password
|
||||
TokenTypeResetPassword = "reset_password"
|
||||
// TokenTypeEmailVerification is a type of a token for verifying email
|
||||
TokenTypeEmailVerification = "email_verification"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -73,10 +73,9 @@ type User struct {
|
|||
// Account is a model for an account
|
||||
type Account struct {
|
||||
Model
|
||||
UserID int `gorm:"index"`
|
||||
Email NullString
|
||||
EmailVerified bool `gorm:"default:false"`
|
||||
Password NullString
|
||||
UserID int `gorm:"index"`
|
||||
Email NullString
|
||||
Password NullString
|
||||
}
|
||||
|
||||
// Token is a model for a token
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ var (
|
|||
EmailTypeResetPassword = "reset_password"
|
||||
// EmailTypeResetPasswordAlert represents a password change notification email
|
||||
EmailTypeResetPasswordAlert = "reset_password_alert"
|
||||
// EmailTypeEmailVerification represents an email verification email
|
||||
EmailTypeEmailVerification = "verify_email"
|
||||
// EmailTypeWelcome represents an welcome email
|
||||
EmailTypeWelcome = "welcome"
|
||||
)
|
||||
|
|
@ -79,10 +77,6 @@ func NewTemplates() Templates {
|
|||
if err != nil {
|
||||
panic(errors.Wrap(err, "initializing welcome template"))
|
||||
}
|
||||
verifyEmailText, err := initTextTmpl(EmailTypeEmailVerification)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "initializing email verification template"))
|
||||
}
|
||||
passwordResetText, err := initTextTmpl(EmailTypeResetPassword)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "initializing password reset template"))
|
||||
|
|
@ -95,7 +89,6 @@ func NewTemplates() Templates {
|
|||
T := Templates{}
|
||||
T.set(EmailTypeResetPassword, EmailKindText, passwordResetText)
|
||||
T.set(EmailTypeResetPasswordAlert, EmailKindText, passwordResetAlertText)
|
||||
T.set(EmailTypeEmailVerification, EmailKindText, verifyEmailText)
|
||||
T.set(EmailTypeWelcome, EmailKindText, welcomeText)
|
||||
|
||||
return T
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ func TestAllTemplatesInitialized(t *testing.T) {
|
|||
emailTypes := []string{
|
||||
EmailTypeResetPassword,
|
||||
EmailTypeResetPasswordAlert,
|
||||
EmailTypeEmailVerification,
|
||||
EmailTypeWelcome,
|
||||
}
|
||||
|
||||
|
|
@ -46,44 +45,6 @@ func TestAllTemplatesInitialized(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEmailVerificationEmail(t *testing.T) {
|
||||
testCases := []struct {
|
||||
token string
|
||||
webURL string
|
||||
}{
|
||||
{
|
||||
token: "someRandomToken1",
|
||||
webURL: "http://localhost:3000",
|
||||
},
|
||||
{
|
||||
token: "someRandomToken2",
|
||||
webURL: "http://localhost:3001",
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := NewTemplates()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("with WebURL %s", tc.webURL), func(t *testing.T) {
|
||||
dat := EmailVerificationTmplData{
|
||||
Token: tc.token,
|
||||
WebURL: tc.webURL,
|
||||
}
|
||||
body, err := tmpl.Execute(EmailTypeEmailVerification, EmailKindText, dat)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "executing"))
|
||||
}
|
||||
|
||||
if ok := strings.Contains(body, tc.webURL); !ok {
|
||||
t.Errorf("email body did not contain %s", tc.webURL)
|
||||
}
|
||||
if ok := strings.Contains(body, tc.token); !ok {
|
||||
t.Errorf("email body did not contain %s", tc.token)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetPasswordEmail(t *testing.T) {
|
||||
testCases := []struct {
|
||||
token string
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
Hi,
|
||||
|
||||
Welcome to Dnote! To verify your email, visit the following link:
|
||||
|
||||
{{ .WebURL }}/verify-email/{{ .Token }}
|
||||
|
|
@ -18,12 +18,6 @@
|
|||
|
||||
package mailer
|
||||
|
||||
// EmailVerificationTmplData is a template data for email verification emails
|
||||
type EmailVerificationTmplData struct {
|
||||
Token string
|
||||
WebURL string
|
||||
}
|
||||
|
||||
// EmailResetPasswordTmplData is a template data for reset password emails
|
||||
type EmailResetPasswordTmplData struct {
|
||||
AccountEmail string
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ func TestTokenAuth(t *testing.T) {
|
|||
user := testutils.SetupUserData(db)
|
||||
tok := database.Token{
|
||||
UserID: user.ID,
|
||||
Type: database.TokenTypeEmailVerification,
|
||||
Type: database.TokenTypeResetPassword,
|
||||
Value: "xpwFnc0MdllFUePDq9DLeQ==",
|
||||
}
|
||||
testutils.MustExec(t, db.Save(&tok), "preparing token")
|
||||
|
|
@ -193,7 +193,7 @@ func TestTokenAuth(t *testing.T) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
server := httptest.NewServer(TokenAuth(db, handler, database.TokenTypeEmailVerification, nil))
|
||||
server := httptest.NewServer(TokenAuth(db, handler, database.TokenTypeResetPassword, nil))
|
||||
defer server.Close()
|
||||
|
||||
t.Run("with token", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -24,16 +24,14 @@ import (
|
|||
|
||||
// Session represents user session
|
||||
type Session struct {
|
||||
UUID string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
UUID string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// New returns a new session for the given user
|
||||
func New(user database.User, account database.Account) Session {
|
||||
return Session{
|
||||
UUID: user.UUID,
|
||||
Email: account.Email.String,
|
||||
EmailVerified: account.EmailVerified,
|
||||
UUID: user.UUID,
|
||||
Email: account.Email.String,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ import (
|
|||
|
||||
func TestNew(t *testing.T) {
|
||||
u1 := database.User{UUID: "0f5f0054-d23f-4be1-b5fb-57673109e9cb"}
|
||||
a1 := database.Account{Email: database.ToNullString("alice@example.com"), EmailVerified: false}
|
||||
a1 := database.Account{Email: database.ToNullString("alice@example.com")}
|
||||
|
||||
u2 := database.User{UUID: "718a1041-bbe6-496e-bbe4-ea7e572c295e"}
|
||||
a2 := database.Account{Email: database.ToNullString("bob@example.com"), EmailVerified: false}
|
||||
a2 := database.Account{Email: database.ToNullString("bob@example.com")}
|
||||
|
||||
testCases := []struct {
|
||||
user database.User
|
||||
|
|
@ -52,9 +52,8 @@ func TestNew(t *testing.T) {
|
|||
// Execute
|
||||
got := New(tc.user, tc.account)
|
||||
expected := Session{
|
||||
UUID: tc.user.UUID,
|
||||
Email: tc.account.Email.String,
|
||||
EmailVerified: tc.account.EmailVerified,
|
||||
UUID: tc.user.UUID,
|
||||
Email: tc.account.Email.String,
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, got, expected, "result mismatch")
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestCreate(t *testing.T) {
|
|||
kind string
|
||||
}{
|
||||
{
|
||||
kind: database.TokenTypeEmailVerification,
|
||||
kind: database.TokenTypeResetPassword,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
{{define "yield"}}
|
||||
{{end}}
|
||||
|
|
@ -144,34 +144,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
<h3 class="setting-name">Email Verified</h3>
|
||||
</div>
|
||||
|
||||
<div class="setting-right">
|
||||
{{ if eq true false }} b{{end}}
|
||||
|
||||
{{if .EmailVerified}}
|
||||
Yes
|
||||
{{else}}
|
||||
No
|
||||
|
||||
<form action="/verification-token" method="POST" class="email-verification-form">
|
||||
<button
|
||||
id="T-send-verification-button"
|
||||
class="button button-second button-small"
|
||||
type="submit"
|
||||
>
|
||||
Send verification email
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-summary">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -116,8 +116,6 @@ func (v *View) Render(w http.ResponseWriter, r *http.Request, data *Data, status
|
|||
}
|
||||
if vd.Account != nil {
|
||||
vd.Yield["Email"] = vd.Account.Email.String
|
||||
vd.Yield["EmailVerified"] = vd.Account.EmailVerified
|
||||
vd.Yield["EmailVerified"] = vd.Account.EmailVerified
|
||||
}
|
||||
vd.Yield["CurrentPath"] = r.URL.Path
|
||||
vd.Yield["Standalone"] = buildinfo.Standalone
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue