feat: update handlers to use GetValidatedDTO instead of manual decoding and update MountRoutes to wrap handlers with WithValidation for all DTO-based routes

This commit is contained in:
2025-11-23 13:42:52 +01:00
parent 738243d945
commit abe4a3dc88

View File

@@ -84,23 +84,15 @@ func NewAuthHandler(authService AuthServiceInterface, userRepo repositories.User
// @Failure 500 {object} AuthResponse "Internal server error" // @Failure 500 {object} AuthResponse "Internal server error"
// @Router /api/auth/login [post] // @Router /api/auth/login [post]
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var req struct { req, ok := GetValidatedDTO[dto.LoginRequest](r)
Username string `json:"username"` if !ok {
Password string `json:"password"` SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
}
if !DecodeJSONRequest(w, r, &req) {
return return
} }
username := security.SanitizeUsername(req.Username) username := security.SanitizeUsername(req.Username)
password := strings.TrimSpace(req.Password) password := strings.TrimSpace(req.Password)
if username == "" || password == "" {
SendErrorResponse(w, "Username and password are required", http.StatusBadRequest)
return
}
if err := validation.ValidatePassword(password); err != nil { if err := validation.ValidatePassword(password); err != nil {
SendErrorResponse(w, err.Error(), http.StatusBadRequest) SendErrorResponse(w, err.Error(), http.StatusBadRequest)
return return
@@ -126,13 +118,9 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {object} AuthResponse "Internal server error" // @Failure 500 {object} AuthResponse "Internal server error"
// @Router /api/auth/register [post] // @Router /api/auth/register [post]
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
var req struct { req, ok := GetValidatedDTO[dto.RegisterRequest](r)
Username string `json:"username"` if !ok {
Email string `json:"email"` SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
Password string `json:"password"`
}
if !DecodeJSONRequest(w, r, &req) {
return return
} }
@@ -140,11 +128,6 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
email := strings.TrimSpace(req.Email) email := strings.TrimSpace(req.Email)
password := strings.TrimSpace(req.Password) password := strings.TrimSpace(req.Password)
if username == "" || email == "" || password == "" {
SendErrorResponse(w, "Username, email, and password are required", http.StatusBadRequest)
return
}
username = security.SanitizeUsername(username) username = security.SanitizeUsername(username)
if err := validation.ValidateUsername(username); err != nil { if err := validation.ValidateUsername(username); err != nil {
SendErrorResponse(w, err.Error(), http.StatusBadRequest) SendErrorResponse(w, err.Error(), http.StatusBadRequest)
@@ -234,19 +217,13 @@ func (h *AuthHandler) ConfirmEmail(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {object} AuthResponse // @Failure 500 {object} AuthResponse
// @Router /api/auth/resend-verification [post] // @Router /api/auth/resend-verification [post]
func (h *AuthHandler) ResendVerificationEmail(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) ResendVerificationEmail(w http.ResponseWriter, r *http.Request) {
var req struct { req, ok := GetValidatedDTO[dto.ResendVerificationRequest](r)
Email string `json:"email"` if !ok {
} SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
if !DecodeJSONRequest(w, r, &req) {
return return
} }
email := strings.TrimSpace(req.Email) email := strings.TrimSpace(req.Email)
if email == "" {
SendErrorResponse(w, "Email address is required", http.StatusBadRequest)
return
}
err := h.authService.ResendVerificationEmail(email) err := h.authService.ResendVerificationEmail(email)
if err != nil { if err != nil {
@@ -308,19 +285,13 @@ func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
// @Failure 400 {object} AuthResponse "Invalid request data" // @Failure 400 {object} AuthResponse "Invalid request data"
// @Router /api/auth/forgot-password [post] // @Router /api/auth/forgot-password [post]
func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request) {
var req struct { req, ok := GetValidatedDTO[dto.ForgotPasswordRequest](r)
UsernameOrEmail string `json:"username_or_email"` if !ok {
} SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
if !DecodeJSONRequest(w, r, &req) {
return return
} }
usernameOrEmail := strings.TrimSpace(req.UsernameOrEmail) usernameOrEmail := strings.TrimSpace(req.UsernameOrEmail)
if usernameOrEmail == "" {
SendErrorResponse(w, "Username or email is required", http.StatusBadRequest)
return
}
if err := h.authService.RequestPasswordReset(usernameOrEmail); err != nil { if err := h.authService.RequestPasswordReset(usernameOrEmail); err != nil {
} }
@@ -339,30 +310,17 @@ func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Reques
// @Failure 500 {object} AuthResponse "Internal server error" // @Failure 500 {object} AuthResponse "Internal server error"
// @Router /api/auth/reset-password [post] // @Router /api/auth/reset-password [post]
func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
var req struct { req, ok := GetValidatedDTO[dto.ResetPasswordRequest](r)
Token string `json:"token"` if !ok {
NewPassword string `json:"new_password"` SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
}
if !DecodeJSONRequest(w, r, &req) {
return return
} }
token := strings.TrimSpace(req.Token) token := strings.TrimSpace(req.Token)
newPassword := strings.TrimSpace(req.NewPassword) newPassword := strings.TrimSpace(req.NewPassword)
if token == "" { if err := validation.ValidatePassword(newPassword); err != nil {
SendErrorResponse(w, "Reset token is required", http.StatusBadRequest) SendErrorResponse(w, err.Error(), http.StatusBadRequest)
return
}
if newPassword == "" {
SendErrorResponse(w, "New password is required", http.StatusBadRequest)
return
}
if len(newPassword) < 8 {
SendErrorResponse(w, "Password must be at least 8 characters long", http.StatusBadRequest)
return return
} }
@@ -401,11 +359,9 @@ func (h *AuthHandler) UpdateEmail(w http.ResponseWriter, r *http.Request) {
return return
} }
var req struct { req, ok := GetValidatedDTO[dto.UpdateEmailRequest](r)
Email string `json:"email"` if !ok {
} SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
if !DecodeJSONRequest(w, r, &req) {
return return
} }
@@ -455,11 +411,9 @@ func (h *AuthHandler) UpdateUsername(w http.ResponseWriter, r *http.Request) {
return return
} }
var req struct { req, ok := GetValidatedDTO[dto.UpdateUsernameRequest](r)
Username string `json:"username"` if !ok {
} SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
if !DecodeJSONRequest(w, r, &req) {
return return
} }
@@ -504,23 +458,15 @@ func (h *AuthHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) {
return return
} }
var req struct { req, ok := GetValidatedDTO[dto.UpdatePasswordRequest](r)
CurrentPassword string `json:"current_password"` if !ok {
NewPassword string `json:"new_password"` SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
}
if !DecodeJSONRequest(w, r, &req) {
return return
} }
currentPassword := strings.TrimSpace(req.CurrentPassword) currentPassword := strings.TrimSpace(req.CurrentPassword)
newPassword := strings.TrimSpace(req.NewPassword) newPassword := strings.TrimSpace(req.NewPassword)
if currentPassword == "" {
SendErrorResponse(w, "Current password is required", http.StatusBadRequest)
return
}
if err := validation.ValidatePassword(newPassword); err != nil { if err := validation.ValidatePassword(newPassword); err != nil {
SendErrorResponse(w, err.Error(), http.StatusBadRequest) SendErrorResponse(w, err.Error(), http.StatusBadRequest)
return return
@@ -584,20 +530,13 @@ func (h *AuthHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {object} AuthResponse "Internal server error" // @Failure 500 {object} AuthResponse "Internal server error"
// @Router /api/auth/account/confirm [post] // @Router /api/auth/account/confirm [post]
func (h *AuthHandler) ConfirmAccountDeletion(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) ConfirmAccountDeletion(w http.ResponseWriter, r *http.Request) {
var req struct { req, ok := GetValidatedDTO[dto.ConfirmAccountDeletionRequest](r)
Token string `json:"token"` if !ok {
DeletePosts bool `json:"delete_posts"` SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
}
if !DecodeJSONRequest(w, r, &req) {
return return
} }
token := strings.TrimSpace(req.Token) token := strings.TrimSpace(req.Token)
if token == "" {
SendErrorResponse(w, "Deletion token is required", http.StatusBadRequest)
return
}
if err := h.authService.ConfirmAccountDeletionWithPosts(token, req.DeletePosts); err != nil { if err := h.authService.ConfirmAccountDeletionWithPosts(token, req.DeletePosts); err != nil {
switch { switch {
@@ -646,14 +585,9 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {object} AuthResponse "Internal server error" // @Failure 500 {object} AuthResponse "Internal server error"
// @Router /api/auth/refresh [post] // @Router /api/auth/refresh [post]
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
var req dto.RefreshTokenRequest req, ok := GetValidatedDTO[dto.RefreshTokenRequest](r)
if !ok {
if !DecodeJSONRequest(w, r, &req) { SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
return
}
if strings.TrimSpace(req.RefreshToken) == "" {
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
return return
} }
@@ -678,14 +612,9 @@ func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {object} AuthResponse "Internal server error" // @Failure 500 {object} AuthResponse "Internal server error"
// @Router /api/auth/revoke [post] // @Router /api/auth/revoke [post]
func (h *AuthHandler) RevokeToken(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) RevokeToken(w http.ResponseWriter, r *http.Request) {
var req dto.RevokeTokenRequest req, ok := GetValidatedDTO[dto.RevokeTokenRequest](r)
if !ok {
if !DecodeJSONRequest(w, r, &req) { SendErrorResponse(w, "Invalid request", http.StatusBadRequest)
return
}
if strings.TrimSpace(req.RefreshToken) == "" {
SendErrorResponse(w, "Refresh token is required", http.StatusBadRequest)
return return
} }
@@ -726,28 +655,28 @@ func (h *AuthHandler) RevokeAllTokens(w http.ResponseWriter, r *http.Request) {
func (h *AuthHandler) MountRoutes(r chi.Router, config RouteModuleConfig) { func (h *AuthHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
if config.GeneralRateLimit != nil { if config.GeneralRateLimit != nil {
rateLimited := config.GeneralRateLimit(r) rateLimited := config.GeneralRateLimit(r)
rateLimited.Post("/auth/refresh", h.RefreshToken) rateLimited.Post("/auth/refresh", WithValidation[dto.RefreshTokenRequest](config.ValidationMiddleware, h.RefreshToken))
rateLimited.Get("/auth/confirm", h.ConfirmEmail) rateLimited.Get("/auth/confirm", h.ConfirmEmail)
rateLimited.Post("/auth/resend-verification", h.ResendVerificationEmail) rateLimited.Post("/auth/resend-verification", WithValidation[dto.ResendVerificationRequest](config.ValidationMiddleware, h.ResendVerificationEmail))
} else { } else {
r.Post("/auth/refresh", h.RefreshToken) r.Post("/auth/refresh", WithValidation[dto.RefreshTokenRequest](config.ValidationMiddleware, h.RefreshToken))
r.Get("/auth/confirm", h.ConfirmEmail) r.Get("/auth/confirm", h.ConfirmEmail)
r.Post("/auth/resend-verification", h.ResendVerificationEmail) r.Post("/auth/resend-verification", WithValidation[dto.ResendVerificationRequest](config.ValidationMiddleware, h.ResendVerificationEmail))
} }
if config.AuthRateLimit != nil { if config.AuthRateLimit != nil {
rateLimited := config.AuthRateLimit(r) rateLimited := config.AuthRateLimit(r)
rateLimited.Post("/auth/register", h.Register) rateLimited.Post("/auth/register", WithValidation[dto.RegisterRequest](config.ValidationMiddleware, h.Register))
rateLimited.Post("/auth/login", h.Login) rateLimited.Post("/auth/login", WithValidation[dto.LoginRequest](config.ValidationMiddleware, h.Login))
rateLimited.Post("/auth/forgot-password", h.RequestPasswordReset) rateLimited.Post("/auth/forgot-password", WithValidation[dto.ForgotPasswordRequest](config.ValidationMiddleware, h.RequestPasswordReset))
rateLimited.Post("/auth/reset-password", h.ResetPassword) rateLimited.Post("/auth/reset-password", WithValidation[dto.ResetPasswordRequest](config.ValidationMiddleware, h.ResetPassword))
rateLimited.Post("/auth/account/confirm", h.ConfirmAccountDeletion) rateLimited.Post("/auth/account/confirm", WithValidation[dto.ConfirmAccountDeletionRequest](config.ValidationMiddleware, h.ConfirmAccountDeletion))
} else { } else {
r.Post("/auth/register", h.Register) r.Post("/auth/register", WithValidation[dto.RegisterRequest](config.ValidationMiddleware, h.Register))
r.Post("/auth/login", h.Login) r.Post("/auth/login", WithValidation[dto.LoginRequest](config.ValidationMiddleware, h.Login))
r.Post("/auth/forgot-password", h.RequestPasswordReset) r.Post("/auth/forgot-password", WithValidation[dto.ForgotPasswordRequest](config.ValidationMiddleware, h.RequestPasswordReset))
r.Post("/auth/reset-password", h.ResetPassword) r.Post("/auth/reset-password", WithValidation[dto.ResetPasswordRequest](config.ValidationMiddleware, h.ResetPassword))
r.Post("/auth/account/confirm", h.ConfirmAccountDeletion) r.Post("/auth/account/confirm", WithValidation[dto.ConfirmAccountDeletionRequest](config.ValidationMiddleware, h.ConfirmAccountDeletion))
} }
protected := r protected := r
@@ -760,10 +689,10 @@ func (h *AuthHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
protected.Get("/auth/me", h.Me) protected.Get("/auth/me", h.Me)
protected.Post("/auth/logout", h.Logout) protected.Post("/auth/logout", h.Logout)
protected.Post("/auth/revoke", h.RevokeToken) protected.Post("/auth/revoke", WithValidation[dto.RevokeTokenRequest](config.ValidationMiddleware, h.RevokeToken))
protected.Post("/auth/revoke-all", h.RevokeAllTokens) protected.Post("/auth/revoke-all", h.RevokeAllTokens)
protected.Put("/auth/email", h.UpdateEmail) protected.Put("/auth/email", WithValidation[dto.UpdateEmailRequest](config.ValidationMiddleware, h.UpdateEmail))
protected.Put("/auth/username", h.UpdateUsername) protected.Put("/auth/username", WithValidation[dto.UpdateUsernameRequest](config.ValidationMiddleware, h.UpdateUsername))
protected.Put("/auth/password", h.UpdatePassword) protected.Put("/auth/password", WithValidation[dto.UpdatePasswordRequest](config.ValidationMiddleware, h.UpdatePassword))
protected.Delete("/auth/account", h.DeleteAccount) protected.Delete("/auth/account", h.DeleteAccount)
} }