From 9b47685002dc2198b97bc7fdf0a7f518df6a9dc4 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Fri, 25 Sep 2026 06:14:53 +0200 Subject: [PATCH] refac: split the auth handler into registration and login, account verification, and password reset handlers, each with its own dependencies --- api/handlers/auth/authHandler.go | 314 ------------------ api/handlers/auth/loginHandler.go | 136 ++++++++ api/handlers/auth/passwordResetHandler.go | 81 +++++ api/handlers/auth/verificationHandler.go | 119 +++++++ api/router.go | 18 +- test/account_verification_http_test.go | 31 +- test/cancel_pending_registration_http_test.go | 15 +- test/hasher_test.go | 24 +- ..._password_reset_rate_limiting_http_test.go | 16 +- test/login_logout_cookie_test.go | 81 +++++ test/not_found_errors_test.go | 10 +- ...ration_gate_and_verification_email_test.go | 10 +- test/request_password_reset_http_test.go | 14 +- test/reset_password_http_test.go | 14 +- test/runtime_settings_injection_http_test.go | 10 +- test/stale_token_invalidation_http_test.go | 23 +- ...nverified_users_and_reregistration_test.go | 10 +- test/user_response_field_hygiene_test.go | 23 +- 18 files changed, 497 insertions(+), 452 deletions(-) delete mode 100644 api/handlers/auth/authHandler.go create mode 100644 api/handlers/auth/loginHandler.go create mode 100644 api/handlers/auth/passwordResetHandler.go create mode 100644 api/handlers/auth/verificationHandler.go create mode 100644 test/login_logout_cookie_test.go diff --git a/api/handlers/auth/authHandler.go b/api/handlers/auth/authHandler.go deleted file mode 100644 index f6dc6db..0000000 --- a/api/handlers/auth/authHandler.go +++ /dev/null @@ -1,314 +0,0 @@ -package auth - -import ( - "RenewCMS/api/pkg/apperr" - "RenewCMS/api/pkg/claims" - validator "RenewCMS/api/pkg/validator" - "RenewCMS/internal/domain/user" - "RenewCMS/internal/infrastructure/useCases" - "encoding/json" - "errors" - "net/http" - "time" - - "github.com/go-chi/jwtauth/v5" -) - -type Handler struct { - Token *jwtauth.JWTAuth - GetUseCase *useCases.GetUserUseCase - ListUseCase *useCases.ListUsersUseCase - CreateUseCase *useCases.CreateUserUseCase - AuthenticateUseCase *useCases.AuthenticateUserUseCase - VerifyUseCase *useCases.VerifyUserUseCase - CancelUseCase *useCases.CancelRegistrationUseCase - RequestPasswordResetUseCase *useCases.RequestPasswordResetUseCase - ResetPasswordUseCase *useCases.ResetPasswordUseCase - IsProduction bool -} - -type RegisterCredentials struct { - Username string `json:"username" validate:"required,min=3,max=20"` - Password string `json:"password" validate:"required,min=8"` - Email string `json:"email" validate:"required,email"` -} - -type LoginCredentials struct { - Username string `json:"username" validate:"required,min=3,max=20"` - Password string `json:"password" validate:"required,min=8"` -} - -type VerifyCredentials struct { - Code string `json:"code" validate:"required"` -} - -type RequestPasswordResetCredentials struct { - Email string `json:"email" validate:"required,email"` -} - -type ResetPasswordCredentials struct { - Email string `json:"email" validate:"required,email"` - Code string `json:"code" validate:"required"` - Password string `json:"password" validate:"required,min=8"` -} - -type PublicUser struct { - ID uint32 `json:"id"` - Username string `json:"username"` - Email string `json:"email"` - IsVerified bool `json:"is_verified"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func newPublicUser(u user.User) PublicUser { - return PublicUser{ - ID: u.ID, - Username: u.Username, - Email: u.Email, - IsVerified: u.IsVerified, - CreatedAt: u.CreatedAt, - UpdatedAt: u.UpdatedAt, - } -} - -func (h *Handler) removeJwtCookie(w http.ResponseWriter) { - http.SetCookie(w, &http.Cookie{ - Name: "jwt", - Value: "", - Expires: time.Now(), - MaxAge: -1, - Secure: h.IsProduction, - HttpOnly: true, - Path: "/", - }) -} - -func NewHandler(token *jwtauth.JWTAuth, get *useCases.GetUserUseCase, list *useCases.ListUsersUseCase, create *useCases.CreateUserUseCase, authenticate *useCases.AuthenticateUserUseCase, verify *useCases.VerifyUserUseCase, cancel *useCases.CancelRegistrationUseCase, requestPasswordReset *useCases.RequestPasswordResetUseCase, resetPassword *useCases.ResetPasswordUseCase, isProduction bool) *Handler { - return &Handler{ - Token: token, - GetUseCase: get, - ListUseCase: list, - CreateUseCase: create, - AuthenticateUseCase: authenticate, - VerifyUseCase: verify, - CancelUseCase: cancel, - RequestPasswordResetUseCase: requestPasswordReset, - ResetPasswordUseCase: resetPassword, - IsProduction: isProduction, - } -} - -func (h *Handler) SetJwtCookie(w *http.ResponseWriter, u user.User) error { - _, tokenString, err := h.Token.Encode(map[string]any{"user_id": u.ID, "token_version": u.TokenVersion}) - if err != nil { - return err - } - http.SetCookie(*w, &http.Cookie{ - Name: "jwt", - Value: tokenString, - Expires: time.Now().Add(24 * time.Hour), - Secure: h.IsProduction, - HttpOnly: true, - Path: "/", - }) - return nil -} - -func (h *Handler) IsUserTableEmpty() bool { - users := h.ListUseCase.ListUsers() - return len(users) == 0 -} - -func (h *Handler) IsLoggedIn(r *http.Request) bool { - token, err := jwtauth.VerifyRequest( - h.Token, - r, - jwtauth.TokenFromCookie, - jwtauth.TokenFromHeader, - jwtauth.TokenFromQuery) - return token != nil && err == nil -} - -func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { - var credentials LoginCredentials - - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - dbUser, err := h.AuthenticateUseCase.Authenticate(credentials.Username, credentials.Password) - if err != nil { - http.Error(w, apperr.LogsErrorMessage, http.StatusForbidden) - return - } - - _ = h.SetJwtCookie(&w, dbUser) - - message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"}) - _, _ = w.Write(message) -} - -func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { - var credentials RegisterCredentials - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - createdUser, err := h.CreateUseCase.CreateUser(useCases.CreateUserCommand{ - Username: credentials.Username, - Password: credentials.Password, - Email: credentials.Email, - }) - if err != nil { - if errors.Is(err, user.ErrRegistrationClosed) { - http.Error(w, apperr.RegistrationClosedErrorMessage, http.StatusForbidden) - return - } - http.Error(w, apperr.RegistrationFailedErrorMessage, http.StatusInternalServerError) - return - } - - _ = h.SetJwtCookie(&w, createdUser) - - message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"}) - _, _ = w.Write(message) -} - -func (h *Handler) Verify(w http.ResponseWriter, r *http.Request) { - var credentials VerifyCredentials - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - userId, ok := claims.UserId(r) - if !ok { - http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) - return - } - - verifiedUser, err := h.VerifyUseCase.Verify(userId, credentials.Code) - if err != nil { - if errors.Is(err, user.ErrUserNotFound) { - http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) - return - } - if errors.Is(err, user.ErrInvalidVerificationCode) || errors.Is(err, user.ErrVerificationCodeExpired) { - http.Error(w, apperr.VerificationFailedErrorMessage, http.StatusForbidden) - return - } - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - body, err := json.Marshal(newPublicUser(verifiedUser)) - if err != nil { - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - _, _ = w.Write(body) -} - -func (h *Handler) CancelRegistration(w http.ResponseWriter, r *http.Request) { - userId, ok := claims.UserId(r) - if !ok { - http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) - return - } - - err := h.CancelUseCase.CancelRegistration(userId) - if err != nil { - if errors.Is(err, user.ErrUserNotFound) { - http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) - return - } - if errors.Is(err, user.ErrAlreadyVerified) { - http.Error(w, apperr.AlreadyVerifiedErrorMessage, http.StatusForbidden) - return - } - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - h.removeJwtCookie(w) - message, _ := json.Marshal(map[string]any{"message": "Registration cancelled! Account deleted and HTTPonly jwt cookie cleared"}) - _, _ = w.Write(message) -} - -func (h *Handler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) { - var credentials RequestPasswordResetCredentials - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - _ = h.RequestPasswordResetUseCase.RequestPasswordReset(credentials.Email) - - message, _ := json.Marshal(map[string]any{"message": apperr.PasswordResetRequestedMessage}) - _, _ = w.Write(message) -} - -func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) { - var credentials ResetPasswordCredentials - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - err = h.ResetPasswordUseCase.ResetPassword(credentials.Email, credentials.Code, credentials.Password) - if err != nil { - if errors.Is(err, user.ErrInvalidPasswordResetCode) || errors.Is(err, user.ErrPasswordResetCodeExpired) { - http.Error(w, apperr.PasswordResetFailedErrorMessage, http.StatusForbidden) - return - } - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - message, _ := json.Marshal(map[string]any{"message": "Your password has been reset. You can now log in."}) - _, _ = w.Write(message) -} - -func (h *Handler) Logout(w http.ResponseWriter, _ *http.Request) { - h.removeJwtCookie(w) - message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"}) - _, _ = w.Write(message) -} diff --git a/api/handlers/auth/loginHandler.go b/api/handlers/auth/loginHandler.go new file mode 100644 index 0000000..24455eb --- /dev/null +++ b/api/handlers/auth/loginHandler.go @@ -0,0 +1,136 @@ +package auth + +import ( + "RenewCMS/api/pkg/apperr" + validator "RenewCMS/api/pkg/validator" + "RenewCMS/internal/domain/user" + "RenewCMS/internal/infrastructure/useCases" + "encoding/json" + "errors" + "net/http" + "time" + + "github.com/go-chi/jwtauth/v5" +) + +type LoginHandler struct { + Token *jwtauth.JWTAuth + CreateUseCase *useCases.CreateUserUseCase + AuthenticateUseCase *useCases.AuthenticateUserUseCase + IsProduction bool +} + +type RegisterCredentials struct { + Username string `json:"username" validate:"required,min=3,max=20"` + Password string `json:"password" validate:"required,min=8"` + Email string `json:"email" validate:"required,email"` +} + +type LoginCredentials struct { + Username string `json:"username" validate:"required,min=3,max=20"` + Password string `json:"password" validate:"required,min=8"` +} + +func NewLoginHandler(token *jwtauth.JWTAuth, create *useCases.CreateUserUseCase, authenticate *useCases.AuthenticateUserUseCase, isProduction bool) *LoginHandler { + return &LoginHandler{ + Token: token, + CreateUseCase: create, + AuthenticateUseCase: authenticate, + IsProduction: isProduction, + } +} + +func removeJwtCookie(w http.ResponseWriter, isProduction bool) { + http.SetCookie(w, &http.Cookie{ + Name: "jwt", + Value: "", + Expires: time.Now(), + MaxAge: -1, + Secure: isProduction, + HttpOnly: true, + Path: "/", + }) +} + +func (h *LoginHandler) SetJwtCookie(w *http.ResponseWriter, u user.User) error { + _, tokenString, err := h.Token.Encode(map[string]any{"user_id": u.ID, "token_version": u.TokenVersion}) + if err != nil { + return err + } + http.SetCookie(*w, &http.Cookie{ + Name: "jwt", + Value: tokenString, + Expires: time.Now().Add(24 * time.Hour), + Secure: h.IsProduction, + HttpOnly: true, + Path: "/", + }) + return nil +} + +func (h *LoginHandler) Login(w http.ResponseWriter, r *http.Request) { + var credentials LoginCredentials + + err := json.NewDecoder(r.Body).Decode(&credentials) + if err != nil { + http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) + return + } + + err = validator.Validate.Struct(credentials) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + dbUser, err := h.AuthenticateUseCase.Authenticate(credentials.Username, credentials.Password) + if err != nil { + http.Error(w, apperr.LogsErrorMessage, http.StatusForbidden) + return + } + + _ = h.SetJwtCookie(&w, dbUser) + + message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"}) + _, _ = w.Write(message) +} + +func (h *LoginHandler) Register(w http.ResponseWriter, r *http.Request) { + var credentials RegisterCredentials + err := json.NewDecoder(r.Body).Decode(&credentials) + if err != nil { + http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) + return + } + + err = validator.Validate.Struct(credentials) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + createdUser, err := h.CreateUseCase.CreateUser(useCases.CreateUserCommand{ + Username: credentials.Username, + Password: credentials.Password, + Email: credentials.Email, + }) + if err != nil { + if errors.Is(err, user.ErrRegistrationClosed) { + http.Error(w, apperr.RegistrationClosedErrorMessage, http.StatusForbidden) + return + } + http.Error(w, apperr.RegistrationFailedErrorMessage, http.StatusInternalServerError) + return + } + + _ = h.SetJwtCookie(&w, createdUser) + + message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"}) + _, _ = w.Write(message) +} + +func (h *LoginHandler) Logout(w http.ResponseWriter, _ *http.Request) { + removeJwtCookie(w, h.IsProduction) + message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"}) + _, _ = w.Write(message) +} diff --git a/api/handlers/auth/passwordResetHandler.go b/api/handlers/auth/passwordResetHandler.go new file mode 100644 index 0000000..903f368 --- /dev/null +++ b/api/handlers/auth/passwordResetHandler.go @@ -0,0 +1,81 @@ +package auth + +import ( + "RenewCMS/api/pkg/apperr" + validator "RenewCMS/api/pkg/validator" + "RenewCMS/internal/domain/user" + "RenewCMS/internal/infrastructure/useCases" + "encoding/json" + "errors" + "net/http" +) + +type PasswordResetHandler struct { + RequestPasswordResetUseCase *useCases.RequestPasswordResetUseCase + ResetPasswordUseCase *useCases.ResetPasswordUseCase +} + +type RequestPasswordResetCredentials struct { + Email string `json:"email" validate:"required,email"` +} + +type ResetPasswordCredentials struct { + Email string `json:"email" validate:"required,email"` + Code string `json:"code" validate:"required"` + Password string `json:"password" validate:"required,min=8"` +} + +func NewPasswordResetHandler(requestPasswordReset *useCases.RequestPasswordResetUseCase, resetPassword *useCases.ResetPasswordUseCase) *PasswordResetHandler { + return &PasswordResetHandler{ + RequestPasswordResetUseCase: requestPasswordReset, + ResetPasswordUseCase: resetPassword, + } +} + +func (h *PasswordResetHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) { + var credentials RequestPasswordResetCredentials + err := json.NewDecoder(r.Body).Decode(&credentials) + if err != nil { + http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) + return + } + + err = validator.Validate.Struct(credentials) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + _ = h.RequestPasswordResetUseCase.RequestPasswordReset(credentials.Email) + + message, _ := json.Marshal(map[string]any{"message": apperr.PasswordResetRequestedMessage}) + _, _ = w.Write(message) +} + +func (h *PasswordResetHandler) ResetPassword(w http.ResponseWriter, r *http.Request) { + var credentials ResetPasswordCredentials + err := json.NewDecoder(r.Body).Decode(&credentials) + if err != nil { + http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) + return + } + + err = validator.Validate.Struct(credentials) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err = h.ResetPasswordUseCase.ResetPassword(credentials.Email, credentials.Code, credentials.Password) + if err != nil { + if errors.Is(err, user.ErrInvalidPasswordResetCode) || errors.Is(err, user.ErrPasswordResetCodeExpired) { + http.Error(w, apperr.PasswordResetFailedErrorMessage, http.StatusForbidden) + return + } + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + message, _ := json.Marshal(map[string]any{"message": "Your password has been reset. You can now log in."}) + _, _ = w.Write(message) +} diff --git a/api/handlers/auth/verificationHandler.go b/api/handlers/auth/verificationHandler.go new file mode 100644 index 0000000..42b7e39 --- /dev/null +++ b/api/handlers/auth/verificationHandler.go @@ -0,0 +1,119 @@ +package auth + +import ( + "RenewCMS/api/pkg/apperr" + "RenewCMS/api/pkg/claims" + validator "RenewCMS/api/pkg/validator" + "RenewCMS/internal/domain/user" + "RenewCMS/internal/infrastructure/useCases" + "encoding/json" + "errors" + "net/http" + "time" +) + +type VerificationHandler struct { + VerifyUseCase *useCases.VerifyUserUseCase + CancelUseCase *useCases.CancelRegistrationUseCase + IsProduction bool +} + +type VerifyCredentials struct { + Code string `json:"code" validate:"required"` +} + +type PublicUser struct { + ID uint32 `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + IsVerified bool `json:"is_verified"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func newPublicUser(u user.User) PublicUser { + return PublicUser{ + ID: u.ID, + Username: u.Username, + Email: u.Email, + IsVerified: u.IsVerified, + CreatedAt: u.CreatedAt, + UpdatedAt: u.UpdatedAt, + } +} + +func NewVerificationHandler(verify *useCases.VerifyUserUseCase, cancel *useCases.CancelRegistrationUseCase, isProduction bool) *VerificationHandler { + return &VerificationHandler{ + VerifyUseCase: verify, + CancelUseCase: cancel, + IsProduction: isProduction, + } +} + +func (h *VerificationHandler) Verify(w http.ResponseWriter, r *http.Request) { + var credentials VerifyCredentials + err := json.NewDecoder(r.Body).Decode(&credentials) + if err != nil { + http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) + return + } + + err = validator.Validate.Struct(credentials) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + userId, ok := claims.UserId(r) + if !ok { + http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) + return + } + + verifiedUser, err := h.VerifyUseCase.Verify(userId, credentials.Code) + if err != nil { + if errors.Is(err, user.ErrUserNotFound) { + http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) + return + } + if errors.Is(err, user.ErrInvalidVerificationCode) || errors.Is(err, user.ErrVerificationCodeExpired) { + http.Error(w, apperr.VerificationFailedErrorMessage, http.StatusForbidden) + return + } + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + body, err := json.Marshal(newPublicUser(verifiedUser)) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + _, _ = w.Write(body) +} + +func (h *VerificationHandler) CancelRegistration(w http.ResponseWriter, r *http.Request) { + userId, ok := claims.UserId(r) + if !ok { + http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) + return + } + + err := h.CancelUseCase.CancelRegistration(userId) + if err != nil { + if errors.Is(err, user.ErrUserNotFound) { + http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized) + return + } + if errors.Is(err, user.ErrAlreadyVerified) { + http.Error(w, apperr.AlreadyVerifiedErrorMessage, http.StatusForbidden) + return + } + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + removeJwtCookie(w, h.IsProduction) + message, _ := json.Marshal(map[string]any{"message": "Registration cancelled! Account deleted and HTTPonly jwt cookie cleared"}) + _, _ = w.Write(message) +} diff --git a/api/router.go b/api/router.go index fa92624..e2a9624 100644 --- a/api/router.go +++ b/api/router.go @@ -55,27 +55,29 @@ func NewArticleRouter(container *UseCases) http.Handler { func NewAuthRouter(container *UseCases, isProduction bool, rateLimit config.AuthRateLimitSettings) http.Handler { r := chi.NewRouter() - authHandler := auth.NewHandler(container.Token, container.GetUserUseCase, container.ListUsersUseCase, container.CreateUserUseCase, container.AuthenticateUserUseCase, container.VerifyUserUseCase, container.CancelRegistrationUseCase, container.RequestPasswordResetUseCase, container.ResetPasswordUseCase, isProduction) + loginHandler := auth.NewLoginHandler(container.Token, container.CreateUserUseCase, container.AuthenticateUserUseCase, isProduction) + verificationHandler := auth.NewVerificationHandler(container.VerifyUserUseCase, container.CancelRegistrationUseCase, isProduction) + passwordResetHandler := auth.NewPasswordResetHandler(container.RequestPasswordResetUseCase, container.ResetPasswordUseCase) - r.Post("/register", authHandler.Register) + r.Post("/register", loginHandler.Register) r.Group(func(r chi.Router) { r.Use(chimiddleware.RealIP) r.Use(middleware.NewRateLimiter(rateLimit.MaxAttempts, rateLimit.Window)) - r.Post("/login", authHandler.Login) + r.Post("/login", loginHandler.Login) }) - r.Post("/logout", authHandler.Logout) + r.Post("/logout", loginHandler.Logout) r.Group(func(r chi.Router) { r.Use(chimiddleware.RealIP) r.Use(middleware.NewRateLimiter(rateLimit.MaxAttempts, rateLimit.Window)) - r.Post("/password-reset", authHandler.RequestPasswordReset) + r.Post("/password-reset", passwordResetHandler.RequestPasswordReset) }) - r.Post("/password-reset/confirm", authHandler.ResetPassword) + r.Post("/password-reset/confirm", passwordResetHandler.ResetPassword) r.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(container.Token)) r.Use(jwtauth.Authenticator(container.Token)) r.Use(middleware.RequireCurrentToken(container.GetUserUseCase)) - r.Post("/verify", authHandler.Verify) - r.Delete("/register", authHandler.CancelRegistration) + r.Post("/verify", verificationHandler.Verify) + r.Delete("/register", verificationHandler.CancelRegistration) }) return r } diff --git a/test/account_verification_http_test.go b/test/account_verification_http_test.go index 8489c54..5a4fd85 100644 --- a/test/account_verification_http_test.go +++ b/test/account_verification_http_test.go @@ -17,18 +17,11 @@ import ( "github.com/go-chi/jwtauth/v5" ) -func newAccountVerificationRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo, token *jwtauth.JWTAuth) *chi.Mux { +func newAccountVerificationRouter(userRepo *mockUserRepo, token *jwtauth.JWTAuth) *chi.Mux { hasher := &stubHasher{} - authHandler := auth.NewHandler( - token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), - useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), - useCases.NewAuthenticateUserUseCase(userRepo, hasher), + verificationHandler := auth.NewVerificationHandler( useCases.NewVerifyUserUseCase(userRepo, hasher), useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) @@ -38,7 +31,7 @@ func newAccountVerificationRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo r.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(token)) r.Use(jwtauth.Authenticator(token)) - r.Post("/verify", authHandler.Verify) + r.Post("/verify", verificationHandler.Verify) }) }) }) @@ -76,7 +69,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("A logged-in user verifies their account with the correct emailed code", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, map[string]any{"user_id": uint32(7)}, "abc123") @@ -101,7 +94,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("A wrong code is refused and nothing changes", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, map[string]any{"user_id": uint32(7)}, "wrong-code") @@ -122,7 +115,7 @@ func TestAccountVerificationHttp(t *testing.T) { expired := repo.users[7] expired.VerificationExpiration = time.Now().Add(-time.Hour) repo.users[7] = expired - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, map[string]any{"user_id": uint32(7)}, "abc123") @@ -140,7 +133,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("The code is cleared once used, so it cannot be replayed", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) first := sendVerifyRequest(router, token, map[string]any{"user_id": uint32(7)}, "abc123") if first.Code != http.StatusOK { @@ -169,7 +162,7 @@ func TestAccountVerificationHttp(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, tc.claims, "abc123") @@ -190,7 +183,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("A repository failure unrelated to the code returns 500", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() repo.fail.failNext("ClearVerificationCode") - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, map[string]any{"user_id": uint32(7)}, "abc123") @@ -204,7 +197,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("A malformed request body returns 400", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) req := httptest.NewRequest(http.MethodPost, "/v1/auth/verify", bytes.NewReader([]byte("not-json"))) _, tokenString, _ := token.Encode(map[string]any{"user_id": uint32(7)}) @@ -222,7 +215,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("A missing code fails validation and returns 400", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, map[string]any{"user_id": uint32(7)}, "") @@ -233,7 +226,7 @@ func TestAccountVerificationHttp(t *testing.T) { t.Run("A request with no jwt cookie is answered 401 by jwtauth itself", func(t *testing.T) { repo := newAccountVerificationTestUserRepo() - router := newAccountVerificationRouter(repo, newMockMailRepo(), token) + router := newAccountVerificationRouter(repo, token) rec := sendVerifyRequest(router, token, nil, "abc123") diff --git a/test/cancel_pending_registration_http_test.go b/test/cancel_pending_registration_http_test.go index 8e22a16..4858e90 100644 --- a/test/cancel_pending_registration_http_test.go +++ b/test/cancel_pending_registration_http_test.go @@ -17,28 +17,27 @@ import ( func newCancelRegistrationRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo, token *jwtauth.JWTAuth) *chi.Mux { hasher := &stubHasher{} - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), + true, + ) + verificationHandler := auth.NewVerificationHandler( useCases.NewVerifyUserUseCase(userRepo, hasher), useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) r := chi.NewRouter() r.Route("/v1", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) + r.Post("/register", loginHandler.Register) r.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(token)) r.Use(jwtauth.Authenticator(token)) - r.Post("/verify", authHandler.Verify) - r.Delete("/register", authHandler.CancelRegistration) + r.Post("/verify", verificationHandler.Verify) + r.Delete("/register", verificationHandler.CancelRegistration) }) }) }) diff --git a/test/hasher_test.go b/test/hasher_test.go index ba6ea79..a502beb 100644 --- a/test/hasher_test.go +++ b/test/hasher_test.go @@ -154,23 +154,17 @@ func TestBcryptHasherHashAndCompare(t *testing.T) { func newHasherRouter(userRepo *mockUserRepo, hasher *stubHasher) *chi.Mux { token := jwtauth.New("HS256", []byte("test-secret"), nil) - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) r := chi.NewRouter() r.Route("/v1/auth", func(r chi.Router) { - r.Post("/login", authHandler.Login) - r.Post("/register", authHandler.Register) + r.Post("/login", loginHandler.Login) + r.Post("/register", loginHandler.Register) }) return r } @@ -178,23 +172,17 @@ func newHasherRouter(userRepo *mockUserRepo, hasher *stubHasher) *chi.Mux { func newRealHasherRouter(userRepo *mockUserRepo) *chi.Mux { token := jwtauth.New("HS256", []byte("test-secret"), nil) hasher := hashing.NewBcryptHasher() - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) r := chi.NewRouter() r.Route("/v1/auth", func(r chi.Router) { - r.Post("/login", authHandler.Login) - r.Post("/register", authHandler.Register) + r.Post("/login", loginHandler.Login) + r.Post("/register", loginHandler.Register) }) return r } diff --git a/test/login_and_password_reset_rate_limiting_http_test.go b/test/login_and_password_reset_rate_limiting_http_test.go index 95cee74..7dfb8b2 100644 --- a/test/login_and_password_reset_rate_limiting_http_test.go +++ b/test/login_and_password_reset_rate_limiting_http_test.go @@ -34,32 +34,30 @@ func newRateLimitedAuthRouter(t *testing.T, userRepo user.Repository, mailRepo * t.Helper() hasher := hashing.NewBcryptHasher() token := jwtauth.New("HS256", []byte("test-secret"), nil) - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), + true, + ) + passwordResetHandler := auth.NewPasswordResetHandler( useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewResetPasswordUseCase(userRepo, hasher), - true, ) r := chi.NewRouter() r.Route("/v1", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) + r.Post("/register", loginHandler.Register) r.Group(func(r chi.Router) { r.Use(chimiddleware.RealIP) r.Use(middleware.NewRateLimiter(maxAttempts, window)) - r.Post("/login", authHandler.Login) + r.Post("/login", loginHandler.Login) }) r.Group(func(r chi.Router) { r.Use(chimiddleware.RealIP) r.Use(middleware.NewRateLimiter(maxAttempts, window)) - r.Post("/password-reset", authHandler.RequestPasswordReset) + r.Post("/password-reset", passwordResetHandler.RequestPasswordReset) }) }) }) diff --git a/test/login_logout_cookie_test.go b/test/login_logout_cookie_test.go new file mode 100644 index 0000000..95ffd4d --- /dev/null +++ b/test/login_logout_cookie_test.go @@ -0,0 +1,81 @@ +package test + +import ( + "RenewCMS/api/handlers/auth" + validator "RenewCMS/api/pkg/validator" + "RenewCMS/internal/domain/user" + "RenewCMS/internal/infrastructure/useCases" + "bytes" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/jwtauth/v5" +) + +func newLoginLogoutCookieRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo, token *jwtauth.JWTAuth) *chi.Mux { + hasher := &stubHasher{} + loginHandler := auth.NewLoginHandler( + token, + useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), + useCases.NewAuthenticateUserUseCase(userRepo, hasher), + true, + ) + + r := chi.NewRouter() + r.Post("/login", loginHandler.Login) + r.Post("/logout", loginHandler.Logout) + return r +} + +func TestLoginSetsA24HourJwtCookieAndLogoutClearsIt(t *testing.T) { + validator.InitValidator() + token := jwtauth.New("HS256", []byte("test-secret"), nil) + + t.Run("Login sets a jwt cookie that expires about 24 hours from now", func(t *testing.T) { + repo := newMockUserRepo() + repo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", Password: "hashed:hunter2example12", IsVerified: true} + repo.nextID = 1 + router := newLoginLogoutCookieRouter(repo, newMockMailRepo(), token) + + req := httptest.NewRequest(http.MethodPost, "/login", bytes.NewBufferString(`{"username":"alice","password":"hunter2example12"}`)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + cookie, ok := jwtSetCookie(t, rec) + if !ok { + t.Fatalf("expected a jwt Set-Cookie header") + } + untilExpiry := time.Until(cookie.Expires) + if untilExpiry < 23*time.Hour || untilExpiry > 25*time.Hour { + t.Errorf("expected the jwt cookie to expire about 24 hours from now, got %v from now", untilExpiry) + } + }) + + t.Run("Logout clears the jwt cookie by expiring it immediately", func(t *testing.T) { + repo := newMockUserRepo() + router := newLoginLogoutCookieRouter(repo, newMockMailRepo(), token) + + req := httptest.NewRequest(http.MethodPost, "/logout", bytes.NewBufferString(`{}`)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + cookie, ok := jwtSetCookie(t, rec) + if !ok { + t.Fatalf("expected a jwt Set-Cookie header") + } + if cookie.Value != "" || cookie.MaxAge >= 0 { + t.Errorf("expected the jwt cookie to be cleared, got %+v", cookie) + } + }) +} diff --git a/test/not_found_errors_test.go b/test/not_found_errors_test.go index 5829045..9eb894f 100644 --- a/test/not_found_errors_test.go +++ b/test/not_found_errors_test.go @@ -134,16 +134,10 @@ func newNotFoundErrorsRouter(articleRepo *mockArticleRepo, userRepo *mockUserRep getUserUseCase, ) hasher := hashing.NewBcryptHasher() - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) @@ -154,7 +148,7 @@ func newNotFoundErrorsRouter(articleRepo *mockArticleRepo, userRepo *mockUserRep r.Get("/{id}", articleHandler.GetArticle) }) r.Route("/auth", func(r chi.Router) { - r.Post("/login", authHandler.Login) + r.Post("/login", loginHandler.Login) }) }) return r diff --git a/test/registration_gate_and_verification_email_test.go b/test/registration_gate_and_verification_email_test.go index 99621ae..f2403c7 100644 --- a/test/registration_gate_and_verification_email_test.go +++ b/test/registration_gate_and_verification_email_test.go @@ -40,22 +40,16 @@ func (s *sequentialHasher) Compare(hashed string, value string) error { func newRegistrationGateRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo, publicURL string) *chi.Mux { token := jwtauth.New("HS256", []byte("test-secret"), nil) hasher := &stubHasher{} - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, publicURL), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, publicURL), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) r := chi.NewRouter() r.Route("/v1/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) + r.Post("/register", loginHandler.Register) }) return r } diff --git a/test/request_password_reset_http_test.go b/test/request_password_reset_http_test.go index 873e606..172be50 100644 --- a/test/request_password_reset_http_test.go +++ b/test/request_password_reset_http_test.go @@ -13,29 +13,19 @@ import ( "time" "github.com/go-chi/chi/v5" - "github.com/go-chi/jwtauth/v5" ) func newRequestPasswordResetRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo, publicURL string) *chi.Mux { hasher := &stubHasher{} - token := jwtauth.New("HS256", []byte("test-secret"), nil) - authHandler := auth.NewHandler( - token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), - useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, publicURL), - useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), + passwordResetHandler := auth.NewPasswordResetHandler( useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, publicURL), useCases.NewResetPasswordUseCase(userRepo, hasher), - true, ) r := chi.NewRouter() r.Route("/v1", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Post("/password-reset", authHandler.RequestPasswordReset) + r.Post("/password-reset", passwordResetHandler.RequestPasswordReset) }) }) return r diff --git a/test/reset_password_http_test.go b/test/reset_password_http_test.go index d58ecce..cbffa9a 100644 --- a/test/reset_password_http_test.go +++ b/test/reset_password_http_test.go @@ -13,29 +13,19 @@ import ( "time" "github.com/go-chi/chi/v5" - "github.com/go-chi/jwtauth/v5" ) func newResetPasswordRouter(userRepo *mockUserRepo, hasher *stubHasher) *chi.Mux { - token := jwtauth.New("HS256", []byte("test-secret"), nil) mailRepo := newMockMailRepo() - authHandler := auth.NewHandler( - token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), - useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), - useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), + passwordResetHandler := auth.NewPasswordResetHandler( useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewResetPasswordUseCase(userRepo, hasher), - true, ) r := chi.NewRouter() r.Route("/v1", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Post("/password-reset/confirm", authHandler.ResetPassword) + r.Post("/password-reset/confirm", passwordResetHandler.ResetPassword) }) }) return r diff --git a/test/runtime_settings_injection_http_test.go b/test/runtime_settings_injection_http_test.go index fc053e7..8908651 100644 --- a/test/runtime_settings_injection_http_test.go +++ b/test/runtime_settings_injection_http_test.go @@ -36,21 +36,15 @@ func newSecureCookieAuthRouter(t *testing.T, isProduction bool) (*chi.Mux, *mock } userRepo.users[1] = domainUser.User{ID: 1, Username: "alice", Password: hashed, IsVerified: true} - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), isProduction, ) r := chi.NewRouter() - r.Post("/login", authHandler.Login) + r.Post("/login", loginHandler.Login) return r, userRepo } diff --git a/test/stale_token_invalidation_http_test.go b/test/stale_token_invalidation_http_test.go index fe8a351..848cf4c 100644 --- a/test/stale_token_invalidation_http_test.go +++ b/test/stale_token_invalidation_http_test.go @@ -30,17 +30,20 @@ func newStaleTokenInvalidationRouter(userRepo *mockUserRepo, articleRepo *mockAr useCases.NewUpdateArticleUseCase(articleRepo), getUserUseCase, ) - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - getUserUseCase, - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), + true, + ) + verificationHandler := auth.NewVerificationHandler( useCases.NewVerifyUserUseCase(userRepo, hasher), useCases.NewCancelRegistrationUseCase(userRepo), + true, + ) + passwordResetHandler := auth.NewPasswordResetHandler( useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewResetPasswordUseCase(userRepo, hasher), - true, ) r := chi.NewRouter() @@ -55,16 +58,16 @@ func newStaleTokenInvalidationRouter(userRepo *mockUserRepo, articleRepo *mockAr }) }) r.Route("/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) - r.Post("/login", authHandler.Login) - r.Post("/password-reset", authHandler.RequestPasswordReset) - r.Post("/password-reset/confirm", authHandler.ResetPassword) + r.Post("/register", loginHandler.Register) + r.Post("/login", loginHandler.Login) + r.Post("/password-reset", passwordResetHandler.RequestPasswordReset) + r.Post("/password-reset/confirm", passwordResetHandler.ResetPassword) r.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(token)) r.Use(jwtauth.Authenticator(token)) r.Use(middleware.RequireCurrentToken(getUserUseCase)) - r.Post("/verify", authHandler.Verify) - r.Delete("/register", authHandler.CancelRegistration) + r.Post("/verify", verificationHandler.Verify) + r.Delete("/register", verificationHandler.CancelRegistration) }) }) }) diff --git a/test/unverified_users_and_reregistration_test.go b/test/unverified_users_and_reregistration_test.go index 8f88e9b..8fe52ba 100644 --- a/test/unverified_users_and_reregistration_test.go +++ b/test/unverified_users_and_reregistration_test.go @@ -153,23 +153,17 @@ func TestUnverifiedUsersAndReregistrationRepository(t *testing.T) { func newUnverifiedRegistrationRouter(userRepo *mockUserRepo, token *jwtauth.JWTAuth) *chi.Mux { hasher := hashing.NewBcryptHasher() - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), - useCases.NewVerifyUserUseCase(userRepo, hasher), - useCases.NewCancelRegistrationUseCase(userRepo), - useCases.NewRequestPasswordResetUseCase(userRepo, hasher, newMockMailRepo(), "https://cms.example.com"), - useCases.NewResetPasswordUseCase(userRepo, hasher), true, ) r := chi.NewRouter() r.Route("/v1", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) + r.Post("/register", loginHandler.Register) }) }) return r diff --git a/test/user_response_field_hygiene_test.go b/test/user_response_field_hygiene_test.go index 25b8989..1fa53b6 100644 --- a/test/user_response_field_hygiene_test.go +++ b/test/user_response_field_hygiene_test.go @@ -19,31 +19,34 @@ import ( func newUserFieldHygieneRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo, token *jwtauth.JWTAuth) *chi.Mux { hasher := &stubHasher{} - authHandler := auth.NewHandler( + loginHandler := auth.NewLoginHandler( token, - useCases.NewGetUserUseCase(userRepo), - useCases.NewListUsersUseCase(userRepo), useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewAuthenticateUserUseCase(userRepo, hasher), + true, + ) + verificationHandler := auth.NewVerificationHandler( useCases.NewVerifyUserUseCase(userRepo, hasher), useCases.NewCancelRegistrationUseCase(userRepo), + true, + ) + passwordResetHandler := auth.NewPasswordResetHandler( useCases.NewRequestPasswordResetUseCase(userRepo, hasher, mailRepo, "https://cms.example.com"), useCases.NewResetPasswordUseCase(userRepo, hasher), - true, ) r := chi.NewRouter() r.Route("/v1", func(r chi.Router) { r.Route("/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) - r.Post("/login", authHandler.Login) - r.Post("/logout", authHandler.Logout) - r.Post("/password-reset", authHandler.RequestPasswordReset) - r.Post("/password-reset/confirm", authHandler.ResetPassword) + r.Post("/register", loginHandler.Register) + r.Post("/login", loginHandler.Login) + r.Post("/logout", loginHandler.Logout) + r.Post("/password-reset", passwordResetHandler.RequestPasswordReset) + r.Post("/password-reset/confirm", passwordResetHandler.ResetPassword) r.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(token)) r.Use(jwtauth.Authenticator(token)) - r.Post("/verify", authHandler.Verify) + r.Post("/verify", verificationHandler.Verify) }) }) })