new api with token

This commit is contained in:
Alireza Ahmadi
2025-02-02 21:46:36 +01:00
parent 4865072f55
commit 1ac7bce6b4
9 changed files with 672 additions and 345 deletions
-343
View File
@@ -1,343 +0,0 @@
package api
import (
"encoding/json"
"s-ui/database"
"s-ui/logger"
"s-ui/service"
"s-ui/util"
"s-ui/util/common"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
type APIHandler struct {
service.SettingService
service.UserService
service.ConfigService
service.ClientService
service.TlsService
service.InboundService
service.OutboundService
service.EndpointService
service.PanelService
service.StatsService
service.ServerService
}
func NewAPIHandler(g *gin.RouterGroup) {
a := &APIHandler{}
a.initRouter(g)
}
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
g.Use(func(c *gin.Context) {
path := c.Request.URL.Path
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
checkLogin(c)
}
})
g.POST("/:postAction", a.postHandler)
g.GET("/:getAction", a.getHandler)
}
func (a *APIHandler) postHandler(c *gin.Context) {
var err error
action := c.Param("postAction")
remoteIP := getRemoteIp(c)
loginUser := GetLoginUser(c)
hostname := getHostname(c)
switch action {
case "login":
loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP)
if err != nil {
jsonMsg(c, "", err)
return
}
sessionMaxAge, err := a.SettingService.GetSessionMaxAge()
if err != nil {
logger.Infof("Unable to get session's max age from DB")
}
err = SetLoginUser(c, loginUser, sessionMaxAge)
if err == nil {
logger.Info("user ", loginUser, " login success")
} else {
logger.Warning("login failed: ", err)
}
jsonMsg(c, "", nil)
case "changePass":
id := c.Request.FormValue("id")
oldPass := c.Request.FormValue("oldPass")
newUsername := c.Request.FormValue("newUsername")
newPass := c.Request.FormValue("newPass")
err = a.UserService.ChangePass(id, oldPass, newUsername, newPass)
if err == nil {
logger.Info("change user credentials success")
jsonMsg(c, "save", nil)
} else {
logger.Warning("change user credentials failed:", err)
jsonMsg(c, "", err)
}
case "save":
obj := c.Request.FormValue("object")
act := c.Request.FormValue("action")
data := c.Request.FormValue("data")
initUsers := c.Request.FormValue("initUsers")
objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), initUsers, loginUser, hostname)
if err != nil {
jsonMsg(c, "save", err)
return
}
err = a.loadPartialData(c, objs)
if err != nil {
jsonMsg(c, obj, err)
}
return
case "restartApp":
err = a.PanelService.RestartPanel(3)
jsonMsg(c, "restartApp", err)
case "restartSb":
err = a.ConfigService.RestartCore()
jsonMsg(c, "restartSb", err)
case "linkConvert":
link := c.Request.FormValue("link")
result, _, err := util.GetOutbound(link, 0)
jsonObj(c, result, err)
case "importdb":
file, _, err := c.Request.FormFile("db")
if err != nil {
jsonMsg(c, "", err)
return
}
defer file.Close()
err = database.ImportDB(file)
jsonMsg(c, "", err)
default:
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
func (a *APIHandler) getHandler(c *gin.Context) {
action := c.Param("getAction")
switch action {
case "logout":
loginUser := GetLoginUser(c)
if loginUser != "" {
logger.Infof("user %s logout", loginUser)
}
ClearSession(c)
jsonMsg(c, "", nil)
case "load":
data, err := a.loadData(c)
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, data, nil)
case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
err := a.loadPartialData(c, []string{action})
if err != nil {
jsonMsg(c, action, err)
}
return
case "users":
users, err := a.UserService.GetUsers()
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, *users, nil)
case "settings":
data, err := a.SettingService.GetAllSetting()
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, data, err)
case "stats":
resource := c.Query("resource")
tag := c.Query("tag")
limit, err := strconv.Atoi(c.Query("limit"))
if err != nil {
limit = 100
}
data, err := a.StatsService.GetStats(resource, tag, limit)
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, data, err)
case "status":
request := c.Query("r")
result := a.ServerService.GetStatus(request)
jsonObj(c, result, nil)
case "onlines":
onlines, err := a.StatsService.GetOnlines()
jsonObj(c, onlines, err)
case "logs":
count := c.Query("c")
level := c.Query("l")
logs := a.ServerService.GetLogs(count, level)
jsonObj(c, logs, nil)
case "changes":
actor := c.Query("a")
chngKey := c.Query("k")
count := c.Query("c")
changes := a.ConfigService.GetChanges(actor, chngKey, count)
jsonObj(c, changes, nil)
case "keypairs":
kType := c.Query("k")
options := c.Query("o")
keypair := a.ServerService.GenKeypair(kType, options)
jsonObj(c, keypair, nil)
case "getdb":
exclude := c.Query("exclude")
db, err := database.GetDb(exclude)
if err != nil {
jsonMsg(c, "", err)
return
}
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=s-ui_"+time.Now().Format("20060102-150405")+".db")
c.Writer.Write(db)
default:
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
data := make(map[string]interface{}, 0)
lu := c.Query("lu")
isUpdated, err := a.ConfigService.CheckChanges(lu)
if err != nil {
return "", err
}
onlines, err := a.StatsService.GetOnlines()
sysInfo := a.ServerService.GetSingboxInfo()
if sysInfo["running"] == false {
logs := a.ServerService.GetLogs("1", "debug")
if len(logs) > 0 {
data["lastLog"] = logs[0]
}
}
if err != nil {
return "", err
}
if isUpdated {
config, err := a.SettingService.GetConfig()
if err != nil {
return "", err
}
clients, err := a.ClientService.GetAll()
if err != nil {
return "", err
}
tlsConfigs, err := a.TlsService.GetAll()
if err != nil {
return "", err
}
inbounds, err := a.InboundService.GetAll()
if err != nil {
return "", err
}
outbounds, err := a.OutboundService.GetAll()
if err != nil {
return "", err
}
endpoints, err := a.EndpointService.GetAll()
if err != nil {
return "", err
}
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
if err != nil {
return "", err
}
data["config"] = json.RawMessage(config)
data["clients"] = clients
data["tls"] = tlsConfigs
data["inbounds"] = inbounds
data["outbounds"] = outbounds
data["endpoints"] = endpoints
data["subURI"] = subURI
data["onlines"] = onlines
} else {
data["onlines"] = onlines
}
return data, nil
}
func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error {
data := make(map[string]interface{}, 0)
id := c.Query("id")
for _, obj := range objs {
switch obj {
case "inbounds":
inbounds, err := a.InboundService.Get(id)
if err != nil {
return err
}
data[obj] = inbounds
case "outbounds":
outbounds, err := a.OutboundService.GetAll()
if err != nil {
return err
}
data[obj] = outbounds
case "endpoints":
endpoints, err := a.EndpointService.GetAll()
if err != nil {
return err
}
data[obj] = endpoints
case "tls":
tlsConfigs, err := a.TlsService.GetAll()
if err != nil {
return err
}
data[obj] = tlsConfigs
case "clients":
clients, err := a.ClientService.Get(id)
if err != nil {
return err
}
data[obj] = clients
case "config":
config, err := a.SettingService.GetConfig()
if err != nil {
return err
}
data[obj] = json.RawMessage(config)
case "settings":
settings, err := a.SettingService.GetAllSetting()
if err != nil {
return err
}
data[obj] = settings
}
}
jsonObj(c, data, nil)
return nil
}
func (a *APIHandler) postActions(c *gin.Context) (string, json.RawMessage, error) {
var data map[string]json.RawMessage
err := c.ShouldBind(&data)
if err != nil {
return "", nil, err
}
return string(data["action"]), data["data"], nil
}
+100
View File
@@ -0,0 +1,100 @@
package api
import (
"s-ui/util/common"
"strings"
"github.com/gin-gonic/gin"
)
type APIHandler struct {
ApiService
apiv2 *APIv2Handler
}
func NewAPIHandler(g *gin.RouterGroup, a2 *APIv2Handler) {
a := &APIHandler{
apiv2: a2,
}
a.initRouter(g)
}
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
g.Use(func(c *gin.Context) {
path := c.Request.URL.Path
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
checkLogin(c)
}
})
g.POST("/:postAction", a.postHandler)
g.GET("/:getAction", a.getHandler)
}
func (a *APIHandler) postHandler(c *gin.Context) {
loginUser := GetLoginUser(c)
action := c.Param("postAction")
switch action {
case "login":
a.ApiService.Login(c)
case "changePass":
a.ApiService.ChangePass(c)
case "save":
a.ApiService.Save(c, loginUser)
case "restartApp":
a.ApiService.RestartApp(c)
case "restartSb":
a.ApiService.RestartSb(c)
case "linkConvert":
a.ApiService.LinkConvert(c)
case "importdb":
a.ApiService.ImportDb(c)
case "addToken":
a.ApiService.AddToken(c)
a.apiv2.ReloadTokens()
case "deleteToken":
a.ApiService.DeleteToken(c)
a.apiv2.ReloadTokens()
default:
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
func (a *APIHandler) getHandler(c *gin.Context) {
action := c.Param("getAction")
switch action {
case "logout":
a.ApiService.Logout(c)
case "load":
a.ApiService.LoadData(c)
case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
err := a.ApiService.LoadPartialData(c, []string{action})
if err != nil {
jsonMsg(c, action, err)
}
return
case "users":
a.ApiService.GetUsers(c)
case "settings":
a.ApiService.GetSettings(c)
case "stats":
a.ApiService.GetStats(c)
case "status":
a.ApiService.GetStatus(c)
case "onlines":
a.ApiService.GetOnlines(c)
case "logs":
a.ApiService.GetLogs(c)
case "changes":
a.ApiService.CheckChanges(c)
case "keypairs":
a.ApiService.GetKeypairs(c)
case "getdb":
a.ApiService.GetDb(c)
case "tokens":
a.ApiService.GetTokens(c)
default:
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
+363
View File
@@ -0,0 +1,363 @@
package api
import (
"encoding/json"
"s-ui/database"
"s-ui/logger"
"s-ui/service"
"s-ui/util"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
type ApiService struct {
service.SettingService
service.UserService
service.ConfigService
service.ClientService
service.TlsService
service.InboundService
service.OutboundService
service.EndpointService
service.PanelService
service.StatsService
service.ServerService
}
func (a *ApiService) LoadData(c *gin.Context) {
data, err := a.getData(c)
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, data, nil)
}
func (a *ApiService) getData(c *gin.Context) (interface{}, error) {
data := make(map[string]interface{}, 0)
lu := c.Query("lu")
isUpdated, err := a.ConfigService.CheckChanges(lu)
if err != nil {
return "", err
}
onlines, err := a.StatsService.GetOnlines()
sysInfo := a.ServerService.GetSingboxInfo()
if sysInfo["running"] == false {
logs := a.ServerService.GetLogs("1", "debug")
if len(logs) > 0 {
data["lastLog"] = logs[0]
}
}
if err != nil {
return "", err
}
if isUpdated {
config, err := a.SettingService.GetConfig()
if err != nil {
return "", err
}
clients, err := a.ClientService.GetAll()
if err != nil {
return "", err
}
tlsConfigs, err := a.TlsService.GetAll()
if err != nil {
return "", err
}
inbounds, err := a.InboundService.GetAll()
if err != nil {
return "", err
}
outbounds, err := a.OutboundService.GetAll()
if err != nil {
return "", err
}
endpoints, err := a.EndpointService.GetAll()
if err != nil {
return "", err
}
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
if err != nil {
return "", err
}
data["config"] = json.RawMessage(config)
data["clients"] = clients
data["tls"] = tlsConfigs
data["inbounds"] = inbounds
data["outbounds"] = outbounds
data["endpoints"] = endpoints
data["subURI"] = subURI
data["onlines"] = onlines
} else {
data["onlines"] = onlines
}
return data, nil
}
func (a *ApiService) LoadPartialData(c *gin.Context, objs []string) error {
data := make(map[string]interface{}, 0)
id := c.Query("id")
for _, obj := range objs {
switch obj {
case "inbounds":
inbounds, err := a.InboundService.Get(id)
if err != nil {
return err
}
data[obj] = inbounds
case "outbounds":
outbounds, err := a.OutboundService.GetAll()
if err != nil {
return err
}
data[obj] = outbounds
case "endpoints":
endpoints, err := a.EndpointService.GetAll()
if err != nil {
return err
}
data[obj] = endpoints
case "tls":
tlsConfigs, err := a.TlsService.GetAll()
if err != nil {
return err
}
data[obj] = tlsConfigs
case "clients":
clients, err := a.ClientService.Get(id)
if err != nil {
return err
}
data[obj] = clients
case "config":
config, err := a.SettingService.GetConfig()
if err != nil {
return err
}
data[obj] = json.RawMessage(config)
case "settings":
settings, err := a.SettingService.GetAllSetting()
if err != nil {
return err
}
data[obj] = settings
}
}
jsonObj(c, data, nil)
return nil
}
func (a *ApiService) GetUsers(c *gin.Context) {
users, err := a.UserService.GetUsers()
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, *users, nil)
}
func (a *ApiService) GetSettings(c *gin.Context) {
data, err := a.SettingService.GetAllSetting()
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, data, err)
}
func (a *ApiService) GetStats(c *gin.Context) {
resource := c.Query("resource")
tag := c.Query("tag")
limit, err := strconv.Atoi(c.Query("limit"))
if err != nil {
limit = 100
}
data, err := a.StatsService.GetStats(resource, tag, limit)
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, data, err)
}
func (a *ApiService) GetStatus(c *gin.Context) {
request := c.Query("r")
result := a.ServerService.GetStatus(request)
jsonObj(c, result, nil)
}
func (a *ApiService) GetOnlines(c *gin.Context) {
onlines, err := a.StatsService.GetOnlines()
jsonObj(c, onlines, err)
}
func (a *ApiService) GetLogs(c *gin.Context) {
count := c.Query("c")
level := c.Query("l")
logs := a.ServerService.GetLogs(count, level)
jsonObj(c, logs, nil)
}
func (a *ApiService) CheckChanges(c *gin.Context) {
actor := c.Query("a")
chngKey := c.Query("k")
count := c.Query("c")
changes := a.ConfigService.GetChanges(actor, chngKey, count)
jsonObj(c, changes, nil)
}
func (a *ApiService) GetKeypairs(c *gin.Context) {
kType := c.Query("k")
options := c.Query("o")
keypair := a.ServerService.GenKeypair(kType, options)
jsonObj(c, keypair, nil)
}
func (a *ApiService) GetDb(c *gin.Context) {
exclude := c.Query("exclude")
db, err := database.GetDb(exclude)
if err != nil {
jsonMsg(c, "", err)
return
}
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=s-ui_"+time.Now().Format("20060102-150405")+".db")
c.Writer.Write(db)
}
func (a *ApiService) postActions(c *gin.Context) (string, json.RawMessage, error) {
var data map[string]json.RawMessage
err := c.ShouldBind(&data)
if err != nil {
return "", nil, err
}
return string(data["action"]), data["data"], nil
}
func (a *ApiService) Login(c *gin.Context) {
remoteIP := getRemoteIp(c)
loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP)
if err != nil {
jsonMsg(c, "", err)
return
}
sessionMaxAge, err := a.SettingService.GetSessionMaxAge()
if err != nil {
logger.Infof("Unable to get session's max age from DB")
}
err = SetLoginUser(c, loginUser, sessionMaxAge)
if err == nil {
logger.Info("user ", loginUser, " login success")
} else {
logger.Warning("login failed: ", err)
}
jsonMsg(c, "", nil)
}
func (a *ApiService) ChangePass(c *gin.Context) {
id := c.Request.FormValue("id")
oldPass := c.Request.FormValue("oldPass")
newUsername := c.Request.FormValue("newUsername")
newPass := c.Request.FormValue("newPass")
err := a.UserService.ChangePass(id, oldPass, newUsername, newPass)
if err == nil {
logger.Info("change user credentials success")
jsonMsg(c, "save", nil)
} else {
logger.Warning("change user credentials failed:", err)
jsonMsg(c, "", err)
}
}
func (a *ApiService) Save(c *gin.Context, loginUser string) {
hostname := getHostname(c)
obj := c.Request.FormValue("object")
act := c.Request.FormValue("action")
data := c.Request.FormValue("data")
initUsers := c.Request.FormValue("initUsers")
objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), initUsers, loginUser, hostname)
if err != nil {
jsonMsg(c, "save", err)
return
}
err = a.LoadPartialData(c, objs)
if err != nil {
jsonMsg(c, obj, err)
}
}
func (a *ApiService) RestartApp(c *gin.Context) {
err := a.PanelService.RestartPanel(3)
jsonMsg(c, "restartApp", err)
}
func (a *ApiService) RestartSb(c *gin.Context) {
err := a.ConfigService.RestartCore()
jsonMsg(c, "restartSb", err)
}
func (a *ApiService) LinkConvert(c *gin.Context) {
link := c.Request.FormValue("link")
result, _, err := util.GetOutbound(link, 0)
jsonObj(c, result, err)
}
func (a *ApiService) ImportDb(c *gin.Context) {
file, _, err := c.Request.FormFile("db")
if err != nil {
jsonMsg(c, "", err)
return
}
defer file.Close()
err = database.ImportDB(file)
jsonMsg(c, "", err)
}
func (a *ApiService) Logout(c *gin.Context) {
loginUser := GetLoginUser(c)
if loginUser != "" {
logger.Infof("user %s logout", loginUser)
}
ClearSession(c)
jsonMsg(c, "", nil)
}
func (a *ApiService) LoadTokens() ([]byte, error) {
return a.UserService.LoadTokens()
}
func (a *ApiService) GetTokens(c *gin.Context) {
loginUser := GetLoginUser(c)
tokens, err := a.UserService.GetUserTokens(loginUser)
jsonObj(c, tokens, err)
}
func (a *ApiService) AddToken(c *gin.Context) {
loginUser := GetLoginUser(c)
expiry := c.Request.FormValue("expiry")
expiryInt, err := strconv.ParseInt(expiry, 10, 64)
if err != nil {
jsonMsg(c, "", err)
return
}
desc := c.Request.FormValue("desc")
token, err := a.UserService.AddToken(loginUser, expiryInt, desc)
jsonObj(c, token, err)
}
func (a *ApiService) DeleteToken(c *gin.Context) {
tokenId := c.Request.FormValue("id")
err := a.UserService.DeleteToken(tokenId)
jsonMsg(c, "", err)
}
+129
View File
@@ -0,0 +1,129 @@
package api
import (
"encoding/json"
"s-ui/logger"
"s-ui/util/common"
"time"
"github.com/gin-gonic/gin"
)
type TokenInMemory struct {
Token string
Expiry int64
Username string
}
type APIv2Handler struct {
ApiService
tokens *[]TokenInMemory
}
func NewAPIv2Handler(g *gin.RouterGroup) *APIv2Handler {
a := &APIv2Handler{}
a.ReloadTokens()
a.initRouter(g)
return a
}
func (a *APIv2Handler) initRouter(g *gin.RouterGroup) {
g.Use(func(c *gin.Context) {
a.checkToken(c)
})
g.POST("/:postAction", a.postHandler)
g.GET("/:getAction", a.getHandler)
}
func (a *APIv2Handler) postHandler(c *gin.Context) {
username := a.findUsername(c)
action := c.Param("postAction")
switch action {
case "save":
a.ApiService.Save(c, username)
case "restartApp":
a.ApiService.RestartApp(c)
case "restartSb":
a.ApiService.RestartSb(c)
case "linkConvert":
a.ApiService.LinkConvert(c)
case "importdb":
a.ApiService.ImportDb(c)
default:
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
func (a *APIv2Handler) getHandler(c *gin.Context) {
action := c.Param("getAction")
switch action {
case "load":
a.ApiService.LoadData(c)
case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
err := a.ApiService.LoadPartialData(c, []string{action})
if err != nil {
jsonMsg(c, action, err)
}
return
case "users":
a.ApiService.GetUsers(c)
case "settings":
a.ApiService.GetSettings(c)
case "stats":
a.ApiService.GetStats(c)
case "status":
a.ApiService.GetStatus(c)
case "onlines":
a.ApiService.GetOnlines(c)
case "logs":
a.ApiService.GetLogs(c)
case "changes":
a.ApiService.CheckChanges(c)
case "keypairs":
a.ApiService.GetKeypairs(c)
case "getdb":
a.ApiService.GetDb(c)
default:
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
func (a *APIv2Handler) findUsername(c *gin.Context) string {
token := c.Request.Header.Get("Token")
for index, t := range *a.tokens {
if t.Expiry > 0 && t.Expiry < time.Now().Unix() {
(*a.tokens) = append((*a.tokens)[:index], (*a.tokens)[index+1:]...)
continue
}
if t.Token == token {
return t.Username
}
}
return ""
}
func (a *APIv2Handler) checkToken(c *gin.Context) {
username := a.findUsername(c)
if username != "" {
c.Next()
return
}
jsonMsg(c, "", common.NewError("invalid token"))
c.Abort()
}
func (a *APIv2Handler) ReloadTokens() {
tokens, err := a.ApiService.LoadTokens()
if err == nil {
var newTokens []TokenInMemory
err = json.Unmarshal(tokens, &newTokens)
if err != nil {
logger.Error("unable to load tokens: ", err)
}
a.tokens = &newTokens
} else {
logger.Error("unable to load tokens: ", err)
}
}