580 lines
20 KiB
Go
580 lines
20 KiB
Go
package v1
|
|
|
|
// Reference: M13 §4.4 — LDAP HTTP endpoints for login, config, and connectivity testing
|
|
// Provides four endpoints for LDAP/Active Directory integration:
|
|
// - POST /api/v1/ldap/login → LDAP bind authentication + JWT issuance
|
|
// - POST /api/v1/ldap/test → Admin-only LDAP connectivity test
|
|
// - GET /api/v1/ldap/config → Admin-only LDAP settings retrieval
|
|
// - PUT /api/v1/ldap/config → Admin-only LDAP settings update
|
|
//
|
|
// Enterprise feature: GoChat extends beyond Chatwoot's SAML-only SSO by adding
|
|
// LDAP support for traditional enterprise AD/LDAP environments.
|
|
//
|
|
// Login flow:
|
|
// 1. Client sends {username, password, account_id} to /api/v1/ldap/login
|
|
// 2. Handler delegates to SSOMiddleware.AuthenticateLDAP for unified SSO processing
|
|
// 3. SSOMiddleware routes to LDAPService.Authenticate (Bind + search + group extraction)
|
|
// 4. On success: SSOMiddleware auto-provisions user, maps groups→roles, creates SSO session
|
|
// 5. Handler issues JWT token pair via JWTService.GenerateTokenPair
|
|
// 6. Store refresh token, return access+refresh tokens to client
|
|
//
|
|
// Config management (admin-only):
|
|
// - Account administrators can configure per-account LDAP settings
|
|
// - TestConnection validates LDAP bind connectivity before saving config
|
|
// - GetConfig/UpdateConfig manage per-account LDAP settings in DB
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/gochat/gochat/internal/auth"
|
|
"github.com/gochat/gochat/internal/config"
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/pkg/response"
|
|
applogger "github.com/gochat/gochat/pkg/logger"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// LDAPHandler handles LDAP authentication HTTP endpoints.
|
|
type LDAPHandler struct {
|
|
ldapService *auth.LDAPService
|
|
ssoMiddleware *auth.SSOMiddleware
|
|
jwtService *auth.JWTService
|
|
refreshStore *auth.RefreshTokenStore
|
|
ssoSessionStore *auth.SSOSessionStore
|
|
ldapCfg *config.LDAPConfig
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewLDAPHandler creates an LDAP handler with service dependencies.
|
|
func NewLDAPHandler(
|
|
ldapService *auth.LDAPService,
|
|
ssoMiddleware *auth.SSOMiddleware,
|
|
jwtService *auth.JWTService,
|
|
refreshStore *auth.RefreshTokenStore,
|
|
ssoSessionStore *auth.SSOSessionStore,
|
|
ldapCfg *config.LDAPConfig,
|
|
db *gorm.DB,
|
|
) *LDAPHandler {
|
|
return &LDAPHandler{
|
|
ldapService: ldapService,
|
|
ssoMiddleware: ssoMiddleware,
|
|
jwtService: jwtService,
|
|
refreshStore: refreshStore,
|
|
ssoSessionStore: ssoSessionStore,
|
|
ldapCfg: ldapCfg,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// ldapLoginRequest is the JSON body for POST /api/v1/ldap/login.
|
|
type ldapLoginRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
AccountID uint `json:"account_id" binding:"required"`
|
|
}
|
|
|
|
// Login authenticates a user via LDAP bind and issues a JWT token pair.
|
|
// POST /api/v1/ldap/login
|
|
// This endpoint is PUBLIC — no AuthMiddleware required (LDAP login is the entry point).
|
|
func (h *LDAPHandler) Login(c *gin.Context) {
|
|
if !h.ldapCfg.Enabled {
|
|
c.JSON(http.StatusNotFound, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrNotFound,
|
|
Message: "LDAP is not enabled",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
var req ldapLoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrBadRequest,
|
|
Message: "Invalid request body",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Delegate to SSO middleware for unified authentication flow
|
|
// (auto-provision, group→role mapping, SSO session creation)
|
|
result, err := h.ssoMiddleware.AuthenticateLDAP(c.Request.Context(), req.AccountID, req.Username, req.Password)
|
|
if err != nil {
|
|
applogger.L().Errorf("LDAP authentication failed (account=%d, username=%s): %v", req.AccountID, req.Username, err)
|
|
|
|
statusCode := http.StatusInternalServerError
|
|
errCode := response.ErrInternal
|
|
if errors.Is(err, auth.ErrLDAPDisabled) {
|
|
statusCode = http.StatusNotFound
|
|
errCode = response.ErrNotFound
|
|
} else if errors.Is(err, auth.ErrLDAPInvalidConfig) {
|
|
statusCode = http.StatusBadRequest
|
|
errCode = response.ErrBadRequest
|
|
} else if errors.Is(err, auth.ErrLDAPConnection) {
|
|
statusCode = http.StatusServiceUnavailable
|
|
errCode = response.ErrServiceUnavail
|
|
} else if errors.Is(err, auth.ErrLDAPUserNotFound) || errors.Is(err, auth.ErrLDAPBindFailed) {
|
|
statusCode = http.StatusUnauthorized
|
|
errCode = response.ErrUnauthorized
|
|
}
|
|
|
|
c.JSON(statusCode, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: errCode,
|
|
Message: "LDAP authentication failed",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Issue JWT token pair using JWTService
|
|
// SSO middleware already created the user and mapped roles
|
|
tokenPair, err := h.jwtService.GenerateTokenPair(
|
|
&model.User{
|
|
Base: model.Base{ID: result.UserID},
|
|
Email: result.Email,
|
|
Name: result.Name,
|
|
Provider: "ldap",
|
|
UID: result.Subject,
|
|
Role: result.Role,
|
|
},
|
|
result.AccountID,
|
|
result.Role,
|
|
)
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to generate JWT for LDAP user (account=%d, user=%d): %v", result.AccountID, result.UserID, err)
|
|
c.JSON(http.StatusUnprocessableEntity, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrInternal,
|
|
Message: "Failed to generate authentication tokens",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Store refresh token
|
|
if h.refreshStore != nil {
|
|
if err := h.refreshStore.Store(c.Request.Context(), result.UserID, tokenPair.RefreshToken); err != nil {
|
|
applogger.L().Warnf("Failed to store refresh token for LDAP user %d: %v", result.UserID, err)
|
|
// Non-fatal: access token is still valid, refresh just won't work until re-login
|
|
}
|
|
}
|
|
|
|
// Create SSO session in Redis (for session tracking and SLO)
|
|
if h.ssoSessionStore != nil {
|
|
sessionData := &auth.SSOSessionData{
|
|
UserID: result.UserID,
|
|
Provider: "ldap",
|
|
IdPEntityID: fmt.Sprintf("ldap-account-%d", result.AccountID), // LDAP server as IdP identifier
|
|
NameID: result.Subject, // LDAP DN as NameID
|
|
AccountID: result.AccountID,
|
|
Role: result.Role,
|
|
CreatedAt: time.Now().Unix(),
|
|
ExpiresAt: time.Now().Add(h.ssoSessionStore.SessionTTL()).Unix(),
|
|
}
|
|
sessionID, err := h.ssoSessionStore.Create(c.Request.Context(), sessionData)
|
|
if err != nil {
|
|
applogger.L().Warnf("Failed to create SSO session for LDAP user %d: %v", result.UserID, err)
|
|
// Non-fatal: JWT tokens are still valid, SSO session is for tracking/SLO only
|
|
} else {
|
|
applogger.L().Infof("SSO session %s created for LDAP user %d (account=%d)", sessionID, result.UserID, result.AccountID)
|
|
}
|
|
}
|
|
|
|
// Return successful auth response (same format as SAML ACS and regular login)
|
|
response.OK(c, gin.H{
|
|
"user": gin.H{
|
|
"id": result.UserID,
|
|
"email": result.Email,
|
|
"name": result.Name,
|
|
"provider": "ldap",
|
|
"uid": result.Subject,
|
|
"role": result.Role,
|
|
},
|
|
"access_token": tokenPair.AccessToken,
|
|
"refresh_token": tokenPair.RefreshToken,
|
|
"expires_at": tokenPair.ExpiresAt,
|
|
})
|
|
}
|
|
|
|
// ldapTestRequest is the JSON body for POST /api/v1/ldap/test.
|
|
type ldapTestRequest struct {
|
|
AccountID uint `json:"account_id" binding:"required"`
|
|
}
|
|
|
|
// getAccountSettingsFromDB loads per-account LDAP configuration from DB directly.
|
|
// This is needed because LDAPService.getAccountSettings is unexported.
|
|
func (h *LDAPHandler) getAccountSettingsFromDB(accountID uint) (*model.AccountLDAPSettings, error) {
|
|
var settings model.AccountLDAPSettings
|
|
err := h.db.Where("account_id = ? AND active = ?", accountID, true).First(&settings).Error
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// No per-account settings — check if global defaults exist
|
|
if h.ldapCfg.DefaultHost == "" {
|
|
return nil, nil // LDAP not configured for this account
|
|
}
|
|
// Use global defaults as a fallback
|
|
settings = model.AccountLDAPSettings{
|
|
AccountID: accountID,
|
|
Host: h.ldapCfg.DefaultHost,
|
|
Port: h.ldapCfg.DefaultPort,
|
|
UseTLS: h.ldapCfg.DefaultUseTLS,
|
|
BaseDN: h.ldapCfg.DefaultBaseDN,
|
|
BindDN: h.ldapCfg.DefaultBindDN,
|
|
BindPassword: h.ldapCfg.DefaultBindPassword,
|
|
UserFilter: h.ldapCfg.DefaultUserFilter,
|
|
EmailAttribute: h.ldapCfg.DefaultEmailAttribute,
|
|
NameAttribute: h.ldapCfg.DefaultNameAttribute,
|
|
GroupAttribute: h.ldapCfg.DefaultGroupAttribute,
|
|
AutoProvision: true,
|
|
Active: true,
|
|
}
|
|
return &settings, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &settings, nil
|
|
}
|
|
|
|
// testLDAPConnectivity tests LDAP bind connectivity for an account's configuration.
|
|
// Connects to the LDAP server and attempts a bind with the service account to verify
|
|
// that the configuration is correct before saving.
|
|
func (h *LDAPHandler) testLDAPConnectivity(settings *model.AccountLDAPSettings) error {
|
|
// Use LDAPService.Authenticate with a dummy test to verify connectivity.
|
|
// The LDAPService handles connection + bind internally, so we use it to validate.
|
|
// We attempt a bind-only test by calling Authenticate with empty credentials
|
|
// and catching the specific error pattern.
|
|
// However, since Authenticate requires a real username/password, we instead
|
|
// try to directly connect and bind using the service account credentials.
|
|
//
|
|
// For simplicity, we delegate to ldapService.Authenticate with a test username.
|
|
// If the connection itself fails, we get ErrLDAPConnection.
|
|
// If the service account bind fails, we get an appropriate error.
|
|
// If the user search fails (expected for test username), we know connectivity works.
|
|
ctx := context.Background()
|
|
_, err := h.ldapService.Authenticate(ctx, settings.AccountID, "__ldap_connectivity_test__", "__invalid_test_password__")
|
|
if err == nil {
|
|
// Unexpected: test credentials actually worked. Still means connectivity is good.
|
|
return nil
|
|
}
|
|
// If connection failed, return that error
|
|
if errors.Is(err, auth.ErrLDAPConnection) || errors.Is(err, auth.ErrLDAPDisabled) || errors.Is(err, auth.ErrLDAPInvalidConfig) {
|
|
return err
|
|
}
|
|
// If we got ErrLDAPUserNotFound or ErrLDAPBindFailed, that means the connection
|
|
// and service account bind succeeded — only the test user lookup/bind failed,
|
|
// which is expected. Connectivity is confirmed.
|
|
return nil
|
|
}
|
|
|
|
// TestConnection tests LDAP bind connectivity for an account's configuration.
|
|
// POST /api/v1/ldap/test
|
|
// Admin-only: requires AuthMiddleware + admin role (enforced at router level).
|
|
func (h *LDAPHandler) TestConnection(c *gin.Context) {
|
|
if !h.ldapCfg.Enabled {
|
|
c.JSON(http.StatusNotFound, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrNotFound,
|
|
Message: "LDAP is not enabled",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
var req ldapTestRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrBadRequest,
|
|
Message: "Invalid request body",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Load per-account LDAP settings from DB
|
|
settings, err := h.getAccountSettingsFromDB(req.AccountID)
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to load LDAP settings for account %d: %v", req.AccountID, err)
|
|
c.JSON(http.StatusUnprocessableEntity, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrInternal,
|
|
Message: "Failed to load LDAP settings",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
if settings == nil {
|
|
c.JSON(http.StatusNotFound, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrNotFound,
|
|
Message: "No LDAP configuration found for this account",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Test LDAP connectivity
|
|
err = h.testLDAPConnectivity(settings)
|
|
if err != nil {
|
|
applogger.L().Errorf("LDAP connectivity test failed (account=%d, host=%s:%d): %v", req.AccountID, settings.Host, settings.Port, err)
|
|
c.JSON(http.StatusBadRequest, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrBadRequest,
|
|
Message: "LDAP connectivity test failed",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
response.OK(c, gin.H{
|
|
"account_id": req.AccountID,
|
|
"host": settings.Host,
|
|
"port": settings.Port,
|
|
"connected": true,
|
|
})
|
|
}
|
|
|
|
// GetConfig retrieves LDAP settings for an account.
|
|
// GET /api/v1/ldap/config?account_id=123
|
|
// Admin-only: requires AuthMiddleware + admin role (enforced at router level).
|
|
func (h *LDAPHandler) GetConfig(c *gin.Context) {
|
|
if !h.ldapCfg.Enabled {
|
|
c.JSON(http.StatusNotFound, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrNotFound,
|
|
Message: "LDAP is not enabled",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
accountIDStr := c.Query("account_id")
|
|
if accountIDStr == "" {
|
|
c.JSON(http.StatusBadRequest, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrBadRequest,
|
|
Message: "account_id query parameter is required",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrBadRequest,
|
|
Message: "Invalid account_id",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
settings, err := h.getAccountSettingsFromDB(uint(accountID))
|
|
if err != nil {
|
|
applogger.L().Errorf("Failed to get LDAP config for account %d: %v", accountID, err)
|
|
c.JSON(http.StatusUnprocessableEntity, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrInternal,
|
|
Message: "Failed to retrieve LDAP configuration",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
if settings == nil {
|
|
c.JSON(http.StatusNotFound, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrNotFound,
|
|
Message: "No LDAP configuration found for this account",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
response.OK(c, settings)
|
|
}
|
|
|
|
// ldapUpdateConfigRequest is the JSON body for PUT /api/v1/ldap/config.
|
|
type ldapUpdateConfigRequest struct {
|
|
AccountID uint `json:"account_id" binding:"required"`
|
|
Host string `json:"host" binding:"required"`
|
|
Port int `json:"port"`
|
|
UseTLS bool `json:"use_tls"`
|
|
BaseDN string `json:"base_dn" binding:"required"`
|
|
BindDN string `json:"bind_dn,omitempty"`
|
|
BindPassword string `json:"bind_password,omitempty"`
|
|
UserFilter string `json:"user_filter"`
|
|
EmailAttribute string `json:"email_attribute"`
|
|
NameAttribute string `json:"name_attribute"`
|
|
FirstNameAttribute string `json:"first_name_attribute"`
|
|
LastNameAttribute string `json:"last_name_attribute"`
|
|
GroupAttribute string `json:"group_attribute"`
|
|
GroupFilter string `json:"group_filter"`
|
|
RoleMappings json.RawMessage `json:"role_mappings"`
|
|
AutoProvision bool `json:"auto_provision"`
|
|
SyncInterval int `json:"sync_interval"`
|
|
Active bool `json:"active"`
|
|
}
|
|
|
|
// UpdateConfig updates per-account LDAP settings.
|
|
// PUT /api/v1/ldap/config
|
|
// Admin-only: requires AuthMiddleware + admin role (enforced at router level).
|
|
func (h *LDAPHandler) UpdateConfig(c *gin.Context) {
|
|
if !h.ldapCfg.Enabled {
|
|
c.JSON(http.StatusNotFound, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrNotFound,
|
|
Message: "LDAP is not enabled",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
var req ldapUpdateConfigRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrBadRequest,
|
|
Message: "Invalid request body",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// Default port values
|
|
if req.Port == 0 {
|
|
if req.UseTLS {
|
|
req.Port = 636
|
|
} else {
|
|
req.Port = 389
|
|
}
|
|
}
|
|
|
|
// Load existing settings or create new
|
|
var settings model.AccountLDAPSettings
|
|
err := h.db.Where("account_id = ?", req.AccountID).First(&settings).Error
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
applogger.L().Errorf("Failed to check existing LDAP settings for account %d: %v", req.AccountID, err)
|
|
c.JSON(http.StatusUnprocessableEntity, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrInternal,
|
|
Message: "Failed to check existing LDAP configuration",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// Create new settings
|
|
settings = model.AccountLDAPSettings{
|
|
AccountID: req.AccountID,
|
|
Host: req.Host,
|
|
Port: req.Port,
|
|
UseTLS: req.UseTLS,
|
|
BaseDN: req.BaseDN,
|
|
BindDN: req.BindDN,
|
|
BindPassword: req.BindPassword,
|
|
UserFilter: req.UserFilter,
|
|
EmailAttribute: req.EmailAttribute,
|
|
NameAttribute: req.NameAttribute,
|
|
FirstNameAttribute: req.FirstNameAttribute,
|
|
LastNameAttribute: req.LastNameAttribute,
|
|
GroupAttribute: req.GroupAttribute,
|
|
GroupFilter: req.GroupFilter,
|
|
RoleMappings: req.RoleMappings,
|
|
AutoProvision: req.AutoProvision,
|
|
SyncInterval: req.SyncInterval,
|
|
Active: req.Active,
|
|
}
|
|
} else {
|
|
// Update existing settings
|
|
settings.Host = req.Host
|
|
settings.Port = req.Port
|
|
settings.UseTLS = req.UseTLS
|
|
settings.BaseDN = req.BaseDN
|
|
settings.BindDN = req.BindDN
|
|
settings.BindPassword = req.BindPassword
|
|
settings.UserFilter = req.UserFilter
|
|
settings.EmailAttribute = req.EmailAttribute
|
|
settings.NameAttribute = req.NameAttribute
|
|
settings.FirstNameAttribute = req.FirstNameAttribute
|
|
settings.LastNameAttribute = req.LastNameAttribute
|
|
settings.GroupAttribute = req.GroupAttribute
|
|
settings.GroupFilter = req.GroupFilter
|
|
settings.RoleMappings = req.RoleMappings
|
|
settings.AutoProvision = req.AutoProvision
|
|
settings.SyncInterval = req.SyncInterval
|
|
settings.Active = req.Active
|
|
}
|
|
|
|
// Save to DB
|
|
if err := h.db.Save(&settings).Error; err != nil {
|
|
applogger.L().Errorf("Failed to save LDAP settings for account %d: %v", req.AccountID, err)
|
|
c.JSON(http.StatusUnprocessableEntity, response.APIResponse{
|
|
Success: false,
|
|
Error: &response.ErrorBody{
|
|
Code: response.ErrInternal,
|
|
Message: "Failed to save LDAP configuration",
|
|
Detail: err.Error(),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
applogger.L().Infof("LDAP settings updated for account %d (host=%s, port=%d, active=%v)", req.AccountID, req.Host, req.Port, req.Active)
|
|
response.OK(c, settings)
|
|
}
|
|
|
|
// RegisterLDAPRoutes sets up LDAP routes on a Gin router group.
|
|
// Login route is PUBLIC — no AuthRequired middleware (LDAP login doesn't require existing JWT).
|
|
// Config management routes require AuthMiddleware + admin role (enforced at router level).
|
|
func RegisterLDAPRoutes(rg *gin.RouterGroup, handler *LDAPHandler) {
|
|
ldapGroup := rg.Group("/ldap")
|
|
{
|
|
// Public route: LDAP login (no AuthRequired middleware)
|
|
ldapGroup.POST("/login", handler.Login)
|
|
|
|
// Admin-only routes: config management + connectivity test
|
|
// These are wired into the authenticated + admin router group externally,
|
|
// so AuthMiddleware + admin role check is enforced at the router level.
|
|
ldapGroup.POST("/test", handler.TestConnection)
|
|
ldapGroup.GET("/config", handler.GetConfig)
|
|
ldapGroup.PUT("/config", handler.UpdateConfig)
|
|
}
|
|
} |