package controllers import ( "apiserver/backend/auth" "apiserver/common" "apiserver/models" "apiserver/store" "apiserver/utils" logger "apiserver/utils" "fmt" "os" "path/filepath" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" "go.uber.org/zap" ) // Define a struct for handling authentication operations type AuthController struct{} // generate JWT token func generateJWT(username string, IP string) (string, error) { //appConfig := utils.AppYamlConfig // Create claims with user info claims := models.JWTClaims{ Username: username, IP: IP, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(utils.AuthenticationYamlConfig.Token.TokenExpireTime) * time.Second)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } // Create token with claims token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Generate signed token secretKey := []byte(utils.AuthenticationYamlConfig.Token.SecretKey) // In production, use environment variable signedToken, err := token.SignedString(secretKey) if err != nil { return "", err } return signedToken, nil } // applyQueueRBACFilter appends a queue_name:in:(...) filter exactly once // based on the user's accessible queues. If the user already specified a // queue_name filter, the RBAC filter is not added. func applyQueueRBACFilter(filters []utils.FilterCondition, accessQueues []string) []utils.FilterCondition { if len(accessQueues) == 0 { return filters } for _, f := range filters { if f.Field == "queue_name" { return filters } } return append(filters, utils.FilterCondition{ Field: "queue_name", Operator: "in", Value: "(" + strings.Join(accessQueues, ",") + ")", }) } func storeToken(token string, username string) error { tokenDir := filepath.Join(utils.Global_TopDir, "tokens", username) if err := os.MkdirAll(tokenDir, 0700); err != nil { logger.Error("Failed to create token directory", zap.Error(err)) return err } tokenFile := filepath.Join(tokenDir, "token") if err := os.WriteFile(tokenFile, []byte(token), 0600); err != nil { logger.Error("Failed to write token file", zap.Error(err)) return err } return nil } // removeToken removes the JWT file for a user. func removeToken(username string) error { tokenDir := filepath.Join(utils.Global_TopDir, "tokens", username) tokenFile := filepath.Join(tokenDir, "token") if err := os.Remove(tokenFile); err != nil { return err } // Remove user directory if empty os.Remove(tokenDir) return nil } // User logon func (a AuthController) Logon(ctx *gin.Context) { var req models.LogonRequest if err := utils.BindJSONStrict(ctx, &req); err != nil { logger.Error("Invalid request parameters", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } if auth.GlobalProvider == nil { logger.Error("No auth provider configured") ReturnError(ctx, common.ERROR_CODE, "authentication provider not configured", nil) return } // Authenticate via provider if err := auth.GlobalProvider.Authenticate(req.Username, req.Password); err != nil { ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // Auto-assign roles from provider if roles := auth.GlobalProvider.GetUserRoles(req.Username); len(roles) > 0 { for _, role := range roles { store.AddRoleForUser(req.Username, role) } } // Generate JWT token if utils.AuthenticationYamlConfig.Token.Type == "jwt" { token, err := generateJWT(req.Username, ctx.ClientIP()) if err != nil { logger.Error("Failed to generate JWT token", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, common.ERROR_JWT_GENERATION_MSG+err.Error(), nil) return } // Store token in server-managed directory if err := storeToken(token, req.Username); err != nil { logger.Error("Failed to store token", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, "Failed to store token", nil) return } tokenDir := filepath.Join(utils.Global_TopDir, "tokens", req.Username) response := models.LogonResponse{ Token: token, Path: tokenDir, IP: ctx.ClientIP(), TokenExpireTime: utils.AuthenticationYamlConfig.Token.TokenExpireTime, } logger.Info("Post /logon successfully from ", zap.String("client_ip", ctx.ClientIP())) ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, response) } } // User logout func (a AuthController) Logout(ctx *gin.Context) { logger.Debug("Enter Logout() function") // get token from header token := ctx.GetHeader("Authorization") // get username from token var err error username, err := GetUsernameFromToken(strings.TrimPrefix(token, "Bearer ")) if err != nil { logger.Error(common.ERROR_GET_USERNAME_MSG, zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, common.ERROR_GET_USERNAME_MSG+": "+err.Error(), nil) return } logger.Debug("username", zap.String("username", username)) // Remove token file from server-managed directory if err := removeToken(username); err != nil { logger.Error(common.ERROR_REMOVE_TOKEN_MSG, zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, common.ERROR_REMOVE_TOKEN_MSG+": "+err.Error(), nil) return } // Mock successful logout response := map[string]interface{}{ "username": username, "message": "Logout successful", } ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, response) } func GetUsernameFromToken(tokenString string) (string, error) { token, err := jwt.Parse(strings.TrimPrefix(tokenString, "Bearer "), func(token *jwt.Token) (interface{}, error) { // Validate the algorithm if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(utils.AuthenticationYamlConfig.Token.SecretKey), nil }) if err != nil { return "", err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { if username, ok := claims["username"].(string); ok { return username, nil } } return "", fmt.Errorf("invalid token") } // getAccessQueues returns the list of queues the user can access for the given permission. // Returns an error if the user has no such permission. func getAccessQueues(username, permID string) ([]string, error) { perm, err := GetRequireResourcePermission(username, permID) if err != nil { return nil, err } switch perm.DomainType { case models.DomainTypeAll: return store.GetAllQueueNames(), nil case models.DomainTypeQueue: return perm.DomainValues, nil default: return nil, nil } }