Files
HellreigN/backend/internal/handlers/auth.go
T
d3m0k1d f8c413a498
ci-agent / build (push) Failing after 2m36s
fix: reg
2026-04-04 20:17:51 +03:00

452 lines
14 KiB
Go

package handlers
import (
"errors"
"fmt"
"log"
"net/http"
"strings"
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
"github.com/gin-gonic/gin"
)
// AuthGroup handles authentication routes.
type AuthGroup struct {
*Handlers
}
// Login authenticates a user by login and password, returns a token.
// @Summary Login
// @Description Authenticate with login and password, returns a token and permissions
// @Tags auth
// @Accept json
// @Param request body repository.LoginRequest true "Login credentials"
// @Success 200 {object} repository.LoginResponse
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 403 {object} map[string]string
// @Router /auth/login [post]
func (ag *AuthGroup) Login(c *gin.Context) {
var req repository.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
resp, err := ag.Repo.Login(req.Login, req.Password)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
if errors.Is(err, repository.ErrAccountInactive) {
c.JSON(http.StatusForbidden, gin.H{"error": "account is not activated by admin"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to authenticate"})
return
}
c.JSON(http.StatusOK, resp)
}
// RegisterUser registers a new user with all permissions set to false.
// @Summary Register user
// @Description Registers a new user with login, password, name, last name. All permissions are set to false.
// @Tags auth
// @Accept json
// @Param request body repository.UserRegister true "Registration data"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /auth/register [post]
func (ag *AuthGroup) RegisterUser(c *gin.Context) {
var req repository.UserRegister
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
id, err := ag.Repo.RegisterUser(req)
if err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint") {
c.JSON(http.StatusConflict, gin.H{"error": "login already exists"})
return
}
log.Printf("[register] failed: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to register user: %v", err)})
return
}
log.Printf("[register] user registered: id=%s login=%s", id, req.Login)
c.JSON(http.StatusOK, gin.H{"message": "user registered"})
}
// CreateToken creates a new user.
// @Summary Create user
// @Description Creates a new user with permissions
// @Tags auth
// @Accept json
// @Param request body repository.TokenCreate true "User data"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/token [post]
func (ag *AuthGroup) CreateToken(c *gin.Context) {
var tc repository.TokenCreate
if err := c.ShouldBindJSON(&tc); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
if _, err := ag.Repo.CreateToken(tc); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user created"})
}
// ValidateToken validates the current Bearer token and returns user info.
// @Summary Validate token
// @Description Check if the provided Bearer token is valid and return its permissions
// @Tags auth
// @Produce json
// @Success 200 {object} repository.Tokens
// @Failure 401 {object} map[string]string
// @Security Bearer
// @Router /auth/validate [get]
func (ag *AuthGroup) ValidateToken(c *gin.Context) {
tokenVal, exists := c.Get(string(tokenContextKey))
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"})
return
}
token, ok := tokenVal.(*repository.Tokens)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token context"})
return
}
c.JSON(http.StatusOK, token)
}
// ListTokens returns all users.
// @Summary List users
// @Description Returns list of all users with their permissions
// @Tags auth
// @Produce json
// @Success 200 {array} repository.Tokens
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/tokens [get]
func (ag *AuthGroup) ListTokens(c *gin.Context) {
tokens, err := ag.Repo.ListTokens()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list users"})
return
}
c.JSON(http.StatusOK, tokens)
}
// DeleteToken deletes a user by login from URL path.
// @Summary Delete user
// @Description Deletes a user by their login
// @Tags auth
// @Param login path string true "Login of the user to delete"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/tokens/:login [delete]
func (ag *AuthGroup) DeleteToken(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
if err := ag.Repo.DeleteTokenByLogin(login); err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete user"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user deleted"})
}
// DeleteMyToken deletes the current user's account.
// @Summary Delete my account
// @Description Deletes the current authenticated user
// @Tags auth
// @Success 200 {object} map[string]string
// @Failure 401 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/token [delete]
func (ag *AuthGroup) DeleteMyToken(c *gin.Context) {
tokenVal, exists := c.Get(string(tokenContextKey))
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"})
return
}
token, ok := tokenVal.(*repository.Tokens)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token context"})
return
}
if err := ag.Repo.DeleteToken(token.Token); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete account"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "account deleted"})
}
// ActivateUser activates a user by login.
// @Summary Activate user
// @Description Activates a user account by login (admin only)
// @Tags auth
// @Param login path string true "Login of the user to activate"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/:login/activate [post]
func (ag *AuthGroup) ActivateUser(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
if err := ag.Repo.ActivateUserByLogin(login); err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to activate user"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user activated"})
}
// DeactivateUser deactivates a user by login.
// @Summary Deactivate user
// @Description Deactivates a user account by login (admin only)
// @Tags auth
// @Param login path string true "Login of the user to deactivate"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/:login/deactivate [post]
func (ag *AuthGroup) DeactivateUser(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
if err := ag.Repo.DeactivateUserByLogin(login); err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to deactivate user"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user deactivated"})
}
// ListInactiveUsers returns all users that are not activated.
// @Summary List inactive users
// @Description Returns list of all users waiting for activation
// @Tags auth
// @Produce json
// @Success 200 {array} repository.Tokens
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/inactive [get]
func (ag *AuthGroup) ListInactiveUsers(c *gin.Context) {
tokens, err := ag.Repo.ListInactiveTokens()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list inactive users"})
return
}
c.JSON(http.StatusOK, tokens)
}
// GetUser returns a user by login.
// @Summary Get user by login
// @Description Returns a user by their login (admin only)
// @Tags auth
// @Produce json
// @Param login path string true "Login of the user"
// @Success 200 {object} repository.Tokens
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/:login [get]
func (ag *AuthGroup) GetUser(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
user, err := ag.Repo.GetTokenByLogin(login)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get user"})
return
}
c.JSON(http.StatusOK, user)
}
// UpdateUser updates user's name and last name.
// @Summary Update user
// @Description Updates a user's name and last name (admin only)
// @Tags auth
// @Accept json
// @Param login path string true "Login of the user"
// @Param request body repository.TokenUpdate true "User data to update"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/:login [put]
func (ag *AuthGroup) UpdateUser(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
var update repository.TokenUpdate
if err := c.ShouldBindJSON(&update); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
if err := ag.Repo.UpdateToken(login, update); err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update user"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user updated"})
}
// UpdateUserPermissions updates user's permissions and activation status.
// @Summary Update user permissions
// @Description Updates a user's permissions and activation status (admin only)
// @Tags auth
// @Accept json
// @Param login path string true "Login of the user"
// @Param request body repository.TokenUpdatePermissions true "Permissions to update"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/:login/permissions [put]
func (ag *AuthGroup) UpdateUserPermissions(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
var update repository.TokenUpdatePermissions
if err := c.ShouldBindJSON(&update); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
if err := ag.Repo.UpdatePermissions(login, update); err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update permissions"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "permissions updated"})
}
// ResetUserPassword resets a user's password.
// @Summary Reset user password
// @Description Resets a user's password to a new value (admin only)
// @Tags auth
// @Accept json
// @Param login path string true "Login of the user"
// @Param request body repository.TokenPasswordReset true "New password"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security Bearer
// @Router /auth/users/:login/password [put]
func (ag *AuthGroup) ResetUserPassword(c *gin.Context) {
login := c.Param("login")
if login == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "login required"})
return
}
var req repository.TokenPasswordReset
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
if err := ag.Repo.UpdatePassword(login, req.NewPassword); err != nil {
if errors.Is(err, repository.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reset password"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "password reset"})
}
// getTokenFromHeader extracts the Bearer token from the Authorization header.
func getTokenFromHeader(c *gin.Context) string {
auth := c.GetHeader("Authorization")
if auth == "" {
return ""
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") {
return ""
}
return parts[1]
}