chore: add auth logic
ci-agent / build (push) Failing after 7m51s

This commit is contained in:
d3m0k1d
2026-04-04 01:09:40 +03:00
parent d917a9e465
commit 8ab7fbc6b2
9 changed files with 543 additions and 7 deletions
+182
View File
@@ -0,0 +1,182 @@
package handlers
import (
"errors"
"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
// @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
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to authenticate"})
return
}
c.JSON(http.StatusOK, resp)
}
// 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
// @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
// @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
// @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
// @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
// @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"})
}
// 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]
}
+86
View File
@@ -0,0 +1,86 @@
package handlers
import (
"net/http"
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
"github.com/gin-gonic/gin"
)
// TokenContextKey is the context key for storing authenticated token info.
type TokenContextKey string
const tokenContextKey TokenContextKey = "token"
// AuthMiddleware validates that a Bearer token exists and is valid.
// It stores the token info in the context for later use.
// Returns 401 if token is missing or invalid.
func (ag *AuthGroup) AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := getTokenFromHeader(c)
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})
c.Abort()
return
}
// Look up user by token value
tokens, err := ag.Repo.GetToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
c.Abort()
return
}
c.Set(string(tokenContextKey), tokens)
c.Next()
}
}
// RequirePermission is a generic permission checker.
func RequirePermission(check func(*repository.Tokens) bool) gin.HandlerFunc {
return func(c *gin.Context) {
tokenVal, exists := c.Get(string(tokenContextKey))
if !exists {
c.JSON(http.StatusForbidden, gin.H{"error": "authentication required"})
c.Abort()
return
}
token, ok := tokenVal.(*repository.Tokens)
if !ok {
c.JSON(http.StatusForbidden, gin.H{"error": "invalid token context"})
c.Abort()
return
}
if !check(token) {
c.JSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
c.Abort()
return
}
c.Next()
}
}
// RequireView requires permission_view.
func RequireView() gin.HandlerFunc {
return RequirePermission(func(t *repository.Tokens) bool {
return t.PermissionView
})
}
// RequireManageAgent requires permission_manage_agent.
func RequireManageAgent() gin.HandlerFunc {
return RequirePermission(func(t *repository.Tokens) bool {
return t.PermissionManage
})
}
// RequireAdmin requires permission_admin.
func RequireAdmin() gin.HandlerFunc {
return RequirePermission(func(t *repository.Tokens) bool {
return t.PermissionAdmin
})
}