Files
tm_back/internal/handler/auth.go
T
2025-07-27 16:20:21 +03:30

359 lines
12 KiB
Go

package handler
import (
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
"tm/internal/domain"
"tm/pkg/logger"
"tm/pkg/response"
)
// AuthHandler handles authentication related HTTP requests (legacy/generic)
// This handler provides backward compatibility and can handle both customer and user authentication
type AuthHandler struct {
customerAuthService domain.CustomerAuthService
userAuthService domain.UserAuthService
validator *validator.Validate
logger logger.Logger
}
// NewAuthHandler creates a new authentication handler
func NewAuthHandler(
customerAuthService domain.CustomerAuthService,
userAuthService domain.UserAuthService,
validator *validator.Validate,
logger logger.Logger,
) *AuthHandler {
return &AuthHandler{
customerAuthService: customerAuthService,
userAuthService: userAuthService,
validator: validator,
logger: logger,
}
}
// Register handles user registration (defaults to customer registration)
// @Summary Register new user
// @Description Create a new user account (defaults to customer)
// @Tags auth
// @Accept json
// @Produce json
// @Param request body domain.RegisterRequest true "Registration request"
// @Success 201 {object} response.APIResponse{data=domain.AuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 409 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/register [post]
func (h *AuthHandler) Register(c echo.Context) error {
var req domain.RegisterRequest
// Bind request body
if err := c.Bind(&req); err != nil {
h.logger.Warn("Failed to bind register request", map[string]interface{}{
"error": err.Error(),
})
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
h.logger.Warn("Register request validation failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
return response.ValidationError(c, "Validation failed", err.Error())
}
// Convert to customer registration request
customerReq := domain.CustomerRegisterRequest{
Email: req.Email,
Password: req.Password,
FirstName: req.FirstName,
LastName: req.LastName,
CompanyID: req.CompanyID,
}
// Call customer service (default registration is for customers)
customerAuthResponse, err := h.customerAuthService.Register(c.Request().Context(), customerReq)
if err != nil {
h.logger.Error("Registration failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
if err.Error() == "customer already exists" {
return response.Conflict(c, "User already exists")
}
return response.InternalServerError(c, "Registration failed")
}
// Convert to generic auth response for backward compatibility
authResponse := &domain.AuthResponse{
Customer: customerAuthResponse.Customer,
AccessToken: customerAuthResponse.AccessToken,
RefreshToken: customerAuthResponse.RefreshToken,
ExpiresIn: customerAuthResponse.ExpiresIn,
}
return response.Created(c, authResponse, "User registered successfully")
}
// Login handles user authentication (tries both customer and panel user)
// @Summary User login
// @Description Authenticate user and return tokens
// @Tags auth
// @Accept json
// @Produce json
// @Param request body domain.LoginRequest true "Login request"
// @Success 200 {object} response.APIResponse{data=domain.AuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/login [post]
func (h *AuthHandler) Login(c echo.Context) error {
var req domain.LoginRequest
// Bind request body
if err := c.Bind(&req); err != nil {
h.logger.Warn("Failed to bind login request", map[string]interface{}{
"error": err.Error(),
})
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
h.logger.Warn("Login request validation failed", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
return response.ValidationError(c, "Validation failed", err.Error())
}
// Try customer login first
if customerAuthResponse, err := h.customerAuthService.Login(c.Request().Context(), req); err == nil {
h.logger.Info("Customer logged in successfully", map[string]interface{}{
"customer_id": customerAuthResponse.Customer.ID.String(),
"email": customerAuthResponse.Customer.Email,
})
// Convert to generic auth response
authResponse := &domain.AuthResponse{
Customer: customerAuthResponse.Customer,
AccessToken: customerAuthResponse.AccessToken,
RefreshToken: customerAuthResponse.RefreshToken,
ExpiresIn: customerAuthResponse.ExpiresIn,
}
return response.Success(c, authResponse, "Login successful")
}
// Try panel user login
if userAuthResponse, err := h.userAuthService.Login(c.Request().Context(), req); err == nil {
h.logger.Info("Panel user logged in successfully", map[string]interface{}{
"user_id": userAuthResponse.User.ID.String(),
"email": userAuthResponse.User.Email,
"role": userAuthResponse.User.Role,
})
// Convert to generic auth response
authResponse := &domain.AuthResponse{
User: userAuthResponse.User,
AccessToken: userAuthResponse.AccessToken,
RefreshToken: userAuthResponse.RefreshToken,
ExpiresIn: userAuthResponse.ExpiresIn,
}
return response.Success(c, authResponse, "Login successful")
}
h.logger.Warn("Login failed for both customer and user", map[string]interface{}{
"email": req.Email,
})
return response.Unauthorized(c, "Invalid email or password")
}
// RefreshToken handles token refresh (tries both customer and panel user)
// @Summary Refresh access token
// @Description Generate new access token using refresh token
// @Tags auth
// @Accept json
// @Produce json
// @Param request body RefreshTokenRequest true "Refresh token request"
// @Success 200 {object} response.APIResponse{data=domain.AuthResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/refresh [post]
func (h *AuthHandler) RefreshToken(c echo.Context) error {
var req struct {
RefreshToken string `json:"refresh_token" validate:"required"`
}
// Bind request body
if err := c.Bind(&req); err != nil {
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
return response.ValidationError(c, "Validation failed", err.Error())
}
// Try customer token refresh first
if customerAuthResponse, err := h.customerAuthService.RefreshToken(c.Request().Context(), req.RefreshToken); err == nil {
// Convert to generic auth response
authResponse := &domain.AuthResponse{
Customer: customerAuthResponse.Customer,
AccessToken: customerAuthResponse.AccessToken,
RefreshToken: customerAuthResponse.RefreshToken,
ExpiresIn: customerAuthResponse.ExpiresIn,
}
return response.Success(c, authResponse, "Token refreshed successfully")
}
// Try panel user token refresh
if userAuthResponse, err := h.userAuthService.RefreshToken(c.Request().Context(), req.RefreshToken); err == nil {
// Convert to generic auth response
authResponse := &domain.AuthResponse{
User: userAuthResponse.User,
AccessToken: userAuthResponse.AccessToken,
RefreshToken: userAuthResponse.RefreshToken,
ExpiresIn: userAuthResponse.ExpiresIn,
}
return response.Success(c, authResponse, "Token refreshed successfully")
}
h.logger.Warn("Token refresh failed for both customer and user", map[string]interface{}{})
return response.Unauthorized(c, "Invalid refresh token")
}
// ChangePassword handles password change (determines user type from context)
// @Summary Change user password
// @Description Change authenticated user's password
// @Tags auth
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body ChangePasswordRequest true "Change password request"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/change-password [post]
func (h *AuthHandler) ChangePassword(c echo.Context) error {
var req struct {
OldPassword string `json:"old_password" validate:"required"`
NewPassword string `json:"new_password" validate:"required,min=8"`
}
// Bind request body
if err := c.Bind(&req); err != nil {
return response.BadRequest(c, "Invalid request format", err.Error())
}
// Validate request
if err := h.validator.Struct(&req); err != nil {
return response.ValidationError(c, "Validation failed", err.Error())
}
// Check if it's a customer
if customer, ok := c.Get("customer").(*domain.Customer); ok {
err := h.customerAuthService.ChangePassword(c.Request().Context(), customer.ID, req.OldPassword, req.NewPassword)
if err != nil {
h.logger.Error("Customer password change failed", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
if err.Error() == "invalid old password" {
return response.BadRequest(c, "Invalid old password", "")
}
return response.InternalServerError(c, "Password change failed")
}
return response.Success(c, nil, "Password changed successfully")
}
// Check if it's a panel user
if user, ok := c.Get("user").(*domain.User); ok {
err := h.userAuthService.ChangePassword(c.Request().Context(), user.ID, req.OldPassword, req.NewPassword)
if err != nil {
h.logger.Error("Panel user password change failed", map[string]interface{}{
"error": err.Error(),
"user_id": user.ID.String(),
})
if err.Error() == "invalid old password" {
return response.BadRequest(c, "Invalid old password", "")
}
return response.InternalServerError(c, "Password change failed")
}
return response.Success(c, nil, "Password changed successfully")
}
return response.Unauthorized(c, "Authentication required")
}
// GetProfile returns current user profile (determines user type from context)
// @Summary Get user profile
// @Description Get authenticated user's profile information
// @Tags auth
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} response.APIResponse{data=domain.User}
// @Failure 401 {object} response.APIResponse
// @Router /auth/profile [get]
func (h *AuthHandler) GetProfile(c echo.Context) error {
// Check if it's a customer
if customer, ok := c.Get("customer").(*domain.Customer); ok {
return response.Success(c, customer, "Profile retrieved successfully")
}
// Check if it's a panel user
if user, ok := c.Get("user").(*domain.User); ok {
return response.Success(c, user, "Profile retrieved successfully")
}
return response.Unauthorized(c, "Authentication required")
}
// Logout handles user logout (determines user type from context)
// @Summary User logout
// @Description Logout user (client should remove tokens)
// @Tags auth
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} response.APIResponse
// @Router /auth/logout [post]
func (h *AuthHandler) Logout(c echo.Context) error {
// Check if it's a customer
if customer, ok := c.Get("customer").(*domain.Customer); ok {
h.logger.Info("Customer logged out", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
})
return response.Success(c, nil, "Logged out successfully")
}
// Check if it's a panel user
if user, ok := c.Get("user").(*domain.User); ok {
h.logger.Info("Panel user logged out", map[string]interface{}{
"user_id": user.ID.String(),
"email": user.Email,
})
return response.Success(c, nil, "Logged out successfully")
}
return response.Success(c, nil, "Logged out successfully")
}