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] }