From 1ac7bce6b452eb264a84db1925a4a88d66b012fe Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Sun, 2 Feb 2025 21:46:36 +0100 Subject: [PATCH] new api with token --- api/api.go | 343 ------------------------------------- api/apiHandler.go | 100 +++++++++++ api/apiService.go | 363 ++++++++++++++++++++++++++++++++++++++++ api/apiV2Handler.go | 129 ++++++++++++++ database/db.go | 1 + database/model/model.go | 9 + service/server.go | 8 +- service/user.go | 59 +++++++ web/web.go | 5 +- 9 files changed, 672 insertions(+), 345 deletions(-) delete mode 100644 api/api.go create mode 100644 api/apiHandler.go create mode 100644 api/apiService.go create mode 100644 api/apiV2Handler.go diff --git a/api/api.go b/api/api.go deleted file mode 100644 index 798d135..0000000 --- a/api/api.go +++ /dev/null @@ -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 -} diff --git a/api/apiHandler.go b/api/apiHandler.go new file mode 100644 index 0000000..7244e17 --- /dev/null +++ b/api/apiHandler.go @@ -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)) + } +} diff --git a/api/apiService.go b/api/apiService.go new file mode 100644 index 0000000..013f5fd --- /dev/null +++ b/api/apiService.go @@ -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) +} diff --git a/api/apiV2Handler.go b/api/apiV2Handler.go new file mode 100644 index 0000000..464d24a --- /dev/null +++ b/api/apiV2Handler.go @@ -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) + } +} diff --git a/database/db.go b/database/db.go index 6f6e75c..20988ad 100644 --- a/database/db.go +++ b/database/db.go @@ -79,6 +79,7 @@ func InitDB(dbPath string) error { &model.Outbound{}, &model.Endpoint{}, &model.User{}, + &model.Tokens{}, &model.Stats{}, &model.Client{}, &model.Changes{}, diff --git a/database/model/model.go b/database/model/model.go index b620707..5685460 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -54,3 +54,12 @@ type Changes struct { Action string `json:"action"` Obj json.RawMessage `json:"obj"` } + +type Tokens struct { + Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Desc string `json:"desc" form:"desc"` + Token string `json:"token" form:"token"` + Expiry int64 `json:"expiry" form:"expiry"` + UserId uint `json:"userId" form:"userId"` + User *User `json:"user" gorm:"foreignKey:UserId;references:Id"` +} diff --git a/service/server.go b/service/server.go index a1887ad..60cd1b9 100644 --- a/service/server.go +++ b/service/server.go @@ -146,7 +146,10 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} { } func (s *ServerService) GetLogs(count string, level string) []string { - c, _ := strconv.Atoi(count) + c, err := strconv.Atoi(count) + if err != nil { + c = 10 + } return logger.GetLogs(c, level) } @@ -171,6 +174,9 @@ func (s *ServerService) GenKeypair(keyType string, options string) []string { func (s *ServerService) generateECHKeyPair(options string) []string { parts := strings.Split(options, ",") + if len(parts) != 2 { + return []string{"Failed to generate ECH keypair: ", "invalid options"} + } configPem, keyPem, err := tls.ECHKeygenDefault(parts[0], parts[1] == "true") if err != nil { return []string{"Failed to generate ECH keypair: ", err.Error()} diff --git a/service/user.go b/service/user.go index 3a476f2..c0f2e43 100644 --- a/service/user.go +++ b/service/user.go @@ -1,6 +1,7 @@ package service import ( + "encoding/json" "s-ui/database" "s-ui/database/model" "s-ui/logger" @@ -99,3 +100,61 @@ func (s *UserService) ChangePass(id string, oldPass string, newUser string, newP user.Password = newPass return db.Save(user).Error } + +func (s *UserService) LoadTokens() ([]byte, error) { + db := database.GetDB() + var tokens []model.Tokens + err := db.Model(model.Tokens{}).Preload("User").Where("expiry == 0 or expiry > ?", time.Now().Unix()).Find(&tokens).Error + if err != nil { + return nil, err + } + var result []map[string]interface{} + for _, t := range tokens { + result = append(result, map[string]interface{}{ + "token": t.Token, + "expiry": t.Expiry, + "username": t.User.Username, + }) + } + jsonResult, _ := json.MarshalIndent(result, "", " ") + return jsonResult, nil +} + +func (s *UserService) GetUserTokens(username string) (*[]model.Tokens, error) { + db := database.GetDB() + var token []model.Tokens + err := db.Model(model.Tokens{}).Select("id,desc,'****' as token,expiry,user_id").Where("user_id = (select id from users where username = ?)", username).Find(&token).Error + if err != nil && !database.IsNotFound(err) { + println(err.Error()) + return nil, err + } + return &token, nil +} + +func (s *UserService) AddToken(username string, expiry int64, desc string) (string, error) { + db := database.GetDB() + var userId uint + err := db.Model(model.User{}).Where("username = ?", username).Select("id").Scan(&userId).Error + if err != nil { + return "", err + } + if expiry > 0 { + expiry = expiry*86400 + time.Now().Unix() + } + token := &model.Tokens{ + Token: common.Random(32), + Desc: desc, + Expiry: expiry, + UserId: userId, + } + err = db.Create(token).Error + if err != nil { + return "", err + } + return token.Token, nil +} + +func (s *UserService) DeleteToken(id string) error { + db := database.GetDB() + return db.Model(model.Tokens{}).Where("id = ?", id).Delete(&model.Tokens{}).Error +} diff --git a/web/web.go b/web/web.go index 2fbfd81..5ac72a7 100644 --- a/web/web.go +++ b/web/web.go @@ -102,8 +102,11 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine.StaticFS(assetsBasePath, http.FS(assetsFS)) + group_apiv2 := engine.Group(base_url + "apiv2") + apiv2 := api.NewAPIv2Handler(group_apiv2) + group_api := engine.Group(base_url + "api") - api.NewAPIHandler(group_api) + api.NewAPIHandler(group_api, apiv2) // Serve index.html as the entry point // Handle all other routes by serving index.html