From 7ab11154e3547efb2bdc9e6300ee7c1af06a431d Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 31 May 2026 20:29:15 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openflare_server/common/constants.go | 2 +- openflare_server/controller/misc.go | 120 ++---- openflare_server/controller/option.go | 102 ++---- openflare_server/controller/origin.go | 3 +- openflare_server/controller/proxy_route.go | 3 +- .../controller/tls_certificate.go | 3 +- openflare_server/controller/user.go | 344 ++++-------------- openflare_server/controller/waf.go | 48 ++- openflare_server/controller/wechat.go | 85 ++--- openflare_server/job/cron.go | 3 +- openflare_server/middleware/rate-limit.go | 31 +- openflare_server/model/apply_log.go | 6 - openflare_server/web/types/public-status.ts | 1 - 13 files changed, 197 insertions(+), 554 deletions(-) diff --git a/openflare_server/common/constants.go b/openflare_server/common/constants.go index 2cd2646f..103f3854 100644 --- a/openflare_server/common/constants.go +++ b/openflare_server/common/constants.go @@ -26,7 +26,7 @@ var OptionMapRWMutex sync.RWMutex var ItemsPerPage = 10 var PasswordLoginEnabled = true -var PasswordRegisterEnabled = true +var PasswordRegisterEnabled = false var EmailVerificationEnabled = false var GitHubOAuthEnabled = false var WeChatAuthEnabled = false diff --git a/openflare_server/controller/misc.go b/openflare_server/controller/misc.go index 8f506343..3e6eb2b0 100644 --- a/openflare_server/controller/misc.go +++ b/openflare_server/controller/misc.go @@ -1,9 +1,7 @@ package controller import ( - "encoding/json" "fmt" - "net/http" "openflare/common" "openflare/model" "openflare/service" @@ -25,65 +23,43 @@ func GetStatus(c *gin.Context) { if err != nil { authSources = []service.PublicAuthSource{} } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": gin.H{ - "version": common.Version, - "start_time": common.StartTime, - "email_verification": common.EmailVerificationEnabled, - "github_oauth": common.GitHubOAuthEnabled, - "github_client_id": common.GitHubClientId, - "system_name": common.SystemName, - "home_page_link": common.HomePageLink, - "footer_html": common.Footer, - "wechat_qrcode": common.WeChatAccountQRCodeImageURL, - "wechat_login": common.WeChatAuthEnabled, - "server_address": common.ServerAddress, - "register_enabled": common.RegisterEnabled, - "password_register_enabled": common.PasswordRegisterEnabled, - "auth_sources": authSources, - }, + respondSuccess(c, gin.H{ + "version": common.Version, + "start_time": common.StartTime, + "email_verification": common.EmailVerificationEnabled, + "github_oauth": common.GitHubOAuthEnabled, + "github_client_id": common.GitHubClientId, + "system_name": common.SystemName, + "home_page_link": common.HomePageLink, + "footer_html": common.Footer, + "wechat_qrcode": common.WeChatAccountQRCodeImageURL, + "wechat_login": common.WeChatAuthEnabled, + "server_address": common.ServerAddress, + "password_register_enabled": common.PasswordRegisterEnabled, + "auth_sources": authSources, }) - return } func GetNotice(c *gin.Context) { common.OptionMapRWMutex.RLock() defer common.OptionMapRWMutex.RUnlock() - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": common.OptionMap["Notice"], - }) - return + respondSuccess(c, common.OptionMap["Notice"]) } func GetAbout(c *gin.Context) { common.OptionMapRWMutex.RLock() defer common.OptionMapRWMutex.RUnlock() - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": common.OptionMap["About"], - }) - return + respondSuccess(c, common.OptionMap["About"]) } func SendEmailVerification(c *gin.Context) { email := c.Query("email") if err := validation.Validate.Var(email, "required,email"); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + respondFailure(c, "无效的参数") return } if model.IsEmailAlreadyTaken(email) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "邮箱地址已被占用", - }) + respondFailure(c, "邮箱地址已被占用") return } code := security.GenerateVerificationCode(6) @@ -101,33 +77,20 @@ func SendEmailVerification(c *gin.Context) { } err := mail.SendEmail(cfg, subject, email, content) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } func SendPasswordResetEmail(c *gin.Context) { email := c.Query("email") if err := validation.Validate.Var(email, "required,email"); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + respondFailure(c, "无效的参数") return } if !model.IsEmailAlreadyTaken(email) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "该邮箱地址未注册", - }) + respondFailure(c, "该邮箱地址未注册") return } code := security.GenerateVerificationCode(0) @@ -146,17 +109,10 @@ func SendPasswordResetEmail(c *gin.Context) { } err := mail.SendEmail(cfg, subject, email, content) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } type PasswordResetRequest struct { @@ -166,35 +122,23 @@ type PasswordResetRequest struct { func ResetPassword(c *gin.Context) { var req PasswordResetRequest - err := json.NewDecoder(c.Request.Body).Decode(&req) + if !bindJSON(c, &req) { + return + } if req.Email == "" || req.Token == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + respondFailure(c, "无效的参数") return } if !security.VerifyCodeWithKey(req.Email, req.Token, security.PasswordResetPurpose) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "重置链接非法或已过期", - }) + respondFailure(c, "重置链接非法或已过期") return } password := security.GenerateVerificationCode(12) - err = model.ResetUserPasswordByEmail(req.Email, password) + err := model.ResetUserPasswordByEmail(req.Email, password) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } security.DeleteKey(req.Email, security.PasswordResetPurpose) - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": password, - }) - return + respondSuccess(c, password) } diff --git a/openflare_server/controller/option.go b/openflare_server/controller/option.go index 3a498aeb..ac55c7d1 100644 --- a/openflare_server/controller/option.go +++ b/openflare_server/controller/option.go @@ -1,9 +1,7 @@ package controller import ( - "encoding/json" "fmt" - "net/http" "openflare/common" "openflare/model" "openflare/service" @@ -231,11 +229,11 @@ func validateOptionWithState(option model.Option, state map[string]string) error switch option.Key { case "GitHubOAuthEnabled": if option.Value == "true" && strings.TrimSpace(state["GitHubClientId"]) == "" { - return fmt.Errorf("鏃犳硶鍚敤 GitHub OAuth锛岃鍏堝~鍏?GitHub Client ID 浠ュ強 GitHub Client Secret锛?") + return fmt.Errorf("无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret!") } case "WeChatAuthEnabled": if option.Value == "true" && strings.TrimSpace(state["WeChatServerAddress"]) == "" { - return fmt.Errorf("鏃犳硶鍚敤寰俊鐧诲綍锛岃鍏堝~鍏ュ井淇$櫥褰曠浉鍏抽厤缃俊鎭紒") + return fmt.Errorf("无法启用微信登录,请先填入微信登录相关配置信息!") } } @@ -260,13 +258,13 @@ func validateOptionWithState(option model.Option, state map[string]string) error func updateOptions(options []model.Option) error { if len(options) == 0 { - return fmt.Errorf("鏃犳晥鐨勫弬鏁?") + return fmt.Errorf("无效的参数") } state := buildOptionValidationState(options) for _, option := range options { if strings.TrimSpace(option.Key) == "" { - return fmt.Errorf("鏃犳晥鐨勫弬鏁?") + return fmt.Errorf("无效的参数") } if err := validateOptionWithState(option, state); err != nil { return err @@ -295,12 +293,7 @@ func GetOptions(c *gin.Context) { }) } common.OptionMapRWMutex.Unlock() - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": options, - }) - return + respondSuccess(c, options) } // UpdateOption godoc @@ -314,80 +307,47 @@ func GetOptions(c *gin.Context) { // @Router /api/option/update [post] func UpdateOption(c *gin.Context) { var option model.Option - err := json.NewDecoder(c.Request.Body).Decode(&option) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "success": false, - "message": "无效的参数", - }) + if !bindJSON(c, &option) { return } switch option.Key { case "GitHubOAuthEnabled": if option.Value == "true" && common.GitHubClientId == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret!", - }) + respondFailure(c, "无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret!") return } case "WeChatAuthEnabled": if option.Value == "true" && common.WeChatServerAddress == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无法启用微信登录,请先填入微信登录相关配置信息!", - }) + respondFailure(c, "无法启用微信登录,请先填入微信登录相关配置信息!") return } } - if err = validateRateLimitOption(option.Key, option.Value); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + if err := validateRateLimitOption(option.Key, option.Value); err != nil { + respondFailure(c, err.Error()) return } - if err = validateOpenRestyOption(option.Key, option.Value); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + if err := validateOpenRestyOption(option.Key, option.Value); err != nil { + respondFailure(c, err.Error()) return } - if err = validateGeoIPOption(option.Key, option.Value); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + if err := validateGeoIPOption(option.Key, option.Value); err != nil { + respondFailure(c, err.Error()) return } - if err = validateDatabaseCleanupOption(option.Key, option.Value); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + if err := validateDatabaseCleanupOption(option.Key, option.Value); err != nil { + respondFailure(c, err.Error()) return } - if err = validateAgentOption(option.Key, option.Value); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + if err := validateAgentOption(option.Key, option.Value); err != nil { + respondFailure(c, err.Error()) return } - err = model.UpdateOption(option.Key, option.Value) + err := model.UpdateOption(option.Key, option.Value) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } // UpdateOptionsBatch godoc @@ -401,24 +361,18 @@ func UpdateOption(c *gin.Context) { // @Router /api/option/update-batch [post] func UpdateOptionsBatch(c *gin.Context) { var payload optionBatchPayload - if err := json.NewDecoder(c.Request.Body).Decode(&payload); err != nil || len(payload.Options) == 0 { - c.JSON(http.StatusBadRequest, gin.H{ - "success": false, - "message": "鏃犳晥鐨勫弬鏁?", - }) + if !bindJSON(c, &payload) { + return + } + if len(payload.Options) == 0 { + respondBadRequest(c, "无效的参数") return } if err := updateOptions(payload.Options); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) + respondSuccessMessage(c, "") } diff --git a/openflare_server/controller/origin.go b/openflare_server/controller/origin.go index c8d0b9f1..c98f2354 100644 --- a/openflare_server/controller/origin.go +++ b/openflare_server/controller/origin.go @@ -1,8 +1,9 @@ package controller import ( - "github.com/gin-gonic/gin" "openflare/service" + + "github.com/gin-gonic/gin" ) func GetOrigins(c *gin.Context) { diff --git a/openflare_server/controller/proxy_route.go b/openflare_server/controller/proxy_route.go index 280241ce..fb3d54b0 100644 --- a/openflare_server/controller/proxy_route.go +++ b/openflare_server/controller/proxy_route.go @@ -1,8 +1,9 @@ package controller import ( - "github.com/gin-gonic/gin" "openflare/service" + + "github.com/gin-gonic/gin" ) // GetProxyRoutes godoc diff --git a/openflare_server/controller/tls_certificate.go b/openflare_server/controller/tls_certificate.go index 2ce5c5d0..8031fe5e 100644 --- a/openflare_server/controller/tls_certificate.go +++ b/openflare_server/controller/tls_certificate.go @@ -1,8 +1,9 @@ package controller import ( - "github.com/gin-gonic/gin" "openflare/service" + + "github.com/gin-gonic/gin" ) // GetTLSCertificates godoc diff --git a/openflare_server/controller/user.go b/openflare_server/controller/user.go index 1cf36fe1..f2aa927f 100644 --- a/openflare_server/controller/user.go +++ b/openflare_server/controller/user.go @@ -1,8 +1,6 @@ package controller import ( - "encoding/json" - "net/http" "openflare/common" "openflare/model" "openflare/utils/security" @@ -22,40 +20,26 @@ type LoginRequest struct { func Login(c *gin.Context) { if !common.PasswordLoginEnabled { - c.JSON(http.StatusOK, gin.H{ - "message": "管理员关闭了密码登录", - "success": false, - }) + respondFailure(c, "管理员关闭了密码登录") return } var loginRequest LoginRequest - err := json.NewDecoder(c.Request.Body).Decode(&loginRequest) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "message": "无效的参数", - "success": false, - }) + if !bindJSON(c, &loginRequest) { return } username := loginRequest.Username password := loginRequest.Password if username == "" || password == "" { - c.JSON(http.StatusOK, gin.H{ - "message": "无效的参数", - "success": false, - }) + respondFailure(c, "无效的参数") return } user := model.User{ Username: username, Password: password, } - err = user.ValidateAndFill() + err := user.ValidateAndFill() if err != nil { - c.JSON(http.StatusOK, gin.H{ - "message": err.Error(), - "success": false, - }) + respondFailure(c, err.Error()) return } setupLogin(&user, c) @@ -96,24 +80,14 @@ func Logout(c *gin.Context) { session.Clear() err := session.Save() if err != nil { - c.JSON(http.StatusOK, gin.H{ - "message": err.Error(), - "success": false, - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "message": "", - "success": true, - }) + respondSuccessMessage(c, "") } func Register(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "非法请求", - "success": false, - }) - return + respondFailure(c, "非法请求") } func GetAllUsers(c *gin.Context) { @@ -123,167 +97,101 @@ func GetAllUsers(c *gin.Context) { } users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": users, - }) - return + respondSuccess(c, users) } func SearchUsers(c *gin.Context) { keyword := c.Query("keyword") users, err := model.SearchUsers(keyword) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": users, - }) - return + respondSuccess(c, users) } func GetUser(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + id, ok := parseIDParam(c) + if !ok { return } - user, err := model.GetUserById(id, false) + user, err := model.GetUserById(int(id), false) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } myRole := c.GetInt("role") if myRole <= user.Role { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权获取同级或更高等级用户的信息", - }) + respondFailure(c, "无权获取同级或更高等级用户的信息") return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": user, - }) - return + respondSuccess(c, user) } func GenerateToken(c *gin.Context) { id := c.GetInt("id") user, err := model.GetUserById(id, true) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } user.Token = uuid.New().String() user.Token = strings.Replace(user.Token, "-", "", -1) if model.DB.Where("token = ?", user.Token).First(user).RowsAffected != 0 { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "请重试,系统生成的 UUID 竟然重复了!", - }) + respondFailure(c, "请重试,系统生成的 UUID 竟然重复了!") return } if err := user.Update(false); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": user.Token, - }) - return + respondSuccess(c, user.Token) } func GetSelf(c *gin.Context) { id := c.GetInt("id") user, err := model.GetUserById(id, false) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": user, - }) - return + respondSuccess(c, user) } func UpdateUser(c *gin.Context) { var updatedUser model.User - err := json.NewDecoder(c.Request.Body).Decode(&updatedUser) - if err != nil || updatedUser.Id == 0 { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + if !bindJSON(c, &updatedUser) { + return + } + if updatedUser.Id == 0 { + respondFailure(c, "无效的参数") return } if updatedUser.Password == "" { updatedUser.Password = "$I_LOVE_U" // make Validator happy :) } if err := validation.Validate.Struct(&updatedUser); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "输入不合法 " + err.Error(), - }) + respondFailure(c, "输入不合法 "+err.Error()) return } originUser, err := model.GetUserById(updatedUser.Id, false) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } myRole := c.GetInt("role") if myRole <= originUser.Role { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权更新同权限等级或更高权限等级的用户信息", - }) + respondFailure(c, "无权更新同权限等级或更高权限等级的用户信息") return } if myRole <= updatedUser.Role { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权将其他用户权限等级提升到大于等于自己的权限等级", - }) + respondFailure(c, "无权将其他用户权限等级提升到大于等于自己的权限等级") return } if updatedUser.Password == "$I_LOVE_U" { @@ -291,37 +199,22 @@ func UpdateUser(c *gin.Context) { } updatePassword := updatedUser.Password != "" if err := updatedUser.Update(updatePassword); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } func UpdateSelf(c *gin.Context) { var user model.User - err := json.NewDecoder(c.Request.Body).Decode(&user) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + if !bindJSON(c, &user) { return } if user.Password == "" { user.Password = "$I_LOVE_U" // make Validator happy :) } if err := validation.Validate.Struct(&user); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "输入不合法 " + err.Error(), - }) + respondFailure(c, "输入不合法 "+err.Error()) return } @@ -337,80 +230,53 @@ func UpdateSelf(c *gin.Context) { } updatePassword := user.Password != "" if err := cleanUser.Update(updatePassword); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } func DeleteUser(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + id, ok := parseIDParam(c) + if !ok { return } - originUser, err := model.GetUserById(id, false) + originUser, err := model.GetUserById(int(id), false) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } myRole := c.GetInt("role") if myRole <= originUser.Role { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权删除同权限等级或更高权限等级的用户", - }) + respondFailure(c, "无权删除同权限等级或更高权限等级的用户") return } - err = model.DeleteUserById(id) + err = model.DeleteUserById(int(id)) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) + respondFailure(c, err.Error()) return } + respondSuccessMessage(c, "") } func DeleteSelf(c *gin.Context) { id := c.GetInt("id") err := model.DeleteUserById(id) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } func CreateUser(c *gin.Context) { var user model.User - err := json.NewDecoder(c.Request.Body).Decode(&user) - if err != nil || user.Username == "" || user.Password == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + if !bindJSON(c, &user) { + return + } + if user.Username == "" || user.Password == "" { + respondFailure(c, "无效的参数") return } if user.DisplayName == "" { @@ -418,10 +284,7 @@ func CreateUser(c *gin.Context) { } myRole := c.GetInt("role") if user.Role >= myRole { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无法创建权限大于等于自己的用户", - }) + respondFailure(c, "无法创建权限大于等于自己的用户") return } // Even for admin users, we cannot fully trust them! @@ -431,18 +294,11 @@ func CreateUser(c *gin.Context) { DisplayName: user.DisplayName, } if err := cleanUser.Insert(); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } type ManageRequest struct { @@ -453,13 +309,7 @@ type ManageRequest struct { // ManageUser Only admin user can do this func ManageUser(c *gin.Context) { var req ManageRequest - err := json.NewDecoder(c.Request.Body).Decode(&req) - - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + if !bindJSON(c, &req) { return } user := model.User{ @@ -468,108 +318,70 @@ func ManageUser(c *gin.Context) { // Fill attributes model.DB.Where(&user).First(&user) if user.Id == 0 { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "用户不存在", - }) + respondFailure(c, "用户不存在") return } myRole := c.GetInt("role") if myRole <= user.Role && myRole != common.RoleRootUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无权更新同权限等级或更高权限等级的用户信息", - }) + respondFailure(c, "无权更新同权限等级或更高权限等级的用户信息") return } switch req.Action { case "disable": user.Status = common.UserStatusDisabled if user.Role == common.RoleRootUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无法禁用超级管理员用户", - }) + respondFailure(c, "无法禁用超级管理员用户") return } case "enable": user.Status = common.UserStatusEnabled case "delete": if user.Role == common.RoleRootUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无法删除超级管理员用户", - }) + respondFailure(c, "无法删除超级管理员用户") return } if err := user.Delete(); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } case "promote": if myRole != common.RoleRootUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "普通管理员用户无法提升其他用户为管理员", - }) + respondFailure(c, "普通管理员用户无法提升其他用户为管理员") return } if user.Role >= common.RoleAdminUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "该用户已经是管理员", - }) + respondFailure(c, "该用户已经是管理员") return } user.Role = common.RoleAdminUser case "demote": if user.Role == common.RoleRootUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无法降级超级管理员用户", - }) + respondFailure(c, "无法降级超级管理员用户") return } if user.Role == common.RoleCommonUser { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "该用户已经是普通用户", - }) + respondFailure(c, "该用户已经是普通用户") return } user.Role = common.RoleCommonUser } if err := user.Update(false); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } clearUser := model.User{ Role: user.Role, Status: user.Status, } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": clearUser, - }) - return + respondSuccess(c, clearUser) } func EmailBind(c *gin.Context) { email := c.Query("email") code := c.Query("code") if !security.VerifyCodeWithKey(email, code, security.EmailVerificationPurpose) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "验证码错误或已过期", - }) + respondFailure(c, "验证码错误或已过期") return } id := c.GetInt("id") @@ -578,25 +390,15 @@ func EmailBind(c *gin.Context) { } err := user.FillUserById() if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } user.Email = email // no need to check if this email already taken, because we have used verification code to check it err = user.Update(false) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) - return + respondSuccessMessage(c, "") } diff --git a/openflare_server/controller/waf.go b/openflare_server/controller/waf.go index 11d9521a..796230b7 100644 --- a/openflare_server/controller/waf.go +++ b/openflare_server/controller/waf.go @@ -1,8 +1,6 @@ package controller import ( - "encoding/json" - "net/http" "openflare/service" "strconv" @@ -16,10 +14,10 @@ type wafIDsRequest struct { func ListWAFRuleGroups(c *gin.Context) { groups, err := service.ListWAFRuleGroups() if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": groups}) + respondSuccess(c, groups) } func GetWAFRuleGroup(c *gin.Context) { @@ -29,24 +27,23 @@ func GetWAFRuleGroup(c *gin.Context) { } group, err := service.GetWAFRuleGroup(id) if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": group}) + respondSuccess(c, group) } func CreateWAFRuleGroup(c *gin.Context) { var input service.WAFRuleGroupInput - if err := json.NewDecoder(c.Request.Body).Decode(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid payload"}) + if !bindJSON(c, &input) { return } group, err := service.CreateWAFRuleGroup(input) if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": group}) + respondSuccess(c, group) } func UpdateWAFRuleGroup(c *gin.Context) { @@ -55,16 +52,15 @@ func UpdateWAFRuleGroup(c *gin.Context) { return } var input service.WAFRuleGroupInput - if err := json.NewDecoder(c.Request.Body).Decode(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid payload"}) + if !bindJSON(c, &input) { return } group, err := service.UpdateWAFRuleGroup(id, input) if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": group}) + respondSuccess(c, group) } func DeleteWAFRuleGroup(c *gin.Context) { @@ -73,10 +69,10 @@ func DeleteWAFRuleGroup(c *gin.Context) { return } if err := service.DeleteWAFRuleGroup(id); err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": ""}) + respondSuccessMessage(c, "") } func ReplaceWAFRuleGroupSites(c *gin.Context) { @@ -85,16 +81,15 @@ func ReplaceWAFRuleGroupSites(c *gin.Context) { return } var request wafIDsRequest - if err := json.NewDecoder(c.Request.Body).Decode(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid payload"}) + if !bindJSON(c, &request) { return } group, err := service.ReplaceWAFRuleGroupSites(id, request.IDs) if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": group}) + respondSuccess(c, group) } func GetWAFSiteRuleGroups(c *gin.Context) { @@ -104,10 +99,10 @@ func GetWAFSiteRuleGroups(c *gin.Context) { } view, err := service.GetWAFSiteRuleGroups(routeID) if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": view}) + respondSuccess(c, view) } func ReplaceWAFSiteRuleGroups(c *gin.Context) { @@ -116,22 +111,21 @@ func ReplaceWAFSiteRuleGroups(c *gin.Context) { return } var request wafIDsRequest - if err := json.NewDecoder(c.Request.Body).Decode(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid payload"}) + if !bindJSON(c, &request) { return } view, err := service.ReplaceWAFSiteRuleGroups(routeID, request.IDs) if err != nil { - c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": view}) + respondSuccess(c, view) } func parseUintPathParam(c *gin.Context, name string) (uint, bool) { id, err := strconv.ParseUint(c.Param(name), 10, 64) if err != nil || id == 0 { - c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) + respondBadRequest(c, "invalid id") return 0, false } return uint(id), true diff --git a/openflare_server/controller/wechat.go b/openflare_server/controller/wechat.go index a3e0b4aa..1b5a97a7 100644 --- a/openflare_server/controller/wechat.go +++ b/openflare_server/controller/wechat.go @@ -4,12 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "github.com/gin-gonic/gin" + "io" + "log/slog" "net/http" "openflare/common" "openflare/model" - "strconv" "time" + + "github.com/gin-gonic/gin" ) type wechatLoginResponse struct { @@ -34,7 +36,12 @@ func getWeChatIdByCode(code string) (string, error) { if err != nil { return "", err } - defer httpResponse.Body.Close() + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + slog.Error("Failed to close response body", "error", err) + } + }(httpResponse.Body) var res wechatLoginResponse err = json.NewDecoder(httpResponse.Body).Decode(&res) if err != nil { @@ -51,19 +58,13 @@ func getWeChatIdByCode(code string) (string, error) { func WeChatAuth(c *gin.Context) { if !common.WeChatAuthEnabled { - c.JSON(http.StatusOK, gin.H{ - "message": "管理员未开启通过微信登录以及注册", - "success": false, - }) + respondFailure(c, "管理员未开启通过微信登录以及注册") return } code := c.Query("code") wechatId, err := getWeChatIdByCode(code) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "message": err.Error(), - "success": false, - }) + respondFailure(c, err.Error()) return } user := model.User{ @@ -72,40 +73,16 @@ func WeChatAuth(c *gin.Context) { if model.IsWeChatIdAlreadyTaken(wechatId) { err := user.FillUserByWeChatId() if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } } else { - if common.RegisterEnabled { - user.Username = "wechat_" + strconv.Itoa(model.GetMaxUserId()+1) - user.DisplayName = "WeChat User" - user.Role = common.RoleCommonUser - user.Status = common.UserStatusEnabled - - if err := user.Insert(); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) - return - } - } else { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "管理员关闭了新用户注册", - }) - return - } + respondFailure(c, "管理员关闭了新用户注册") + return } if user.Status != common.UserStatusEnabled { - c.JSON(http.StatusOK, gin.H{ - "message": "用户已被封禁", - "success": false, - }) + respondFailure(c, "用户已被封禁") return } setupLogin(&user, c) @@ -113,26 +90,17 @@ func WeChatAuth(c *gin.Context) { func WeChatBind(c *gin.Context) { if !common.WeChatAuthEnabled { - c.JSON(http.StatusOK, gin.H{ - "message": "管理员未开启通过微信登录以及注册", - "success": false, - }) + respondFailure(c, "管理员未开启通过微信登录以及注册") return } code := c.Query("code") wechatId, err := getWeChatIdByCode(code) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "message": err.Error(), - "success": false, - }) + respondFailure(c, err.Error()) return } if model.IsWeChatIdAlreadyTaken(wechatId) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "该微信账号已被绑定", - }) + respondFailure(c, "该微信账号已被绑定") return } id := c.GetInt("id") @@ -141,24 +109,15 @@ func WeChatBind(c *gin.Context) { } err = user.FillUserById() if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } user.WeChatId = wechatId err = user.Update(false) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + respondFailure(c, err.Error()) return } - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - }) + respondSuccessMessage(c, "") return } diff --git a/openflare_server/job/cron.go b/openflare_server/job/cron.go index cd1eb2d3..3b558945 100644 --- a/openflare_server/job/cron.go +++ b/openflare_server/job/cron.go @@ -1,8 +1,9 @@ package job import ( - "github.com/robfig/cron/v3" "log/slog" + + "github.com/robfig/cron/v3" ) var cronRunner *cron.Cron diff --git a/openflare_server/middleware/rate-limit.go b/openflare_server/middleware/rate-limit.go index 00d46f16..6db459a7 100644 --- a/openflare_server/middleware/rate-limit.go +++ b/openflare_server/middleware/rate-limit.go @@ -2,12 +2,13 @@ package middleware import ( "context" - "github.com/gin-gonic/gin" "log/slog" "net/http" "openflare/common" "openflare/utils/ratelimit" "time" + + "github.com/gin-gonic/gin" ) var timeFormat = "2006-01-02T15:04:05.000Z" @@ -52,11 +53,11 @@ func redisRateLimiter(c *gin.Context, maxRequestNum int, duration int64, mark st c.Status(http.StatusTooManyRequests) c.Abort() return - } else { - rdb.LPush(ctx, key, time.Now().Format(timeFormat)) - rdb.LTrim(ctx, key, 0, int64(maxRequestNum-1)) - rdb.Expire(ctx, key, common.RateLimitKeyExpirationDuration) } + + rdb.LPush(ctx, key, time.Now().Format(timeFormat)) + rdb.LTrim(ctx, key, 0, int64(maxRequestNum-1)) + rdb.Expire(ctx, key, common.RateLimitKeyExpirationDuration) } } @@ -74,12 +75,12 @@ func rateLimitFactory(maxRequestNum int, duration int64, mark string) func(c *gi return func(c *gin.Context) { redisRateLimiter(c, maxRequestNum, duration, mark) } - } else { - // It's safe to call multi times. - inMemoryRateLimiter.Init(common.RateLimitKeyExpirationDuration) - return func(c *gin.Context) { - memoryRateLimiter(c, maxRequestNum, duration, mark) - } + } + + // It's safe to call multi times. + inMemoryRateLimiter.Init(common.RateLimitKeyExpirationDuration) + return func(c *gin.Context) { + memoryRateLimiter(c, maxRequestNum, duration, mark) } } @@ -94,11 +95,3 @@ func GlobalAPIRateLimit() func(c *gin.Context) { func CriticalRateLimit() func(c *gin.Context) { return rateLimitFactory(common.CriticalRateLimitNum, common.CriticalRateLimitDuration, "CT") } - -func DownloadRateLimit() func(c *gin.Context) { - return rateLimitFactory(common.DownloadRateLimitNum, common.DownloadRateLimitDuration, "DW") -} - -func UploadRateLimit() func(c *gin.Context) { - return rateLimitFactory(common.UploadRateLimitNum, common.UploadRateLimitDuration, "UP") -} diff --git a/openflare_server/model/apply_log.go b/openflare_server/model/apply_log.go index abbf03f8..cb5f2e74 100644 --- a/openflare_server/model/apply_log.go +++ b/openflare_server/model/apply_log.go @@ -50,12 +50,6 @@ func CountApplyLogs(nodeID string) (total int64, err error) { return total, err } -func GetLatestApplyLog(nodeID string) (*ApplyLog, error) { - log := &ApplyLog{} - err := DB.Where("node_id = ?", nodeID).Order("id desc").First(log).Error - return log, err -} - func GetLatestApplyLogsByNodeIDs(nodeIDs []string) (map[string]*ApplyLog, error) { result := make(map[string]*ApplyLog) if len(nodeIDs) == 0 { diff --git a/openflare_server/web/types/public-status.ts b/openflare_server/web/types/public-status.ts index 923b25f9..898b24c1 100644 --- a/openflare_server/web/types/public-status.ts +++ b/openflare_server/web/types/public-status.ts @@ -19,7 +19,6 @@ export interface PublicStatus { wechat_qrcode: string; wechat_login: boolean; server_address: string; - register_enabled: boolean; password_register_enabled: boolean; auth_sources: PublicAuthSource[]; }