diff --git a/README.md b/README.md index 515bfba..33cf019 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,7 @@ TODO ## Demo TODO + +## TODOs + +- Cancel button when email validation pending or expired diff --git a/adapters/secondary/gateways/userRepository.go b/adapters/secondary/gateways/userRepository.go index 37326e0..2551078 100644 --- a/adapters/secondary/gateways/userRepository.go +++ b/adapters/secondary/gateways/userRepository.go @@ -44,10 +44,11 @@ func (u *UserRepository) Create(user domain.User) (domain.User, error) { hashedVerificationCode, _ := bcrypt.GenerateFromPassword([]byte(user.VerificationCode), 12) creationResult := u.db.Create(&entity.User{ - Username: user.Username, - Password: string(hashedPassword), - Email: user.Email, - VerificationCode: string(hashedVerificationCode), + Username: user.Username, + Password: string(hashedPassword), + Email: user.Email, + VerificationCode: string(hashedVerificationCode), + VerificationExpiration: user.VerificationExpiration, }) if creationResult.Error != nil { return domain.User{}, creationResult.Error @@ -82,4 +83,20 @@ func (u *UserRepository) GetByUsername(username string) (domain.User, error) { return mapUserToDomain(user), nil } +func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domain.User, error) { + var user entity.User + err := u.db.Model(&entity.User{}).First(&user, userId).Error + if err != nil { + return domain.User{}, err + } + + user.IsVerified = isVerified + err = u.db.Save(&user).Error + if err != nil { + return domain.User{}, err + } + + return mapUserToDomain(user), nil +} + var _ gateways.IUserRepository = &UserRepository{} diff --git a/adapters/secondary/gateways/web/templates/login.html b/adapters/secondary/gateways/web/templates/login.html index 85bbe52..a17a9b7 100644 --- a/adapters/secondary/gateways/web/templates/login.html +++ b/adapters/secondary/gateways/web/templates/login.html @@ -17,7 +17,7 @@

GohCMS

Login

-
+
- GohCMS | Setup - {{.Head}} + GohCMS | Setup + {{.Head}} - +
-
-
-

GohCMS

-

E-mail verification

-
-

Verifying your e-mail, please wait...

-
+
+
+

GohCMS

+

E-mail verification

+
+ {{ if .PageError.IsError }} +

{{.PageError.Message}}

+ {{ else }} +

Your e-mail was successfully validated!

