From 7c30c6d15084e5724a24db8bb85d45349624f735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=BB=84=E6=9E=97?= Date: Sun, 17 Sep 2023 01:35:42 +0800 Subject: [PATCH] completely save config with toml format --- assets/lang/en.json | 1 + assets/lang/zh.json | 1 + assets/static/css/color.css | 16 +++ assets/static/js/index-user-list.js | 60 ++++++-- assets/templates/index.html | 2 +- cmd/frps-panel/cmd.go | 28 ++-- config/frps-panel.ini | 36 ----- config/frps-tokens.toml | 12 +- pkg/server/controller/authorizer.go | 4 +- pkg/server/controller/controller.go | 180 +++++++++++------------- pkg/server/controller/handler.go | 17 +-- pkg/server/controller/utils.go | 205 ++++++++++++++++++++++++++++ pkg/server/controller/variables.go | 43 ++++-- 13 files changed, 398 insertions(+), 207 deletions(-) delete mode 100644 config/frps-panel.ini create mode 100644 pkg/server/controller/utils.go diff --git a/assets/lang/en.json b/assets/lang/en.json index 14e3cb5..8e72149 100644 --- a/assets/lang/en.json +++ b/assets/lang/en.json @@ -29,6 +29,7 @@ "Other error": "Other error", "Param error": "Param error", "User exist": "User exist", + "User not exist": "User not exist", "User format error": "User cannot be empty or include space char. It only allowed alphanumeric and underline.", "Token format error": "Token cannot be empty or include space char. It allow include those special char: _!@#$%^&*()", "Please check at least one user": "Please Check at least one user", diff --git a/assets/lang/zh.json b/assets/lang/zh.json index 0b359be..845749e 100644 --- a/assets/lang/zh.json +++ b/assets/lang/zh.json @@ -29,6 +29,7 @@ "Other error": "其他异常", "Param error": "参数异常", "User exist": "用户已经存在", + "User not exist": "用户不存在", "User format error": "用户不能为空或包含空格。只允许英文数字、字母、下划线", "Token format error": "Token不能为空或包含空格。允许的特殊符号:_!@#$%^&*()", "Please check at least one user": "请选中需要操作的用户", diff --git a/assets/static/css/color.css b/assets/static/css/color.css index dcc7d9a..a637f31 100644 --- a/assets/static/css/color.css +++ b/assets/static/css/color.css @@ -46,6 +46,14 @@ border-color: #79bbff; } +.layui-form-checkbox[lay-skin=primary] > .layui-icon-indeterminate:before { + background-color: #79bbff; +} + +.layui-form-checkbox[lay-skin=primary]>.layui-icon-indeterminate{ + border-color: #79bbff; +} + .layui-form-checked[lay-skin=primary] > i { background-color: #409eff; border-color: #409eff !important; @@ -173,6 +181,14 @@ section.proxy-list .proxy-info .layui-row .layui-row > div:first-child { border-color: #5f5f60; } + .layui-form-checkbox[lay-skin=primary] > .layui-icon-indeterminate:before { + background-color: #5f5f60; + } + + .layui-form-checkbox[lay-skin=primary]>.layui-icon-indeterminate{ + border-color: #5f5f60; + } + .layui-form-checked[lay-skin=primary] > i { background-color: #484849; border-color: #484849 !important; diff --git a/assets/static/js/index-user-list.js b/assets/static/js/index-user-list.js index 80ef350..c317f10 100644 --- a/assets/static/js/index-user-list.js +++ b/assets/static/js/index-user-list.js @@ -184,7 +184,7 @@ var loadUserList = (function ($) { if (domains.trim() !== '') { try { domains.split(',').forEach(function (domain) { - if (!/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}){1,3}$/.test(domain.trim())) { + if (!/^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$/.test(domain.trim())) { valid = false; throw 'break'; } @@ -209,7 +209,7 @@ var loadUserList = (function ($) { if (subdomains.trim() !== '') { try { subdomains.split(',').forEach(function (subdomain) { - if (!/^[a-zA-z0-9][a-zA-Z0-9-]{0,19}$/.test(subdomain.trim())) { + if (!/^[a-zA-z0-9][a-zA-z0-9-]{0,19}$/.test(subdomain.trim())) { valid = false; throw 'break'; } @@ -227,7 +227,7 @@ var loadUserList = (function ($) { * set verify rule of layui.form */ (function setFormVerifyRule() { - layui.form.verify(verifyRules); + // layui.form.verify(verifyRules); })(); /** @@ -251,7 +251,9 @@ var loadUserList = (function ($) { where: {}, dataType: 'json', editTrigger: 'dblclick', - page: navigator.language.indexOf("zh") !== -1, + page: { + layout: navigator.language.indexOf("zh") === -1 ? ['first', 'prev', 'next', 'last'] : ['prev', 'page', 'next', 'skip', 'count', 'limit'] + }, toolbar: '#userListToolbarTemplate', defaultToolbar: false, cols: [[ @@ -270,7 +272,14 @@ var loadUserList = (function ($) { sort: true }, {title: i18n['Operation'], width: 150, toolbar: '#userListOperationTemplate'} - ]] + ]], + parseData: function (res) { + res.data.forEach(function (data) { + data.ports = data.ports.join(','); + data.domains = data.domains.join(','); + data.subdomains = data.subdomains.join(','); + }); + } }); bindFormEvent(); @@ -348,7 +357,6 @@ var loadUserList = (function ($) { before.ports = before.ports.split(',') before.domains = before.domains.split(',') before.subdomains = before.subdomains.split(',') - after.ports = after.ports.split(',') after.domains = after.domains.split(',') after.subdomains = after.subdomains.split(',') @@ -359,8 +367,14 @@ var loadUserList = (function ($) { layui.table.on('toolbar(tokenTable)', function (obj) { var id = obj.config.id; var checkStatus = layui.table.checkStatus(id); - var data = checkStatus.data; + + data.forEach(function (temp) { + temp.ports = temp.ports.split(',') + temp.domains = temp.domains.split(',') + temp.subdomains = temp.subdomains.split(',') + }); + switch (obj.event) { case 'add': addPopup(); @@ -379,6 +393,10 @@ var loadUserList = (function ($) { layui.table.on('tool(tokenTable)', function (obj) { var data = obj.data; + + data.ports = data.ports.split(',') + data.domains = data.domains.split(',') + data.subdomains = data.subdomains.split(',') switch (obj.event) { case 'remove': removePopup(data); @@ -418,9 +436,15 @@ var loadUserList = (function ($) { btn1: function (index) { if (layui.form.validate('#addUserForm')) { var formData = layui.form.val('addUserForm'); - formData.ports = formData.ports.split(',') - formData.domains = formData.domains.split(',') - formData.subdomains = formData.subdomains.split(',') + if (formData.ports != null) { + formData.ports = formData.ports.split(',') + } + if (formData.domains != null) { + formData.domains = formData.domains.split(',') + } + if (formData.subdomains != null) { + formData.subdomains = formData.subdomains.split(',') + } add(formData, index); } }, @@ -638,17 +662,27 @@ var loadUserList = (function ($) { * @param result */ function errorMsg(result) { - var reason = i18n['Other Error']; + var reason = i18n['OtherError']; if (result.code === 1) reason = i18n['ParamError']; else if (result.code === 2) reason = i18n['UserExist']; else if (result.code === 3) - reason = i18n['ParamError']; + reason = i18n['UserNotExist']; else if (result.code === 4) - reason = i18n['UserFormatError']; + reason = i18n['ParamError']; else if (result.code === 5) + reason = i18n['UserFormatError']; + else if (result.code === 6) reason = i18n['TokenFormatError']; + else if (result.code === 7) + reason = i18n['CommentInvalid']; + else if (result.code === 8) + reason = i18n['PortsInvalid']; + else if (result.code === 9) + reason = i18n['DomainsInvalid']; + else if (result.code === 10) + reason = i18n['SubdomainsInvalid']; layui.layer.msg(i18n['OperateFailed'] + ',' + reason) } diff --git a/assets/templates/index.html b/assets/templates/index.html index 2f026f5..5953d21 100644 --- a/assets/templates/index.html +++ b/assets/templates/index.html @@ -1,5 +1,5 @@ - + ${ .FrpsPanel } diff --git a/cmd/frps-panel/cmd.go b/cmd/frps-panel/cmd.go index 6d69ca3..513fe55 100644 --- a/cmd/frps-panel/cmd.go +++ b/cmd/frps-panel/cmd.go @@ -70,34 +70,22 @@ func Execute() { } func parseConfigFile(configFile, tokensFile string) (controller.HandleController, server.TLS, error) { - var config controller.Config - _, err := toml.DecodeFile(configFile, &config) + var common controller.Common + var tokens controller.Tokens + _, err := toml.DecodeFile(configFile, &common) if err != nil { log.Fatalf("decode config file %v error: %v", configFile, err) } - _, err = toml.DecodeFile(tokensFile, &config) + _, err = toml.DecodeFile(tokensFile, &tokens) if err != nil { log.Fatalf("decode token file %v error: %v", tokensFile, err) } - config.Common.DashboardTls = strings.HasPrefix("https://", strings.ToLower(config.Common.DashboardAddr)) - - //f, err := os.Create("/Volumes/Work/Git Sources/frps-panel/config/frps-panel-new.toml") - //if err != nil { - // log.Fatal(err) - //} - //if err := toml.NewEncoder(f).Encode(config); err != nil { - // // failed to encode - // log.Fatal(err) - //} - //if err := f.Close(); err != nil { - // // failed to close the file - // log.Fatal(err) - //} + common.Common.DashboardTls = strings.HasPrefix("https://", strings.ToLower(common.Common.DashboardAddr)) tls := server.TLS{ - Enable: config.Common.TlsMode, + Enable: common.Common.TlsMode, Protocol: "HTTP", } @@ -112,8 +100,8 @@ func parseConfigFile(configFile, tokensFile string) (controller.HandleController } return controller.HandleController{ - CommonInfo: config.Common, - Tokens: config.Tokens.Tokens, + CommonInfo: common.Common, + Tokens: tokens.Tokens, Version: version, ConfigFile: configFile, TokensFile: tokensFile, diff --git a/config/frps-panel.ini b/config/frps-panel.ini deleted file mode 100644 index 3d1e3bc..0000000 --- a/config/frps-panel.ini +++ /dev/null @@ -1,36 +0,0 @@ -; basic options -[common] -; frps panel config info -plugin_addr = 127.0.0.1 -plugin_port = 7200 -admin_user = admin -admin_pwd = admin -; specified login state keep time -admin_keep_time = 0 - -; enable tls -tls_mode = false -; tls_cert_file = cert.crt -; tls_key_file = cert.key - -; frp dashboard info -dashboard_addr = 127.0.0.1 -dashboard_port = 7500 -dashboard_user = admin -dashboard_pwd = admin - -; user tokens -[users] -user1 = token1 - -; user been disabled -[disabled] - -; user allowed ports. it will be used when a new proxy connect on -[ports] - -; user allowed domains. it will be used when a new proxy connect on -[domains] - -; user allowed subdomains. it will be used when a new proxy connect on -[subdomains] diff --git a/config/frps-tokens.toml b/config/frps-tokens.toml index b5b565a..a69f66b 100644 --- a/config/frps-tokens.toml +++ b/config/frps-tokens.toml @@ -3,15 +3,15 @@ user = "user1" token = "token1" comment = "张三" - ports = ["1", "2", "3", "10-100"] - domains = ["aaa.com", "bbb.com"] - subdomains = ["a", "b"] + ports = [""] + domains = [""] + subdomains = [""] status = true [tokens.user2] user = "user2" token = "token2" comment = "李四" - ports = ["11", "22", "33", "110-200"] - domains = ["ccc.com", "ddd.com"] - subdomains = ["c", "d"] + ports = [""] + domains = [""] + subdomains = [""] status = true diff --git a/pkg/server/controller/authorizer.go b/pkg/server/controller/authorizer.go index 75a98ee..34f062a 100644 --- a/pkg/server/controller/authorizer.go +++ b/pkg/server/controller/authorizer.go @@ -12,7 +12,7 @@ import ( func (c *HandleController) BasicAuth() gin.HandlerFunc { return func(context *gin.Context) { - if strings.TrimSpace(c.CommonInfo.AdminUser) == "" || strings.TrimSpace(c.CommonInfo.AdminPwd) == "" { + if trimString(c.CommonInfo.AdminUser) == "" || trimString(c.CommonInfo.AdminPwd) == "" { if context.Request.RequestURI == LoginUrl { context.Redirect(http.StatusTemporaryRedirect, LoginSuccessUrl) } @@ -54,7 +54,7 @@ func (c *HandleController) BasicAuth() gin.HandlerFunc { } func (c *HandleController) LoginAuth(username, password string, context *gin.Context) bool { - if strings.TrimSpace(c.CommonInfo.AdminUser) == "" || strings.TrimSpace(c.CommonInfo.AdminPwd) == "" { + if trimString(c.CommonInfo.AdminUser) == "" || trimString(c.CommonInfo.AdminPwd) == "" { return true } diff --git a/pkg/server/controller/controller.go b/pkg/server/controller/controller.go index 2abbafa..e82f84c 100644 --- a/pkg/server/controller/controller.go +++ b/pkg/server/controller/controller.go @@ -5,14 +5,12 @@ import ( "encoding/json" "errors" "fmt" - "github.com/BurntSushi/toml" plugin "github.com/fatedier/frp/pkg/plugin/server" ginI18n "github.com/gin-contrib/i18n" "github.com/gin-gonic/gin" "io" "log" "net/http" - "os" "sort" "strconv" "strings" @@ -123,7 +121,7 @@ func (c *HandleController) MakeIndexFunc() func(context *gin.Context) { return func(context *gin.Context) { context.HTML(http.StatusOK, "index.html", gin.H{ "version": c.Version, - "showExit": strings.TrimSpace(c.CommonInfo.AdminUser) != "" && strings.TrimSpace(c.CommonInfo.AdminPwd) != "", + "showExit": trimString(c.CommonInfo.AdminUser) != "" && trimString(c.CommonInfo.AdminPwd) != "", "FrpsPanel": ginI18n.MustGetMessage(context, "Frps Panel"), "User": ginI18n.MustGetMessage(context, "User"), "Token": ginI18n.MustGetMessage(context, "Token"), @@ -206,6 +204,7 @@ func (c *HandleController) MakeLangFunc() func(context *gin.Context) { "OperateError": ginI18n.MustGetMessage(context, "Operate error"), "OperateFailed": ginI18n.MustGetMessage(context, "Operate failed"), "UserExist": ginI18n.MustGetMessage(context, "User exist"), + "UserNotExist": ginI18n.MustGetMessage(context, "User not exist"), "UserFormatError": ginI18n.MustGetMessage(context, "User format error"), "TokenFormatError": ginI18n.MustGetMessage(context, "Token format error"), "ShouldCheckUser": ginI18n.MustGetMessage(context, "Please check at least one user"), @@ -220,6 +219,7 @@ func (c *HandleController) MakeLangFunc() func(context *gin.Context) { "SubdomainsInvalid": ginI18n.MustGetMessage(context, "Subdomains is invalid"), "CommentInvalid": ginI18n.MustGetMessage(context, "Comment is invalid"), "ParamError": ginI18n.MustGetMessage(context, "Param error"), + "OtherError": ginI18n.MustGetMessage(context, "Other error"), "Name": ginI18n.MustGetMessage(context, "Name"), "Port": ginI18n.MustGetMessage(context, "Port"), "Connections": ginI18n.MustGetMessage(context, "Connections"), @@ -287,52 +287,6 @@ func (c *HandleController) MakeQueryTokensFunc() func(context *gin.Context) { } } -func filter(main TokenInfo, sub TokenInfo) bool { - replaceSpaceUser := TrimAllSpaceReg.ReplaceAllString(sub.User, "") - if len(replaceSpaceUser) != 0 { - if !strings.Contains(main.User, replaceSpaceUser) { - return false - } - } - - replaceSpaceToken := TrimAllSpaceReg.ReplaceAllString(sub.Token, "") - if len(replaceSpaceToken) != 0 { - if !strings.Contains(main.Token, replaceSpaceToken) { - return false - } - } - - replaceSpaceComment := TrimAllSpaceReg.ReplaceAllString(sub.Comment, "") - if len(replaceSpaceComment) != 0 { - if !strings.Contains(main.Comment, replaceSpaceComment) { - return false - } - } - return true -} - -func TokensList(tokens map[string]TokenInfo) Tokens { - return Tokens{ - tokens, - } -} - -func (c *HandleController) SaveToken() error { - tokenFile, err := os.Create(c.TokensFile) - if err != nil { - log.Printf("error to crate file %v: %v", c.TokensFile, err) - } - - if err = toml.NewEncoder(tokenFile).Encode(TokensList(c.Tokens)); err != nil { - log.Printf("error to encode tokens: %v", err) - } - if err = tokenFile.Close(); err != nil { - log.Printf("error to close file %v: %v", c.TokensFile, err) - } - - return err -} - func (c *HandleController) MakeAddTokenFunc() func(context *gin.Context) { return func(context *gin.Context) { info := TokenInfo{ @@ -345,46 +299,34 @@ func (c *HandleController) MakeAddTokenFunc() func(context *gin.Context) { } err := context.BindJSON(&info) if err != nil { - log.Printf("user add failed, param error : %v", err) response.Success = false response.Code = ParamError - response.Message = "user add failed, param error " + response.Message = fmt.Sprintf("user add failed, param error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } - if !UserFormatReg.MatchString(info.User) { - log.Printf("user add failed, user format error") - response.Success = false - response.Code = UserFormatError - response.Message = fmt.Sprintf("user add failed, user format error") - context.JSON(http.StatusOK, &response) - return - } - if _, exist := c.Tokens[info.User]; exist { - log.Printf("user add failed, user [%v] exist", info.User) - response.Success = false - response.Code = UserExist - response.Message = fmt.Sprintf("user add failed, user [%s] exist ", info.User) - context.JSON(http.StatusOK, &response) - return - } - if !TokenFormatReg.MatchString(info.Token) { - log.Printf("user add failed, token format error") - response.Success = false - response.Code = TokenFormatError - response.Message = fmt.Sprintf("user add failed, token format error") - context.JSON(http.StatusOK, &response) + + result := c.verifyToken(info, TOKEN_ADD) + + if !result.Success { + context.JSON(http.StatusOK, &result) return } + info.Comment = cleanString(info.Comment) + info.Ports = cleanStrings(info.Ports) + info.Domains = cleanStrings(info.Domains) + info.Subdomains = cleanStrings(info.Subdomains) + c.Tokens[info.User] = info - err = c.SaveToken() + err = c.saveToken() if err != nil { - log.Printf("add failed, error : %v", err) response.Success = false response.Code = SaveError - response.Message = "user add failed" + response.Message = fmt.Sprintf("add failed, error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } @@ -403,10 +345,10 @@ func (c *HandleController) MakeUpdateTokensFunc() func(context *gin.Context) { update := TokenUpdate{} err := context.BindJSON(&update) if err != nil { - log.Printf("update failed, param error : %v", err) response.Success = false response.Code = ParamError - response.Message = "user update failed, param error" + response.Message = fmt.Sprintf("update failed, param error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } @@ -414,32 +356,35 @@ func (c *HandleController) MakeUpdateTokensFunc() func(context *gin.Context) { before := update.Before after := update.After - if after.User != before.User { - log.Printf("update failed, user not match") + if before.User != after.User { response.Success = false response.Code = ParamError - response.Message = "update failed, user not match" + response.Message = fmt.Sprintf("update failed, user should be same : before -> %v, after -> %v", before.User, after.User) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } - if !TokenFormatReg.MatchString(after.Token) { - log.Printf("update failed, token format error") - response.Success = false - response.Code = TokenFormatError - response.Message = "user update failed, token format error" - context.JSON(http.StatusOK, &response) + result := c.verifyToken(after, TOKEN_UPDATE) + + if !result.Success { + context.JSON(http.StatusOK, &result) return } + after.Comment = cleanString(after.Comment) + after.Ports = cleanStrings(after.Ports) + after.Domains = cleanStrings(after.Domains) + after.Subdomains = cleanStrings(after.Subdomains) + c.Tokens[after.User] = after - err = c.SaveToken() + err = c.saveToken() if err != nil { - log.Printf("user update failed, error : %v", err) response.Success = false response.Code = SaveError - response.Message = "user update failed" + response.Message = fmt.Sprintf("user update failed, error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } @@ -458,24 +403,33 @@ func (c *HandleController) MakeRemoveTokensFunc() func(context *gin.Context) { remove := TokenRemove{} err := context.BindJSON(&remove) if err != nil { - log.Printf("user remove failed, param error : %v", err) response.Success = false response.Code = ParamError - response.Message = "user remove failed, param error " + response.Message = fmt.Sprintf("user remove failed, param error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } + for _, user := range remove.Users { + result := c.verifyToken(user, TOKEN_REMOVE) + + if !result.Success { + context.JSON(http.StatusOK, &result) + return + } + } + for _, user := range remove.Users { delete(c.Tokens, user.User) } - err = c.SaveToken() + err = c.saveToken() if err != nil { - log.Printf("user update failed, error : %v", err) response.Success = false response.Code = SaveError - response.Message = "user update failed" + response.Message = fmt.Sprintf("user update failed, error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } @@ -494,27 +448,36 @@ func (c *HandleController) MakeDisableTokensFunc() func(context *gin.Context) { disable := TokenDisable{} err := context.BindJSON(&disable) if err != nil { - log.Printf("disable failed, param error : %v", err) response.Success = false response.Code = ParamError - response.Message = "disable failed, param error " + response.Message = fmt.Sprintf("disable failed, param error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } + for _, user := range disable.Users { + result := c.verifyToken(user, TOKEN_DISABLE) + + if !result.Success { + context.JSON(http.StatusOK, &result) + return + } + } + for _, user := range disable.Users { token := c.Tokens[user.User] token.Status = false c.Tokens[user.User] = token } - err = c.SaveToken() + err = c.saveToken() if err != nil { - log.Printf("disable failed, error : %v", err) response.Success = false response.Code = SaveError - response.Message = "disable failed" + response.Message = fmt.Sprintf("disable failed, error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } @@ -533,21 +496,30 @@ func (c *HandleController) MakeEnableTokensFunc() func(context *gin.Context) { enable := TokenEnable{} err := context.BindJSON(&enable) if err != nil { - log.Printf("enable failed, param error : %v", err) response.Success = false response.Code = ParamError - response.Message = "enable failed, param error " + response.Message = fmt.Sprintf("enable failed, param error : %v", err) + log.Printf(response.Message) context.JSON(http.StatusOK, &response) return } + for _, user := range enable.Users { + result := c.verifyToken(user, TOKEN_ENABLE) + + if !result.Success { + context.JSON(http.StatusOK, &result) + return + } + } + for _, user := range enable.Users { token := c.Tokens[user.User] token.Status = true c.Tokens[user.User] = token } - err = c.SaveToken() + err = c.saveToken() if err != nil { log.Printf("enable failed, error : %v", err) @@ -588,7 +560,7 @@ func (c *HandleController) MakeProxyFunc() func(context *gin.Context) { request, _ := http.NewRequest("GET", requestUrl, nil) username := c.CommonInfo.DashboardUser password := c.CommonInfo.DashboardPwd - if len(strings.TrimSpace(username)) != 0 && len(strings.TrimSpace(password)) != 0 { + if trimString(username) != "" && trimString(password) != "" { request.SetBasicAuth(username, password) log.Printf("Proxy to %s", requestUrl) } diff --git a/pkg/server/controller/handler.go b/pkg/server/controller/handler.go index 735d500..9114d48 100644 --- a/pkg/server/controller/handler.go +++ b/pkg/server/controller/handler.go @@ -78,7 +78,7 @@ func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Res } proxyType := content.ProxyType - if StringIndexOf(proxyType, supportProxyTypes) == -1 { + if stringContains(proxyType, supportProxyTypes) { log.Printf("proxy type [%v] not support, plugin do nothing", proxyType) res.Unchange = true return res @@ -100,12 +100,12 @@ func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Res portErr = fmt.Errorf("user [%v] port range [%v] format error", user, port) break } - start, err := strconv.Atoi(strings.TrimSpace(allowedRanges[0])) + start, err := strconv.Atoi(trimString(allowedRanges[0])) if err != nil { portErr = fmt.Errorf("user [%v] port rang [%v] start port [%v] is not a number", user, port, allowedRanges[0]) break } - end, err := strconv.Atoi(strings.TrimSpace(allowedRanges[1])) + end, err := strconv.Atoi(trimString(allowedRanges[1])) if err != nil { portErr = fmt.Errorf("user [%v] port rang [%v] end port [%v] is not a number", user, port, allowedRanges[0]) break @@ -141,7 +141,7 @@ func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Res if portAllowed { if token, exist := c.Tokens[user]; exist { for _, userDomain := range userDomains { - if StringIndexOf(userDomain, token.Domains) == -1 { + if stringContains(userDomain, token.Domains) { domainAllowed = false break } @@ -183,12 +183,3 @@ func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Res } return res } - -func StringIndexOf(element string, data []string) int { - for k, v := range data { - if element == v { - return k - } - } - return -1 -} diff --git a/pkg/server/controller/utils.go b/pkg/server/controller/utils.go new file mode 100644 index 0000000..6181e4f --- /dev/null +++ b/pkg/server/controller/utils.go @@ -0,0 +1,205 @@ +package controller + +import ( + "fmt" + "github.com/BurntSushi/toml" + "log" + "os" + "strings" +) + +func filter(main TokenInfo, sub TokenInfo) bool { + replaceSpaceUser := trimAllSpace.ReplaceAllString(sub.User, "") + if len(replaceSpaceUser) != 0 { + if !strings.Contains(main.User, replaceSpaceUser) { + return false + } + } + + replaceSpaceToken := trimAllSpace.ReplaceAllString(sub.Token, "") + if len(replaceSpaceToken) != 0 { + if !strings.Contains(main.Token, replaceSpaceToken) { + return false + } + } + + replaceSpaceComment := trimAllSpace.ReplaceAllString(sub.Comment, "") + if len(replaceSpaceComment) != 0 { + if !strings.Contains(main.Comment, replaceSpaceComment) { + return false + } + } + return true +} + +func trimString(str string) string { + return strings.TrimSpace(str) +} + +func (c *HandleController) verifyToken(token TokenInfo, operate int) OperationResponse { + response := OperationResponse{ + Success: true, + Code: Success, + Message: "operate success", + } + + var ( + validateExist = false + validateNotExist = false + validateUser = false + validateToken = false + validateComment = false + validatePorts = false + validateDomains = false + validateSubdomains = false + ) + + if operate == TOKEN_ADD { + validateExist = true + validateUser = true + validateToken = true + validateComment = true + validatePorts = true + validateDomains = true + validateSubdomains = true + } else if operate == TOKEN_UPDATE { + validateNotExist = true + validateUser = true + validateToken = true + validateComment = true + validatePorts = true + validateDomains = true + validateSubdomains = true + } else if operate == TOKEN_ENABLE || operate == TOKEN_DISABLE || operate == TOKEN_REMOVE { + validateNotExist = true + } + + if validateUser && !userFormat.MatchString(token.User) { + response.Success = false + response.Code = UserFormatError + response.Message = fmt.Sprintf("operate failed, user [%s] format error", token.User) + log.Printf(response.Message) + return response + } + + if validateExist { + if _, exist := c.Tokens[token.User]; exist { + response.Success = false + response.Code = UserExist + response.Message = fmt.Sprintf("operate failed, user [%s] exist ", token.User) + log.Printf(response.Message) + return response + } + } + + if validateNotExist { + if _, exist := c.Tokens[token.User]; !exist { + response.Success = false + response.Code = UserNotExist + response.Message = fmt.Sprintf("operate failed, user [%s] not exist ", token.User) + log.Printf(response.Message) + return response + } + } + + if validateToken && !tokenFormat.MatchString(token.Token) { + response.Success = false + response.Code = TokenFormatError + response.Message = fmt.Sprintf("operate failed, token [%s] format error", token.Token) + log.Printf(response.Message) + return response + } + + trimmedComment := trimString(token.Comment) + if validateComment && trimmedComment != "" && commentFormat.MatchString(trimmedComment) { + response.Success = false + response.Code = CommentFormatError + response.Message = fmt.Sprintf("operate failed, comment [%s] format error", token.Comment) + log.Printf(response.Message) + return response + } + + if validatePorts { + for _, port := range token.Ports { + trimmedPort := trimString(port) + if trimmedPort != "" && !portsFormatSingle.MatchString(trimmedPort) && !portsFormatRange.MatchString(trimmedPort) { + response.Success = false + response.Code = PortsFormatError + response.Message = fmt.Sprintf("operate failed, ports [%v] format error", token.Ports) + log.Printf(response.Message) + return response + } + } + } + + if validateDomains { + for _, domain := range token.Domains { + trimmedDomain := trimString(domain) + if trimmedDomain != "" && !domainFormat.MatchString(trimmedDomain) { + response.Success = false + response.Code = DomainsFormatError + response.Message = fmt.Sprintf("operate failed, domains [%v] format error", token.Domains) + log.Printf(response.Message) + return response + } + } + } + + if validateSubdomains { + for _, subdomain := range token.Subdomains { + trimmedSubdomain := trimString(subdomain) + if trimmedSubdomain != "" && !subdomainFormat.MatchString(trimmedSubdomain) { + response.Success = false + response.Code = SubdomainsFormatError + response.Message = fmt.Sprintf("operate failed, subdomains [%v] format error", token.Subdomains) + log.Printf(response.Message) + return response + } + } + } + + return response +} + +func cleanStrings(originalStrings []string) []string { + cleanedStrings := make([]string, len(originalStrings)) + for i, str := range originalStrings { + cleanedStrings[i] = cleanString(str) + } + return cleanedStrings +} + +func cleanString(originalString string) string { + return trimAllSpace.ReplaceAllString(originalString, "") +} + +func stringContains(element string, data []string) bool { + for _, v := range data { + if element == v { + return true + } + } + return false +} + +func tokensList(tokens map[string]TokenInfo) Tokens { + return Tokens{ + tokens, + } +} + +func (c *HandleController) saveToken() error { + tokenFile, err := os.Create(c.TokensFile) + if err != nil { + log.Printf("error to crate file %v: %v", c.TokensFile, err) + } + + if err = toml.NewEncoder(tokenFile).Encode(tokensList(c.Tokens)); err != nil { + log.Printf("error to encode tokens: %v", err) + } + if err = tokenFile.Close(); err != nil { + log.Printf("error to close file %v: %v", c.TokensFile, err) + } + + return err +} diff --git a/pkg/server/controller/variables.go b/pkg/server/controller/variables.go index 0d40038..07d074b 100644 --- a/pkg/server/controller/variables.go +++ b/pkg/server/controller/variables.go @@ -5,14 +5,29 @@ import ( ) const ( - Success = 0 - ParamError = 1 - UserExist = 2 - SaveError = 3 - UserFormatError = 4 - TokenFormatError = 5 - FrpServerError = 6 + Success int = iota + ParamError + UserExist + UserNotExist + SaveError + UserFormatError + TokenFormatError + CommentFormatError + PortsFormatError + DomainsFormatError + SubdomainsFormatError + FrpServerError +) +const ( + TOKEN_ADD int = iota + TOKEN_UPDATE + TOKEN_REMOVE + TOKEN_ENABLE + TOKEN_DISABLE +) + +const ( SessionName = "GOSESSION" AuthName = "_PANEL_AUTH" LoginUrl = "/login" @@ -22,9 +37,14 @@ const ( ) var ( - UserFormatReg = regexp.MustCompile("^\\w+$") - TokenFormatReg = regexp.MustCompile("^[\\w!@#$%^&*()]+$") - TrimAllSpaceReg = regexp.MustCompile("[\\n\\t\\r\\s]") + userFormat = regexp.MustCompile("^\\w+$") + tokenFormat = regexp.MustCompile("^[\\w!@#$%^&*()]+$") + commentFormat = regexp.MustCompile("[\\n\\t\\r]") + portsFormatSingle = regexp.MustCompile("^\\s*\\d{1,5}\\s*$") + portsFormatRange = regexp.MustCompile("^\\s*\\d{1,5}\\s*-\\s*\\d{1,5}\\s*$") + domainFormat = regexp.MustCompile("^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$") + subdomainFormat = regexp.MustCompile("^[a-zA-z0-9-]{1,20}$") + trimAllSpace = regexp.MustCompile("[\\n\\t\\r\\s]") ) type Response struct { @@ -36,9 +56,8 @@ type HTTPError struct { Err error } -type Config struct { +type Common struct { Common CommonInfo - Tokens } type CommonInfo struct {