mirror of
https://github.com/yhl452493373/frpc-panel.git
synced 2026-04-04 06:17:00 +08:00
first commit:add basic layout
This commit is contained in:
104
pkg/server/controller/authorizer.go
Normal file
104
pkg/server/controller/authorizer.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *HandleController) BasicAuth() gin.HandlerFunc {
|
||||
return func(context *gin.Context) {
|
||||
if trimString(c.CommonInfo.AdminUser) == "" || trimString(c.CommonInfo.AdminPwd) == "" {
|
||||
if context.Request.RequestURI == LoginUrl {
|
||||
context.Redirect(http.StatusTemporaryRedirect, LoginSuccessUrl)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
session := sessions.Default(context)
|
||||
auth := session.Get(AuthName)
|
||||
|
||||
if auth != nil {
|
||||
if c.CommonInfo.AdminKeepTime > 0 {
|
||||
cookie, _ := context.Request.Cookie(SessionName)
|
||||
if cookie != nil {
|
||||
//important thx https://blog.csdn.net/zhanghongxia8285/article/details/107321838/
|
||||
cookie.Expires = time.Now().Add(time.Second * time.Duration(c.CommonInfo.AdminKeepTime))
|
||||
http.SetCookie(context.Writer, cookie)
|
||||
}
|
||||
}
|
||||
|
||||
username, password, _ := parseBasicAuth(fmt.Sprintf("%v", auth))
|
||||
|
||||
usernameMatch := username == c.CommonInfo.AdminUser
|
||||
passwordMatch := password == c.CommonInfo.AdminPwd
|
||||
|
||||
if usernameMatch && passwordMatch {
|
||||
context.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isAjax := context.GetHeader("X-Requested-With") == "XMLHttpRequest"
|
||||
|
||||
if !isAjax && context.Request.RequestURI != LoginUrl {
|
||||
context.Redirect(http.StatusTemporaryRedirect, LoginUrl)
|
||||
} else {
|
||||
context.AbortWithStatus(http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) LoginAuth(username, password string, context *gin.Context) bool {
|
||||
if trimString(c.CommonInfo.AdminUser) == "" || trimString(c.CommonInfo.AdminPwd) == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
session := sessions.Default(context)
|
||||
|
||||
sessionAuth := session.Get(AuthName)
|
||||
internalAuth := encodeBasicAuth(c.CommonInfo.AdminUser, c.CommonInfo.AdminPwd)
|
||||
|
||||
if sessionAuth == internalAuth {
|
||||
return true
|
||||
} else {
|
||||
basicAuth := encodeBasicAuth(username, password)
|
||||
if basicAuth == internalAuth {
|
||||
session.Set(AuthName, basicAuth)
|
||||
_ = session.Save()
|
||||
return true
|
||||
} else {
|
||||
session.Delete(AuthName)
|
||||
_ = session.Save()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ClearAuth(context *gin.Context) {
|
||||
session := sessions.Default(context)
|
||||
session.Delete(AuthName)
|
||||
_ = session.Save()
|
||||
}
|
||||
|
||||
func parseBasicAuth(auth string) (username, password string, ok bool) {
|
||||
c, err := base64.StdEncoding.DecodeString(auth)
|
||||
if err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
cs := string(c)
|
||||
username, password, ok = strings.Cut(cs, ":")
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
return username, password, true
|
||||
}
|
||||
|
||||
func encodeBasicAuth(username, password string) string {
|
||||
authString := fmt.Sprintf("%s:%s", username, password)
|
||||
return base64.StdEncoding.EncodeToString([]byte(authString))
|
||||
}
|
||||
428
pkg/server/controller/controller.go
Normal file
428
pkg/server/controller/controller.go
Normal file
@@ -0,0 +1,428 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *HandleController) MakeLoginFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
if context.Request.Method == "GET" {
|
||||
if c.LoginAuth("", "", context) {
|
||||
if context.Request.RequestURI == LoginUrl {
|
||||
context.Redirect(http.StatusTemporaryRedirect, LoginSuccessUrl)
|
||||
}
|
||||
return
|
||||
}
|
||||
context.HTML(http.StatusOK, "login.html", gin.H{
|
||||
"version": c.Version,
|
||||
"FrpcPanel": ginI18n.MustGetMessage(context, "Frpc Panel"),
|
||||
"Username": ginI18n.MustGetMessage(context, "Username"),
|
||||
"Password": ginI18n.MustGetMessage(context, "Password"),
|
||||
"Login": ginI18n.MustGetMessage(context, "Login"),
|
||||
"PleaseInputUsername": ginI18n.MustGetMessage(context, "Please input username"),
|
||||
"PleaseInputPassword": ginI18n.MustGetMessage(context, "Please input password"),
|
||||
})
|
||||
} else if context.Request.Method == "POST" {
|
||||
username := context.PostForm("username")
|
||||
password := context.PostForm("password")
|
||||
if c.LoginAuth(username, password, context) {
|
||||
context.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": ginI18n.MustGetMessage(context, "Login success"),
|
||||
})
|
||||
} else {
|
||||
context.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": ginI18n.MustGetMessage(context, "Username or password incorrect"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeLogoutFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
ClearAuth(context)
|
||||
context.Redirect(http.StatusTemporaryRedirect, LogoutSuccessUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeIndexFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
context.HTML(http.StatusOK, "index.html", gin.H{
|
||||
"version": c.Version,
|
||||
"FrpcPanel": ginI18n.MustGetMessage(context, "Frpc Panel"),
|
||||
"showExit": trimString(c.CommonInfo.AdminUser) != "" && trimString(c.CommonInfo.AdminPwd) != "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeLangFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
context.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeQueryTokensFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
|
||||
search := TokenSearch{}
|
||||
search.Limit = 0
|
||||
|
||||
err := context.BindQuery(&search)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var tokenList []TokenInfo
|
||||
for _, tokenInfo := range c.Tokens {
|
||||
tokenList = append(tokenList, tokenInfo)
|
||||
}
|
||||
sort.Slice(tokenList, func(i, j int) bool {
|
||||
return strings.Compare(tokenList[i].User, tokenList[j].User) < 0
|
||||
})
|
||||
|
||||
var filtered []TokenInfo
|
||||
for _, tokenInfo := range tokenList {
|
||||
if filter(tokenInfo, search.TokenInfo) {
|
||||
filtered = append(filtered, tokenInfo)
|
||||
}
|
||||
}
|
||||
if filtered == nil {
|
||||
filtered = []TokenInfo{}
|
||||
}
|
||||
|
||||
count := len(filtered)
|
||||
if search.Limit > 0 {
|
||||
start := max((search.Page-1)*search.Limit, 0)
|
||||
end := min(search.Page*search.Limit, len(filtered))
|
||||
filtered = filtered[start:end]
|
||||
}
|
||||
|
||||
context.JSON(http.StatusOK, &TokenResponse{
|
||||
Code: 0,
|
||||
Msg: "query Tokens success",
|
||||
Count: count,
|
||||
Data: filtered,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeAddTokenFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
info := TokenInfo{
|
||||
Enable: true,
|
||||
}
|
||||
response := OperationResponse{
|
||||
Success: true,
|
||||
Code: Success,
|
||||
Message: "user add success",
|
||||
}
|
||||
err := context.BindJSON(&info)
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = ParamError
|
||||
response.Message = fmt.Sprintf("user add failed, param error : %v", err)
|
||||
log.Printf(response.Message)
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
result := c.verifyToken(info, TOKEN_ADD)
|
||||
|
||||
if !result.Success {
|
||||
context.JSON(http.StatusOK, &result)
|
||||
return
|
||||
}
|
||||
|
||||
info.Comment = cleanString(info.Comment)
|
||||
info.Ports = cleanPorts(info.Ports)
|
||||
info.Domains = cleanStrings(info.Domains)
|
||||
info.Subdomains = cleanStrings(info.Subdomains)
|
||||
|
||||
c.Tokens[info.User] = info
|
||||
|
||||
err = c.saveToken()
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = SaveError
|
||||
response.Message = fmt.Sprintf("add failed, error : %v", err)
|
||||
log.Printf(response.Message)
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
context.JSON(0, &response)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeUpdateTokensFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
response := OperationResponse{
|
||||
Success: true,
|
||||
Code: Success,
|
||||
Message: "user update success",
|
||||
}
|
||||
update := TokenUpdate{}
|
||||
err := context.BindJSON(&update)
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = ParamError
|
||||
response.Message = fmt.Sprintf("update failed, param error : %v", err)
|
||||
log.Printf(response.Message)
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
before := update.Before
|
||||
after := update.After
|
||||
|
||||
if before.User != after.User {
|
||||
response.Success = false
|
||||
response.Code = ParamError
|
||||
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
|
||||
}
|
||||
|
||||
result := c.verifyToken(after, TOKEN_UPDATE)
|
||||
|
||||
if !result.Success {
|
||||
context.JSON(http.StatusOK, &result)
|
||||
return
|
||||
}
|
||||
|
||||
after.Comment = cleanString(after.Comment)
|
||||
after.Ports = cleanPorts(after.Ports)
|
||||
after.Domains = cleanStrings(after.Domains)
|
||||
after.Subdomains = cleanStrings(after.Subdomains)
|
||||
|
||||
c.Tokens[after.User] = after
|
||||
|
||||
err = c.saveToken()
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = SaveError
|
||||
response.Message = fmt.Sprintf("user update failed, error : %v", err)
|
||||
log.Printf(response.Message)
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
context.JSON(http.StatusOK, &response)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeRemoveTokensFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
response := OperationResponse{
|
||||
Success: true,
|
||||
Code: Success,
|
||||
Message: "user remove success",
|
||||
}
|
||||
remove := TokenRemove{}
|
||||
err := context.BindJSON(&remove)
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = ParamError
|
||||
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()
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = SaveError
|
||||
response.Message = fmt.Sprintf("user update failed, error : %v", err)
|
||||
log.Printf(response.Message)
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
context.JSON(http.StatusOK, &response)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeDisableTokensFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
response := OperationResponse{
|
||||
Success: true,
|
||||
Code: Success,
|
||||
Message: "remove success",
|
||||
}
|
||||
disable := TokenDisable{}
|
||||
err := context.BindJSON(&disable)
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = ParamError
|
||||
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.Enable = false
|
||||
c.Tokens[user.User] = token
|
||||
}
|
||||
|
||||
err = c.saveToken()
|
||||
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = SaveError
|
||||
response.Message = fmt.Sprintf("disable failed, error : %v", err)
|
||||
log.Printf(response.Message)
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
context.JSON(http.StatusOK, &response)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeEnableTokensFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
response := OperationResponse{
|
||||
Success: true,
|
||||
Code: Success,
|
||||
Message: "remove success",
|
||||
}
|
||||
enable := TokenEnable{}
|
||||
err := context.BindJSON(&enable)
|
||||
if err != nil {
|
||||
response.Success = false
|
||||
response.Code = ParamError
|
||||
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.Enable = true
|
||||
c.Tokens[user.User] = token
|
||||
}
|
||||
|
||||
err = c.saveToken()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("enable failed, error : %v", err)
|
||||
response.Success = false
|
||||
response.Code = SaveError
|
||||
response.Message = "enable failed"
|
||||
context.JSON(http.StatusOK, &response)
|
||||
return
|
||||
}
|
||||
|
||||
context.JSON(http.StatusOK, &response)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HandleController) MakeProxyFunc() func(context *gin.Context) {
|
||||
return func(context *gin.Context) {
|
||||
var client *http.Client
|
||||
var protocol string
|
||||
|
||||
if c.CommonInfo.DashboardTls {
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol = "https://"
|
||||
} else {
|
||||
client = http.DefaultClient
|
||||
protocol = "http://"
|
||||
}
|
||||
|
||||
res := ProxyResponse{}
|
||||
host := c.CommonInfo.DashboardAddr
|
||||
port := c.CommonInfo.DashboardPort
|
||||
requestUrl := protocol + host + ":" + strconv.Itoa(port) + context.Param("serverApi")
|
||||
request, _ := http.NewRequest("GET", requestUrl, nil)
|
||||
username := c.CommonInfo.DashboardUser
|
||||
password := c.CommonInfo.DashboardPwd
|
||||
if trimString(username) != "" && trimString(password) != "" {
|
||||
request.SetBasicAuth(username, password)
|
||||
log.Printf("Proxy to %s", requestUrl)
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
|
||||
if err != nil {
|
||||
res.Code = FrpServerError
|
||||
res.Success = false
|
||||
res.Message = err.Error()
|
||||
log.Print(err)
|
||||
context.JSON(http.StatusOK, &res)
|
||||
return
|
||||
}
|
||||
|
||||
res.Code = response.StatusCode
|
||||
body, err := io.ReadAll(response.Body)
|
||||
|
||||
if err != nil {
|
||||
res.Success = false
|
||||
res.Message = err.Error()
|
||||
} else {
|
||||
if res.Code == http.StatusOK {
|
||||
res.Success = true
|
||||
res.Data = string(body)
|
||||
res.Message = "Proxy to " + requestUrl + " success"
|
||||
} else {
|
||||
res.Success = false
|
||||
res.Message = "Proxy to " + requestUrl + " error: " + string(body)
|
||||
}
|
||||
}
|
||||
log.Printf(res.Message)
|
||||
context.JSON(http.StatusOK, &res)
|
||||
}
|
||||
}
|
||||
193
pkg/server/controller/handler.go
Normal file
193
pkg/server/controller/handler.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *HandleController) HandleLogin(content *plugin.LoginContent) plugin.Response {
|
||||
token := content.Metas["token"]
|
||||
user := content.User
|
||||
return c.JudgeToken(user, token)
|
||||
}
|
||||
|
||||
func (c *HandleController) HandleNewProxy(content *plugin.NewProxyContent) plugin.Response {
|
||||
token := content.User.Metas["token"]
|
||||
user := content.User.User
|
||||
judgeToken := c.JudgeToken(user, token)
|
||||
if judgeToken.Reject {
|
||||
return judgeToken
|
||||
}
|
||||
return c.JudgePort(content)
|
||||
}
|
||||
|
||||
func (c *HandleController) HandlePing(content *plugin.PingContent) plugin.Response {
|
||||
token := content.User.Metas["token"]
|
||||
user := content.User.User
|
||||
return c.JudgeToken(user, token)
|
||||
}
|
||||
|
||||
func (c *HandleController) HandleNewWorkConn(content *plugin.NewWorkConnContent) plugin.Response {
|
||||
token := content.User.Metas["token"]
|
||||
user := content.User.User
|
||||
return c.JudgeToken(user, token)
|
||||
}
|
||||
|
||||
func (c *HandleController) HandleNewUserConn(content *plugin.NewUserConnContent) plugin.Response {
|
||||
token := content.User.Metas["token"]
|
||||
user := content.User.User
|
||||
return c.JudgeToken(user, token)
|
||||
}
|
||||
|
||||
func (c *HandleController) JudgeToken(user string, token string) plugin.Response {
|
||||
var res plugin.Response
|
||||
if len(c.Tokens) == 0 {
|
||||
res.Unchange = true
|
||||
} else if user == "" || token == "" {
|
||||
res.Reject = true
|
||||
res.RejectReason = "user or meta token can not be empty"
|
||||
} else if info, exist := c.Tokens[user]; exist {
|
||||
if !info.Enable {
|
||||
res.Reject = true
|
||||
res.RejectReason = fmt.Sprintf("user [%s] is disabled", user)
|
||||
} else {
|
||||
if info.Token != token {
|
||||
res.Reject = true
|
||||
res.RejectReason = fmt.Sprintf("invalid meta token for user [%s]", user)
|
||||
} else {
|
||||
res.Unchange = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.Reject = true
|
||||
res.RejectReason = fmt.Sprintf("user [%s] not exist", user)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (c *HandleController) JudgePort(content *plugin.NewProxyContent) plugin.Response {
|
||||
var res plugin.Response
|
||||
var portErr error
|
||||
var reject = false
|
||||
supportProxyTypes := []string{
|
||||
"tcp", "tcpmux", "udp", "http", "https",
|
||||
}
|
||||
proxyType := content.ProxyType
|
||||
if stringContains(proxyType, supportProxyTypes) {
|
||||
log.Printf("proxy type [%v] not support, plugin do nothing", proxyType)
|
||||
res.Unchange = true
|
||||
return res
|
||||
}
|
||||
|
||||
user := content.User.User
|
||||
userPort := content.RemotePort
|
||||
userDomains := content.CustomDomains
|
||||
userSubdomain := content.SubDomain
|
||||
|
||||
portAllowed := true
|
||||
if proxyType == "tcp" || proxyType == "udp" {
|
||||
portAllowed = false
|
||||
if token, exist := c.Tokens[user]; exist {
|
||||
for _, port := range token.Ports {
|
||||
if str, ok := port.(string); ok {
|
||||
if strings.Contains(str, "-") {
|
||||
allowedRanges := strings.Split(str, "-")
|
||||
if len(allowedRanges) != 2 {
|
||||
portErr = fmt.Errorf("user [%v] port range [%v] format error", user, port)
|
||||
break
|
||||
}
|
||||
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(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
|
||||
}
|
||||
if max(userPort, start) == userPort && min(userPort, end) == userPort {
|
||||
portAllowed = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
allowed, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
portErr = fmt.Errorf("user [%v] allowed port [%v] is not a number", user, port)
|
||||
}
|
||||
if allowed == userPort {
|
||||
portAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allowed := port
|
||||
if allowed == userPort {
|
||||
portAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
portAllowed = true
|
||||
}
|
||||
}
|
||||
if !portAllowed {
|
||||
if portErr == nil {
|
||||
portErr = fmt.Errorf("user [%v] port [%v] is not allowed", user, userPort)
|
||||
}
|
||||
reject = true
|
||||
}
|
||||
|
||||
domainAllowed := true
|
||||
if proxyType == "http" || proxyType == "https" || proxyType == "tcpmux" {
|
||||
if portAllowed {
|
||||
if token, exist := c.Tokens[user]; exist {
|
||||
for _, userDomain := range userDomains {
|
||||
if stringContains(userDomain, token.Domains) {
|
||||
domainAllowed = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !domainAllowed {
|
||||
portErr = fmt.Errorf("user [%v] domain [%v] is not allowed", user, strings.Join(userDomains, ","))
|
||||
reject = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subdomainAllowed := true
|
||||
if proxyType == "http" || proxyType == "https" {
|
||||
subdomainAllowed = false
|
||||
if portAllowed && domainAllowed {
|
||||
if token, exist := c.Tokens[user]; exist {
|
||||
for _, subdomain := range token.Subdomains {
|
||||
if subdomain == userSubdomain {
|
||||
subdomainAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
subdomainAllowed = true
|
||||
}
|
||||
if !subdomainAllowed {
|
||||
portErr = fmt.Errorf("user [%v] subdomain [%v] is not allowed", user, userSubdomain)
|
||||
reject = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reject {
|
||||
res.Reject = true
|
||||
res.RejectReason = portErr.Error()
|
||||
} else {
|
||||
res.Unchange = true
|
||||
}
|
||||
return res
|
||||
}
|
||||
50
pkg/server/controller/register.go
Normal file
50
pkg/server/controller/register.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type HandleController struct {
|
||||
CommonInfo CommonInfo
|
||||
Tokens map[string]TokenInfo
|
||||
Version string
|
||||
ConfigFile string
|
||||
TokensFile string
|
||||
}
|
||||
|
||||
func NewHandleController(config *HandleController) *HandleController {
|
||||
return config
|
||||
}
|
||||
|
||||
func (c *HandleController) Register(rootDir string, engine *gin.Engine) {
|
||||
assets := filepath.Join(rootDir, "assets")
|
||||
_, err := os.Stat(assets)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
assets = "./assets"
|
||||
}
|
||||
|
||||
engine.Delims("${", "}")
|
||||
engine.LoadHTMLGlob(filepath.Join(assets, "templates/*"))
|
||||
engine.Static("/static", filepath.Join(assets, "static"))
|
||||
engine.GET("/lang.json", c.MakeLangFunc())
|
||||
engine.GET(LoginUrl, c.MakeLoginFunc())
|
||||
engine.POST(LoginUrl, c.MakeLoginFunc())
|
||||
engine.GET(LogoutUrl, c.MakeLogoutFunc())
|
||||
|
||||
var group *gin.RouterGroup
|
||||
if len(c.CommonInfo.AdminUser) != 0 {
|
||||
group = engine.Group("/", c.BasicAuth())
|
||||
} else {
|
||||
group = engine.Group("/")
|
||||
}
|
||||
group.GET("/", c.MakeIndexFunc())
|
||||
group.GET("/tokens", c.MakeQueryTokensFunc())
|
||||
group.POST("/add", c.MakeAddTokenFunc())
|
||||
group.POST("/update", c.MakeUpdateTokensFunc())
|
||||
group.POST("/remove", c.MakeRemoveTokensFunc())
|
||||
group.POST("/disable", c.MakeDisableTokensFunc())
|
||||
group.POST("/enable", c.MakeEnableTokensFunc())
|
||||
group.GET("/proxy/*serverApi", c.MakeProxyFunc())
|
||||
}
|
||||
222
pkg/server/controller/utils.go
Normal file
222
pkg/server/controller/utils.go
Normal file
@@ -0,0 +1,222 @@
|
||||
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 {
|
||||
if str, ok := port.(string); ok {
|
||||
trimmedPort := trimString(str)
|
||||
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 cleanPorts(ports []any) []any {
|
||||
cleanedPorts := make([]any, len(ports))
|
||||
for i, port := range ports {
|
||||
if str, ok := port.(string); ok {
|
||||
cleanedPorts[i] = cleanString(str)
|
||||
} else {
|
||||
//float64, for JSON numbers
|
||||
cleanedPorts[i] = int(port.(float64))
|
||||
}
|
||||
}
|
||||
return cleanedPorts
|
||||
}
|
||||
|
||||
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 trimString(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)
|
||||
}
|
||||
|
||||
encoder := toml.NewEncoder(tokenFile)
|
||||
encoder.Indent = " "
|
||||
if err = encoder.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
|
||||
}
|
||||
136
pkg/server/controller/variables.go
Normal file
136
pkg/server/controller/variables.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
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"
|
||||
LoginSuccessUrl = "/"
|
||||
LogoutUrl = "/logout"
|
||||
LogoutSuccessUrl = "/login"
|
||||
)
|
||||
|
||||
var (
|
||||
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 {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
Code int
|
||||
Err error
|
||||
}
|
||||
|
||||
type Common struct {
|
||||
Common CommonInfo
|
||||
}
|
||||
|
||||
type CommonInfo struct {
|
||||
PluginAddr string `toml:"plugin_addr"`
|
||||
PluginPort int `toml:"plugin_port"`
|
||||
AdminUser string `toml:"admin_user"`
|
||||
AdminPwd string `toml:"admin_pwd"`
|
||||
AdminKeepTime int `toml:"admin_keep_time"`
|
||||
TlsMode bool `toml:"tls_mode"`
|
||||
TlsCertFile string `toml:"tls_cert_file"`
|
||||
TlsKeyFile string `toml:"tls_key_file"`
|
||||
DashboardAddr string `toml:"dashboard_addr"`
|
||||
DashboardPort int `toml:"dashboard_port"`
|
||||
DashboardUser string `toml:"dashboard_user"`
|
||||
DashboardPwd string `toml:"dashboard_pwd"`
|
||||
DashboardTls bool
|
||||
}
|
||||
|
||||
type Tokens struct {
|
||||
Tokens map[string]TokenInfo `toml:"tokens"`
|
||||
}
|
||||
|
||||
type TokenInfo struct {
|
||||
User string `toml:"user" json:"user" form:"user"`
|
||||
Token string `toml:"token" json:"token" form:"token"`
|
||||
Comment string `toml:"comment" json:"comment" form:"comment"`
|
||||
Ports []any `toml:"ports" json:"ports" from:"ports"`
|
||||
Domains []string `toml:"domains" json:"domains" from:"domains"`
|
||||
Subdomains []string `toml:"subdomains" json:"subdomains" from:"subdomains"`
|
||||
Enable bool `toml:"enable" json:"enable" form:"enable"`
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Count int `json:"count"`
|
||||
Data []TokenInfo `json:"data"`
|
||||
}
|
||||
|
||||
type OperationResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ProxyResponse struct {
|
||||
OperationResponse
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type TokenSearch struct {
|
||||
TokenInfo
|
||||
Page int `form:"page"`
|
||||
Limit int `form:"limit"`
|
||||
}
|
||||
|
||||
type TokenUpdate struct {
|
||||
Before TokenInfo `json:"before"`
|
||||
After TokenInfo `json:"after"`
|
||||
}
|
||||
|
||||
type TokenRemove struct {
|
||||
Users []TokenInfo `json:"users"`
|
||||
}
|
||||
|
||||
type TokenDisable struct {
|
||||
TokenRemove
|
||||
}
|
||||
|
||||
type TokenEnable struct {
|
||||
TokenDisable
|
||||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
Reference in New Issue
Block a user