+ + + + {{ end }} +
diff --git a/api/auth.go b/api/auth.go index a893dae..167f61a 100644 --- a/api/auth.go +++ b/api/auth.go @@ -63,9 +63,17 @@ func IsLoggedIn(r *http.Request) bool { } func IsVerified(r *http.Request) bool { - _, claims, _ := jwtauth.FromContext(r.Context()) - userIDClaim, _ := claims["user_id"].(uint32) - currentUser, _ := Container.GetUserUseCase.GetUser(userIDClaim) + token, err := jwtauth.VerifyRequest( + TokenAuth, + r, + jwtauth.TokenFromCookie, + jwtauth.TokenFromHeader, + jwtauth.TokenFromQuery) + if err != nil { + return false + } + userId := token.PrivateClaims()["user_id"].(float64) + currentUser, _ := Container.GetUserUseCase.GetUser(uint32(userId)) return currentUser.IsVerified } diff --git a/api/dependecyInjection.go b/api/dependecyInjection.go index 9bd3c0b..de582aa 100644 --- a/api/dependecyInjection.go +++ b/api/dependecyInjection.go @@ -16,6 +16,7 @@ type UseCases struct { ListPostsUseCase *useCases.ListPostsUseCase GetUserUseCase *useCases.GetUserUseCase CreateUserUseCase *useCases.CreateUserUseCase + UpdateUserUseCase *useCases.UpdateUserUseCase ListUsersUseCase *useCases.ListUsersUseCase GetPageUseCase *useCases.GetPageUseCase SendMailUseCase *useCases.SendMailUseCase @@ -49,6 +50,7 @@ func InitContainer() { ListPostsUseCase: useCases.NewListPostsUseCase(db), GetUserUseCase: useCases.NewGetUserUseCase(db), CreateUserUseCase: useCases.NewCreateUserUseCase(db), + UpdateUserUseCase: useCases.NewUpdateUserUseCase(db), ListUsersUseCase: useCases.NewListUsersUseCase(db), GetPageUseCase: useCases.NewGetPageUseCase(), SendMailUseCase: useCases.NewSendMailUseCase(), diff --git a/api/page.go b/api/page.go index a401e47..ec39ab1 100644 --- a/api/page.go +++ b/api/page.go @@ -57,6 +57,16 @@ func IsVerifiedMiddleware(next http.Handler) http.Handler { }) } +func IsNotVerifiedMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if IsVerified(r) { + http.Redirect(w, r, "/register/pending", http.StatusSeeOther) + return + } + next.ServeHTTP(w, r) + }) +} + func GetLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, LoginRoute, http.StatusPermanentRedirect) } @@ -101,6 +111,7 @@ func NewPageRouter() http.Handler { r.Group(func(r chi.Router) { r.Use(IsLoggedInMiddleware) + r.Use(IsNotVerifiedMiddleware) r.Get("/register/pending", GetRegisterPendingPage) r.Get("/register/validate", GetRegisterValidatePage) }) diff --git a/api/pageRegisterValidate.go b/api/pageRegisterValidate.go index 7e06242..ba8bd28 100644 --- a/api/pageRegisterValidate.go +++ b/api/pageRegisterValidate.go @@ -14,17 +14,25 @@ func GetRegisterValidatePage(w http.ResponseWriter, r *http.Request) { return } - _, claims, _ := jwtauth.FromContext(r.Context()) - userId, _ := claims["user_id"].(uint32) + token, _ := jwtauth.VerifyRequest( + TokenAuth, + r, + jwtauth.TokenFromCookie, + jwtauth.TokenFromHeader, + jwtauth.TokenFromQuery) - user, _ := Container.GetUserUseCase.GetUser(userId) + userId := token.PrivateClaims()["user_id"].(float64) + user, _ := Container.GetUserUseCase.GetUser(uint32(userId)) errorMessage := "" err := bcrypt.CompareHashAndPassword([]byte(user.VerificationCode), []byte(queryVerificationCode)) if err != nil || user.VerificationExpiration.Before(time.Now()) { errorMessage = "Verification link is incorrect or has expired." } else { - // TODO New usecase "UpdateUserUseCase" to update its verification status + _, err := Container.UpdateUserUseCase.UpdateVerificationStatus(user.ID, true) + if err != nil { + errorMessage = "Something went wrong server-side. User account may not exist." + } } registerValidateTmpl, _ := Container.GetPageUseCase.GetPage("registerValidate", map[string]interface{}{ diff --git a/domain/gateways/IUserRepository.go b/domain/gateways/IUserRepository.go index aa83c34..1d43a9f 100644 --- a/domain/gateways/IUserRepository.go +++ b/domain/gateways/IUserRepository.go @@ -7,4 +7,5 @@ type IUserRepository interface { GetByUsername(username string) (user.User, error) GetAll() []user.User Create(user user.User) (user.User, error) + UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) } diff --git a/domain/user/user.go b/domain/user/user.go index b85cf72..19cd249 100644 --- a/domain/user/user.go +++ b/domain/user/user.go @@ -22,13 +22,14 @@ func FromApi( email string, verificationCode string, ) User { + expiration := time.Now().Add(2 * time.Hour) return User{ Username: username, Password: password, Email: email, IsVerified: false, VerificationCode: verificationCode, - VerificationExpiration: time.Now().Add(2 * time.Hour), + VerificationExpiration: expiration, } } diff --git a/useCases/UpdateUserUseCase.go b/useCases/UpdateUserUseCase.go new file mode 100644 index 0000000..3e546b0 --- /dev/null +++ b/useCases/UpdateUserUseCase.go @@ -0,0 +1,25 @@ +package useCases + +import ( + "GohCMS2/adapters/secondary/gateways" + "GohCMS2/domain/user" + "gorm.io/gorm" +) + +type UpdateUserUseCase struct { + userRepository gateways.UserRepository +} + +type UpdateVerificationStatusCommand struct { + isVerified bool +} + +func NewUpdateUserUseCase(db *gorm.DB) *UpdateUserUseCase { + return &UpdateUserUseCase{ + userRepository: *gateways.NewUserRepository(db), + } +} + +func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) { + return g.userRepository.UpdateVerificationStatus(userId, isVerified) +}