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:
Rogee
2026-07-29 16:26:19 +08:00
parent 7ed53bec47
commit fa6737e258
15 changed files with 209 additions and 732 deletions
@@ -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)