package public import ( "net/http" "net/url" "strings" "github.com/gin-gonic/gin" "github.com/mattnite/forgejo-tickets/internal/auth" "github.com/mattnite/forgejo-tickets/internal/models" "github.com/rs/zerolog/log" ) type AuthHandler struct { deps Dependencies } func (h *AuthHandler) LoginForm(c *gin.Context) { if auth.CurrentUser(c) != nil { c.Redirect(http.StatusSeeOther, "/tickets") return } h.deps.Renderer.Render(c.Writer, c.Request, "login", nil) } func (h *AuthHandler) Login(c *gin.Context) { email := strings.TrimSpace(c.PostForm("email")) password := c.PostForm("password") user, err := h.deps.Auth.Login(c.Request.Context(), email, password) if err != nil { h.deps.Renderer.Render(c.Writer, c.Request, "login", map[string]interface{}{ "Error": err.Error(), "Email": email, }) return } if err := h.deps.Auth.CreateSession(c.Request, c.Writer, user.ID); err != nil { log.Error().Err(err).Msg("create session error") h.deps.Renderer.Render(c.Writer, c.Request, "login", map[string]interface{}{ "Error": "An unexpected error occurred", "Email": email, }) return } c.Redirect(http.StatusSeeOther, "/tickets") } func (h *AuthHandler) RegisterForm(c *gin.Context) { if auth.CurrentUser(c) != nil { c.Redirect(http.StatusSeeOther, "/tickets") return } h.deps.Renderer.Render(c.Writer, c.Request, "register", nil) } func (h *AuthHandler) Register(c *gin.Context) { name := strings.TrimSpace(c.PostForm("name")) email := strings.TrimSpace(c.PostForm("email")) password := c.PostForm("password") confirmPassword := c.PostForm("confirm_password") data := map[string]interface{}{ "Name": name, "Email": email, } if password != confirmPassword { data["Error"] = "Passwords do not match" h.deps.Renderer.Render(c.Writer, c.Request, "register", data) return } if len(password) < 8 { data["Error"] = "Password must be at least 8 characters" h.deps.Renderer.Render(c.Writer, c.Request, "register", data) return } user, err := h.deps.Auth.Register(c.Request.Context(), email, password, name) if err != nil { if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "unique") { data["Error"] = "An account with this email already exists" } else { data["Error"] = "Registration failed. Please try again." } h.deps.Renderer.Render(c.Writer, c.Request, "register", data) return } token, err := h.deps.Auth.GenerateVerificationToken(c.Request.Context(), user.ID) if err != nil { log.Error().Err(err).Msg("generate verification token error") } else { if err := h.deps.EmailClient.SendVerificationEmail(user.Email, user.Name, token); err != nil { log.Error().Err(err).Msg("send verification email error") } } redirectURL := "/login?" + url.Values{ "flash": {"Please check your email to verify your account"}, "flash_type": {"success"}, }.Encode() c.Redirect(http.StatusSeeOther, redirectURL) } func (h *AuthHandler) Logout(c *gin.Context) { if err := h.deps.Auth.DestroySession(c.Request, c.Writer); err != nil { log.Error().Err(err).Msg("destroy session error") } c.Redirect(http.StatusSeeOther, "/") } func (h *AuthHandler) VerifyEmail(c *gin.Context) { token := c.Query("token") if token == "" { h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Missing verification token") return } _, err := h.deps.Auth.VerifyEmailToken(c.Request.Context(), token) if err != nil { h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid or expired verification token") return } redirectURL := "/login?" + url.Values{ "flash": {"Email verified successfully. You can now log in."}, "flash_type": {"success"}, }.Encode() c.Redirect(http.StatusSeeOther, redirectURL) } func (h *AuthHandler) ForgotPasswordForm(c *gin.Context) { h.deps.Renderer.Render(c.Writer, c.Request, "forgot-password", nil) } func (h *AuthHandler) ForgotPassword(c *gin.Context) { email := strings.TrimSpace(c.PostForm("email")) var user models.User if err := h.deps.DB.Where("email = ?", email).First(&user).Error; err == nil { token, err := h.deps.Auth.GeneratePasswordResetToken(c.Request.Context(), user.ID) if err != nil { log.Error().Err(err).Msg("generate reset token error") } else { if err := h.deps.EmailClient.SendPasswordResetEmail(user.Email, user.Name, token); err != nil { log.Error().Err(err).Msg("send reset email error") } } } h.deps.Renderer.Render(c.Writer, c.Request, "forgot-password", map[string]interface{}{ "Success": "If an account exists with that email, we've sent a password reset link.", }) } func (h *AuthHandler) ResetPasswordForm(c *gin.Context) { token := c.Query("token") h.deps.Renderer.Render(c.Writer, c.Request, "reset-password", map[string]interface{}{ "Token": token, }) } func (h *AuthHandler) ResetPassword(c *gin.Context) { token := c.PostForm("token") password := c.PostForm("password") confirmPassword := c.PostForm("confirm_password") if password != confirmPassword { h.deps.Renderer.Render(c.Writer, c.Request, "reset-password", map[string]interface{}{ "Token": token, "Error": "Passwords do not match", }) return } if len(password) < 8 { h.deps.Renderer.Render(c.Writer, c.Request, "reset-password", map[string]interface{}{ "Token": token, "Error": "Password must be at least 8 characters", }) return } _, err := h.deps.Auth.RedeemPasswordResetToken(c.Request.Context(), token, password) if err != nil { h.deps.Renderer.Render(c.Writer, c.Request, "reset-password", map[string]interface{}{ "Token": token, "Error": "Invalid or expired reset token", }) return } redirectURL := "/login?" + url.Values{ "flash": {"Password reset successfully. You can now log in."}, "flash_type": {"success"}, }.Encode() c.Redirect(http.StatusSeeOther, redirectURL) }