forgejo-tickets/internal/handlers/public/auth.go

206 lines
6.1 KiB
Go

package public
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/mattnite/forgejo-tickets/internal/auth"
"github.com/mattnite/forgejo-tickets/internal/middleware"
"github.com/mattnite/forgejo-tickets/internal/models"
"github.com/rs/zerolog/log"
)
type AuthHandler struct {
deps Dependencies
}
func (h *AuthHandler) loginData(extra map[string]interface{}) map[string]interface{} {
data := map[string]interface{}{
"GoogleEnabled": h.deps.Config.GoogleClientID != "",
"MicrosoftEnabled": h.deps.Config.MicrosoftClientID != "",
"AppleEnabled": h.deps.Config.AppleClientID != "",
}
for k, v := range extra {
data[k] = v
}
return data
}
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", h.loginData(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", h.loginData(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", h.loginData(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")
}
}
middleware.SetFlash(c, "success", "Account requested! Please check your email to verify your address. After verification, an admin will review your request.")
c.Redirect(http.StatusSeeOther, "/login")
}
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
}
middleware.SetFlash(c, "success", "Email verified successfully. Your account is pending admin approval.")
c.Redirect(http.StatusSeeOther, "/login")
}
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
}
middleware.SetFlash(c, "success", "Password reset successfully. You can now log in.")
c.Redirect(http.StatusSeeOther, "/login")
}