myxce/src/frontend/apiserver/controllers/auth.go

219 lines
6.5 KiB
Go

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