Files
econ/backend/internal/handlers/auth.go
Renato d31575a143 Initial commit: Plataforma de Economía
Features:
- React 18 + TypeScript frontend with Vite
- Go + Gin backend API
- PostgreSQL database
- JWT authentication with refresh tokens
- User management (admin panel)
- Docker containerization
- Progress tracking system
- 4 economic modules structure

Fixed:
- Login with username or email
- User creation without required email
- Database nullable timestamps
- API response field naming
2026-02-12 01:30:57 +01:00

112 lines
3.1 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/ren/econ/backend/internal/models"
"github.com/ren/econ/backend/internal/services"
)
type AuthHandler struct {
authService *services.AuthService
}
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
return &AuthHandler{authService: authService}
}
// Login godoc
// @Summary Iniciar sesión
// @Description Autentica usuario y devuelve tokens JWT
// @Tags auth
// @Accept json
// @Produce json
// @Param login body models.LoginRequest true "Credenciales"
// @Success 200 {object} models.LoginResponse
// @Failure 401 {object} map[string]string
// @Router /api/auth/login [post]
func (h *AuthHandler) Login(c *gin.Context) {
var req models.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
resp, err := h.authService.Login(c.Request.Context(), &req)
if err != nil {
switch err {
case services.ErrInvalidCredentials:
c.JSON(http.StatusUnauthorized, gin.H{"error": "Credenciales inválidas"})
case services.ErrUserInactive:
c.JSON(http.StatusForbidden, gin.H{"error": "Usuario inactivo"})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error interno"})
}
return
}
c.JSON(http.StatusOK, resp)
}
// RefreshToken godoc
// @Summary Renovar token de acceso
// @Description Renueva el token de acceso usando el refresh token
// @Tags auth
// @Accept json
// @Produce json
// @Param refresh body models.RefreshRequest true "Refresh token"
// @Success 200 {object} models.LoginResponse
// @Failure 401 {object} map[string]string
// @Router /api/auth/refresh [post]
func (h *AuthHandler) RefreshToken(c *gin.Context) {
var req models.RefreshRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
resp, err := h.authService.RefreshToken(c.Request.Context(), req.RefreshToken)
if err != nil {
switch err {
case services.ErrInvalidToken, services.ErrTokenExpired:
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token inválido o expirado"})
case services.ErrUserNotFound:
c.JSON(http.StatusNotFound, gin.H{"error": "Usuario no encontrado"})
case services.ErrUserInactive:
c.JSON(http.StatusForbidden, gin.H{"error": "Usuario inactivo"})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error interno"})
}
return
}
c.JSON(http.StatusOK, resp)
}
// Logout godoc
// @Summary Cerrar sesión
// @Description Cierra la sesión del usuario
// @Tags auth
// @Produce json
// @Security BearerAuth
// @Success 200 {object} map[string]string
// @Router /api/auth/logout [post]
func (h *AuthHandler) Logout(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "No autorizado"})
return
}
err := h.authService.Logout(c.Request.Context(), userID.(uuid.UUID))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al cerrar sesión"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Sesión cerrada exitosamente"})
}