feat: Implement Authentication Handlers (Issue #3)

- Create AuthService with login, refresh token, and user management
- Implement Login with password verification
- Implement RefreshToken for token renewal
- Implement GetCurrentUser endpoint
- Add ChangePassword functionality
- Add CreateUser for admin user creation
- Create AuthHandler with HTTP endpoints
- Add comprehensive validation and error handling
- Add google/uuid dependency to go.mod

Closes #3

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-02-05 00:48:55 +03:00
parent bdf19fdb62
commit d0624a2bc2
3 changed files with 416 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/yourusername/victorialogs-manager/internal/services"
)
// AuthHandler handles authentication endpoints
type AuthHandler struct {
authService *services.AuthService
}
// NewAuthHandler creates a new auth handler
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
return &AuthHandler{
authService: authService,
}
}
// Login handles user login
// @Summary User login
// @Description Authenticate user and return JWT tokens
// @Tags auth
// @Accept json
// @Produce json
// @Param request body services.LoginRequest true "Login credentials"
// @Success 200 {object} services.LoginResponse
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Router /api/v1/auth/login [post]
func (h *AuthHandler) Login(c *gin.Context) {
var req services.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
// Validate input
if req.Username == "" || req.Password == "" {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "validation error",
Message: "username and password are required",
})
return
}
resp, err := h.authService.Login(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusUnauthorized, ErrorResponse{
Error: "authentication failed",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, resp)
}
// RefreshToken handles token refresh
// @Summary Refresh access token
// @Description Get new access and refresh tokens using refresh token
// @Tags auth
// @Accept json
// @Produce json
// @Param request body services.RefreshTokenRequest true "Refresh token"
// @Success 200 {object} services.RefreshTokenResponse
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Router /api/v1/auth/refresh [post]
func (h *AuthHandler) RefreshToken(c *gin.Context) {
var req services.RefreshTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
resp, err := h.authService.RefreshToken(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusUnauthorized, ErrorResponse{
Error: "refresh failed",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, resp)
}
// GetMe returns the current user
// @Summary Get current user
// @Description Get current authenticated user information
// @Tags auth
// @Produce json
// @Success 200 {object} models.User
// @Failure 401 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/auth/me [get]
func (h *AuthHandler) GetMe(c *gin.Context) {
// Get user ID from context (set by auth middleware)
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, ErrorResponse{
Error: "unauthorized",
Message: "user not authenticated",
})
return
}
user, err := h.authService.GetCurrentUser(c.Request.Context(), userID.(string))
if err != nil {
c.JSON(http.StatusNotFound, ErrorResponse{
Error: "user not found",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, user)
}
// ChangePassword handles password change
// @Summary Change password
// @Description Change current user's password
// @Tags auth
// @Accept json
// @Produce json
// @Param request body services.ChangePasswordRequest true "Password change request"
// @Success 200 {object} SuccessResponse
// @Failure 400 {object} ErrorResponse
// @Failure 401 {object} ErrorResponse
// @Security BearerAuth
// @Router /api/v1/auth/change-password [post]
func (h *AuthHandler) ChangePassword(c *gin.Context) {
// Get user ID from context
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, ErrorResponse{
Error: "unauthorized",
Message: "user not authenticated",
})
return
}
var req services.ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "invalid request",
Message: err.Error(),
})
return
}
err := h.authService.ChangePassword(c.Request.Context(), userID.(string), &req)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{
Error: "password change failed",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, SuccessResponse{
Message: "password changed successfully",
})
}
// ErrorResponse represents an error response
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message"`
}
// SuccessResponse represents a success response
type SuccessResponse struct {
Message string `json:"message"`
}