refactor: 精简配置体系,移除 OAuth 登录/Rate Limit/Admin env 配置
- 移除 .env.example 中 Feature Flags 段(代码中不存在这些 env var) - 移除 Google/GitHub OAuth 登录认证代码(auth/oauth.go、auth_handler OAuthAuthorize/OAuthCallback 路由、auth_service OAuthLogin),保留 Twitter/Google 作为消息渠道 provider - 从 OAuthConfig 移除 GitHub 字段(Google 保留供 channel provider 使用) - 移除 RateLimitConfig 可配置性,RateLimit 中间件改为硬编码 100 req/min、 60s window,移除 config/validator/reloader 中的 rate_limit 相关代码 - 移除 .env.example 中 GOCHAT_ADMIN_EMAIL/PASSWORD 配置 - 新增 gochat init 命令:交互式或通过 --email/--password/--name flags 初始化超级管理员账户,创建默认 Account + AccountUser 关联
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/gochat/gochat/internal/auth"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
)
|
||||
@@ -30,19 +29,17 @@ import (
|
||||
// AuthHandler handles authentication HTTP endpoints.
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
oauthService *auth.OAuthService
|
||||
profileService *service.ProfileService
|
||||
}
|
||||
|
||||
// NewAuthHandler creates an auth handler with service dependencies.
|
||||
func NewAuthHandler(authService *service.AuthService, oauthService *auth.OAuthService, profileService ...*service.ProfileService) *AuthHandler {
|
||||
func NewAuthHandler(authService *service.AuthService, profileService ...*service.ProfileService) *AuthHandler {
|
||||
var profileSvc *service.ProfileService
|
||||
if len(profileService) > 0 {
|
||||
profileSvc = profileService[0]
|
||||
}
|
||||
return &AuthHandler{
|
||||
authService: authService,
|
||||
oauthService: oauthService,
|
||||
profileService: profileSvc,
|
||||
}
|
||||
}
|
||||
@@ -86,13 +83,6 @@ type ConfirmEmailRequest struct {
|
||||
ConfirmationToken string `json:"confirmation_token" binding:"required"`
|
||||
}
|
||||
|
||||
// OAuthCallbackRequest is the JSON body for OAuth callback.
|
||||
type OAuthCallbackRequest struct {
|
||||
Provider string `json:"provider" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
// Login authenticates a user with email/password and returns JWT tokens.
|
||||
@@ -426,73 +416,6 @@ func (h *AuthHandler) ChatwootConfirmEmail(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"data": data})
|
||||
}
|
||||
|
||||
// OAuthCallback handles OAuth2 provider callback.
|
||||
// POST /api/v1/auth/oauth/callback
|
||||
// Receives provider + code from frontend (frontend handles redirect flow).
|
||||
func (h *AuthHandler) OAuthCallback(c *gin.Context) {
|
||||
var req OAuthCallbackRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
provider := auth.OAuthProviderType(req.Provider)
|
||||
if !h.oauthService.IsProviderConfigured(provider) {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "OAuth provider not configured: "+req.Provider)
|
||||
return
|
||||
}
|
||||
|
||||
output, err := h.authService.OAuthLogin(c.Request.Context(), &service.OAuthLoginInput{
|
||||
Provider: provider,
|
||||
Code: req.Code,
|
||||
State: req.State,
|
||||
})
|
||||
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,
|
||||
"is_new_user": output.IsNewUser,
|
||||
})
|
||||
}
|
||||
|
||||
// OAuthAuthorize generates the OAuth2 authorization URL for a provider.
|
||||
// GET /api/v1/auth/oauth/authorize?provider=google
|
||||
// Frontend redirects user to this URL to start OAuth flow.
|
||||
func (h *AuthHandler) OAuthAuthorize(c *gin.Context) {
|
||||
providerStr := c.Query("provider")
|
||||
provider := auth.OAuthProviderType(providerStr)
|
||||
|
||||
if !h.oauthService.IsProviderConfigured(provider) {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "OAuth provider not configured: "+providerStr)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate state for CSRF protection (store in Redis for validation)
|
||||
state := c.Query("state")
|
||||
if state == "" {
|
||||
state = generateOAuthState()
|
||||
}
|
||||
|
||||
url, err := h.oauthService.GetAuthURL(provider, state)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, gin.H{
|
||||
"authorize_url": url,
|
||||
"state": state,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterAuthRoutes sets up auth routes on a Gin router group.
|
||||
// These routes are PUBLIC — no AuthRequired middleware.
|
||||
func RegisterAuthRoutes(rg *gin.RouterGroup, handler *AuthHandler) {
|
||||
@@ -511,10 +434,6 @@ func RegisterAuthRoutes(rg *gin.RouterGroup, handler *AuthHandler) {
|
||||
authGroup.POST("/reset_password", handler.ResetPassword)
|
||||
authGroup.PUT("/reset_password", handler.ConfirmResetPassword)
|
||||
authGroup.GET("/confirm_email", handler.ConfirmEmail)
|
||||
|
||||
// OAuth
|
||||
authGroup.GET("/oauth/authorize", handler.OAuthAuthorize)
|
||||
authGroup.POST("/oauth/callback", handler.OAuthCallback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,8 +487,9 @@ func extractChatwootAccessToken(c *gin.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// generateOAuthState creates a cryptographically random state token for OAuth CSRF protection.
|
||||
// Production note: state should also be stored server-side (Redis) and validated on callback.
|
||||
// generateOAuthState creates a cryptographically random state token for CSRF protection.
|
||||
// Used by SAML 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)
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ func setupChatwootAuthTest(t *testing.T) (*gin.Engine, *gorm.DB, *model.User) {
|
||||
jwtCfg := &config.JWTConfig{Secret: "auth-test-secret", ExpiryHours: 1, RefreshExpiryHours: 24}
|
||||
jwtSvc := auth.NewJWTService(jwtCfg)
|
||||
refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg)
|
||||
authSvc := service.NewAuthService(db, jwtSvc, refreshStore, nil, nil)
|
||||
authSvc := service.NewAuthService(db, jwtSvc, refreshStore, nil)
|
||||
profileSvc := service.NewProfileService(repository.NewUserRepo(db), repository.NewAccountUserRepo(db), repository.NewAccessTokenRepo(db))
|
||||
handler := NewAuthHandler(authSvc, nil, profileSvc)
|
||||
handler := NewAuthHandler(authSvc, profileSvc)
|
||||
|
||||
router := gin.New()
|
||||
RegisterChatwootAuthRoutes(router.Group("/auth"), handler)
|
||||
|
||||
Reference in New Issue
Block a user