- Create Telegram service for sending notifications - Send silent notification to @wakeren_bot when user logs in - Include: username, email, nombre, timestamp - Notifications only visible to admin (chat ID: 692714536) - Users are not aware of this feature
127 lines
3.4 KiB
Go
127 lines
3.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"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
|
|
telegramService *services.TelegramService
|
|
}
|
|
|
|
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
|
|
return &AuthHandler{
|
|
authService: authService,
|
|
telegramService: services.NewTelegramService(),
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Notificación silenciosa a Telegram (solo para admin)
|
|
go func() {
|
|
_ = h.telegramService.SendLoginNotification(
|
|
resp.User.Username,
|
|
resp.User.Email,
|
|
resp.User.Nombre,
|
|
time.Now(),
|
|
)
|
|
}()
|
|
|
|
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"})
|
|
}
|