修复 「1.2.2 用户管理-用户列表勾选单一用户会全选 」 close #216
This commit is contained in:
@ -1,16 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func AccessGatewayCreateEndpoint(c echo.Context) error {
|
||||
type AccessGatewayApi struct{}
|
||||
|
||||
func (api AccessGatewayApi) AccessGatewayCreateEndpoint(c echo.Context) error {
|
||||
var item model.AccessGateway
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -19,16 +24,16 @@ func AccessGatewayCreateEndpoint(c echo.Context) error {
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := accessGatewayRepository.Create(&item); err != nil {
|
||||
if err := repository.GatewayRepository.Create(context.TODO(), &item); err != nil {
|
||||
return err
|
||||
}
|
||||
// 连接网关
|
||||
accessGatewayService.ReConnect(&item)
|
||||
service.GatewayService.ReConnect(&item)
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func AccessGatewayAllEndpoint(c echo.Context) error {
|
||||
gateways, err := accessGatewayRepository.FindAll()
|
||||
func (api AccessGatewayApi) AccessGatewayAllEndpoint(c echo.Context) error {
|
||||
gateways, err := repository.GatewayRepository.FindAll(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -39,7 +44,7 @@ func AccessGatewayAllEndpoint(c echo.Context) error {
|
||||
return Success(c, simpleGateways)
|
||||
}
|
||||
|
||||
func AccessGatewayPagingEndpoint(c echo.Context) error {
|
||||
func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
ip := c.QueryParam("ip")
|
||||
@ -48,12 +53,12 @@ func AccessGatewayPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := accessGatewayRepository.Find(pageIndex, pageSize, ip, name, order, field)
|
||||
items, total, err := repository.GatewayRepository.Find(context.TODO(), pageIndex, pageSize, ip, name, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(items); i++ {
|
||||
g, err := accessGatewayService.GetGatewayById(items[i].ID)
|
||||
g, err := service.GatewayService.GetGatewayById(items[i].ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -61,13 +66,13 @@ func AccessGatewayPagingEndpoint(c echo.Context) error {
|
||||
items[i].Message = g.Message
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func AccessGatewayUpdateEndpoint(c echo.Context) error {
|
||||
func (api AccessGatewayApi) AccessGatewayUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.AccessGateway
|
||||
@ -75,30 +80,30 @@ func AccessGatewayUpdateEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := accessGatewayRepository.UpdateById(&item, id); err != nil {
|
||||
if err := repository.GatewayRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
accessGatewayService.ReConnect(&item)
|
||||
service.GatewayService.ReConnect(&item)
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AccessGatewayDeleteEndpoint(c echo.Context) error {
|
||||
func (api AccessGatewayApi) AccessGatewayDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
id := split[i]
|
||||
if err := accessGatewayRepository.DeleteById(id); err != nil {
|
||||
if err := repository.GatewayRepository.DeleteById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
accessGatewayService.DisconnectById(id)
|
||||
service.GatewayService.DisconnectById(id)
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AccessGatewayGetEndpoint(c echo.Context) error {
|
||||
func (api AccessGatewayApi) AccessGatewayGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := accessGatewayRepository.FindById(id)
|
||||
item, err := repository.GatewayRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -106,13 +111,13 @@ func AccessGatewayGetEndpoint(c echo.Context) error {
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func AccessGatewayReconnectEndpoint(c echo.Context) error {
|
||||
func (api AccessGatewayApi) AccessGatewayReconnectEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := accessGatewayRepository.FindById(id)
|
||||
item, err := repository.GatewayRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessGatewayService.ReConnect(&item)
|
||||
service.GatewayService.ReConnect(&item)
|
||||
return Success(c, "")
|
||||
}
|
||||
|
@ -1,58 +1,34 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"next-terminal/server/constant"
|
||||
"context"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/totp"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
RememberEffectiveTime = time.Hour * time.Duration(24*14)
|
||||
NotRememberEffectiveTime = time.Hour * time.Duration(2)
|
||||
)
|
||||
type AccountApi struct{}
|
||||
|
||||
type LoginAccount struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Remember bool `json:"remember"`
|
||||
TOTP string `json:"totp"`
|
||||
}
|
||||
|
||||
type ConfirmTOTP struct {
|
||||
Secret string `json:"secret"`
|
||||
TOTP string `json:"totp"`
|
||||
}
|
||||
|
||||
type ChangePassword struct {
|
||||
NewPassword string `json:"newPassword"`
|
||||
OldPassword string `json:"oldPassword"`
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
Token string
|
||||
Remember bool
|
||||
User model.User
|
||||
}
|
||||
|
||||
func LoginEndpoint(c echo.Context) error {
|
||||
var loginAccount LoginAccount
|
||||
func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
||||
var loginAccount dto.LoginAccount
|
||||
if err := c.Bind(&loginAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 存储登录失败次数信息
|
||||
loginFailCountKey := c.RealIP() + loginAccount.Username
|
||||
v, ok := cache.GlobalCache.Get(loginFailCountKey)
|
||||
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
|
||||
if !ok {
|
||||
v = 1
|
||||
}
|
||||
@ -61,12 +37,12 @@ func LoginEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "登录失败次数过多,请等待5分钟后再试")
|
||||
}
|
||||
|
||||
user, err := userRepository.FindByUsername(loginAccount.Username)
|
||||
user, err := repository.UserRepository.FindByUsername(context.TODO(), loginAccount.Username)
|
||||
if err != nil {
|
||||
count++
|
||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||
@ -78,9 +54,9 @@ func LoginEndpoint(c echo.Context) error {
|
||||
|
||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||
count++
|
||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||
@ -90,73 +66,49 @@ func LoginEndpoint(c echo.Context) error {
|
||||
return Fail(c, 0, "")
|
||||
}
|
||||
|
||||
token, err := LoginSuccess(loginAccount, user)
|
||||
token, err := api.LoginSuccess(loginAccount, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, token)
|
||||
}
|
||||
|
||||
func SaveLoginLog(clientIP, clientUserAgent string, username string, success, remember bool, id, reason string) error {
|
||||
loginLog := model.LoginLog{
|
||||
Username: username,
|
||||
ClientIP: clientIP,
|
||||
ClientUserAgent: clientUserAgent,
|
||||
LoginTime: utils.NowJsonTime(),
|
||||
Reason: reason,
|
||||
Remember: remember,
|
||||
}
|
||||
if success {
|
||||
loginLog.State = "1"
|
||||
loginLog.ID = id
|
||||
} else {
|
||||
loginLog.State = "0"
|
||||
loginLog.ID = utils.UUID()
|
||||
}
|
||||
func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.User) (string, error) {
|
||||
token := utils.LongUUID()
|
||||
|
||||
if err := loginLogRepository.Create(&loginLog); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoginSuccess(loginAccount LoginAccount, user model.User) (token string, err error) {
|
||||
token = strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "")
|
||||
|
||||
authorization := Authorization{
|
||||
authorization := dto.Authorization{
|
||||
Token: token,
|
||||
Type: constant.LoginToken,
|
||||
Remember: loginAccount.Remember,
|
||||
User: user,
|
||||
User: &user,
|
||||
}
|
||||
|
||||
cacheKey := userService.BuildCacheKeyByToken(token)
|
||||
|
||||
if authorization.Remember {
|
||||
// 记住登录有效期两周
|
||||
cache.GlobalCache.Set(cacheKey, authorization, RememberEffectiveTime)
|
||||
cache.TokenManager.Set(token, authorization, cache.RememberMeExpiration)
|
||||
} else {
|
||||
cache.GlobalCache.Set(cacheKey, authorization, NotRememberEffectiveTime)
|
||||
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
|
||||
}
|
||||
|
||||
// 修改登录状态
|
||||
err = userRepository.Update(&model.User{Online: true, ID: user.ID})
|
||||
err := repository.UserRepository.Update(context.TODO(), &model.User{Online: true, ID: user.ID})
|
||||
return token, err
|
||||
}
|
||||
|
||||
func loginWithTotpEndpoint(c echo.Context) error {
|
||||
var loginAccount LoginAccount
|
||||
func (api AccountApi) LoginWithTotpEndpoint(c echo.Context) error {
|
||||
var loginAccount dto.LoginAccount
|
||||
if err := c.Bind(&loginAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 存储登录失败次数信息
|
||||
loginFailCountKey := c.RealIP() + loginAccount.Username
|
||||
v, ok := cache.GlobalCache.Get(loginFailCountKey)
|
||||
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
|
||||
if !ok {
|
||||
v = 1
|
||||
}
|
||||
@ -165,12 +117,12 @@ func loginWithTotpEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "登录失败次数过多,请等待5分钟后再试")
|
||||
}
|
||||
|
||||
user, err := userRepository.FindByUsername(loginAccount.Username)
|
||||
user, err := repository.UserRepository.FindByUsername(context.TODO(), loginAccount.Username)
|
||||
if err != nil {
|
||||
count++
|
||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||
@ -182,9 +134,9 @@ func loginWithTotpEndpoint(c echo.Context) error {
|
||||
|
||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||
count++
|
||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||
@ -192,42 +144,42 @@ func loginWithTotpEndpoint(c echo.Context) error {
|
||||
|
||||
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
|
||||
count++
|
||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Minute*time.Duration(5))
|
||||
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
|
||||
}
|
||||
|
||||
token, err := LoginSuccess(loginAccount, user)
|
||||
token, err := api.LoginSuccess(loginAccount, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 保存登录日志
|
||||
if err := SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, token)
|
||||
}
|
||||
|
||||
func LogoutEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) LogoutEndpoint(c echo.Context) error {
|
||||
token := GetToken(c)
|
||||
err := userService.LogoutByToken(token)
|
||||
err := service.UserService.LogoutByToken(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func ConfirmTOTPEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) ConfirmTOTPEndpoint(c echo.Context) error {
|
||||
if config.GlobalCfg.Demo {
|
||||
return Fail(c, 0, "演示模式禁止开启两步验证")
|
||||
}
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
var confirmTOTP ConfirmTOTP
|
||||
var confirmTOTP dto.ConfirmTOTP
|
||||
if err := c.Bind(&confirmTOTP); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -241,14 +193,14 @@ func ConfirmTOTPEndpoint(c echo.Context) error {
|
||||
ID: account.ID,
|
||||
}
|
||||
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func ReloadTOTPEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) ReloadTOTPEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
key, err := totp.NewTOTP(totp.GenerateOpts{
|
||||
@ -275,25 +227,25 @@ func ReloadTOTPEndpoint(c echo.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func ResetTOTPEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) ResetTOTPEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
u := &model.User{
|
||||
TOTPSecret: "-",
|
||||
ID: account.ID,
|
||||
}
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func ChangePasswordEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) ChangePasswordEndpoint(c echo.Context) error {
|
||||
if config.GlobalCfg.Demo {
|
||||
return Fail(c, 0, "演示模式禁止修改密码")
|
||||
}
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
var changePassword ChangePassword
|
||||
var changePassword dto.ChangePassword
|
||||
if err := c.Bind(&changePassword); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -311,11 +263,11 @@ func ChangePasswordEndpoint(c echo.Context) error {
|
||||
ID: account.ID,
|
||||
}
|
||||
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return LogoutEndpoint(c)
|
||||
return api.LogoutEndpoint(c)
|
||||
}
|
||||
|
||||
type AccountInfo struct {
|
||||
@ -326,10 +278,10 @@ type AccountInfo struct {
|
||||
EnableTotp bool `json:"enableTotp"`
|
||||
}
|
||||
|
||||
func InfoEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) InfoEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
user, err := userRepository.FindById(account.ID)
|
||||
user, err := repository.UserRepository.FindById(context.TODO(), account.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -344,7 +296,7 @@ func InfoEndpoint(c echo.Context) error {
|
||||
return Success(c, info)
|
||||
}
|
||||
|
||||
func AccountAssetEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) AccountAssetEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -359,26 +311,26 @@ func AccountAssetEndpoint(c echo.Context) error {
|
||||
field := c.QueryParam("field")
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
items, total, err := assetRepository.Find(pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
|
||||
items, total, err := repository.AssetRepository.Find(context.TODO(), pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func AccountStorageEndpoint(c echo.Context) error {
|
||||
func (api AccountApi) AccountStorageEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
storageId := account.ID
|
||||
storage, err := storageRepository.FindById(storageId)
|
||||
storage, err := repository.StorageRepository.FindById(context.TODO(), storageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structMap := utils.StructToMap(storage)
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
drivePath := service.StorageService.GetBaseDrivePath()
|
||||
dirSize, err := utils.DirSize(path.Join(drivePath, storageId))
|
||||
if err != nil {
|
||||
structMap["usedSize"] = -1
|
||||
@ -388,3 +340,20 @@ func AccountStorageEndpoint(c echo.Context) error {
|
||||
|
||||
return Success(c, structMap)
|
||||
}
|
||||
|
||||
func (api AccountApi) AccessTokenGetEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
accessToken, err := repository.AccessTokenRepository.FindByUserId(context.TODO(), account.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, accessToken)
|
||||
}
|
||||
|
||||
func (api AccountApi) AccessTokenGenEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
if err := service.AccessTokenService.GenAccessToken(account.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
@ -2,23 +2,24 @@ package api
|
||||
|
||||
import (
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/model"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type H map[string]interface{}
|
||||
type Map map[string]interface{}
|
||||
|
||||
func Fail(c echo.Context, code int, message string) error {
|
||||
return c.JSON(200, H{
|
||||
return c.JSON(200, Map{
|
||||
"code": code,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func FailWithData(c echo.Context, code int, message string, data interface{}) error {
|
||||
return c.JSON(200, H{
|
||||
return c.JSON(200, Map{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
@ -26,20 +27,13 @@ func FailWithData(c echo.Context, code int, message string, data interface{}) er
|
||||
}
|
||||
|
||||
func Success(c echo.Context, data interface{}) error {
|
||||
return c.JSON(200, H{
|
||||
return c.JSON(200, Map{
|
||||
"code": 1,
|
||||
"message": "success",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
func NotFound(c echo.Context, message string) error {
|
||||
return c.JSON(200, H{
|
||||
"code": -1,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func GetToken(c echo.Context) string {
|
||||
token := c.Request().Header.Get(constant.Token)
|
||||
if len(token) > 0 {
|
||||
@ -48,14 +42,13 @@ func GetToken(c echo.Context) string {
|
||||
return c.QueryParam(constant.Token)
|
||||
}
|
||||
|
||||
func GetCurrentAccount(c echo.Context) (model.User, bool) {
|
||||
func GetCurrentAccount(c echo.Context) (*model.User, bool) {
|
||||
token := GetToken(c)
|
||||
cacheKey := userService.BuildCacheKeyByToken(token)
|
||||
get, b := cache.GlobalCache.Get(cacheKey)
|
||||
get, b := cache.TokenManager.Get(token)
|
||||
if b {
|
||||
return get.(Authorization).User, true
|
||||
return get.(dto.Authorization).User, true
|
||||
}
|
||||
return model.User{}, false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func HasPermission(c echo.Context, owner string) bool {
|
||||
|
@ -2,58 +2,40 @@ package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func AssetCreateEndpoint(c echo.Context) error {
|
||||
type AssetApi struct{}
|
||||
|
||||
func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
|
||||
m := echo.Map{}
|
||||
if err := c.Bind(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(m)
|
||||
var item model.Asset
|
||||
if err := json.Unmarshal(data, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
item.Owner = account.ID
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
item.Active = true
|
||||
m["owner"] = account.ID
|
||||
|
||||
if err := assetRepository.Create(&item); err != nil {
|
||||
if _, err := service.AssetService.Create(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assetRepository.UpdateAttributes(item.ID, item.Protocol, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
active, _ := assetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||
|
||||
if item.Active != active {
|
||||
_ = assetRepository.UpdateActiveById(active, item.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
return Success(c, item)
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AssetImportEndpoint(c echo.Context) error {
|
||||
func (assetApi AssetApi) AssetImportEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
@ -66,7 +48,9 @@ func AssetImportEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer src.Close()
|
||||
defer func() {
|
||||
_ = src.Close()
|
||||
}()
|
||||
reader := csv.NewReader(bufio.NewReader(src))
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
@ -107,7 +91,7 @@ func AssetImportEndpoint(c echo.Context) error {
|
||||
asset.Tags = tags
|
||||
}
|
||||
|
||||
err := assetRepository.Create(&asset)
|
||||
err := repository.AssetRepository.Create(context.TODO(), &asset)
|
||||
if err != nil {
|
||||
errorCount++
|
||||
m[strconv.Itoa(i)] = err.Error()
|
||||
@ -124,7 +108,7 @@ func AssetImportEndpoint(c echo.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func AssetPagingEndpoint(c echo.Context) error {
|
||||
func (assetApi AssetApi) AssetPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -140,26 +124,26 @@ func AssetPagingEndpoint(c echo.Context) error {
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
items, total, err := assetRepository.Find(pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
|
||||
items, total, err := repository.AssetRepository.Find(context.TODO(), pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func AssetAllEndpoint(c echo.Context) error {
|
||||
func (assetApi AssetApi) AssetAllEndpoint(c echo.Context) error {
|
||||
protocol := c.QueryParam("protocol")
|
||||
items, _ := assetRepository.FindByProtocol(protocol)
|
||||
items, _ := repository.AssetRepository.FindByProtocol(context.TODO(), protocol)
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func AssetUpdateEndpoint(c echo.Context) error {
|
||||
func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := PreCheckAssetPermission(c, id); err != nil {
|
||||
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -167,67 +151,20 @@ func AssetUpdateEndpoint(c echo.Context) error {
|
||||
if err := c.Bind(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(m)
|
||||
var item model.Asset
|
||||
if err := json.Unmarshal(data, &item); err != nil {
|
||||
if err := service.AssetService.UpdateById(id, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch item.AccountType {
|
||||
case "credential":
|
||||
item.Username = "-"
|
||||
item.Password = "-"
|
||||
item.PrivateKey = "-"
|
||||
item.Passphrase = "-"
|
||||
case "private-key":
|
||||
item.Password = "-"
|
||||
item.CredentialId = "-"
|
||||
if len(item.Username) == 0 {
|
||||
item.Username = "-"
|
||||
}
|
||||
if len(item.Passphrase) == 0 {
|
||||
item.Passphrase = "-"
|
||||
}
|
||||
case "custom":
|
||||
item.PrivateKey = "-"
|
||||
item.Passphrase = "-"
|
||||
item.CredentialId = "-"
|
||||
}
|
||||
|
||||
if len(item.Tags) == 0 {
|
||||
item.Tags = "-"
|
||||
}
|
||||
|
||||
if item.Description == "" {
|
||||
item.Description = "-"
|
||||
}
|
||||
|
||||
if err := assetRepository.Encrypt(&item, config.GlobalCfg.EncryptionPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assetRepository.UpdateById(&item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assetRepository.UpdateAttributes(id, item.Protocol, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AssetDeleteEndpoint(c echo.Context) error {
|
||||
func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
if err := PreCheckAssetPermission(c, split[i]); err != nil {
|
||||
if err := assetApi.PreCheckAssetPermission(c, split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assetRepository.DeleteById(split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除资产与用户的关系
|
||||
if err := resourceSharerRepository.DeleteResourceSharerByResourceId(split[i]); err != nil {
|
||||
if err := service.AssetService.DeleteById(split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -235,17 +172,17 @@ func AssetDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AssetGetEndpoint(c echo.Context) (err error) {
|
||||
func (assetApi AssetApi) AssetGetEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
if err := PreCheckAssetPermission(c, id); err != nil {
|
||||
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var item model.Asset
|
||||
if item, err = assetRepository.FindByIdAndDecrypt(id); err != nil {
|
||||
if item, err = service.AssetService.FindByIdAndDecrypt(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
attributeMap, err := assetRepository.FindAssetAttrMapByAssetId(id)
|
||||
attributeMap, err := repository.AssetRepository.FindAssetAttrMapByAssetId(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -257,18 +194,18 @@ func AssetGetEndpoint(c echo.Context) (err error) {
|
||||
return Success(c, itemMap)
|
||||
}
|
||||
|
||||
func AssetTcpingEndpoint(c echo.Context) (err error) {
|
||||
func (assetApi AssetApi) AssetTcpingEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Asset
|
||||
if item, err = assetRepository.FindById(id); err != nil {
|
||||
if item, err = repository.AssetRepository.FindById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
active, err := assetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||
active, err := service.AssetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||
|
||||
if item.Active != active {
|
||||
if err := assetRepository.UpdateActiveById(active, item.ID); err != nil {
|
||||
if err := repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -278,36 +215,36 @@ func AssetTcpingEndpoint(c echo.Context) (err error) {
|
||||
message = err.Error()
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"active": active,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func AssetTagsEndpoint(c echo.Context) (err error) {
|
||||
func (assetApi AssetApi) AssetTagsEndpoint(c echo.Context) (err error) {
|
||||
var items []string
|
||||
if items, err = assetRepository.FindTags(); err != nil {
|
||||
if items, err = repository.AssetRepository.FindTags(context.TODO()); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func AssetChangeOwnerEndpoint(c echo.Context) (err error) {
|
||||
func (assetApi AssetApi) AssetChangeOwnerEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := PreCheckAssetPermission(c, id); err != nil {
|
||||
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner := c.QueryParam("owner")
|
||||
if err := assetRepository.UpdateById(&model.Asset{Owner: owner}, id); err != nil {
|
||||
if err := repository.AssetRepository.UpdateById(context.TODO(), &model.Asset{Owner: owner}, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func PreCheckAssetPermission(c echo.Context, id string) error {
|
||||
item, err := assetRepository.FindById(id)
|
||||
func (assetApi AssetApi) PreCheckAssetPermission(c echo.Context, id string) error {
|
||||
item, err := repository.AssetRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -5,133 +5,21 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/security"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/utils"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/service"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Backup struct {
|
||||
Users []model.User `json:"users"`
|
||||
UserGroups []model.UserGroup `json:"user_groups"`
|
||||
type BackupApi struct{}
|
||||
|
||||
Storages []model.Storage `json:"storages"`
|
||||
Strategies []model.Strategy `json:"strategies"`
|
||||
AccessSecurities []model.AccessSecurity `json:"access_securities"`
|
||||
AccessGateways []model.AccessGateway `json:"access_gateways"`
|
||||
Commands []model.Command `json:"commands"`
|
||||
Credentials []model.Credential `json:"credentials"`
|
||||
Assets []map[string]interface{} `json:"assets"`
|
||||
ResourceSharers []model.ResourceSharer `json:"resource_sharers"`
|
||||
Jobs []model.Job `json:"jobs"`
|
||||
}
|
||||
|
||||
func BackupExportEndpoint(c echo.Context) error {
|
||||
users, err := userRepository.FindAll()
|
||||
func (api BackupApi) BackupExportEndpoint(c echo.Context) error {
|
||||
err, backup := service.BackupService.Export()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
userGroups, err := userGroupRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(userGroups) > 0 {
|
||||
for i := range userGroups {
|
||||
members, err := userGroupRepository.FindMembersById(userGroups[i].ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userGroups[i].Members = members
|
||||
}
|
||||
}
|
||||
|
||||
storages, err := storageRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
strategies, err := strategyRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobs, err := jobRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessSecurities, err := accessSecurityRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessGateways, err := accessGatewayRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commands, err := commandRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
credentials, err := credentialRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(credentials) > 0 {
|
||||
for i := range credentials {
|
||||
if err := credentialRepository.Decrypt(&credentials[i], config.GlobalCfg.EncryptionPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
assets, err := assetRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var assetMaps = make([]map[string]interface{}, 0)
|
||||
if len(assets) > 0 {
|
||||
for i := range assets {
|
||||
asset := assets[i]
|
||||
if err := assetRepository.Decrypt(&asset, config.GlobalCfg.EncryptionPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
attributeMap, err := assetRepository.FindAssetAttrMapByAssetId(asset.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itemMap := utils.StructToMap(asset)
|
||||
for key := range attributeMap {
|
||||
itemMap[key] = attributeMap[key]
|
||||
}
|
||||
itemMap["created"] = asset.Created.Format("2006-01-02 15:04:05")
|
||||
assetMaps = append(assetMaps, itemMap)
|
||||
}
|
||||
}
|
||||
|
||||
resourceSharers, err := resourceSharerRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backup := Backup{
|
||||
Users: users,
|
||||
UserGroups: userGroups,
|
||||
Storages: storages,
|
||||
Strategies: strategies,
|
||||
Jobs: jobs,
|
||||
AccessSecurities: accessSecurities,
|
||||
AccessGateways: accessGateways,
|
||||
Commands: commands,
|
||||
Credentials: credentials,
|
||||
Assets: assetMaps,
|
||||
ResourceSharers: resourceSharers,
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(backup)
|
||||
if err != nil {
|
||||
@ -141,200 +29,13 @@ func BackupExportEndpoint(c echo.Context) error {
|
||||
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(jsonBytes))
|
||||
}
|
||||
|
||||
func BackupImportEndpoint(c echo.Context) error {
|
||||
var backup Backup
|
||||
func (api BackupApi) BackupImportEndpoint(c echo.Context) error {
|
||||
var backup dto.Backup
|
||||
if err := c.Bind(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var userIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Users) > 0 {
|
||||
for _, item := range backup.Users {
|
||||
if userRepository.ExistByUsername(item.Username) {
|
||||
continue
|
||||
}
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
item.Password = utils.GenPassword()
|
||||
if err := userRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
userIdMapping[oldId] = newId
|
||||
}
|
||||
if err := service.BackupService.Import(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var userGroupIdMapping = make(map[string]string, 0)
|
||||
if len(backup.UserGroups) > 0 {
|
||||
for _, item := range backup.UserGroups {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
|
||||
var members = make([]string, 0)
|
||||
if len(item.Members) > 0 {
|
||||
for _, member := range item.Members {
|
||||
members = append(members, userIdMapping[member])
|
||||
}
|
||||
}
|
||||
|
||||
if err := userGroupRepository.Create(&item, members); err != nil {
|
||||
return err
|
||||
}
|
||||
userGroupIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.Storages) > 0 {
|
||||
for _, item := range backup.Storages {
|
||||
item.ID = utils.UUID()
|
||||
item.Owner = userIdMapping[item.Owner]
|
||||
if err := storageRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var strategyIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Strategies) > 0 {
|
||||
for _, item := range backup.Strategies {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := strategyRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
strategyIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.AccessSecurities) > 0 {
|
||||
for _, item := range backup.AccessSecurities {
|
||||
item.ID = utils.UUID()
|
||||
if err := accessSecurityRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
rule := &security.Security{
|
||||
ID: item.ID,
|
||||
IP: item.IP,
|
||||
Rule: item.Rule,
|
||||
Priority: item.Priority,
|
||||
}
|
||||
security.GlobalSecurityManager.Add <- rule
|
||||
}
|
||||
}
|
||||
|
||||
var accessGatewayIdMapping = make(map[string]string, 0)
|
||||
if len(backup.AccessGateways) > 0 {
|
||||
for _, item := range backup.AccessGateways {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := accessGatewayRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
accessGatewayIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.Commands) > 0 {
|
||||
for _, item := range backup.Commands {
|
||||
item.ID = utils.UUID()
|
||||
if err := commandRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var credentialIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Credentials) > 0 {
|
||||
for _, item := range backup.Credentials {
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := credentialRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
credentialIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
var assetIdMapping = make(map[string]string, 0)
|
||||
if len(backup.Assets) > 0 {
|
||||
for _, m := range backup.Assets {
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var item model.Asset
|
||||
if err := json.Unmarshal(data, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if item.CredentialId != "" && item.CredentialId != "-" {
|
||||
item.CredentialId = credentialIdMapping[item.CredentialId]
|
||||
}
|
||||
if item.AccessGatewayId != "" && item.AccessGatewayId != "-" {
|
||||
item.AccessGatewayId = accessGatewayIdMapping[item.AccessGatewayId]
|
||||
}
|
||||
|
||||
oldId := item.ID
|
||||
newId := utils.UUID()
|
||||
item.ID = newId
|
||||
if err := assetRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assetRepository.UpdateAttributes(item.ID, item.Protocol, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
active, _ := assetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
|
||||
|
||||
if item.Active != active {
|
||||
_ = assetRepository.UpdateActiveById(active, item.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
assetIdMapping[oldId] = newId
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.ResourceSharers) > 0 {
|
||||
for _, item := range backup.ResourceSharers {
|
||||
|
||||
userGroupId := userGroupIdMapping[item.UserGroupId]
|
||||
userId := userIdMapping[item.UserId]
|
||||
strategyId := strategyIdMapping[item.StrategyId]
|
||||
resourceId := assetIdMapping[item.ResourceId]
|
||||
|
||||
if err := resourceSharerRepository.AddSharerResources(userGroupId, userId, strategyId, item.ResourceType, []string{resourceId}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(backup.Jobs) > 0 {
|
||||
for _, item := range backup.Jobs {
|
||||
if item.Func == constant.FuncCheckAssetStatusJob {
|
||||
continue
|
||||
}
|
||||
|
||||
resourceIds := strings.Split(item.ResourceIds, ",")
|
||||
if len(resourceIds) > 0 {
|
||||
var newResourceIds = make([]string, 0)
|
||||
for _, resourceId := range resourceIds {
|
||||
newResourceIds = append(newResourceIds, assetIdMapping[resourceId])
|
||||
}
|
||||
item.ResourceIds = strings.Join(newResourceIds, ",")
|
||||
}
|
||||
if err := jobService.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Success(c, "")
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func CommandCreateEndpoint(c echo.Context) error {
|
||||
type CommandApi struct{}
|
||||
|
||||
func (api CommandApi) CommandCreateEndpoint(c echo.Context) error {
|
||||
var item model.Command
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -22,20 +26,23 @@ func CommandCreateEndpoint(c echo.Context) error {
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := commandRepository.Create(&item); err != nil {
|
||||
if err := repository.CommandRepository.Create(context.TODO(), &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func CommandAllEndpoint(c echo.Context) error {
|
||||
func (api CommandApi) CommandAllEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
items, _ := commandRepository.FindByUser(account)
|
||||
items, err := repository.CommandRepository.FindByUser(context.TODO(), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func CommandPagingEndpoint(c echo.Context) error {
|
||||
func (api CommandApi) CommandPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -45,20 +52,20 @@ func CommandPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := commandRepository.Find(pageIndex, pageSize, name, content, order, field, account)
|
||||
items, total, err := repository.CommandRepository.Find(context.TODO(), pageIndex, pageSize, name, content, order, field, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func CommandUpdateEndpoint(c echo.Context) error {
|
||||
func (api CommandApi) CommandUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := PreCheckCommandPermission(c, id); err != nil {
|
||||
if err := api.PreCheckCommandPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -67,61 +74,57 @@ func CommandUpdateEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := commandRepository.UpdateById(&item, id); err != nil {
|
||||
if err := repository.CommandRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CommandDeleteEndpoint(c echo.Context) error {
|
||||
func (api CommandApi) CommandDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
if err := PreCheckCommandPermission(c, split[i]); err != nil {
|
||||
if err := api.PreCheckCommandPermission(c, split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := commandRepository.DeleteById(split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除资产与用户的关系
|
||||
if err := resourceSharerRepository.DeleteResourceSharerByResourceId(split[i]); err != nil {
|
||||
if err := repository.CommandRepository.DeleteById(context.TODO(), split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CommandGetEndpoint(c echo.Context) (err error) {
|
||||
func (api CommandApi) CommandGetEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := PreCheckCommandPermission(c, id); err != nil {
|
||||
if err := api.PreCheckCommandPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var item model.Command
|
||||
if item, err = commandRepository.FindById(id); err != nil {
|
||||
if item, err = repository.CommandRepository.FindById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func CommandChangeOwnerEndpoint(c echo.Context) (err error) {
|
||||
func (api CommandApi) CommandChangeOwnerEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := PreCheckCommandPermission(c, id); err != nil {
|
||||
if err := api.PreCheckCommandPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner := c.QueryParam("owner")
|
||||
if err := commandRepository.UpdateById(&model.Command{Owner: owner}, id); err != nil {
|
||||
if err := repository.CommandRepository.UpdateById(context.TODO(), &model.Command{Owner: owner}, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func PreCheckCommandPermission(c echo.Context, id string) error {
|
||||
item, err := commandRepository.FindById(id)
|
||||
func (api CommandApi) PreCheckCommandPermission(c echo.Context, id string) error {
|
||||
item, err := repository.CommandRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strconv"
|
||||
@ -9,17 +10,23 @@ import (
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func CredentialAllEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
items, _ := credentialRepository.FindByUser(account)
|
||||
type CredentialApi struct{}
|
||||
|
||||
func (api CredentialApi) CredentialAllEndpoint(c echo.Context) error {
|
||||
items, err := repository.CredentialRepository.FindByUser(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, items)
|
||||
}
|
||||
func CredentialCreateEndpoint(c echo.Context) error {
|
||||
func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
|
||||
var item model.Credential
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -56,14 +63,15 @@ func CredentialCreateEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
item.Encrypted = true
|
||||
if err := credentialRepository.Create(&item); err != nil {
|
||||
|
||||
if err := service.CredentialService.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func CredentialPagingEndpoint(c echo.Context) error {
|
||||
func (api CredentialApi) CredentialPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -72,21 +80,21 @@ func CredentialPagingEndpoint(c echo.Context) error {
|
||||
field := c.QueryParam("field")
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
items, total, err := credentialRepository.Find(pageIndex, pageSize, name, order, field, account)
|
||||
items, total, err := repository.CredentialRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func CredentialUpdateEndpoint(c echo.Context) error {
|
||||
func (api CredentialApi) CredentialUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := PreCheckCredentialPermission(c, id); err != nil {
|
||||
if err := api.PreCheckCredentialPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -142,25 +150,21 @@ func CredentialUpdateEndpoint(c echo.Context) error {
|
||||
}
|
||||
item.Encrypted = true
|
||||
|
||||
if err := credentialRepository.UpdateById(&item, id); err != nil {
|
||||
if err := repository.CredentialRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CredentialDeleteEndpoint(c echo.Context) error {
|
||||
func (api CredentialApi) CredentialDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
if err := PreCheckCredentialPermission(c, split[i]); err != nil {
|
||||
if err := api.PreCheckCredentialPermission(c, split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := credentialRepository.DeleteById(split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除资产与用户的关系
|
||||
if err := resourceSharerRepository.DeleteResourceSharerByResourceId(split[i]); err != nil {
|
||||
if err := repository.CredentialRepository.DeleteById(context.TODO(), split[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -168,13 +172,13 @@ func CredentialDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CredentialGetEndpoint(c echo.Context) error {
|
||||
func (api CredentialApi) CredentialGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := PreCheckCredentialPermission(c, id); err != nil {
|
||||
if err := api.PreCheckCredentialPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item, err := credentialRepository.FindByIdAndDecrypt(id)
|
||||
item, err := service.CredentialService.FindByIdAndDecrypt(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -186,22 +190,22 @@ func CredentialGetEndpoint(c echo.Context) error {
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func CredentialChangeOwnerEndpoint(c echo.Context) error {
|
||||
func (api CredentialApi) CredentialChangeOwnerEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := PreCheckCredentialPermission(c, id); err != nil {
|
||||
if err := api.PreCheckCredentialPermission(c, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner := c.QueryParam("owner")
|
||||
if err := credentialRepository.UpdateById(&model.Credential{Owner: owner}, id); err != nil {
|
||||
if err := repository.CredentialRepository.UpdateById(context.TODO(), &model.Credential{Owner: owner}, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func PreCheckCredentialPermission(c echo.Context, id string) error {
|
||||
item, err := credentialRepository.FindById(id)
|
||||
func (api CredentialApi) PreCheckCredentialPermission(c echo.Context, id string) error {
|
||||
item, err := repository.CredentialRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -2,11 +2,10 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
@ -14,6 +13,8 @@ import (
|
||||
"next-terminal/server/guacd"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@ -31,18 +32,27 @@ const (
|
||||
AssetNotActive int = 805
|
||||
)
|
||||
|
||||
func TunEndpoint(c echo.Context) error {
|
||||
var UpGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
Subprotocols: []string{"guacamole"},
|
||||
}
|
||||
|
||||
type GuacamoleApi struct {
|
||||
}
|
||||
|
||||
func (api GuacamoleApi) Guacamole(c echo.Context) error {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
log.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
width := c.QueryParam("width")
|
||||
height := c.QueryParam("height")
|
||||
dpi := c.QueryParam("dpi")
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
sessionId := c.Param("id")
|
||||
connectionId := c.QueryParam("connectionId")
|
||||
|
||||
intWidth, _ := strconv.Atoi(width)
|
||||
@ -50,12 +60,12 @@ func TunEndpoint(c echo.Context) error {
|
||||
|
||||
configuration := guacd.NewConfiguration()
|
||||
|
||||
propertyMap := propertyRepository.FindAllMap()
|
||||
propertyMap := repository.PropertyRepository.FindAllMap(ctx)
|
||||
|
||||
var s model.Session
|
||||
|
||||
if len(connectionId) > 0 {
|
||||
s, err = sessionRepository.FindByConnectionId(connectionId)
|
||||
s, err = repository.SessionRepository.FindByConnectionId(ctx, connectionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -71,28 +81,28 @@ func TunEndpoint(c echo.Context) error {
|
||||
configuration.SetParameter("width", width)
|
||||
configuration.SetParameter("height", height)
|
||||
configuration.SetParameter("dpi", dpi)
|
||||
s, err = sessionRepository.FindByIdAndDecrypt(sessionId)
|
||||
s, err = service.SessionService.FindByIdAndDecrypt(ctx, sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setConfig(propertyMap, s, configuration)
|
||||
api.setConfig(propertyMap, s, configuration)
|
||||
var (
|
||||
ip = s.IP
|
||||
port = s.Port
|
||||
)
|
||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||
g, err := accessGatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
||||
g, err := service.GatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
||||
if err != nil {
|
||||
disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
|
||||
utils.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
|
||||
return nil
|
||||
}
|
||||
if !g.Connected {
|
||||
disconnect(ws, AccessGatewayUnAvailable, "接入网关不可用:"+g.Message)
|
||||
utils.Disconnect(ws, AccessGatewayUnAvailable, "接入网关不可用:"+g.Message)
|
||||
return nil
|
||||
}
|
||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
||||
if err != nil {
|
||||
disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败:"+err.Error())
|
||||
utils.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败:"+err.Error())
|
||||
return nil
|
||||
}
|
||||
defer g.CloseSshTunnel(s.ID)
|
||||
@ -101,7 +111,7 @@ func TunEndpoint(c echo.Context) error {
|
||||
}
|
||||
active, err := utils.Tcping(ip, port)
|
||||
if !active {
|
||||
disconnect(ws, AssetNotActive, "目标资产不在线: "+err.Error())
|
||||
utils.Disconnect(ws, AssetNotActive, "目标资产不在线: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -109,12 +119,12 @@ func TunEndpoint(c echo.Context) error {
|
||||
configuration.SetParameter("port", strconv.Itoa(port))
|
||||
|
||||
// 加载资产配置的属性,优先级比全局配置的高,因此最后加载,覆盖掉全局配置
|
||||
attributes, err := assetRepository.FindAssetAttrMapByAssetId(s.AssetId)
|
||||
attributes, err := repository.AssetRepository.FindAssetAttrMapByAssetId(ctx, s.AssetId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(attributes) > 0 {
|
||||
setAssetConfig(attributes, s, configuration)
|
||||
api.setAssetConfig(attributes, s, configuration)
|
||||
}
|
||||
}
|
||||
for name := range configuration.Parameters {
|
||||
@ -130,7 +140,7 @@ func TunEndpoint(c echo.Context) error {
|
||||
guacdTunnel, err := guacd.NewTunnel(addr, configuration)
|
||||
if err != nil {
|
||||
if connectionId == "" {
|
||||
disconnect(ws, NewTunnelError, err.Error())
|
||||
utils.Disconnect(ws, NewTunnelError, err.Error())
|
||||
}
|
||||
log.Printf("[%v:%v] 建立连接失败: %v", sessionId, connectionId, err.Error())
|
||||
return err
|
||||
@ -144,7 +154,7 @@ func TunEndpoint(c echo.Context) error {
|
||||
GuacdTunnel: guacdTunnel,
|
||||
}
|
||||
|
||||
if len(s.ConnectionId) == 0 {
|
||||
if connectionId == "" {
|
||||
if configuration.Protocol == constant.SSH {
|
||||
nextTerminal, err := CreateNextTerminalBySession(s)
|
||||
if err == nil {
|
||||
@ -168,14 +178,14 @@ func TunEndpoint(c echo.Context) error {
|
||||
}
|
||||
// 创建新会话
|
||||
log.Debugf("[%v:%v] 创建新会话: %v", sessionId, connectionId, sess.ConnectionId)
|
||||
if err := sessionRepository.UpdateById(&sess, sessionId); err != nil {
|
||||
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 要监控会话
|
||||
forObsSession := session.GlobalSessionManager.GetById(sessionId)
|
||||
if forObsSession == nil {
|
||||
disconnect(ws, NotFoundSession, "获取会话失败")
|
||||
utils.Disconnect(ws, NotFoundSession, "获取会话失败")
|
||||
return nil
|
||||
}
|
||||
nextSession.ID = utils.UUID()
|
||||
@ -183,56 +193,8 @@ func TunEndpoint(c echo.Context) error {
|
||||
log.Debugf("[%v:%v] 观察者[%v]加入会话[%v]", sessionId, connectionId, nextSession.ID, s.ConnectionId)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tick := time.NewTicker(time.Millisecond * time.Duration(60))
|
||||
defer tick.Stop()
|
||||
var buf []byte
|
||||
dataChan := make(chan []byte)
|
||||
|
||||
go func() {
|
||||
GuacdLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Debugf("[%v:%v] WebSocket 已关闭,即将关闭 Guacd 连接...", sessionId, connectionId)
|
||||
break GuacdLoop
|
||||
default:
|
||||
instruction, err := guacdTunnel.Read()
|
||||
if err != nil {
|
||||
log.Debugf("[%v:%v] Guacd 读取失败,即将退出循环...", sessionId, connectionId)
|
||||
disconnect(ws, TunnelClosed, "远程连接已关闭")
|
||||
break GuacdLoop
|
||||
}
|
||||
if len(instruction) == 0 {
|
||||
continue
|
||||
}
|
||||
dataChan <- instruction
|
||||
}
|
||||
}
|
||||
log.Debugf("[%v:%v] Guacd 连接已关闭,退出 Guacd 循环。", sessionId, connectionId)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
tickLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break tickLoop
|
||||
case <-tick.C:
|
||||
if len(buf) > 0 {
|
||||
err = ws.WriteMessage(websocket.TextMessage, buf)
|
||||
if err != nil {
|
||||
log.Debugf("[%v:%v] WebSocket写入失败,即将关闭Guacd连接...", sessionId, connectionId)
|
||||
break tickLoop
|
||||
}
|
||||
buf = []byte{}
|
||||
}
|
||||
case data := <-dataChan:
|
||||
buf = append(buf, data...)
|
||||
}
|
||||
}
|
||||
log.Debugf("[%v:%v] Guacd连接已关闭,退出定时器循环。", sessionId, connectionId)
|
||||
}()
|
||||
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
|
||||
guacamoleHandler.Start()
|
||||
|
||||
for {
|
||||
_, message, err := ws.ReadMessage()
|
||||
@ -250,20 +212,20 @@ func TunEndpoint(c echo.Context) error {
|
||||
log.Debugf("[%v:%v] 观察者[%v]退出会话", sessionId, connectionId, observerId)
|
||||
}
|
||||
} else {
|
||||
CloseSessionById(sessionId, Normal, "用户正常退出")
|
||||
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
|
||||
}
|
||||
cancel()
|
||||
break
|
||||
guacamoleHandler.Stop()
|
||||
return nil
|
||||
}
|
||||
_, err = guacdTunnel.WriteAndFlush(message)
|
||||
if err != nil {
|
||||
CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAssetConfig(attributes map[string]string, s model.Session, configuration *guacd.Configuration) {
|
||||
func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Session, configuration *guacd.Configuration) {
|
||||
for key, value := range attributes {
|
||||
if guacd.DrivePath == key {
|
||||
// 忽略该参数
|
||||
@ -275,7 +237,7 @@ func setAssetConfig(attributes map[string]string, s model.Session, configuration
|
||||
// 默认空间ID和用户ID相同
|
||||
storageId = s.Creator
|
||||
}
|
||||
realPath := path.Join(storageService.GetBaseDrivePath(), storageId)
|
||||
realPath := path.Join(service.StorageService.GetBaseDrivePath(), storageId)
|
||||
configuration.SetParameter(guacd.EnableDrive, "true")
|
||||
configuration.SetParameter(guacd.DriveName, "Next Terminal Filesystem")
|
||||
configuration.SetParameter(guacd.DrivePath, realPath)
|
||||
@ -286,7 +248,7 @@ func setAssetConfig(attributes map[string]string, s model.Session, configuration
|
||||
}
|
||||
}
|
||||
|
||||
func setConfig(propertyMap map[string]string, s model.Session, configuration *guacd.Configuration) {
|
||||
func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session, configuration *guacd.Configuration) {
|
||||
if propertyMap[guacd.EnableRecording] == "true" {
|
||||
configuration.SetParameter(guacd.RecordingPath, path.Join(config.GlobalCfg.Guacd.Recording, s.ID))
|
||||
configuration.SetParameter(guacd.CreateRecordingPath, "true")
|
||||
@ -312,7 +274,8 @@ func setConfig(propertyMap map[string]string, s model.Session, configuration *gu
|
||||
configuration.SetParameter(guacd.EnableMenuAnimations, propertyMap[guacd.EnableMenuAnimations])
|
||||
configuration.SetParameter(guacd.DisableBitmapCaching, propertyMap[guacd.DisableBitmapCaching])
|
||||
configuration.SetParameter(guacd.DisableOffscreenCaching, propertyMap[guacd.DisableOffscreenCaching])
|
||||
configuration.SetParameter(guacd.DisableGlyphCaching, propertyMap[guacd.DisableGlyphCaching])
|
||||
configuration.SetParameter(guacd.ColorDepth, propertyMap[guacd.ColorDepth])
|
||||
configuration.SetParameter(guacd.ForceLossless, propertyMap[guacd.ForceLossless])
|
||||
case "ssh":
|
||||
if len(s.PrivateKey) > 0 && s.PrivateKey != "-" {
|
||||
configuration.SetParameter("username", s.Username)
|
||||
@ -350,12 +313,3 @@ func setConfig(propertyMap map[string]string, s model.Session, configuration *gu
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(ws *websocket.Conn, code int, reason string) {
|
||||
// guacd 无法处理中文字符,所以进行了base64编码。
|
||||
encodeReason := base64.StdEncoding.EncodeToString([]byte(reason))
|
||||
err := guacd.NewInstruction("error", encodeReason, strconv.Itoa(code))
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.String()))
|
||||
disconnect := guacd.NewInstruction("disconnect")
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(disconnect.String()))
|
||||
}
|
84
server/api/guacamole_handler.go
Normal file
84
server/api/guacamole_handler.go
Normal file
@ -0,0 +1,84 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/guacd"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type GuacamoleHandler struct {
|
||||
ws *websocket.Conn
|
||||
tunnel *guacd.Tunnel
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
dataChan chan []byte
|
||||
tick *time.Ticker
|
||||
}
|
||||
|
||||
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacd.Tunnel) *GuacamoleHandler {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tick := time.NewTicker(time.Millisecond * time.Duration(60))
|
||||
return &GuacamoleHandler{
|
||||
ws: ws,
|
||||
tunnel: tunnel,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
dataChan: make(chan []byte),
|
||||
tick: tick,
|
||||
}
|
||||
}
|
||||
|
||||
func (r GuacamoleHandler) Start() {
|
||||
go r.readFormTunnel()
|
||||
go r.writeToWebsocket()
|
||||
}
|
||||
|
||||
func (r GuacamoleHandler) Stop() {
|
||||
r.tick.Stop()
|
||||
r.cancel()
|
||||
}
|
||||
|
||||
func (r GuacamoleHandler) readFormTunnel() {
|
||||
for {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
default:
|
||||
instruction, err := r.tunnel.Read()
|
||||
if err != nil {
|
||||
utils.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
|
||||
return
|
||||
}
|
||||
if len(instruction) == 0 {
|
||||
continue
|
||||
}
|
||||
r.dataChan <- instruction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r GuacamoleHandler) writeToWebsocket() {
|
||||
var buf []byte
|
||||
for {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
case <-r.tick.C:
|
||||
if len(buf) > 0 {
|
||||
err := r.ws.WriteMessage(websocket.TextMessage, buf)
|
||||
if err != nil {
|
||||
log.Debugf("WebSocket写入失败,即将关闭Guacd连接...")
|
||||
return
|
||||
}
|
||||
buf = []byte{}
|
||||
}
|
||||
case data := <-r.dataChan:
|
||||
buf = append(buf, data...)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func JobCreateEndpoint(c echo.Context) error {
|
||||
type JobApi struct{}
|
||||
|
||||
func (api JobApi) JobCreateEndpoint(c echo.Context) error {
|
||||
var item model.Job
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -19,13 +25,13 @@ func JobCreateEndpoint(c echo.Context) error {
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := jobService.Create(&item); err != nil {
|
||||
if err := service.JobService.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func JobPagingEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -34,18 +40,18 @@ func JobPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := jobRepository.Find(pageIndex, pageSize, name, status, order, field)
|
||||
items, total, err := repository.JobRepository.Find(context.TODO(), pageIndex, pageSize, name, status, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func JobUpdateEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Job
|
||||
@ -53,37 +59,37 @@ func JobUpdateEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
item.ID = id
|
||||
if err := jobService.UpdateById(&item); err != nil {
|
||||
if err := service.JobService.UpdateById(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func JobChangeStatusEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobChangeStatusEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
status := c.QueryParam("status")
|
||||
if err := jobService.ChangeStatusById(id, status); err != nil {
|
||||
if err := service.JobService.ChangeStatusById(id, status); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func JobExecEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobExecEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := jobService.ExecJobById(id); err != nil {
|
||||
if err := service.JobService.ExecJobById(id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func JobDeleteEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
jobId := split[i]
|
||||
if err := jobService.DeleteJobById(jobId); err != nil {
|
||||
if err := service.JobService.DeleteJobById(jobId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -91,10 +97,10 @@ func JobDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func JobGetEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := jobRepository.FindById(id)
|
||||
item, err := repository.JobRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -102,10 +108,10 @@ func JobGetEndpoint(c echo.Context) error {
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func JobGetLogsEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobGetLogsEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
items, err := jobLogRepository.FindByJobId(id)
|
||||
items, err := repository.JobLogRepository.FindByJobId(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -113,9 +119,9 @@ func JobGetLogsEndpoint(c echo.Context) error {
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func JobDeleteLogsEndpoint(c echo.Context) error {
|
||||
func (api JobApi) JobDeleteLogsEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := jobLogRepository.DeleteByJobId(id); err != nil {
|
||||
if err := repository.JobLogRepository.DeleteByJobId(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
|
@ -1,43 +1,49 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func LoginLogPagingEndpoint(c echo.Context) error {
|
||||
type LoginLogApi struct{}
|
||||
|
||||
func (api LoginLogApi) LoginLogPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
username := c.QueryParam("username")
|
||||
clientIp := c.QueryParam("clientIp")
|
||||
state := c.QueryParam("state")
|
||||
|
||||
items, total, err := loginLogRepository.Find(pageIndex, pageSize, username, clientIp, state)
|
||||
items, total, err := repository.LoginLogRepository.Find(context.TODO(), pageIndex, pageSize, username, clientIp, state)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func LoginLogDeleteEndpoint(c echo.Context) error {
|
||||
func (api LoginLogApi) LoginLogDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
tokens := strings.Split(ids, ",")
|
||||
if err := userService.DeleteLoginLogs(tokens); err != nil {
|
||||
if err := service.UserService.DeleteLoginLogs(tokens); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func LoginLogClearEndpoint(c echo.Context) error {
|
||||
loginLogs, err := loginLogRepository.FindAllLoginLogs()
|
||||
func (api LoginLogApi) LoginLogClearEndpoint(c echo.Context) error {
|
||||
loginLogs, err := repository.LoginLogRepository.FindAllLoginLogs(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -46,7 +52,7 @@ func LoginLogClearEndpoint(c echo.Context) error {
|
||||
tokens = append(tokens, loginLogs[i].ID)
|
||||
}
|
||||
|
||||
if err := userService.DeleteLoginLogs(tokens); err != nil {
|
||||
if err := service.UserService.DeleteLoginLogs(tokens); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
|
@ -1,140 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/global/security"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
||||
if err := next(c); err != nil {
|
||||
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
message := fmt.Sprintf("%v", he.Message)
|
||||
return Fail(c, he.Code, message)
|
||||
}
|
||||
|
||||
return Fail(c, 0, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
|
||||
return func(c echo.Context) error {
|
||||
securities := security.GlobalSecurityManager.Values()
|
||||
if len(securities) == 0 {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
ip := c.RealIP()
|
||||
|
||||
for _, s := range securities {
|
||||
if strings.Contains(s.IP, "/") {
|
||||
// CIDR
|
||||
_, ipNet, err := net.ParseCIDR(s.IP)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !ipNet.Contains(net.ParseIP(ip)) {
|
||||
continue
|
||||
}
|
||||
} else if strings.Contains(s.IP, "-") {
|
||||
// 范围段
|
||||
split := strings.Split(s.IP, "-")
|
||||
if len(split) < 2 {
|
||||
continue
|
||||
}
|
||||
start := split[0]
|
||||
end := split[1]
|
||||
intReqIP := utils.IpToInt(ip)
|
||||
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// IP
|
||||
if s.IP != ip {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if s.Rule == constant.AccessRuleAllow {
|
||||
return next(c)
|
||||
}
|
||||
if s.Rule == constant.AccessRuleReject {
|
||||
if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(constant.Token) != "" {
|
||||
return Fail(c, 0, "您的访问请求被拒绝 :(")
|
||||
} else {
|
||||
return c.HTML(666, "您的访问请求被拒绝 :(")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
|
||||
anonymousUrls := []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
|
||||
uri := c.Request().RequestURI
|
||||
if uri == "/" || strings.HasPrefix(uri, "/#") {
|
||||
return next(c)
|
||||
}
|
||||
// 路由拦截 - 登录身份、资源权限判断等
|
||||
for i := range anonymousUrls {
|
||||
if strings.HasPrefix(uri, anonymousUrls[i]) {
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
token := GetToken(c)
|
||||
if token == "" {
|
||||
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
|
||||
}
|
||||
cacheKey := userService.BuildCacheKeyByToken(token)
|
||||
authorization, found := cache.GlobalCache.Get(cacheKey)
|
||||
if !found {
|
||||
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
|
||||
}
|
||||
|
||||
if authorization.(Authorization).Remember {
|
||||
// 记住登录有效期两周
|
||||
cache.GlobalCache.Set(cacheKey, authorization, time.Hour*time.Duration(24*14))
|
||||
} else {
|
||||
cache.GlobalCache.Set(cacheKey, authorization, time.Hour*time.Duration(2))
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func Admin(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
||||
account, found := GetCurrentAccount(c)
|
||||
if !found {
|
||||
return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
|
||||
}
|
||||
|
||||
if account.Type != constant.TypeAdmin {
|
||||
return Fail(c, 403, "permission denied")
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
@ -1,39 +1,30 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/repository"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Counter struct {
|
||||
User int64 `json:"user"`
|
||||
Asset int64 `json:"asset"`
|
||||
Credential int64 `json:"credential"`
|
||||
OnlineSession int64 `json:"onlineSession"`
|
||||
}
|
||||
|
||||
func OverviewCounterEndPoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
type OverviewApi struct{}
|
||||
|
||||
func (api OverviewApi) OverviewCounterEndPoint(c echo.Context) error {
|
||||
var (
|
||||
countUser int64
|
||||
countOnlineSession int64
|
||||
credential int64
|
||||
asset int64
|
||||
)
|
||||
if constant.TypeUser == account.Type {
|
||||
countUser, _ = userRepository.CountOnlineUser()
|
||||
countOnlineSession, _ = sessionRepository.CountOnlineSession()
|
||||
credential, _ = credentialRepository.CountByUserId(account.ID)
|
||||
asset, _ = assetRepository.CountByUserId(account.ID)
|
||||
} else {
|
||||
countUser, _ = userRepository.CountOnlineUser()
|
||||
countOnlineSession, _ = sessionRepository.CountOnlineSession()
|
||||
credential, _ = credentialRepository.Count()
|
||||
asset, _ = assetRepository.Count()
|
||||
}
|
||||
counter := Counter{
|
||||
countUser, _ = repository.UserRepository.CountOnlineUser(context.TODO())
|
||||
countOnlineSession, _ = repository.SessionRepository.CountOnlineSession(context.TODO())
|
||||
credential, _ = repository.CredentialRepository.Count(context.TODO())
|
||||
asset, _ = repository.AssetRepository.Count(context.TODO())
|
||||
|
||||
counter := dto.Counter{
|
||||
User: countUser,
|
||||
OnlineSession: countOnlineSession,
|
||||
Credential: credential,
|
||||
@ -43,8 +34,7 @@ func OverviewCounterEndPoint(c echo.Context) error {
|
||||
return Success(c, counter)
|
||||
}
|
||||
|
||||
func OverviewAssetEndPoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
func (api OverviewApi) OverviewAssetEndPoint(c echo.Context) error {
|
||||
var (
|
||||
ssh int64
|
||||
rdp int64
|
||||
@ -52,19 +42,13 @@ func OverviewAssetEndPoint(c echo.Context) error {
|
||||
telnet int64
|
||||
kubernetes int64
|
||||
)
|
||||
if constant.TypeUser == account.Type {
|
||||
ssh, _ = assetRepository.CountByUserIdAndProtocol(account.ID, constant.SSH)
|
||||
rdp, _ = assetRepository.CountByUserIdAndProtocol(account.ID, constant.RDP)
|
||||
vnc, _ = assetRepository.CountByUserIdAndProtocol(account.ID, constant.VNC)
|
||||
telnet, _ = assetRepository.CountByUserIdAndProtocol(account.ID, constant.Telnet)
|
||||
kubernetes, _ = assetRepository.CountByUserIdAndProtocol(account.ID, constant.K8s)
|
||||
} else {
|
||||
ssh, _ = assetRepository.CountByProtocol(constant.SSH)
|
||||
rdp, _ = assetRepository.CountByProtocol(constant.RDP)
|
||||
vnc, _ = assetRepository.CountByProtocol(constant.VNC)
|
||||
telnet, _ = assetRepository.CountByProtocol(constant.Telnet)
|
||||
kubernetes, _ = assetRepository.CountByProtocol(constant.K8s)
|
||||
}
|
||||
|
||||
ssh, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.SSH)
|
||||
rdp, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.RDP)
|
||||
vnc, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.VNC)
|
||||
telnet, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.Telnet)
|
||||
kubernetes, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.K8s)
|
||||
|
||||
m := echo.Map{
|
||||
"ssh": ssh,
|
||||
"rdp": rdp,
|
||||
@ -75,9 +59,8 @@ func OverviewAssetEndPoint(c echo.Context) error {
|
||||
return Success(c, m)
|
||||
}
|
||||
|
||||
func OverviewAccessEndPoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
access, err := sessionRepository.OverviewAccess(account)
|
||||
func (api OverviewApi) OverviewAccessEndPoint(c echo.Context) error {
|
||||
access, err := repository.SessionRepository.OverviewAccess(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,47 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"context"
|
||||
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func PropertyGetEndpoint(c echo.Context) error {
|
||||
properties := propertyRepository.FindAllMap()
|
||||
type PropertyApi struct{}
|
||||
|
||||
func (api PropertyApi) PropertyGetEndpoint(c echo.Context) error {
|
||||
properties := repository.PropertyRepository.FindAllMap(context.TODO())
|
||||
return Success(c, properties)
|
||||
}
|
||||
|
||||
func PropertyUpdateEndpoint(c echo.Context) error {
|
||||
func (api PropertyApi) PropertyUpdateEndpoint(c echo.Context) error {
|
||||
var item map[string]interface{}
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key := range item {
|
||||
value := fmt.Sprintf("%v", item[key])
|
||||
if value == "" {
|
||||
value = "-"
|
||||
}
|
||||
|
||||
property := model.Property{
|
||||
Name: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
_, err := propertyRepository.FindByName(key)
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err := propertyRepository.Create(&property); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := propertyRepository.UpdateByName(&property, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := service.PropertyService.Update(item); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
@ -1,55 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/repository"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type RU struct {
|
||||
UserGroupId string `json:"userGroupId"`
|
||||
UserId string `json:"userId"`
|
||||
StrategyId string `json:"strategyId"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
ResourceIds []string `json:"resourceIds"`
|
||||
}
|
||||
type ResourceSharerApi struct{}
|
||||
|
||||
type UR struct {
|
||||
ResourceId string `json:"resourceId"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
UserIds []string `json:"userIds"`
|
||||
}
|
||||
|
||||
func RSGetSharersEndPoint(c echo.Context) error {
|
||||
func (api ResourceSharerApi) RSGetSharersEndPoint(c echo.Context) error {
|
||||
resourceId := c.QueryParam("resourceId")
|
||||
resourceType := c.QueryParam("resourceType")
|
||||
userId := c.QueryParam("userId")
|
||||
userGroupId := c.QueryParam("userGroupId")
|
||||
userIds, err := resourceSharerRepository.Find(resourceId, resourceType, userId, userGroupId)
|
||||
userIds, err := repository.ResourceSharerRepository.Find(context.TODO(), resourceId, resourceType, userId, userGroupId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, userIds)
|
||||
}
|
||||
|
||||
func ResourceRemoveByUserIdAssignEndPoint(c echo.Context) error {
|
||||
var ru RU
|
||||
func (api ResourceSharerApi) ResourceRemoveByUserIdAssignEndPoint(c echo.Context) error {
|
||||
var ru dto.RU
|
||||
if err := c.Bind(&ru); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := resourceSharerRepository.DeleteByUserIdAndResourceTypeAndResourceIdIn(ru.UserGroupId, ru.UserId, ru.ResourceType, ru.ResourceIds); err != nil {
|
||||
if err := repository.ResourceSharerRepository.DeleteByUserIdAndResourceTypeAndResourceIdIn(context.TODO(), ru.UserGroupId, ru.UserId, ru.ResourceType, ru.ResourceIds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func ResourceAddByUserIdAssignEndPoint(c echo.Context) error {
|
||||
var ru RU
|
||||
func (api ResourceSharerApi) ResourceAddByUserIdAssignEndPoint(c echo.Context) error {
|
||||
var ru dto.RU
|
||||
if err := c.Bind(&ru); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := resourceSharerRepository.AddSharerResources(ru.UserGroupId, ru.UserId, ru.StrategyId, ru.ResourceType, ru.ResourceIds); err != nil {
|
||||
if err := repository.ResourceSharerRepository.AddSharerResources(ru.UserGroupId, ru.UserId, ru.StrategyId, ru.ResourceType, ru.ResourceIds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1,484 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
userRepository *repository.UserRepository
|
||||
userGroupRepository *repository.UserGroupRepository
|
||||
resourceSharerRepository *repository.ResourceSharerRepository
|
||||
assetRepository *repository.AssetRepository
|
||||
credentialRepository *repository.CredentialRepository
|
||||
propertyRepository *repository.PropertyRepository
|
||||
commandRepository *repository.CommandRepository
|
||||
sessionRepository *repository.SessionRepository
|
||||
accessSecurityRepository *repository.AccessSecurityRepository
|
||||
accessGatewayRepository *repository.AccessGatewayRepository
|
||||
jobRepository *repository.JobRepository
|
||||
jobLogRepository *repository.JobLogRepository
|
||||
loginLogRepository *repository.LoginLogRepository
|
||||
storageRepository *repository.StorageRepository
|
||||
strategyRepository *repository.StrategyRepository
|
||||
|
||||
jobService *service.JobService
|
||||
propertyService *service.PropertyService
|
||||
userService *service.UserService
|
||||
sessionService *service.SessionService
|
||||
mailService *service.MailService
|
||||
assetService *service.AssetService
|
||||
credentialService *service.CredentialService
|
||||
storageService *service.StorageService
|
||||
accessGatewayService *service.AccessGatewayService
|
||||
)
|
||||
|
||||
func SetupRoutes(db *gorm.DB) *echo.Echo {
|
||||
|
||||
InitRepository(db)
|
||||
InitService()
|
||||
|
||||
cache.GlobalCache.OnEvicted(userService.OnEvicted)
|
||||
|
||||
if err := InitDBData(); err != nil {
|
||||
log.Errorf("初始化数据异常: %v", err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if err := ReloadData(); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := echo.New()
|
||||
e.HideBanner = true
|
||||
//e.Logger = log.GetEchoLogger()
|
||||
//e.Use(log.Hook())
|
||||
e.File("/", "web/build/index.html")
|
||||
e.File("/asciinema.html", "web/build/asciinema.html")
|
||||
e.File("/", "web/build/index.html")
|
||||
e.File("/favicon.ico", "web/build/favicon.ico")
|
||||
e.Static("/static", "web/build/static")
|
||||
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
Skipper: middleware.DefaultSkipper,
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
|
||||
}))
|
||||
e.Use(ErrorHandler)
|
||||
e.Use(TcpWall)
|
||||
e.Use(Auth)
|
||||
|
||||
e.POST("/login", LoginEndpoint)
|
||||
e.POST("/loginWithTotp", loginWithTotpEndpoint)
|
||||
|
||||
e.GET("/tunnel", TunEndpoint)
|
||||
e.GET("/ssh", SSHEndpoint)
|
||||
e.GET("/ssh-monitor", SshMonitor)
|
||||
e.POST("/logout", LogoutEndpoint)
|
||||
e.POST("/change-password", ChangePasswordEndpoint)
|
||||
e.GET("/reload-totp", ReloadTOTPEndpoint)
|
||||
e.POST("/reset-totp", ResetTOTPEndpoint)
|
||||
e.POST("/confirm-totp", ConfirmTOTPEndpoint)
|
||||
e.GET("/info", InfoEndpoint)
|
||||
|
||||
account := e.Group("/account")
|
||||
{
|
||||
account.GET("/assets", AccountAssetEndpoint)
|
||||
account.GET("/storage", AccountStorageEndpoint)
|
||||
}
|
||||
|
||||
users := e.Group("/users", Admin)
|
||||
{
|
||||
users.POST("", UserCreateEndpoint)
|
||||
users.GET("/paging", UserPagingEndpoint)
|
||||
users.PUT("/:id", UserUpdateEndpoint)
|
||||
users.PATCH("/:id/status", UserUpdateStatusEndpoint)
|
||||
users.DELETE("/:id", UserDeleteEndpoint)
|
||||
users.GET("/:id", UserGetEndpoint)
|
||||
users.POST("/:id/change-password", UserChangePasswordEndpoint)
|
||||
users.POST("/:id/reset-totp", UserResetTotpEndpoint)
|
||||
}
|
||||
|
||||
userGroups := e.Group("/user-groups", Admin)
|
||||
{
|
||||
userGroups.POST("", UserGroupCreateEndpoint)
|
||||
userGroups.GET("/paging", UserGroupPagingEndpoint)
|
||||
userGroups.PUT("/:id", UserGroupUpdateEndpoint)
|
||||
userGroups.DELETE("/:id", UserGroupDeleteEndpoint)
|
||||
userGroups.GET("/:id", UserGroupGetEndpoint)
|
||||
}
|
||||
|
||||
assets := e.Group("/assets", Admin)
|
||||
{
|
||||
assets.GET("", AssetAllEndpoint)
|
||||
assets.POST("", AssetCreateEndpoint)
|
||||
assets.POST("/import", AssetImportEndpoint)
|
||||
assets.GET("/paging", AssetPagingEndpoint)
|
||||
assets.POST("/:id/tcping", AssetTcpingEndpoint)
|
||||
assets.PUT("/:id", AssetUpdateEndpoint)
|
||||
assets.GET("/:id", AssetGetEndpoint)
|
||||
assets.DELETE("/:id", AssetDeleteEndpoint)
|
||||
assets.POST("/:id/change-owner", AssetChangeOwnerEndpoint)
|
||||
}
|
||||
|
||||
e.GET("/tags", AssetTagsEndpoint)
|
||||
|
||||
commands := e.Group("/commands")
|
||||
{
|
||||
commands.GET("", CommandAllEndpoint)
|
||||
commands.GET("/paging", CommandPagingEndpoint)
|
||||
commands.POST("", CommandCreateEndpoint)
|
||||
commands.PUT("/:id", CommandUpdateEndpoint)
|
||||
commands.DELETE("/:id", CommandDeleteEndpoint)
|
||||
commands.GET("/:id", CommandGetEndpoint)
|
||||
commands.POST("/:id/change-owner", CommandChangeOwnerEndpoint, Admin)
|
||||
}
|
||||
|
||||
credentials := e.Group("/credentials", Admin)
|
||||
{
|
||||
credentials.GET("", CredentialAllEndpoint)
|
||||
credentials.GET("/paging", CredentialPagingEndpoint)
|
||||
credentials.POST("", CredentialCreateEndpoint)
|
||||
credentials.PUT("/:id", CredentialUpdateEndpoint)
|
||||
credentials.DELETE("/:id", CredentialDeleteEndpoint)
|
||||
credentials.GET("/:id", CredentialGetEndpoint)
|
||||
credentials.POST("/:id/change-owner", CredentialChangeOwnerEndpoint)
|
||||
}
|
||||
|
||||
sessions := e.Group("/sessions")
|
||||
{
|
||||
sessions.GET("/paging", Admin(SessionPagingEndpoint))
|
||||
sessions.POST("/:id/disconnect", Admin(SessionDisconnectEndpoint))
|
||||
sessions.DELETE("/:id", Admin(SessionDeleteEndpoint))
|
||||
sessions.GET("/:id/recording", Admin(SessionRecordingEndpoint))
|
||||
sessions.GET("/:id", Admin(SessionGetEndpoint))
|
||||
sessions.POST("/:id/reviewed", Admin(SessionReviewedEndpoint))
|
||||
sessions.POST("/:id/unreviewed", Admin(SessionUnViewedEndpoint))
|
||||
sessions.POST("/clear", Admin(SessionClearEndpoint))
|
||||
sessions.POST("/reviewed", Admin(SessionReviewedAllEndpoint))
|
||||
|
||||
sessions.POST("", SessionCreateEndpoint)
|
||||
sessions.POST("/:id/connect", SessionConnectEndpoint)
|
||||
sessions.POST("/:id/resize", SessionResizeEndpoint)
|
||||
sessions.GET("/:id/stats", SessionStatsEndpoint)
|
||||
|
||||
sessions.POST("/:id/ls", SessionLsEndpoint)
|
||||
sessions.GET("/:id/download", SessionDownloadEndpoint)
|
||||
sessions.POST("/:id/upload", SessionUploadEndpoint)
|
||||
sessions.POST("/:id/edit", SessionEditEndpoint)
|
||||
sessions.POST("/:id/mkdir", SessionMkDirEndpoint)
|
||||
sessions.POST("/:id/rm", SessionRmEndpoint)
|
||||
sessions.POST("/:id/rename", SessionRenameEndpoint)
|
||||
}
|
||||
|
||||
resourceSharers := e.Group("/resource-sharers", Admin)
|
||||
{
|
||||
resourceSharers.GET("", RSGetSharersEndPoint)
|
||||
resourceSharers.POST("/remove-resources", ResourceRemoveByUserIdAssignEndPoint)
|
||||
resourceSharers.POST("/add-resources", ResourceAddByUserIdAssignEndPoint)
|
||||
}
|
||||
|
||||
loginLogs := e.Group("login-logs", Admin)
|
||||
{
|
||||
loginLogs.GET("/paging", LoginLogPagingEndpoint)
|
||||
loginLogs.DELETE("/:id", LoginLogDeleteEndpoint)
|
||||
loginLogs.POST("/clear", LoginLogClearEndpoint)
|
||||
}
|
||||
|
||||
e.GET("/properties", Admin(PropertyGetEndpoint))
|
||||
e.PUT("/properties", Admin(PropertyUpdateEndpoint))
|
||||
|
||||
overview := e.Group("overview", Admin)
|
||||
{
|
||||
overview.GET("/counter", OverviewCounterEndPoint)
|
||||
overview.GET("/asset", OverviewAssetEndPoint)
|
||||
overview.GET("/access", OverviewAccessEndPoint)
|
||||
}
|
||||
|
||||
jobs := e.Group("/jobs", Admin)
|
||||
{
|
||||
jobs.POST("", JobCreateEndpoint)
|
||||
jobs.GET("/paging", JobPagingEndpoint)
|
||||
jobs.PUT("/:id", JobUpdateEndpoint)
|
||||
jobs.POST("/:id/change-status", JobChangeStatusEndpoint)
|
||||
jobs.POST("/:id/exec", JobExecEndpoint)
|
||||
jobs.DELETE("/:id", JobDeleteEndpoint)
|
||||
jobs.GET("/:id", JobGetEndpoint)
|
||||
jobs.GET("/:id/logs", JobGetLogsEndpoint)
|
||||
jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint)
|
||||
}
|
||||
|
||||
securities := e.Group("/securities", Admin)
|
||||
{
|
||||
securities.POST("", SecurityCreateEndpoint)
|
||||
securities.GET("/paging", SecurityPagingEndpoint)
|
||||
securities.PUT("/:id", SecurityUpdateEndpoint)
|
||||
securities.DELETE("/:id", SecurityDeleteEndpoint)
|
||||
securities.GET("/:id", SecurityGetEndpoint)
|
||||
}
|
||||
|
||||
storages := e.Group("/storages")
|
||||
{
|
||||
storages.GET("/paging", StoragePagingEndpoint, Admin)
|
||||
storages.POST("", StorageCreateEndpoint, Admin)
|
||||
storages.DELETE("/:id", StorageDeleteEndpoint, Admin)
|
||||
storages.PUT("/:id", StorageUpdateEndpoint, Admin)
|
||||
storages.GET("/shares", StorageSharesEndpoint, Admin)
|
||||
storages.GET("/:id", StorageGetEndpoint, Admin)
|
||||
|
||||
storages.POST("/:storageId/ls", StorageLsEndpoint)
|
||||
storages.GET("/:storageId/download", StorageDownloadEndpoint)
|
||||
storages.POST("/:storageId/upload", StorageUploadEndpoint)
|
||||
storages.POST("/:storageId/mkdir", StorageMkDirEndpoint)
|
||||
storages.POST("/:storageId/rm", StorageRmEndpoint)
|
||||
storages.POST("/:storageId/rename", StorageRenameEndpoint)
|
||||
storages.POST("/:storageId/edit", StorageEditEndpoint)
|
||||
}
|
||||
|
||||
strategies := e.Group("/strategies", Admin)
|
||||
{
|
||||
strategies.GET("", StrategyAllEndpoint)
|
||||
strategies.GET("/paging", StrategyPagingEndpoint)
|
||||
strategies.POST("", StrategyCreateEndpoint)
|
||||
strategies.DELETE("/:id", StrategyDeleteEndpoint)
|
||||
strategies.PUT("/:id", StrategyUpdateEndpoint)
|
||||
}
|
||||
|
||||
accessGateways := e.Group("/access-gateways", Admin)
|
||||
{
|
||||
accessGateways.GET("", AccessGatewayAllEndpoint)
|
||||
accessGateways.POST("", AccessGatewayCreateEndpoint)
|
||||
accessGateways.GET("/paging", AccessGatewayPagingEndpoint)
|
||||
accessGateways.PUT("/:id", AccessGatewayUpdateEndpoint)
|
||||
accessGateways.DELETE("/:id", AccessGatewayDeleteEndpoint)
|
||||
accessGateways.GET("/:id", AccessGatewayGetEndpoint)
|
||||
accessGateways.POST("/:id/reconnect", AccessGatewayReconnectEndpoint)
|
||||
}
|
||||
|
||||
backup := e.Group("/backup", Admin)
|
||||
{
|
||||
backup.GET("/export", BackupExportEndpoint)
|
||||
backup.POST("/import", BackupImportEndpoint)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func ReloadData() error {
|
||||
if err := ReloadAccessSecurity(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ReloadToken(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitRepository(db *gorm.DB) {
|
||||
userRepository = repository.NewUserRepository(db)
|
||||
userGroupRepository = repository.NewUserGroupRepository(db)
|
||||
resourceSharerRepository = repository.NewResourceSharerRepository(db)
|
||||
assetRepository = repository.NewAssetRepository(db)
|
||||
credentialRepository = repository.NewCredentialRepository(db)
|
||||
propertyRepository = repository.NewPropertyRepository(db)
|
||||
commandRepository = repository.NewCommandRepository(db)
|
||||
sessionRepository = repository.NewSessionRepository(db)
|
||||
accessSecurityRepository = repository.NewAccessSecurityRepository(db)
|
||||
accessGatewayRepository = repository.NewAccessGatewayRepository(db)
|
||||
jobRepository = repository.NewJobRepository(db)
|
||||
jobLogRepository = repository.NewJobLogRepository(db)
|
||||
loginLogRepository = repository.NewLoginLogRepository(db)
|
||||
storageRepository = repository.NewStorageRepository(db)
|
||||
strategyRepository = repository.NewStrategyRepository(db)
|
||||
}
|
||||
|
||||
func InitService() {
|
||||
propertyService = service.NewPropertyService(propertyRepository)
|
||||
userService = service.NewUserService(userRepository, loginLogRepository)
|
||||
sessionService = service.NewSessionService(sessionRepository)
|
||||
mailService = service.NewMailService(propertyRepository)
|
||||
assetService = service.NewAssetService(assetRepository)
|
||||
jobService = service.NewJobService(jobRepository, jobLogRepository, assetRepository, credentialRepository, assetService)
|
||||
credentialService = service.NewCredentialService(credentialRepository)
|
||||
storageService = service.NewStorageService(storageRepository, userRepository, propertyRepository)
|
||||
accessGatewayService = service.NewAccessGatewayService(accessGatewayRepository)
|
||||
}
|
||||
|
||||
func InitDBData() (err error) {
|
||||
if err := propertyService.DeleteDeprecatedProperty(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := accessGatewayService.ReConnectAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := propertyService.InitProperties(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := userService.InitUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jobService.InitJob(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := userService.FixUserOnlineState(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sessionService.FixSessionState(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sessionService.EmptyPassword(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := credentialService.Encrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assetService.Encrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storageService.InitStorages(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ResetPassword(username string) error {
|
||||
user, err := userRepository.FindByUsername(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password := "next-terminal"
|
||||
passwd, err := utils.Encoder.Encode([]byte(password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := &model.User{
|
||||
Password: string(passwd),
|
||||
ID: user.ID,
|
||||
}
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("用户「%v」密码初始化为: %v", user.Username, password)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ResetTotp(username string) error {
|
||||
user, err := userRepository.FindByUsername(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := &model.User{
|
||||
TOTPSecret: "-",
|
||||
ID: user.ID,
|
||||
}
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("用户「%v」已重置TOTP", user.Username)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ChangeEncryptionKey(oldEncryptionKey, newEncryptionKey string) error {
|
||||
|
||||
oldPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(oldEncryptionKey))))
|
||||
newPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(newEncryptionKey))))
|
||||
|
||||
credentials, err := credentialRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range credentials {
|
||||
credential := credentials[i]
|
||||
if err := credentialRepository.Decrypt(&credential, oldPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := credentialRepository.Encrypt(&credential, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := credentialRepository.UpdateById(&credential, credential.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
assets, err := assetRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range assets {
|
||||
asset := assets[i]
|
||||
if err := assetRepository.Decrypt(&asset, oldPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assetRepository.Encrypt(&asset, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assetRepository.UpdateById(&asset, asset.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Infof("encryption key has being changed.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupDB() *gorm.DB {
|
||||
|
||||
var logMode logger.Interface
|
||||
if config.GlobalCfg.Debug {
|
||||
logMode = logger.Default.LogMode(logger.Info)
|
||||
} else {
|
||||
logMode = logger.Default.LogMode(logger.Silent)
|
||||
}
|
||||
|
||||
fmt.Printf("当前数据库模式为:%v\n", config.GlobalCfg.DB)
|
||||
var err error
|
||||
var db *gorm.DB
|
||||
if config.GlobalCfg.DB == "mysql" {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=60s",
|
||||
config.GlobalCfg.Mysql.Username,
|
||||
config.GlobalCfg.Mysql.Password,
|
||||
config.GlobalCfg.Mysql.Hostname,
|
||||
config.GlobalCfg.Mysql.Port,
|
||||
config.GlobalCfg.Mysql.Database,
|
||||
)
|
||||
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logMode,
|
||||
})
|
||||
} else {
|
||||
db, err = gorm.Open(sqlite.Open(config.GlobalCfg.Sqlite.File), &gorm.Config{
|
||||
Logger: logMode,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("连接数据库异常: %v", err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(&model.User{}, &model.Asset{}, &model.AssetAttribute{}, &model.Session{}, &model.Command{},
|
||||
&model.Credential{}, &model.Property{}, &model.ResourceSharer{}, &model.UserGroup{}, &model.UserGroupMember{},
|
||||
&model.LoginLog{}, &model.Job{}, &model.JobLog{}, &model.AccessSecurity{}, &model.AccessGateway{},
|
||||
&model.Storage{}, &model.Strategy{}); err != nil {
|
||||
log.Errorf("初始化数据库表结构异常: %v", err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
return db
|
||||
}
|
@ -1,17 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/global/security"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func SecurityCreateEndpoint(c echo.Context) error {
|
||||
type SecurityApi struct{}
|
||||
|
||||
func (api SecurityApi) SecurityCreateEndpoint(c echo.Context) error {
|
||||
var item model.AccessSecurity
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -20,7 +25,7 @@ func SecurityCreateEndpoint(c echo.Context) error {
|
||||
item.ID = utils.UUID()
|
||||
item.Source = "管理员添加"
|
||||
|
||||
if err := accessSecurityRepository.Create(&item); err != nil {
|
||||
if err := repository.SecurityRepository.Create(context.TODO(), &item); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
@ -35,29 +40,7 @@ func SecurityCreateEndpoint(c echo.Context) error {
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func ReloadAccessSecurity() error {
|
||||
rules, err := accessSecurityRepository.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rules) > 0 {
|
||||
// 先清空
|
||||
security.GlobalSecurityManager.Clear()
|
||||
// 再添加到全局的安全管理器中
|
||||
for i := 0; i < len(rules); i++ {
|
||||
rule := &security.Security{
|
||||
ID: rules[i].ID,
|
||||
IP: rules[i].IP,
|
||||
Rule: rules[i].Rule,
|
||||
Priority: rules[i].Priority,
|
||||
}
|
||||
security.GlobalSecurityManager.Add <- rule
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SecurityPagingEndpoint(c echo.Context) error {
|
||||
func (api SecurityApi) SecurityPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
ip := c.QueryParam("ip")
|
||||
@ -66,18 +49,18 @@ func SecurityPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := accessSecurityRepository.Find(pageIndex, pageSize, ip, rule, order, field)
|
||||
items, total, err := repository.SecurityRepository.Find(context.TODO(), pageIndex, pageSize, ip, rule, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func SecurityUpdateEndpoint(c echo.Context) error {
|
||||
func (api SecurityApi) SecurityUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.AccessSecurity
|
||||
@ -85,7 +68,7 @@ func SecurityUpdateEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := accessSecurityRepository.UpdateById(&item, id); err != nil {
|
||||
if err := repository.SecurityRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
@ -101,13 +84,13 @@ func SecurityUpdateEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SecurityDeleteEndpoint(c echo.Context) error {
|
||||
func (api SecurityApi) SecurityDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
id := split[i]
|
||||
if err := accessSecurityRepository.DeleteById(id); err != nil {
|
||||
if err := repository.SecurityRepository.DeleteById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新内存中的安全规则
|
||||
@ -117,10 +100,10 @@ func SecurityDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SecurityGetEndpoint(c echo.Context) error {
|
||||
func (api SecurityApi) SecurityGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := accessSecurityRepository.FindById(id)
|
||||
item, err := repository.SecurityRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -11,23 +12,22 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/guacd"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/sftp"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SessionPagingEndpoint(c echo.Context) error {
|
||||
type SessionApi struct{}
|
||||
|
||||
func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
status := c.QueryParam("status")
|
||||
@ -37,7 +37,7 @@ func SessionPagingEndpoint(c echo.Context) error {
|
||||
protocol := c.QueryParam("protocol")
|
||||
reviewed := c.QueryParam("reviewed")
|
||||
|
||||
items, total, err := sessionRepository.Find(pageIndex, pageSize, status, userId, clientIp, assetId, protocol, reviewed)
|
||||
items, total, err := repository.SessionRepository.Find(context.TODO(), pageIndex, pageSize, status, userId, clientIp, assetId, protocol, reviewed)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@ -63,15 +63,15 @@ func SessionPagingEndpoint(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func SessionDeleteEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionDeleteEndpoint(c echo.Context) error {
|
||||
sessionIds := strings.Split(c.Param("id"), ",")
|
||||
err := sessionRepository.DeleteByIds(sessionIds)
|
||||
err := repository.SessionRepository.DeleteByIds(context.TODO(), sessionIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -79,38 +79,38 @@ func SessionDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionClearEndpoint(c echo.Context) error {
|
||||
err := sessionService.ClearOfflineSession()
|
||||
func (api SessionApi) SessionClearEndpoint(c echo.Context) error {
|
||||
err := service.SessionService.ClearOfflineSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionReviewedEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionReviewedEndpoint(c echo.Context) error {
|
||||
sessionIds := strings.Split(c.Param("id"), ",")
|
||||
if err := sessionRepository.UpdateReadByIds(true, sessionIds); err != nil {
|
||||
if err := repository.SessionRepository.UpdateReadByIds(context.TODO(), true, sessionIds); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionUnViewedEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionUnViewedEndpoint(c echo.Context) error {
|
||||
sessionIds := strings.Split(c.Param("id"), ",")
|
||||
if err := sessionRepository.UpdateReadByIds(false, sessionIds); err != nil {
|
||||
if err := repository.SessionRepository.UpdateReadByIds(context.TODO(), false, sessionIds); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionReviewedAllEndpoint(c echo.Context) error {
|
||||
if err := sessionService.ReviewedAll(); err != nil {
|
||||
func (api SessionApi) SessionReviewedAllEndpoint(c echo.Context) error {
|
||||
if err := service.SessionService.ReviewedAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionConnectEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionConnectEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
|
||||
s := model.Session{}
|
||||
@ -118,112 +118,37 @@ func SessionConnectEndpoint(c echo.Context) error {
|
||||
s.Status = constant.Connected
|
||||
s.ConnectedTime = utils.NowJsonTime()
|
||||
|
||||
if err := sessionRepository.UpdateById(&s, sessionId); err != nil {
|
||||
if err := repository.SessionRepository.UpdateById(context.TODO(), &s, sessionId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o, err := sessionRepository.FindById(sessionId)
|
||||
o, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
asset, err := assetRepository.FindById(o.AssetId)
|
||||
asset, err := repository.AssetRepository.FindById(context.TODO(), o.AssetId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !asset.Active {
|
||||
asset.Active = true
|
||||
_ = assetRepository.UpdateById(&asset, asset.ID)
|
||||
_ = repository.AssetRepository.UpdateById(context.TODO(), &asset, asset.ID)
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionDisconnectEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionDisconnectEndpoint(c echo.Context) error {
|
||||
sessionIds := c.Param("id")
|
||||
|
||||
split := strings.Split(sessionIds, ",")
|
||||
for i := range split {
|
||||
CloseSessionById(split[i], ForcedDisconnect, "管理员强制关闭了此会话")
|
||||
service.SessionService.CloseSessionById(split[i], ForcedDisconnect, "管理员强制关闭了此会话")
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
var mutex sync.Mutex
|
||||
|
||||
func CloseSessionById(sessionId string, code int, reason string) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
nextSession := session.GlobalSessionManager.GetById(sessionId)
|
||||
if nextSession != nil {
|
||||
log.Debugf("[%v] 会话关闭,原因:%v", sessionId, reason)
|
||||
WriteCloseMessage(nextSession.WebSocket, nextSession.Mode, code, reason)
|
||||
|
||||
if nextSession.Observer != nil {
|
||||
obs := nextSession.Observer.All()
|
||||
for _, ob := range obs {
|
||||
WriteCloseMessage(ob.WebSocket, ob.Mode, code, reason)
|
||||
log.Debugf("[%v] 强制踢出会话的观察者: %v", sessionId, ob.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
session.GlobalSessionManager.Del <- sessionId
|
||||
|
||||
DisDBSess(sessionId, code, reason)
|
||||
}
|
||||
|
||||
func WriteCloseMessage(ws *websocket.Conn, mode string, code int, reason string) {
|
||||
switch mode {
|
||||
case constant.Guacd:
|
||||
if ws != nil {
|
||||
err := guacd.NewInstruction("error", "", strconv.Itoa(code))
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.String()))
|
||||
disconnect := guacd.NewInstruction("disconnect")
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(disconnect.String()))
|
||||
}
|
||||
case constant.Naive:
|
||||
if ws != nil {
|
||||
msg := `0` + reason
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
}
|
||||
case constant.Terminal:
|
||||
// 这里是关闭观察者的ssh会话
|
||||
if ws != nil {
|
||||
msg := `0` + reason
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DisDBSess(sessionId string, code int, reason string) {
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Status == constant.Disconnected {
|
||||
return
|
||||
}
|
||||
|
||||
if s.Status == constant.Connecting {
|
||||
// 会话还未建立成功,无需保留数据
|
||||
_ = sessionRepository.DeleteById(sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
ss := model.Session{}
|
||||
ss.ID = sessionId
|
||||
ss.Status = constant.Disconnected
|
||||
ss.DisconnectedTime = utils.NowJsonTime()
|
||||
ss.Code = code
|
||||
ss.Message = reason
|
||||
ss.Password = "-"
|
||||
ss.PrivateKey = "-"
|
||||
ss.Passphrase = "-"
|
||||
|
||||
_ = sessionRepository.UpdateById(&ss, sessionId)
|
||||
}
|
||||
|
||||
func SessionResizeEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionResizeEndpoint(c echo.Context) error {
|
||||
width := c.QueryParam("width")
|
||||
height := c.QueryParam("height")
|
||||
sessionId := c.Param("id")
|
||||
@ -235,13 +160,13 @@ func SessionResizeEndpoint(c echo.Context) error {
|
||||
intWidth, _ := strconv.Atoi(width)
|
||||
intHeight, _ := strconv.Atoi(height)
|
||||
|
||||
if err := sessionRepository.UpdateWindowSizeById(intWidth, intHeight, sessionId); err != nil {
|
||||
if err := repository.SessionRepository.UpdateWindowSizeById(context.TODO(), intWidth, intHeight, sessionId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func SessionCreateEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionCreateEndpoint(c echo.Context) error {
|
||||
assetId := c.QueryParam("assetId")
|
||||
mode := c.QueryParam("mode")
|
||||
|
||||
@ -253,106 +178,11 @@ func SessionCreateEndpoint(c echo.Context) error {
|
||||
|
||||
user, _ := GetCurrentAccount(c)
|
||||
|
||||
asset, err := assetRepository.FindById(assetId)
|
||||
s, err := service.SessionService.Create(c.RealIP(), assetId, mode, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
upload = "1"
|
||||
download = "1"
|
||||
_delete = "1"
|
||||
rename = "1"
|
||||
edit = "1"
|
||||
fileSystem = "1"
|
||||
)
|
||||
if asset.Owner != user.ID && constant.TypeUser == user.Type {
|
||||
// 普通用户访问非自己创建的资产需要校验权限
|
||||
resourceSharers, err := resourceSharerRepository.FindByResourceIdAndUserId(assetId, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resourceSharers) == 0 {
|
||||
return errors.New("您没有权限访问此资产")
|
||||
}
|
||||
strategyId := resourceSharers[0].StrategyId
|
||||
if strategyId != "" {
|
||||
strategy, err := strategyRepository.FindById(strategyId)
|
||||
if err != nil {
|
||||
if !errors.Is(gorm.ErrRecordNotFound, err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
upload = strategy.Upload
|
||||
download = strategy.Download
|
||||
_delete = strategy.Delete
|
||||
rename = strategy.Rename
|
||||
edit = strategy.Edit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var storageId = ""
|
||||
if constant.RDP == asset.Protocol {
|
||||
attr, err := assetRepository.FindAssetAttrMapByAssetId(assetId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if "true" == attr[guacd.EnableDrive] {
|
||||
fileSystem = "1"
|
||||
storageId = attr[guacd.DrivePath]
|
||||
if storageId == "" {
|
||||
storageId = user.ID
|
||||
}
|
||||
} else {
|
||||
fileSystem = "0"
|
||||
}
|
||||
}
|
||||
|
||||
s := &model.Session{
|
||||
ID: utils.UUID(),
|
||||
AssetId: asset.ID,
|
||||
Username: asset.Username,
|
||||
Password: asset.Password,
|
||||
PrivateKey: asset.PrivateKey,
|
||||
Passphrase: asset.Passphrase,
|
||||
Protocol: asset.Protocol,
|
||||
IP: asset.IP,
|
||||
Port: asset.Port,
|
||||
Status: constant.NoConnect,
|
||||
Creator: user.ID,
|
||||
ClientIP: c.RealIP(),
|
||||
Mode: mode,
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
Delete: _delete,
|
||||
Rename: rename,
|
||||
Edit: edit,
|
||||
StorageId: storageId,
|
||||
AccessGatewayId: asset.AccessGatewayId,
|
||||
Reviewed: false,
|
||||
}
|
||||
|
||||
if asset.AccountType == "credential" {
|
||||
credential, err := credentialRepository.FindById(asset.CredentialId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if credential.Type == constant.Custom {
|
||||
s.Username = credential.Username
|
||||
s.Password = credential.Password
|
||||
} else {
|
||||
s.Username = credential.Username
|
||||
s.PrivateKey = credential.PrivateKey
|
||||
s.Passphrase = credential.Passphrase
|
||||
}
|
||||
}
|
||||
|
||||
if err := sessionRepository.Create(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, echo.Map{
|
||||
"id": s.ID,
|
||||
"upload": s.Upload,
|
||||
@ -361,13 +191,15 @@ func SessionCreateEndpoint(c echo.Context) error {
|
||||
"rename": s.Rename,
|
||||
"edit": s.Edit,
|
||||
"storageId": s.StorageId,
|
||||
"fileSystem": fileSystem,
|
||||
"fileSystem": s.FileSystem,
|
||||
"copy": s.Copy,
|
||||
"paste": s.Paste,
|
||||
})
|
||||
}
|
||||
|
||||
func SessionUploadEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionUploadEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -414,15 +246,18 @@ func SessionUploadEndpoint(c echo.Context) error {
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == s.Protocol {
|
||||
return StorageUpload(c, file, s.StorageId)
|
||||
if err := service.StorageService.StorageUpload(c, file, s.StorageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func SessionEditEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionEditEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -453,14 +288,17 @@ func SessionEditEndpoint(c echo.Context) error {
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == s.Protocol {
|
||||
return StorageEdit(c, file, fileContent, s.StorageId)
|
||||
if err := service.StorageService.StorageEdit(file, fileContent, s.StorageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func SessionDownloadEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionDownloadEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -492,15 +330,15 @@ func SessionDownloadEndpoint(c echo.Context) error {
|
||||
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes()))
|
||||
} else if "rdp" == s.Protocol {
|
||||
storageId := s.StorageId
|
||||
return StorageDownload(c, remoteFile, storageId)
|
||||
return service.StorageService.StorageDownload(c, remoteFile, storageId)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func SessionLsEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionLsEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindByIdAndDecrypt(sessionId)
|
||||
s, err := service.SessionService.FindByIdAndDecrypt(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -550,15 +388,19 @@ func SessionLsEndpoint(c echo.Context) error {
|
||||
return Success(c, files)
|
||||
} else if "rdp" == s.Protocol {
|
||||
storageId := s.StorageId
|
||||
return StorageLs(c, remoteDir, storageId)
|
||||
err, files := service.StorageService.StorageLs(remoteDir, storageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, files)
|
||||
}
|
||||
|
||||
return errors.New("当前协议不支持此操作")
|
||||
}
|
||||
|
||||
func SessionMkDirEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionMkDirEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -576,14 +418,18 @@ func SessionMkDirEndpoint(c echo.Context) error {
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == s.Protocol {
|
||||
return StorageMkDir(c, remoteDir, s.StorageId)
|
||||
storageId := s.StorageId
|
||||
if err := service.StorageService.StorageMkDir(remoteDir, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
return errors.New("当前协议不支持此操作")
|
||||
}
|
||||
|
||||
func SessionRmEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionRmEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -628,15 +474,19 @@ func SessionRmEndpoint(c echo.Context) error {
|
||||
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == s.Protocol {
|
||||
return StorageRm(c, file, s.StorageId)
|
||||
storageId := s.StorageId
|
||||
if err := service.StorageService.StorageRm(file, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
return errors.New("当前协议不支持此操作")
|
||||
}
|
||||
|
||||
func SessionRenameEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionRenameEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -659,14 +509,18 @@ func SessionRenameEndpoint(c echo.Context) error {
|
||||
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == s.Protocol {
|
||||
return StorageRename(c, oldName, newName, s.StorageId)
|
||||
storageId := s.StorageId
|
||||
if err := service.StorageService.StorageRename(oldName, newName, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
return errors.New("当前协议不支持此操作")
|
||||
}
|
||||
|
||||
func SessionRecordingEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionRecordingEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -677,24 +531,24 @@ func SessionRecordingEndpoint(c echo.Context) error {
|
||||
} else {
|
||||
recording = s.Recording + "/recording"
|
||||
}
|
||||
_ = sessionRepository.UpdateReadByIds(true, []string{sessionId})
|
||||
_ = repository.SessionRepository.UpdateReadByIds(context.TODO(), true, []string{sessionId})
|
||||
|
||||
log.Debugf("读取录屏文件:%v,是否存在: %v, 是否为文件: %v", recording, utils.FileExists(recording), utils.IsFile(recording))
|
||||
return c.File(recording)
|
||||
}
|
||||
|
||||
func SessionGetEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionGetEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, s)
|
||||
}
|
||||
|
||||
func SessionStatsEndpoint(c echo.Context) error {
|
||||
func (api SessionApi) SessionStatsEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
s, err := sessionRepository.FindByIdAndDecrypt(sessionId)
|
||||
s, err := service.SessionService.FindByIdAndDecrypt(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,435 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/guacd"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/term"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
var UpGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
Subprotocols: []string{"guacamole"},
|
||||
}
|
||||
|
||||
const (
|
||||
Closed = 0
|
||||
Connected = 1
|
||||
Data = 2
|
||||
Resize = 3
|
||||
Ping = 4
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Type int `json:"type"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (r Message) ToString() string {
|
||||
if r.Content != "" {
|
||||
return strconv.Itoa(r.Type) + r.Content
|
||||
} else {
|
||||
return strconv.Itoa(r.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func NewMessage(_type int, content string) Message {
|
||||
return Message{Content: content, Type: _type}
|
||||
}
|
||||
|
||||
func ParseMessage(value string) (message Message, err error) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
|
||||
_type, err := strconv.Atoi(value[:1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var content = value[1:]
|
||||
message = NewMessage(_type, content)
|
||||
return
|
||||
}
|
||||
|
||||
type WindowSize struct {
|
||||
Cols int `json:"cols"`
|
||||
Rows int `json:"rows"`
|
||||
}
|
||||
|
||||
func SSHEndpoint(c echo.Context) (err error) {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
log.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
cols, _ := strconv.Atoi(c.QueryParam("cols"))
|
||||
rows, _ := strconv.Atoi(c.QueryParam("rows"))
|
||||
|
||||
s, err := sessionRepository.FindByIdAndDecrypt(sessionId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "获取会话失败"))
|
||||
}
|
||||
|
||||
if err := permissionCheck(c, s.AssetId); err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, err.Error()))
|
||||
}
|
||||
|
||||
var (
|
||||
username = s.Username
|
||||
password = s.Password
|
||||
privateKey = s.PrivateKey
|
||||
passphrase = s.Passphrase
|
||||
ip = s.IP
|
||||
port = s.Port
|
||||
)
|
||||
|
||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||
g, err := accessGatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "获取接入网关失败:"+err.Error()))
|
||||
}
|
||||
if !g.Connected {
|
||||
return WriteMessage(ws, NewMessage(Closed, "接入网关不可用:"+g.Message))
|
||||
}
|
||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "创建隧道失败:"+err.Error()))
|
||||
}
|
||||
defer g.CloseSshTunnel(s.ID)
|
||||
ip = exposedIP
|
||||
port = exposedPort
|
||||
}
|
||||
|
||||
recording := ""
|
||||
var isRecording = false
|
||||
property, err := propertyRepository.FindByName(guacd.EnableRecording)
|
||||
if err == nil && property.Value == "true" {
|
||||
isRecording = true
|
||||
}
|
||||
|
||||
if isRecording {
|
||||
recording = path.Join(config.GlobalCfg.Guacd.Recording, sessionId, "recording.cast")
|
||||
}
|
||||
|
||||
attributes, err := assetRepository.FindAssetAttrMapByAssetId(s.AssetId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "获取资产属性失败:"+err.Error()))
|
||||
}
|
||||
|
||||
var xterm = "xterm-256color"
|
||||
var nextTerminal *term.NextTerminal
|
||||
if "true" == attributes[constant.SocksProxyEnable] {
|
||||
nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[constant.SocksProxyHost], attributes[constant.SocksProxyPort], attributes[constant.SocksProxyUsername], attributes[constant.SocksProxyPassword])
|
||||
} else {
|
||||
nextTerminal, err = term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "创建SSH客户端失败:"+err.Error()))
|
||||
}
|
||||
|
||||
if err := nextTerminal.RequestPty(xterm, rows, cols); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nextTerminal.Shell(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := model.Session{
|
||||
ConnectionId: sessionId,
|
||||
Width: cols,
|
||||
Height: rows,
|
||||
Status: constant.Connecting,
|
||||
Recording: recording,
|
||||
}
|
||||
if sess.Recording == "" {
|
||||
// 未录屏时无需审计
|
||||
sess.Reviewed = true
|
||||
}
|
||||
// 创建新会话
|
||||
log.Debugf("创建新会话 %v", sess.ConnectionId)
|
||||
if err := sessionRepository.UpdateById(&sess, sessionId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := WriteMessage(ws, NewMessage(Connected, "")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nextSession := &session.Session{
|
||||
ID: s.ID,
|
||||
Protocol: s.Protocol,
|
||||
Mode: s.Mode,
|
||||
WebSocket: ws,
|
||||
GuacdTunnel: nil,
|
||||
NextTerminal: nextTerminal,
|
||||
Observer: session.NewObserver(s.ID),
|
||||
}
|
||||
go nextSession.Observer.Run()
|
||||
session.GlobalSessionManager.Add <- nextSession
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tick := time.NewTicker(time.Millisecond * time.Duration(60))
|
||||
defer tick.Stop()
|
||||
|
||||
var buf []byte
|
||||
dataChan := make(chan rune)
|
||||
|
||||
go func() {
|
||||
SshLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Debugf("WebSocket已关闭,即将关闭SSH连接...")
|
||||
break SshLoop
|
||||
default:
|
||||
r, size, err := nextTerminal.StdoutReader.ReadRune()
|
||||
if err != nil {
|
||||
log.Debugf("SSH 读取失败,即将退出循环...")
|
||||
_ = WriteMessage(ws, NewMessage(Closed, ""))
|
||||
break SshLoop
|
||||
}
|
||||
if size > 0 {
|
||||
dataChan <- r
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("SSH 连接已关闭,退出循环。")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
tickLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break tickLoop
|
||||
case <-tick.C:
|
||||
if len(buf) > 0 {
|
||||
s := string(buf)
|
||||
// 录屏
|
||||
if isRecording {
|
||||
_ = nextTerminal.Recorder.WriteData(s)
|
||||
}
|
||||
// 监控
|
||||
if len(nextSession.Observer.All()) > 0 {
|
||||
obs := nextSession.Observer.All()
|
||||
for _, ob := range obs {
|
||||
_ = WriteMessage(ob.WebSocket, NewMessage(Data, s))
|
||||
}
|
||||
}
|
||||
if err := WriteMessage(ws, NewMessage(Data, s)); err != nil {
|
||||
log.Debugf("WebSocket写入失败,即将退出循环...")
|
||||
cancel()
|
||||
}
|
||||
buf = []byte{}
|
||||
}
|
||||
case data := <-dataChan:
|
||||
if data != utf8.RuneError {
|
||||
p := make([]byte, utf8.RuneLen(data))
|
||||
utf8.EncodeRune(p, data)
|
||||
buf = append(buf, p...)
|
||||
} else {
|
||||
buf = append(buf, []byte("@")...)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("SSH 连接已关闭,退出定时器循环。")
|
||||
}()
|
||||
|
||||
//var enterKeys []rune
|
||||
//enterIndex := 0
|
||||
for {
|
||||
_, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
// web socket会话关闭后主动关闭ssh会话
|
||||
log.Debugf("WebSocket已关闭")
|
||||
CloseSessionById(sessionId, Normal, "用户正常退出")
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
|
||||
msg, err := ParseMessage(string(message))
|
||||
if err != nil {
|
||||
log.Warnf("消息解码失败: %v, 原始字符串:%v", err, string(message))
|
||||
continue
|
||||
}
|
||||
|
||||
switch msg.Type {
|
||||
case Resize:
|
||||
decodeString, err := base64.StdEncoding.DecodeString(msg.Content)
|
||||
if err != nil {
|
||||
log.Warnf("Base64解码失败: %v,原始字符串:%v", err, msg.Content)
|
||||
continue
|
||||
}
|
||||
var winSize WindowSize
|
||||
err = json.Unmarshal(decodeString, &winSize)
|
||||
if err != nil {
|
||||
log.Warnf("解析SSH会话窗口大小失败: %v,原始字符串:%v", err, msg.Content)
|
||||
continue
|
||||
}
|
||||
if err := nextTerminal.WindowChange(winSize.Rows, winSize.Cols); err != nil {
|
||||
log.Warnf("更改SSH会话窗口大小失败: %v", err)
|
||||
}
|
||||
_ = sessionRepository.UpdateWindowSizeById(winSize.Rows, winSize.Cols, sessionId)
|
||||
case Data:
|
||||
input := []byte(msg.Content)
|
||||
//hexInput := hex.EncodeToString(input)
|
||||
//switch hexInput {
|
||||
//case "0d": // 回车
|
||||
// DealCommand(enterKeys)
|
||||
// // 清空输入的字符
|
||||
// enterKeys = enterKeys[:0]
|
||||
// enterIndex = 0
|
||||
//case "7f": // backspace
|
||||
// enterIndex--
|
||||
// if enterIndex < 0 {
|
||||
// enterIndex = 0
|
||||
// }
|
||||
// temp := enterKeys[:enterIndex]
|
||||
// if len(enterKeys) > enterIndex {
|
||||
// enterKeys = append(temp, enterKeys[enterIndex+1:]...)
|
||||
// } else {
|
||||
// enterKeys = temp
|
||||
// }
|
||||
//case "1b5b337e": // del
|
||||
// temp := enterKeys[:enterIndex]
|
||||
// if len(enterKeys) > enterIndex {
|
||||
// enterKeys = append(temp, enterKeys[enterIndex+1:]...)
|
||||
// } else {
|
||||
// enterKeys = temp
|
||||
// }
|
||||
// enterIndex--
|
||||
// if enterIndex < 0 {
|
||||
// enterIndex = 0
|
||||
// }
|
||||
//case "1b5b41":
|
||||
//case "1b5b42":
|
||||
// break
|
||||
//case "1b5b43": // ->
|
||||
// enterIndex++
|
||||
// if enterIndex > len(enterKeys) {
|
||||
// enterIndex = len(enterKeys)
|
||||
// }
|
||||
//case "1b5b44": // <-
|
||||
// enterIndex--
|
||||
// if enterIndex < 0 {
|
||||
// enterIndex = 0
|
||||
// }
|
||||
//default:
|
||||
// enterKeys = utils.InsertSlice(enterIndex, []rune(msg.Content), enterKeys)
|
||||
// enterIndex++
|
||||
//}
|
||||
_, err := nextTerminal.Write(input)
|
||||
if err != nil {
|
||||
CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
}
|
||||
case Ping:
|
||||
_, _, err := nextTerminal.SshClient.Conn.SendRequest("helloworld1024@foxmail.com", true, nil)
|
||||
if err != nil {
|
||||
CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
} else {
|
||||
_ = WriteMessage(ws, NewMessage(Ping, ""))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func permissionCheck(c echo.Context, assetId string) error {
|
||||
user, _ := GetCurrentAccount(c)
|
||||
if constant.TypeUser == user.Type {
|
||||
// 检测是否有访问权限
|
||||
assetIds, err := resourceSharerRepository.FindAssetIdsByUserId(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !utils.Contains(assetIds, assetId) {
|
||||
return errors.New("您没有权限访问此资产")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteMessage(ws *websocket.Conn, msg Message) error {
|
||||
message := []byte(msg.ToString())
|
||||
return ws.WriteMessage(websocket.TextMessage, message)
|
||||
}
|
||||
|
||||
func CreateNextTerminalBySession(session model.Session) (*term.NextTerminal, error) {
|
||||
var (
|
||||
username = session.Username
|
||||
password = session.Password
|
||||
privateKey = session.PrivateKey
|
||||
passphrase = session.Passphrase
|
||||
ip = session.IP
|
||||
port = session.Port
|
||||
)
|
||||
return term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, 10, 10, "", "", false)
|
||||
}
|
||||
|
||||
func SshMonitor(c echo.Context) error {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
log.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
s, err := sessionRepository.FindById(sessionId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "获取会话失败"))
|
||||
}
|
||||
|
||||
nextSession := session.GlobalSessionManager.GetById(sessionId)
|
||||
if nextSession == nil {
|
||||
return WriteMessage(ws, NewMessage(Closed, "会话已离线"))
|
||||
}
|
||||
|
||||
obId := utils.UUID()
|
||||
obSession := &session.Session{
|
||||
ID: obId,
|
||||
Protocol: s.Protocol,
|
||||
Mode: s.Mode,
|
||||
WebSocket: ws,
|
||||
}
|
||||
nextSession.Observer.Add <- obSession
|
||||
log.Debugf("会话 %v 观察者 %v 进入", sessionId, obId)
|
||||
|
||||
for {
|
||||
_, _, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
log.Debugf("会话 %v 观察者 %v 退出", sessionId, obId)
|
||||
nextSession.Observer.Del <- obId
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,525 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"next-terminal/server/global/security"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/guacd"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/term"
|
||||
"next-terminal/server/totp"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/manifoldco/promptui"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func sessionHandler(sess *ssh.Session) {
|
||||
defer func() {
|
||||
_ = (*sess).Close()
|
||||
}()
|
||||
|
||||
username := (*sess).User()
|
||||
remoteAddr := strings.Split((*sess).RemoteAddr().String(), ":")[0]
|
||||
|
||||
user, err := userRepository.FindByUsername(username)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
_, _ = io.WriteString(*sess, "您输入的账户或密码不正确.\n")
|
||||
} else {
|
||||
_, _ = io.WriteString(*sess, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是否需要进行双因素认证
|
||||
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
|
||||
totpUI(sess, user, remoteAddr, username)
|
||||
} else {
|
||||
// 保存登录日志
|
||||
_ = SaveLoginLog(remoteAddr, "terminal", username, true, false, utils.UUID(), "")
|
||||
mainUI(sess, user)
|
||||
}
|
||||
}
|
||||
|
||||
func totpUI(sess *ssh.Session, user model.User, remoteAddr string, username string) {
|
||||
|
||||
validate := func(input string) error {
|
||||
if len(input) < 6 {
|
||||
return errors.New("双因素认证授权码必须为6个数字")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: "请输入双因素认证授权码",
|
||||
Validate: validate,
|
||||
Mask: '*',
|
||||
Stdin: *sess,
|
||||
Stdout: *sess,
|
||||
}
|
||||
|
||||
var success = false
|
||||
for i := 0; i < 5; i++ {
|
||||
result, err := prompt.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Prompt failed %v\n", err)
|
||||
return
|
||||
}
|
||||
loginFailCountKey := remoteAddr + username
|
||||
|
||||
v, ok := cache.GlobalCache.Get(loginFailCountKey)
|
||||
if !ok {
|
||||
v = 1
|
||||
}
|
||||
count := v.(int)
|
||||
if count >= 5 {
|
||||
_, _ = io.WriteString(*sess, "登录失败次数过多,请等待30秒后再试\r\n")
|
||||
continue
|
||||
}
|
||||
if !totp.Validate(result, user.TOTPSecret) {
|
||||
count++
|
||||
println(count)
|
||||
cache.GlobalCache.Set(loginFailCountKey, count, time.Second*time.Duration(30))
|
||||
// 保存登录日志
|
||||
_ = SaveLoginLog(remoteAddr, "terminal", username, false, false, "", "双因素认证授权码不正确")
|
||||
_, _ = io.WriteString(*sess, "您输入的双因素认证授权码不匹配\r\n")
|
||||
continue
|
||||
}
|
||||
success = true
|
||||
break
|
||||
}
|
||||
|
||||
if success {
|
||||
// 保存登录日志
|
||||
_ = SaveLoginLog(remoteAddr, "terminal", username, true, false, utils.UUID(), "")
|
||||
mainUI(sess, user)
|
||||
}
|
||||
}
|
||||
|
||||
func mainUI(sess *ssh.Session, user model.User) {
|
||||
prompt := promptui.Select{
|
||||
Label: "欢迎使用 Next Terminal,请选择您要使用的功能",
|
||||
Items: []string{"我的资产", "退出系统"},
|
||||
Stdin: *sess,
|
||||
Stdout: *sess,
|
||||
}
|
||||
|
||||
MainLoop:
|
||||
for {
|
||||
_, result, err := prompt.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Prompt failed %v\n", err)
|
||||
return
|
||||
}
|
||||
switch result {
|
||||
case "我的资产":
|
||||
AssetUI(sess, user)
|
||||
case "退出系统":
|
||||
break MainLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AssetUI(sess *ssh.Session, user model.User) {
|
||||
assets, err := assetRepository.FindByProtocolAndUser(constant.SSH, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
quitItem := model.Asset{ID: "quit", Name: "返回上级菜单", Description: "这里是返回上级菜单的选项"}
|
||||
assets = append([]model.Asset{quitItem}, assets...)
|
||||
|
||||
templates := &promptui.SelectTemplates{
|
||||
Label: "{{ . }}?",
|
||||
Active: "\U0001F336 {{ .Name | cyan }} ({{ .IP | red }}:{{ .Port | red }})",
|
||||
Inactive: " {{ .Name | cyan }} ({{ .IP | red }}:{{ .Port | red }})",
|
||||
Selected: "\U0001F336 {{ .Name | red | cyan }}",
|
||||
Details: `
|
||||
--------- 详细信息 ----------
|
||||
{{ "名称:" | faint }} {{ .Name }}
|
||||
{{ "主机:" | faint }} {{ .IP }}
|
||||
{{ "端口:" | faint }} {{ .Port }}
|
||||
{{ "标签:" | faint }} {{ .Tags }}
|
||||
{{ "备注:" | faint }} {{ .Description }}
|
||||
`,
|
||||
}
|
||||
|
||||
searcher := func(input string, index int) bool {
|
||||
asset := assets[index]
|
||||
name := strings.Replace(strings.ToLower(asset.Name), " ", "", -1)
|
||||
input = strings.Replace(strings.ToLower(input), " ", "", -1)
|
||||
|
||||
return strings.Contains(name, input)
|
||||
}
|
||||
|
||||
prompt := promptui.Select{
|
||||
Label: "请选择您要访问的资产",
|
||||
Items: assets,
|
||||
Templates: templates,
|
||||
Size: 4,
|
||||
Searcher: searcher,
|
||||
Stdin: *sess,
|
||||
Stdout: *sess,
|
||||
}
|
||||
|
||||
AssetUILoop:
|
||||
for {
|
||||
i, _, err := prompt.Run()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Prompt failed %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
chooseAssetId := assets[i].ID
|
||||
switch chooseAssetId {
|
||||
case "quit":
|
||||
break AssetUILoop
|
||||
default:
|
||||
if err := createSession(sess, assets[i].ID, user.ID); err != nil {
|
||||
_, _ = io.WriteString(*sess, err.Error()+"\r\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createSession(sess *ssh.Session, assetId, creator string) (err error) {
|
||||
asset, err := assetRepository.FindById(assetId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ClientIP := strings.Split((*sess).RemoteAddr().String(), ":")[0]
|
||||
|
||||
s := &model.Session{
|
||||
ID: utils.UUID(),
|
||||
AssetId: asset.ID,
|
||||
Username: asset.Username,
|
||||
Password: asset.Password,
|
||||
PrivateKey: asset.PrivateKey,
|
||||
Passphrase: asset.Passphrase,
|
||||
Protocol: asset.Protocol,
|
||||
IP: asset.IP,
|
||||
Port: asset.Port,
|
||||
Status: constant.NoConnect,
|
||||
Creator: creator,
|
||||
ClientIP: ClientIP,
|
||||
Mode: constant.Terminal,
|
||||
Upload: "0",
|
||||
Download: "0",
|
||||
Delete: "0",
|
||||
Rename: "0",
|
||||
StorageId: "",
|
||||
AccessGatewayId: asset.AccessGatewayId,
|
||||
}
|
||||
|
||||
if asset.AccountType == "credential" {
|
||||
credential, err := credentialRepository.FindById(asset.CredentialId)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if credential.Type == constant.Custom {
|
||||
s.Username = credential.Username
|
||||
s.Password = credential.Password
|
||||
} else {
|
||||
s.Username = credential.Username
|
||||
s.PrivateKey = credential.PrivateKey
|
||||
s.Passphrase = credential.Passphrase
|
||||
}
|
||||
}
|
||||
|
||||
if err := sessionRepository.Create(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return handleAccessAsset(sess, s.ID)
|
||||
}
|
||||
|
||||
func handleAccessAsset(sess *ssh.Session, sessionId string) (err error) {
|
||||
s, err := sessionRepository.FindByIdAndDecrypt(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
username = s.Username
|
||||
password = s.Password
|
||||
privateKey = s.PrivateKey
|
||||
passphrase = s.Passphrase
|
||||
ip = s.IP
|
||||
port = s.Port
|
||||
)
|
||||
|
||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||
g, err := accessGatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
||||
if err != nil {
|
||||
return errors.New("获取接入网关失败:" + err.Error())
|
||||
}
|
||||
if !g.Connected {
|
||||
return errors.New("接入网关不可用:" + g.Message)
|
||||
}
|
||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
||||
if err != nil {
|
||||
return errors.New("开启SSH隧道失败:" + err.Error())
|
||||
}
|
||||
defer g.CloseSshTunnel(s.ID)
|
||||
ip = exposedIP
|
||||
port = exposedPort
|
||||
}
|
||||
|
||||
pty, winCh, isPty := (*sess).Pty()
|
||||
if !isPty {
|
||||
return errors.New("No PTY requested.\n")
|
||||
}
|
||||
|
||||
recording := ""
|
||||
property, err := propertyRepository.FindByName(guacd.EnableRecording)
|
||||
if err == nil && property.Value == "true" {
|
||||
recording = path.Join(config.GlobalCfg.Guacd.Recording, sessionId, "recording.cast")
|
||||
}
|
||||
|
||||
nextTerminal, err := term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, pty.Window.Height, pty.Window.Width, recording, pty.Term, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sshSession := nextTerminal.SshSession
|
||||
|
||||
writer := NewWriter(sessionId, sess, nextTerminal.Recorder)
|
||||
|
||||
sshSession.Stdout = writer
|
||||
sshSession.Stdin = *sess
|
||||
sshSession.Stderr = *sess
|
||||
|
||||
if err := nextTerminal.RequestPty(pty.Term, pty.Window.Height, pty.Window.Width); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nextTerminal.Shell(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Debugf("开启窗口大小监控...")
|
||||
for win := range winCh {
|
||||
_ = sshSession.WindowChange(win.Height, win.Width)
|
||||
}
|
||||
log.Debugf("退出窗口大小监控")
|
||||
// ==== 修改数据库中的会话状态为已断开,修复用户直接关闭窗口时会话状态不正确的问题 ====
|
||||
CloseSessionById(sessionId, Normal, "用户正常退出")
|
||||
// ==== 修改数据库中的会话状态为已断开,修复用户直接关闭窗口时会话状态不正确的问题 ====
|
||||
}()
|
||||
|
||||
// ==== 修改数据库中的会话状态为已连接 ====
|
||||
sessionForUpdate := model.Session{}
|
||||
sessionForUpdate.ID = sessionId
|
||||
sessionForUpdate.Status = constant.Connected
|
||||
sessionForUpdate.Recording = recording
|
||||
sessionForUpdate.ConnectedTime = utils.NowJsonTime()
|
||||
|
||||
if sessionForUpdate.Recording == "" {
|
||||
// 未录屏时无需审计
|
||||
sessionForUpdate.Reviewed = true
|
||||
}
|
||||
|
||||
if err := sessionRepository.UpdateById(&sessionForUpdate, sessionId); err != nil {
|
||||
return err
|
||||
}
|
||||
// ==== 修改数据库中的会话状态为已连接 ====
|
||||
|
||||
nextSession := &session.Session{
|
||||
ID: s.ID,
|
||||
Protocol: s.Protocol,
|
||||
Mode: s.Mode,
|
||||
NextTerminal: nextTerminal,
|
||||
Observer: session.NewObserver(s.ID),
|
||||
}
|
||||
go nextSession.Observer.Run()
|
||||
session.GlobalSessionManager.Add <- nextSession
|
||||
|
||||
if err := sshSession.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ==== 修改数据库中的会话状态为已断开 ====
|
||||
CloseSessionById(sessionId, Normal, "用户正常退出")
|
||||
// ==== 修改数据库中的会话状态为已断开 ====
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func passwordAuth(ctx ssh.Context, pass string) bool {
|
||||
username := ctx.User()
|
||||
remoteAddr := strings.Split(ctx.RemoteAddr().String(), ":")[0]
|
||||
user, err := userRepository.FindByUsername(username)
|
||||
|
||||
if err != nil {
|
||||
// 保存登录日志
|
||||
_ = SaveLoginLog(remoteAddr, "terminal", username, false, false, "", "账号或密码不正确")
|
||||
return false
|
||||
}
|
||||
|
||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(pass)); err != nil {
|
||||
// 保存登录日志
|
||||
_ = SaveLoginLog(remoteAddr, "terminal", username, false, false, "", "账号或密码不正确")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func connCallback(ctx ssh.Context, conn net.Conn) net.Conn {
|
||||
securities := security.GlobalSecurityManager.Values()
|
||||
if len(securities) == 0 {
|
||||
return conn
|
||||
}
|
||||
|
||||
ip := strings.Split(conn.RemoteAddr().String(), ":")[0]
|
||||
|
||||
for _, s := range securities {
|
||||
if strings.Contains(s.IP, "/") {
|
||||
// CIDR
|
||||
_, ipNet, err := net.ParseCIDR(s.IP)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !ipNet.Contains(net.ParseIP(ip)) {
|
||||
continue
|
||||
}
|
||||
} else if strings.Contains(s.IP, "-") {
|
||||
// 范围段
|
||||
split := strings.Split(s.IP, "-")
|
||||
if len(split) < 2 {
|
||||
continue
|
||||
}
|
||||
start := split[0]
|
||||
end := split[1]
|
||||
intReqIP := utils.IpToInt(ip)
|
||||
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// IP
|
||||
if s.IP != ip {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if s.Rule == constant.AccessRuleAllow {
|
||||
return conn
|
||||
}
|
||||
if s.Rule == constant.AccessRuleReject {
|
||||
_, _ = conn.Write([]byte("your access request was denied :(\n"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func Setup() {
|
||||
ssh.Handle(func(s ssh.Session) {
|
||||
_, _ = io.WriteString(s, fmt.Sprintf(constant.Banner, constant.Version))
|
||||
defer func() {
|
||||
if e, ok := recover().(error); ok {
|
||||
log.Fatal(e)
|
||||
}
|
||||
}()
|
||||
sessionHandler(&s)
|
||||
})
|
||||
|
||||
fmt.Printf("⇨ sshd server started on %v\n", config.GlobalCfg.Sshd.Addr)
|
||||
err := ssh.ListenAndServe(
|
||||
config.GlobalCfg.Sshd.Addr,
|
||||
nil,
|
||||
ssh.PasswordAuth(passwordAuth),
|
||||
ssh.HostKeyFile(config.GlobalCfg.Sshd.Key),
|
||||
ssh.WrapConn(connCallback),
|
||||
)
|
||||
log.Fatal(fmt.Sprintf("启动sshd服务失败: %v", err.Error()))
|
||||
}
|
||||
|
||||
func init() {
|
||||
if config.GlobalCfg.Sshd.Enable {
|
||||
go Setup()
|
||||
}
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
sessionId string
|
||||
sess *ssh.Session
|
||||
recorder *term.Recorder
|
||||
rz bool
|
||||
sz bool
|
||||
}
|
||||
|
||||
func NewWriter(sessionId string, sess *ssh.Session, recorder *term.Recorder) *Writer {
|
||||
return &Writer{sessionId: sessionId, sess: sess, recorder: recorder}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
if w.recorder != nil {
|
||||
s := string(p)
|
||||
if !w.sz && !w.rz {
|
||||
// rz的开头字符
|
||||
hexData := hex.EncodeToString(p)
|
||||
if strings.Contains(hexData, "727a0d2a2a184230303030303030303030303030300d8a11") {
|
||||
w.sz = true
|
||||
} else if strings.Contains(hexData, "727a2077616974696e6720746f20726563656976652e2a2a184230313030303030303233626535300d8a11") {
|
||||
w.rz = true
|
||||
}
|
||||
}
|
||||
|
||||
if w.sz {
|
||||
// sz 会以 OO 结尾
|
||||
if "OO" == s {
|
||||
w.sz = false
|
||||
}
|
||||
} else if w.rz {
|
||||
// rz 最后会显示 Received /home/xxx
|
||||
if strings.Contains(s, "Received") {
|
||||
w.rz = false
|
||||
// 把上传的文件名称也显示一下
|
||||
err := w.recorder.WriteData(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sendObData(w.sessionId, s)
|
||||
}
|
||||
} else {
|
||||
err := w.recorder.WriteData(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sendObData(w.sessionId, s)
|
||||
}
|
||||
}
|
||||
return (*w.sess).Write(p)
|
||||
}
|
||||
|
||||
func sendObData(sessionId, s string) {
|
||||
nextSession := session.GlobalSessionManager.GetById(sessionId)
|
||||
if nextSession != nil {
|
||||
if nextSession.Observer != nil {
|
||||
obs := nextSession.Observer.All()
|
||||
for _, ob := range obs {
|
||||
_ = WriteMessage(ob.WebSocket, NewMessage(Data, s))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@ -12,12 +10,16 @@ import (
|
||||
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func StoragePagingEndpoint(c echo.Context) error {
|
||||
type StorageApi struct{}
|
||||
|
||||
func (api StorageApi) StoragePagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -25,12 +27,12 @@ func StoragePagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := storageRepository.Find(pageIndex, pageSize, name, order, field)
|
||||
items, total, err := repository.StorageRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
drivePath := service.StorageService.GetBaseDrivePath()
|
||||
|
||||
for i := range items {
|
||||
item := items[i]
|
||||
@ -42,13 +44,13 @@ func StoragePagingEndpoint(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func StorageCreateEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageCreateEndpoint(c echo.Context) error {
|
||||
var item model.Storage
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -60,24 +62,24 @@ func StorageCreateEndpoint(c echo.Context) error {
|
||||
item.Created = utils.NowJsonTime()
|
||||
item.Owner = account.ID
|
||||
// 创建对应的目录文件夹
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
drivePath := service.StorageService.GetBaseDrivePath()
|
||||
if err := os.MkdirAll(path.Join(drivePath, item.ID), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storageRepository.Create(&item); err != nil {
|
||||
if err := repository.StorageRepository.Create(context.TODO(), &item); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func StorageUpdateEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
var item model.Storage
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
drivePath := service.StorageService.GetBaseDrivePath()
|
||||
dirSize, err := utils.DirSize(path.Join(drivePath, item.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -87,7 +89,7 @@ func StorageUpdateEndpoint(c echo.Context) error {
|
||||
return errors.New("空间大小不能小于已使用大小")
|
||||
}
|
||||
|
||||
storage, err := storageRepository.FindById(id)
|
||||
storage, err := repository.StorageRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -95,20 +97,20 @@ func StorageUpdateEndpoint(c echo.Context) error {
|
||||
storage.LimitSize = item.LimitSize
|
||||
storage.IsShare = item.IsShare
|
||||
|
||||
if err := storageRepository.UpdateById(&storage, id); err != nil {
|
||||
if err := repository.StorageRepository.UpdateById(context.TODO(), &storage, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func StorageGetEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageGetEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("id")
|
||||
storage, err := storageRepository.FindById(storageId)
|
||||
storage, err := repository.StorageRepository.FindById(context.TODO(), storageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structMap := utils.StructToMap(storage)
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
drivePath := service.StorageService.GetBaseDrivePath()
|
||||
dirSize, err := utils.DirSize(path.Join(drivePath, storageId))
|
||||
if err != nil {
|
||||
structMap["usedSize"] = -1
|
||||
@ -119,28 +121,28 @@ func StorageGetEndpoint(c echo.Context) error {
|
||||
return Success(c, structMap)
|
||||
}
|
||||
|
||||
func StorageSharesEndpoint(c echo.Context) error {
|
||||
storages, err := storageRepository.FindShares()
|
||||
func (api StorageApi) StorageSharesEndpoint(c echo.Context) error {
|
||||
storages, err := repository.StorageRepository.FindShares(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, storages)
|
||||
}
|
||||
|
||||
func StorageDeleteEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
id := split[i]
|
||||
if err := storageService.DeleteStorageById(id, false); err != nil {
|
||||
if err := service.StorageService.DeleteStorageById(id, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func PermissionCheck(c echo.Context, id string) error {
|
||||
storage, err := storageRepository.FindById(id)
|
||||
func (api StorageApi) PermissionCheck(c echo.Context, id string) error {
|
||||
storage, err := repository.StorageRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -153,49 +155,31 @@ func PermissionCheck(c echo.Context, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StorageLsEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageLsEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
remoteDir := c.FormValue("dir")
|
||||
return StorageLs(c, remoteDir, storageId)
|
||||
}
|
||||
|
||||
func StorageLs(c echo.Context, remoteDir, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
if strings.Contains(remoteDir, "../") {
|
||||
return Fail(c, -1, "非法请求 :(")
|
||||
}
|
||||
files, err := storageService.Ls(path.Join(drivePath, storageId), remoteDir)
|
||||
err, files := service.StorageService.StorageLs(remoteDir, storageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, files)
|
||||
}
|
||||
|
||||
func StorageDownloadEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageDownloadEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
remoteFile := c.QueryParam("file")
|
||||
return StorageDownload(c, remoteFile, storageId)
|
||||
return service.StorageService.StorageDownload(c, remoteFile, storageId)
|
||||
}
|
||||
|
||||
func StorageDownload(c echo.Context, remoteFile, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
if strings.Contains(remoteFile, "../") {
|
||||
return Fail(c, -1, "非法请求 :(")
|
||||
}
|
||||
// 获取带后缀的文件名称
|
||||
filenameWithSuffix := path.Base(remoteFile)
|
||||
return c.Attachment(path.Join(path.Join(drivePath, storageId), remoteFile), filenameWithSuffix)
|
||||
}
|
||||
|
||||
func StorageUploadEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageUploadEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
@ -203,150 +187,58 @@ func StorageUploadEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return StorageUpload(c, file, storageId)
|
||||
}
|
||||
|
||||
func StorageUpload(c echo.Context, file *multipart.FileHeader, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
storage, _ := storageRepository.FindById(storageId)
|
||||
if storage.LimitSize > 0 {
|
||||
dirSize, err := utils.DirSize(path.Join(drivePath, storageId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dirSize+file.Size > storage.LimitSize {
|
||||
return errors.New("可用空间不足")
|
||||
}
|
||||
}
|
||||
|
||||
filename := file.Filename
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteDir := c.QueryParam("dir")
|
||||
remoteFile := path.Join(remoteDir, filename)
|
||||
|
||||
if strings.Contains(remoteDir, "../") {
|
||||
return Fail(c, -1, "非法请求 :(")
|
||||
}
|
||||
if strings.Contains(remoteFile, "../") {
|
||||
return Fail(c, -1, "非法请求 :(")
|
||||
}
|
||||
|
||||
// 判断文件夹不存在时自动创建
|
||||
dir := path.Join(path.Join(drivePath, storageId), remoteDir)
|
||||
if !utils.FileExists(dir) {
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Destination
|
||||
dst, err := os.Create(path.Join(path.Join(drivePath, storageId), remoteFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// Copy
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
if err := service.StorageService.StorageUpload(c, file, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func StorageMkDirEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageMkDirEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
remoteDir := c.QueryParam("dir")
|
||||
return StorageMkDir(c, remoteDir, storageId)
|
||||
}
|
||||
|
||||
func StorageMkDir(c echo.Context, remoteDir, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
if strings.Contains(remoteDir, "../") {
|
||||
return Fail(c, -1, ":) 非法请求")
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(path.Join(drivePath, storageId), remoteDir), os.ModePerm); err != nil {
|
||||
if err := service.StorageService.StorageMkDir(remoteDir, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func StorageRmEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageRmEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
// 文件夹或者文件
|
||||
file := c.FormValue("file")
|
||||
return StorageRm(c, file, storageId)
|
||||
}
|
||||
|
||||
func StorageRm(c echo.Context, file, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
if strings.Contains(file, "../") {
|
||||
return Fail(c, -1, ":) 非法请求")
|
||||
}
|
||||
if err := os.RemoveAll(path.Join(path.Join(drivePath, storageId), file)); err != nil {
|
||||
if err := service.StorageService.StorageRm(file, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func StorageRenameEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageRenameEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
oldName := c.QueryParam("oldName")
|
||||
newName := c.QueryParam("newName")
|
||||
return StorageRename(c, oldName, newName, storageId)
|
||||
}
|
||||
|
||||
func StorageRename(c echo.Context, oldName, newName, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
if strings.Contains(oldName, "../") {
|
||||
return Fail(c, -1, ":) 非法请求")
|
||||
}
|
||||
if strings.Contains(newName, "../") {
|
||||
return Fail(c, -1, ":) 非法请求")
|
||||
}
|
||||
if err := os.Rename(path.Join(path.Join(drivePath, storageId), oldName), path.Join(path.Join(drivePath, storageId), newName)); err != nil {
|
||||
if err := service.StorageService.StorageRename(oldName, newName, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func StorageEditEndpoint(c echo.Context) error {
|
||||
func (api StorageApi) StorageEditEndpoint(c echo.Context) error {
|
||||
storageId := c.Param("storageId")
|
||||
if err := PermissionCheck(c, storageId); err != nil {
|
||||
if err := api.PermissionCheck(c, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
file := c.FormValue("file")
|
||||
fileContent := c.FormValue("fileContent")
|
||||
return StorageEdit(c, file, fileContent, storageId)
|
||||
}
|
||||
|
||||
func StorageEdit(c echo.Context, file string, fileContent string, storageId string) error {
|
||||
drivePath := storageService.GetBaseDrivePath()
|
||||
if strings.Contains(file, "../") {
|
||||
return Fail(c, -1, ":) 非法请求")
|
||||
}
|
||||
realFilePath := path.Join(path.Join(drivePath, storageId), file)
|
||||
dstFile, err := os.OpenFile(realFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
write := bufio.NewWriter(dstFile)
|
||||
if _, err := write.WriteString(fileContent); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := write.Flush(); err != nil {
|
||||
if err := service.StorageService.StorageEdit(file, fileContent, storageId); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
|
@ -1,24 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func StrategyAllEndpoint(c echo.Context) error {
|
||||
items, err := strategyRepository.FindAll()
|
||||
type StrategyApi struct{}
|
||||
|
||||
func (api StrategyApi) StrategyAllEndpoint(c echo.Context) error {
|
||||
items, err := repository.StrategyRepository.FindAll(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func StrategyPagingEndpoint(c echo.Context) error {
|
||||
func (api StrategyApi) StrategyPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -26,18 +31,18 @@ func StrategyPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := strategyRepository.Find(pageIndex, pageSize, name, order, field)
|
||||
items, total, err := repository.StrategyRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func StrategyCreateEndpoint(c echo.Context) error {
|
||||
func (api StrategyApi) StrategyCreateEndpoint(c echo.Context) error {
|
||||
var item model.Strategy
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
@ -45,32 +50,32 @@ func StrategyCreateEndpoint(c echo.Context) error {
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := strategyRepository.Create(&item); err != nil {
|
||||
if err := repository.StrategyRepository.Create(context.TODO(), &item); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func StrategyDeleteEndpoint(c echo.Context) error {
|
||||
func (api StrategyApi) StrategyDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
id := split[i]
|
||||
if err := strategyRepository.DeleteById(id); err != nil {
|
||||
if err := repository.StrategyRepository.DeleteById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func StrategyUpdateEndpoint(c echo.Context) error {
|
||||
func (api StrategyApi) StrategyUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
var item model.Strategy
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := strategyRepository.UpdateById(&item, id); err != nil {
|
||||
if err := repository.StrategyRepository.UpdateById(context.TODO(), &item, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
|
288
server/api/term.go
Normal file
288
server/api/term.go
Normal file
@ -0,0 +1,288 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"next-terminal/server/config"
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/guacd"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/term"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
Closed = 0
|
||||
Connected = 1
|
||||
Data = 2
|
||||
Resize = 3
|
||||
Ping = 4
|
||||
)
|
||||
|
||||
type WebTerminalApi struct {
|
||||
}
|
||||
|
||||
func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
log.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = ws.Close()
|
||||
}()
|
||||
ctx := context.TODO()
|
||||
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
cols, _ := strconv.Atoi(c.QueryParam("cols"))
|
||||
rows, _ := strconv.Atoi(c.QueryParam("rows"))
|
||||
|
||||
s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "获取会话失败"))
|
||||
}
|
||||
|
||||
if err := api.permissionCheck(c, s.AssetId); err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, err.Error()))
|
||||
}
|
||||
|
||||
var (
|
||||
username = s.Username
|
||||
password = s.Password
|
||||
privateKey = s.PrivateKey
|
||||
passphrase = s.Passphrase
|
||||
ip = s.IP
|
||||
port = s.Port
|
||||
)
|
||||
|
||||
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
|
||||
g, err := service.GatewayService.GetGatewayAndReconnectById(s.AccessGatewayId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "获取接入网关失败:"+err.Error()))
|
||||
}
|
||||
if !g.Connected {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "接入网关不可用:"+g.Message))
|
||||
}
|
||||
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "创建隧道失败:"+err.Error()))
|
||||
}
|
||||
defer g.CloseSshTunnel(s.ID)
|
||||
ip = exposedIP
|
||||
port = exposedPort
|
||||
}
|
||||
|
||||
recording := ""
|
||||
var isRecording = false
|
||||
property, err := repository.PropertyRepository.FindByName(ctx, guacd.EnableRecording)
|
||||
if err == nil && property.Value == "true" {
|
||||
isRecording = true
|
||||
}
|
||||
|
||||
if isRecording {
|
||||
recording = path.Join(config.GlobalCfg.Guacd.Recording, sessionId, "recording.cast")
|
||||
}
|
||||
|
||||
attributes, err := repository.AssetRepository.FindAssetAttrMapByAssetId(ctx, s.AssetId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "获取资产属性失败:"+err.Error()))
|
||||
}
|
||||
|
||||
var xterm = "xterm-256color"
|
||||
var nextTerminal *term.NextTerminal
|
||||
if "true" == attributes[constant.SocksProxyEnable] {
|
||||
nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[constant.SocksProxyHost], attributes[constant.SocksProxyPort], attributes[constant.SocksProxyUsername], attributes[constant.SocksProxyPassword])
|
||||
} else {
|
||||
nextTerminal, err = term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "创建SSH客户端失败:"+err.Error()))
|
||||
}
|
||||
|
||||
if err := nextTerminal.RequestPty(xterm, rows, cols); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nextTerminal.Shell(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := model.Session{
|
||||
ConnectionId: sessionId,
|
||||
Width: cols,
|
||||
Height: rows,
|
||||
Status: constant.Connecting,
|
||||
Recording: recording,
|
||||
}
|
||||
if sess.Recording == "" {
|
||||
// 未录屏时无需审计
|
||||
sess.Reviewed = true
|
||||
}
|
||||
// 创建新会话
|
||||
log.Debugf("创建新会话 %v", sess.ConnectionId)
|
||||
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := WriteMessage(ws, dto.NewMessage(Connected, "")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nextSession := &session.Session{
|
||||
ID: s.ID,
|
||||
Protocol: s.Protocol,
|
||||
Mode: s.Mode,
|
||||
WebSocket: ws,
|
||||
GuacdTunnel: nil,
|
||||
NextTerminal: nextTerminal,
|
||||
Observer: session.NewObserver(s.ID),
|
||||
}
|
||||
go nextSession.Observer.Run()
|
||||
session.GlobalSessionManager.Add <- nextSession
|
||||
|
||||
termHandler := NewTermHandler(sessionId, isRecording, ws, nextTerminal)
|
||||
termHandler.Start()
|
||||
|
||||
for {
|
||||
_, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
// web socket会话关闭后主动关闭ssh会话
|
||||
log.Debugf("WebSocket已关闭")
|
||||
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
|
||||
termHandler.Stop()
|
||||
break
|
||||
}
|
||||
|
||||
msg, err := dto.ParseMessage(string(message))
|
||||
if err != nil {
|
||||
log.Warnf("消息解码失败: %v, 原始字符串:%v", err, string(message))
|
||||
continue
|
||||
}
|
||||
|
||||
switch msg.Type {
|
||||
case Resize:
|
||||
decodeString, err := base64.StdEncoding.DecodeString(msg.Content)
|
||||
if err != nil {
|
||||
log.Warnf("Base64解码失败: %v,原始字符串:%v", err, msg.Content)
|
||||
continue
|
||||
}
|
||||
var winSize dto.WindowSize
|
||||
err = json.Unmarshal(decodeString, &winSize)
|
||||
if err != nil {
|
||||
log.Warnf("解析SSH会话窗口大小失败: %v,原始字符串:%v", err, msg.Content)
|
||||
continue
|
||||
}
|
||||
if err := nextTerminal.WindowChange(winSize.Rows, winSize.Cols); err != nil {
|
||||
log.Warnf("更改SSH会话窗口大小失败: %v", err)
|
||||
}
|
||||
_ = repository.SessionRepository.UpdateWindowSizeById(ctx, winSize.Rows, winSize.Cols, sessionId)
|
||||
case Data:
|
||||
input := []byte(msg.Content)
|
||||
_, err := nextTerminal.Write(input)
|
||||
if err != nil {
|
||||
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
}
|
||||
case Ping:
|
||||
_, _, err := nextTerminal.SshClient.Conn.SendRequest("helloworld1024@foxmail.com", true, nil)
|
||||
if err != nil {
|
||||
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
} else {
|
||||
_ = WriteMessage(ws, dto.NewMessage(Ping, ""))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
log.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = ws.Close()
|
||||
}()
|
||||
ctx := context.TODO()
|
||||
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
s, err := repository.SessionRepository.FindById(ctx, sessionId)
|
||||
if err != nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "获取会话失败"))
|
||||
}
|
||||
|
||||
nextSession := session.GlobalSessionManager.GetById(sessionId)
|
||||
if nextSession == nil {
|
||||
return WriteMessage(ws, dto.NewMessage(Closed, "会话已离线"))
|
||||
}
|
||||
|
||||
obId := utils.UUID()
|
||||
obSession := &session.Session{
|
||||
ID: obId,
|
||||
Protocol: s.Protocol,
|
||||
Mode: s.Mode,
|
||||
WebSocket: ws,
|
||||
}
|
||||
nextSession.Observer.Add <- obSession
|
||||
log.Debugf("会话 %v 观察者 %v 进入", sessionId, obId)
|
||||
|
||||
for {
|
||||
_, _, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
log.Debugf("会话 %v 观察者 %v 退出", sessionId, obId)
|
||||
nextSession.Observer.Del <- obId
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api WebTerminalApi) permissionCheck(c echo.Context, assetId string) error {
|
||||
user, _ := GetCurrentAccount(c)
|
||||
if constant.TypeUser == user.Type {
|
||||
// 检测是否有访问权限
|
||||
assetIds, err := repository.ResourceSharerRepository.FindAssetIdsByUserId(context.TODO(), user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !utils.Contains(assetIds, assetId) {
|
||||
return errors.New("您没有权限访问此资产")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteMessage(ws *websocket.Conn, msg dto.Message) error {
|
||||
message := []byte(msg.ToString())
|
||||
return ws.WriteMessage(websocket.TextMessage, message)
|
||||
}
|
||||
|
||||
func CreateNextTerminalBySession(session model.Session) (*term.NextTerminal, error) {
|
||||
var (
|
||||
username = session.Username
|
||||
password = session.Password
|
||||
privateKey = session.PrivateKey
|
||||
passphrase = session.Passphrase
|
||||
ip = session.IP
|
||||
port = session.Port
|
||||
)
|
||||
return term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, 10, 10, "", "", false)
|
||||
}
|
104
server/api/term_handler.go
Normal file
104
server/api/term_handler.go
Normal file
@ -0,0 +1,104 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/term"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type TermHandler struct {
|
||||
sessionId string
|
||||
isRecording bool
|
||||
ws *websocket.Conn
|
||||
nextTerminal *term.NextTerminal
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
dataChan chan rune
|
||||
tick *time.Ticker
|
||||
}
|
||||
|
||||
func NewTermHandler(sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tick := time.NewTicker(time.Millisecond * time.Duration(60))
|
||||
return &TermHandler{
|
||||
sessionId: sessionId,
|
||||
isRecording: isRecording,
|
||||
ws: ws,
|
||||
nextTerminal: nextTerminal,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
dataChan: make(chan rune),
|
||||
tick: tick,
|
||||
}
|
||||
}
|
||||
|
||||
func (r TermHandler) Start() {
|
||||
go r.readFormTunnel()
|
||||
go r.writeToWebsocket()
|
||||
}
|
||||
|
||||
func (r TermHandler) Stop() {
|
||||
r.tick.Stop()
|
||||
r.cancel()
|
||||
}
|
||||
|
||||
func (r TermHandler) readFormTunnel() {
|
||||
for {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
default:
|
||||
rn, size, err := r.nextTerminal.StdoutReader.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if size > 0 {
|
||||
r.dataChan <- rn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r TermHandler) writeToWebsocket() {
|
||||
var buf []byte
|
||||
for {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
case <-r.tick.C:
|
||||
if len(buf) > 0 {
|
||||
s := string(buf)
|
||||
if err := WriteMessage(r.ws, dto.NewMessage(Data, s)); err != nil {
|
||||
return
|
||||
}
|
||||
// 录屏
|
||||
if r.isRecording {
|
||||
_ = r.nextTerminal.Recorder.WriteData(s)
|
||||
}
|
||||
nextSession := session.GlobalSessionManager.GetById(r.sessionId)
|
||||
// 监控
|
||||
if nextSession != nil && len(nextSession.Observer.All()) > 0 {
|
||||
obs := nextSession.Observer.All()
|
||||
for _, ob := range obs {
|
||||
_ = WriteMessage(ob.WebSocket, dto.NewMessage(Data, s))
|
||||
}
|
||||
}
|
||||
buf = []byte{}
|
||||
}
|
||||
case data := <-r.dataChan:
|
||||
if data != utf8.RuneError {
|
||||
p := make([]byte, utf8.RuneLen(data))
|
||||
utf8.EncodeRune(p, data)
|
||||
buf = append(buf, p...)
|
||||
} else {
|
||||
buf = append(buf, []byte("@")...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +1,33 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/utils"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type UserGroup struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Members []string `json:"members"`
|
||||
}
|
||||
type UserGroupApi struct{}
|
||||
|
||||
func UserGroupCreateEndpoint(c echo.Context) error {
|
||||
var item UserGroup
|
||||
func (userGroupApi UserGroupApi) UserGroupCreateEndpoint(c echo.Context) error {
|
||||
var item dto.UserGroup
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userGroup := model.UserGroup{
|
||||
ID: utils.UUID(),
|
||||
Created: utils.NowJsonTime(),
|
||||
Name: item.Name,
|
||||
}
|
||||
|
||||
if err := userGroupRepository.Create(&userGroup, item.Members); err != nil {
|
||||
if _, err := service.UserGroupService.Create(item.Name, item.Members); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func UserGroupPagingEndpoint(c echo.Context) error {
|
||||
func (userGroupApi UserGroupApi) UserGroupPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
@ -43,41 +35,38 @@ func UserGroupPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := userGroupRepository.Find(pageIndex, pageSize, name, order, field)
|
||||
items, total, err := repository.UserGroupRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func UserGroupUpdateEndpoint(c echo.Context) error {
|
||||
func (userGroupApi UserGroupApi) UserGroupUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item UserGroup
|
||||
var item dto.UserGroup
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
userGroup := model.UserGroup{
|
||||
Name: item.Name,
|
||||
}
|
||||
|
||||
if err := userGroupRepository.Update(&userGroup, item.Members, id); err != nil {
|
||||
if err := service.UserGroupService.Update(id, item.Name, item.Members); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserGroupDeleteEndpoint(c echo.Context) error {
|
||||
func (userGroupApi UserGroupApi) UserGroupDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
split := strings.Split(ids, ",")
|
||||
for i := range split {
|
||||
userId := split[i]
|
||||
if err := userGroupRepository.DeleteById(userId); err != nil {
|
||||
if err := service.UserGroupService.DeleteById(userId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -85,20 +74,20 @@ func UserGroupDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserGroupGetEndpoint(c echo.Context) error {
|
||||
func (userGroupApi UserGroupApi) UserGroupGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := userGroupRepository.FindById(id)
|
||||
item, err := repository.UserGroupRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
members, err := userGroupRepository.FindMembersById(id)
|
||||
members, err := repository.UserGroupMemberRepository.FindUserIdsByUserGroupId(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userGroup := UserGroup{
|
||||
userGroup := dto.UserGroup{
|
||||
Id: item.ID,
|
||||
Name: item.Name,
|
||||
Members: members,
|
||||
|
@ -1,56 +1,35 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"context"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"next-terminal/server/constant"
|
||||
"next-terminal/server/global/cache"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/service"
|
||||
"next-terminal/server/utils"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func UserCreateEndpoint(c echo.Context) (err error) {
|
||||
type UserApi struct{}
|
||||
|
||||
func (userApi UserApi) UserCreateEndpoint(c echo.Context) (err error) {
|
||||
var item model.User
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
if userRepository.ExistByUsername(item.Username) {
|
||||
return Fail(c, -1, "username is already in use")
|
||||
}
|
||||
|
||||
password := item.Password
|
||||
|
||||
var pass []byte
|
||||
if pass, err = utils.Encoder.Encode([]byte(password)); err != nil {
|
||||
return err
|
||||
}
|
||||
item.Password = string(pass)
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
item.Status = constant.StatusEnabled
|
||||
|
||||
if err := userRepository.Create(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
err = storageService.CreateStorageByUser(&item)
|
||||
if err != nil {
|
||||
if err := service.UserService.CreateUser(item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if item.Mail != "" {
|
||||
go mailService.SendMail(item.Mail, "[Next Terminal] 注册通知", "你好,"+item.Nickname+"。管理员为你注册了账号:"+item.Username+" 密码:"+password)
|
||||
}
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func UserPagingEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
username := c.QueryParam("username")
|
||||
@ -60,19 +39,18 @@ func UserPagingEndpoint(c echo.Context) error {
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
items, total, err := userRepository.Find(pageIndex, pageSize, username, nickname, mail, order, field, account)
|
||||
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
return Success(c, Map{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func UserUpdateEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
@ -86,14 +64,14 @@ func UserUpdateEndpoint(c echo.Context) error {
|
||||
}
|
||||
item.ID = id
|
||||
|
||||
if err := userRepository.Update(&item); err != nil {
|
||||
if err := repository.UserRepository.Update(context.TODO(), &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserUpdateStatusEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserUpdateStatusEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
status := c.QueryParam("status")
|
||||
account, _ := GetCurrentAccount(c)
|
||||
@ -101,14 +79,14 @@ func UserUpdateStatusEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "不能操作自身账户")
|
||||
}
|
||||
|
||||
if err := userService.UpdateStatusById(id, status); err != nil {
|
||||
if err := service.UserService.UpdateStatusById(id, status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserDeleteEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserDeleteEndpoint(c echo.Context) error {
|
||||
ids := c.Param("id")
|
||||
account, found := GetCurrentAccount(c)
|
||||
if !found {
|
||||
@ -120,16 +98,7 @@ func UserDeleteEndpoint(c echo.Context) error {
|
||||
if account.ID == userId {
|
||||
return Fail(c, -1, "不允许删除自身账户")
|
||||
}
|
||||
// 下线该用户
|
||||
if err := userService.LogoutById(userId); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除用户
|
||||
if err := userRepository.DeleteById(userId); err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除用户的默认磁盘空间
|
||||
if err := storageService.DeleteStorageById(userId, true); err != nil {
|
||||
if err := service.UserService.DeleteUserById(userId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -137,10 +106,10 @@ func UserDeleteEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserGetEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := userRepository.FindById(id)
|
||||
item, err := repository.UserRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -148,14 +117,14 @@ func UserGetEndpoint(c echo.Context) error {
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func UserChangePasswordEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserChangePasswordEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
password := c.FormValue("password")
|
||||
if password == "" {
|
||||
return Fail(c, -1, "请输入密码")
|
||||
}
|
||||
|
||||
user, err := userRepository.FindById(id)
|
||||
user, err := repository.UserRepository.FindById(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -168,61 +137,25 @@ func UserChangePasswordEndpoint(c echo.Context) error {
|
||||
Password: string(passwd),
|
||||
ID: id,
|
||||
}
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user.Mail != "" {
|
||||
go mailService.SendMail(user.Mail, "[Next Terminal] 密码修改通知", "你好,"+user.Nickname+"。管理员已将你的密码修改为:"+password)
|
||||
go service.MailService.SendMail(user.Mail, "[Next Terminal] 密码修改通知", "你好,"+user.Nickname+"。管理员已将你的密码修改为:"+password)
|
||||
}
|
||||
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func UserResetTotpEndpoint(c echo.Context) error {
|
||||
func (userApi UserApi) UserResetTotpEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
u := &model.User{
|
||||
TOTPSecret: "-",
|
||||
ID: id,
|
||||
}
|
||||
if err := userRepository.Update(u); err != nil {
|
||||
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, "")
|
||||
}
|
||||
|
||||
func ReloadToken() error {
|
||||
loginLogs, err := loginLogRepository.FindAliveLoginLogs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range loginLogs {
|
||||
loginLog := loginLogs[i]
|
||||
token := loginLog.ID
|
||||
user, err := userRepository.FindByUsername(loginLog.Username)
|
||||
if err != nil {
|
||||
if errors.Is(gorm.ErrRecordNotFound, err) {
|
||||
_ = loginLogRepository.DeleteById(token)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
authorization := Authorization{
|
||||
Token: token,
|
||||
Remember: loginLog.Remember,
|
||||
User: user,
|
||||
}
|
||||
|
||||
cacheKey := userService.BuildCacheKeyByToken(token)
|
||||
|
||||
if authorization.Remember {
|
||||
// 记住登录有效期两周
|
||||
cache.GlobalCache.Set(cacheKey, authorization, RememberEffectiveTime)
|
||||
} else {
|
||||
cache.GlobalCache.Set(cacheKey, authorization, NotRememberEffectiveTime)
|
||||
}
|
||||
log.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user