refactor: 移除 SAML/LDAP/MFA 登录方式,仅保留本地账号密码和 OIDC

后端移除:
- SAML: auth/saml.go, handler/saml_handler.go, account_saml_settings_handler.go,
  model/account_saml_settings.go, model/saml_idp_config.go, repo/*.go
- LDAP: auth/ldap.go, handler/ldap_handler.go, model/account_ldap_settings.go,
  repo/account_ldap_settings_repo.go
- MFA: auth/mfa.go, handler/mfa_handler.go
- auth_service: 移除 mfaService 依赖、MFARequired 字段、LoginWithMFA 方法
- auth_handler: 移除 LoginMFA handler、MFA 分支逻辑
- bootstrap: 移除 SAML/LDAP/MFA service 初始化和 handler 注册
- sso_middleware: 精简为仅支持 OIDC provider
- router: 移除 SAML/LDAP/MFA 路由注册
- config: 移除 SAMLConfig/LDAPConfig struct 和 defaults

前端移除:
- v3/login: 移除 MFA 验证流程和 SAML 登录入口
- v3/api/auth: 移除 MFA 响应处理
- v3/routes: 移除 SSO login 路由
- dashboard: 移除 MFA 设置页面、SAML 安全设置页面
- i18n: 移除 mfa.json
- featureFlags: 移除 SAML feature flag

.env.example / .env: 移除 SAML/LDAP 配置段
This commit is contained in:
Rogee
2026-07-29 19:03:04 +08:00
parent 09f274e965
commit 851ca7e372
66 changed files with 154 additions and 10590 deletions
@@ -52,12 +52,6 @@ type LoginRequest struct {
Password string `json:"password" binding:"required,min=6"`
}
// LoginMFAResquest is the JSON body for MFA login verification.
type LoginMFAResquest struct {
UserID uint `json:"user_id" binding:"required"`
TOTPCode string `json:"totp_code" binding:"required"`
}
// RefreshRequest is the JSON body for refresh endpoint.
type RefreshRequest struct {
RefreshToken string `json:"refresh_token" binding:"required"`
@@ -87,7 +81,6 @@ type ConfirmEmailRequest struct {
// Login authenticates a user with email/password and returns JWT tokens.
// POST /api/v1/auth/login
// If MFA is enabled, returns mfa_required=true with user_id for TOTP verification.
func (h *AuthHandler) Login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -104,15 +97,6 @@ func (h *AuthHandler) Login(c *gin.Context) {
return
}
if output.MFARequired {
response.OK(c, gin.H{
"mfa_required": true,
"user_id": output.User.ID,
"message": "MFA verification required, please provide TOTP code",
})
return
}
response.OK(c, gin.H{
"user": output.User,
"access_token": output.TokenPair.AccessToken,
@@ -141,13 +125,6 @@ func (h *AuthHandler) ChatwootSignIn(c *gin.Context) {
return
}
if output.MFARequired {
c.JSON(http.StatusPartialContent, gin.H{
"mfa_required": true,
"mfa_token": strconv.FormatUint(uint64(output.User.ID), 10),
})
return
}
if err := h.trackChatwootSession(c, output); err != nil {
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to create session")
return
@@ -213,31 +190,6 @@ func (h *AuthHandler) ChatwootSignOut(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
}
// LoginMFA completes login after MFA TOTP code verification.
// POST /api/v1/auth/login/mfa
func (h *AuthHandler) LoginMFA(c *gin.Context) {
var req LoginMFAResquest
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
output, err := h.authService.LoginWithMFA(c.Request.Context(), req.UserID, req.TOTPCode)
if err != nil {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, err.Error())
return
}
response.OK(c, gin.H{
"user": output.User,
"access_token": output.TokenPair.AccessToken,
"refresh_token": output.TokenPair.RefreshToken,
"expires_at": output.TokenPair.ExpiresAt,
"account_id": output.AccountID,
"role": output.Role,
})
}
// Refresh rotates a refresh token and returns new JWT pair.
// POST /api/v1/auth/refresh
// Implements refresh token rotation per P2E §1.4 security requirement.
@@ -423,7 +375,6 @@ func RegisterAuthRoutes(rg *gin.RouterGroup, handler *AuthHandler) {
{
// Core auth endpoints
authGroup.POST("/login", handler.Login)
authGroup.POST("/login/mfa", handler.LoginMFA)
authGroup.POST("/refresh", handler.Refresh)
authGroup.DELETE("/logout", handler.Logout)
@@ -488,7 +439,7 @@ func extractChatwootAccessToken(c *gin.Context) string {
}
// generateOAuthState creates a cryptographically random state token for CSRF protection.
// Used by SAML and other auth flows. Production note: state should also be stored
// Used by OIDC and other auth flows. Production note: state should also be stored
// server-side (Redis) and validated on callback.
func generateOAuthState() string {
return "gochat_oauth_" + randomHex(16)