[优化] 代码优化

This commit is contained in:
ryan
2026-05-31 20:29:15 +08:00
parent 97c10b8d0b
commit 7ab11154e3
13 changed files with 197 additions and 554 deletions
+1 -1
View File
@@ -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
+32 -88
View File
@@ -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)
}
+28 -74
View File
@@ -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, "")
}
+2 -1
View File
@@ -1,8 +1,9 @@
package controller
import (
"github.com/gin-gonic/gin"
"openflare/service"
"github.com/gin-gonic/gin"
)
func GetOrigins(c *gin.Context) {
+2 -1
View File
@@ -1,8 +1,9 @@
package controller
import (
"github.com/gin-gonic/gin"
"openflare/service"
"github.com/gin-gonic/gin"
)
// GetProxyRoutes godoc
@@ -1,8 +1,9 @@
package controller
import (
"github.com/gin-gonic/gin"
"openflare/service"
"github.com/gin-gonic/gin"
)
// GetTLSCertificates godoc
+73 -271
View File
@@ -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, "")
}
+21 -27
View File
@@ -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
+22 -63
View File
@@ -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
}
+2 -1
View File
@@ -1,8 +1,9 @@
package job
import (
"github.com/robfig/cron/v3"
"log/slog"
"github.com/robfig/cron/v3"
)
var cronRunner *cron.Cron
+12 -19
View File
@@ -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")
}
-6
View File
@@ -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 {
@@ -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[];
}