188 lines
5.4 KiB
Go
188 lines
5.4 KiB
Go
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"})
|
|
}
|