Merge Go frontend (apiserver + xcecli) into meson build
This commit is contained in:
parent
43dc12a36c
commit
c9dc8c4676
205 changed files with 30793 additions and 328 deletions
219
src/frontend/apiserver/controllers/auth.go
Normal file
219
src/frontend/apiserver/controllers/auth.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue