package auth import ( "log" "net/http" "photum-backend/internal/config" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgconn" ) type Handler struct { service *Service cfg *config.Config } func NewHandler(service *Service, cfg *config.Config) *Handler { return &Handler{service: service, cfg: cfg} } type registerRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"senha" binding:"required,min=6"` } // Register godoc // @Summary Register a new user // @Description Create a new user account with email and password // @Tags auth // @Accept json // @Produce json // @Param request body registerRequest true "Register Request" // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /auth/register [post] func (h *Handler) Register(c *gin.Context) { log.Println("Register endpoint called") var req registerRequest if err := c.ShouldBindJSON(&req); err != nil { log.Printf("Bind error: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } log.Printf("Attempting to register user: %s", req.Email) user, err := h.service.Register(c.Request.Context(), req.Email, req.Password) if err != nil { if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == "23505" { c.JSON(http.StatusBadRequest, gin.H{"error": "email já cadastrado"}) return } log.Printf("Error registering user: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "falha ao registrar usuário"}) return } log.Printf("User registered: %s", user.Email) c.JSON(http.StatusCreated, gin.H{"id": user.ID, "email": user.Email}) } type loginRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"senha" binding:"required"` } // Login godoc // @Summary Login user // @Description Authenticate user and return access token and refresh token // @Tags auth // @Accept json // @Produce json // @Param request body loginRequest true "Login Request" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 401 {object} map[string]string // @Router /auth/login [post] func (h *Handler) Login(c *gin.Context) { var req loginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } userAgent := c.Request.UserAgent() ip := c.ClientIP() accessToken, refreshToken, accessExp, user, err := h.service.Login( c.Request.Context(), req.Email, req.Password, userAgent, ip, ) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) return } // Set Refresh Token in Cookie (HttpOnly) maxAge := h.cfg.JwtRefreshTTLDays * 24 * 60 * 60 secure := h.cfg.AppEnv == "production" c.SetCookie("refresh_token", refreshToken, maxAge, "/", "", secure, true) // Use %v for UUID (or .String()) c.JSON(http.StatusOK, gin.H{ "access_token": accessToken, "expires_at": accessExp, "user": gin.H{ "id": user.ID, // %v works fine; no formatting needed here "email": user.Email, "role": user.Role, }, }) } // Refresh godoc // @Summary Refresh access token // @Description Get a new access token using a valid refresh token (cookie or body) // @Tags auth // @Accept json // @Produce json // @Param refresh_token body string false "Refresh Token (optional if in cookie)" // @Success 200 {object} map[string]interface{} // @Failure 401 {object} map[string]string // @Router /auth/refresh [post] func (h *Handler) Refresh(c *gin.Context) { // Try to get from cookie first refreshToken, err := c.Cookie("refresh_token") if err != nil { // Try from body if mobile var req struct { RefreshToken string `json:"refresh_token"` } if err := c.ShouldBindJSON(&req); err == nil { refreshToken = req.RefreshToken } } if refreshToken == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "refresh token required"}) return } accessToken, accessExp, err := h.service.Refresh(c.Request.Context(), refreshToken) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid refresh token"}) return } c.JSON(http.StatusOK, gin.H{ "access_token": accessToken, "expires_at": accessExp, }) } // Logout godoc // @Summary Logout user // @Description Revoke refresh token and clear cookie // @Tags auth // @Accept json // @Produce json // @Param refresh_token body string false "Refresh Token (optional if in cookie)" // @Success 200 {object} map[string]string // @Router /auth/logout [post] func (h *Handler) Logout(c *gin.Context) { refreshToken, err := c.Cookie("refresh_token") if err != nil { // Try from body var req struct { RefreshToken string `json:"refresh_token"` } if err := c.ShouldBindJSON(&req); err == nil { refreshToken = req.RefreshToken } } if refreshToken != "" { _ = h.service.Logout(c.Request.Context(), refreshToken) } // Clear cookie secure := h.cfg.AppEnv == "production" c.SetCookie("refresh_token", "", -1, "/", "", secure, true) c.JSON(http.StatusOK, gin.H{"message": "logged out"}) }