diff --git a/README.md b/README.md index ecd9036..4b72f1f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,10 @@ test/test 如果您觉得 next-terminal 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 -![捐赠](./screenshot/donate.png) +
+微信 +支付宝 +
## 联系方式 @@ -53,8 +56,6 @@ test/test - QQ群 938145268 - - - Telegram https://t.me/next_terminal \ No newline at end of file diff --git a/main.go b/main.go index 0ad83c8..a359535 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "next-terminal/pkg/utils" "os" "strconv" + "strings" "time" ) @@ -166,8 +167,11 @@ func Run() error { // 配置缓存器 global.Cache = cache.New(5*time.Minute, 10*time.Minute) global.Cache.OnEvicted(func(key string, value interface{}) { - logrus.Debugf("用户Token「%v」过期", key) - model.Logout(key) + if strings.HasPrefix(key, api.Token) { + token := strings.Split(key, ":")[0] + logrus.Debugf("用户Token「%v」过期", token) + model.Logout(token) + } }) global.Store = global.NewStore() @@ -191,11 +195,13 @@ func Run() error { User: user, } + cacheKey := strings.Join([]string{api.Token, token}, ":") + if authorization.Remember { // 记住登录有效期两周 - global.Cache.Set(token, authorization, api.RememberEffectiveTime) + global.Cache.Set(cacheKey, authorization, api.RememberEffectiveTime) } else { - global.Cache.Set(token, authorization, api.NotRememberEffectiveTime) + global.Cache.Set(cacheKey, authorization, api.NotRememberEffectiveTime) } logrus.Debugf("重新加载用户「%v」授权Token「%v」到缓存", user.Nickname, token) } diff --git a/pkg/api/account.go b/pkg/api/account.go index a045f08..4b3ed06 100644 --- a/pkg/api/account.go +++ b/pkg/api/account.go @@ -46,19 +46,35 @@ func LoginEndpoint(c echo.Context) error { } user, err := model.FindUserByUsername(loginAccount.Username) + + // 存储登录失败次数信息 + loginFailCountKey := loginAccount.Username + v, ok := global.Cache.Get(loginFailCountKey) + if !ok { + v = 1 + } + count := v.(int) + if count >= 5 { + return Fail(c, -1, "登录失败次数过多,请稍后再试") + } + if err != nil { - return Fail(c, -1, "您输入的账号或密码不正确") + count++ + global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5)) + return FailWithData(c, -1, "您输入的账号或密码不正确", count) } if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil { - return Fail(c, -1, "您输入的账号或密码不正确") + count++ + global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5)) + return FailWithData(c, -1, "您输入的账号或密码不正确", count) } if user.TOTPSecret != "" && user.TOTPSecret != "-" { return Fail(c, 0, "") } - token, err := Login(c, loginAccount, user) + token, err := LoginSuccess(c, loginAccount, user) if err != nil { return err } @@ -66,7 +82,7 @@ func LoginEndpoint(c echo.Context) error { return Success(c, token) } -func Login(c echo.Context, loginAccount LoginAccount, user model.User) (token string, err error) { +func LoginSuccess(c echo.Context, loginAccount LoginAccount, user model.User) (token string, err error) { token = strings.Join([]string{utils.UUID(), utils.UUID(), utils.UUID(), utils.UUID()}, "") authorization := Authorization{ @@ -75,11 +91,13 @@ func Login(c echo.Context, loginAccount LoginAccount, user model.User) (token st User: user, } + cacheKey := strings.Join([]string{Token, token}, ":") + if authorization.Remember { // 记住登录有效期两周 - global.Cache.Set(token, authorization, RememberEffectiveTime) + global.Cache.Set(cacheKey, authorization, RememberEffectiveTime) } else { - global.Cache.Set(token, authorization, NotRememberEffectiveTime) + global.Cache.Set(cacheKey, authorization, NotRememberEffectiveTime) } // 保存登录日志 @@ -107,20 +125,37 @@ func loginWithTotpEndpoint(c echo.Context) error { return err } + // 存储登录失败次数信息 + loginFailCountKey := loginAccount.Username + v, ok := global.Cache.Get(loginFailCountKey) + if !ok { + v = 1 + } + count := v.(int) + if count >= 5 { + return Fail(c, -1, "登录失败次数过多,请稍后再试") + } + user, err := model.FindUserByUsername(loginAccount.Username) if err != nil { - return Fail(c, -1, "您输入的账号或密码不正确") + count++ + global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5)) + return FailWithData(c, -1, "您输入的账号或密码不正确", count) } if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil { - return Fail(c, -1, "您输入的账号或密码不正确") + count++ + global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5)) + return FailWithData(c, -1, "您输入的账号或密码不正确", count) } if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) { - return Fail(c, -2, "您的TOTP不匹配") + count++ + global.Cache.Set(loginFailCountKey, count, time.Minute*time.Duration(5)) + return FailWithData(c, -1, "您输入双因素认证授权码不正确", count) } - token, err := Login(c, loginAccount, user) + token, err := LoginSuccess(c, loginAccount, user) if err != nil { return err } diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go index f0ea3c0..8e925e5 100644 --- a/pkg/api/middleware.go +++ b/pkg/api/middleware.go @@ -41,7 +41,8 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc { } token := GetToken(c) - authorization, found := global.Cache.Get(token) + cacheKey := strings.Join([]string{Token, token}, ":") + authorization, found := global.Cache.Get(cacheKey) if !found { return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。") } diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 6be1300..3caf5d6 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -156,6 +156,14 @@ func Fail(c echo.Context, code int, message string) error { }) } +func FailWithData(c echo.Context, code int, message string, data interface{}) error { + return c.JSON(200, H{ + "code": code, + "message": message, + "data": data, + }) +} + func Success(c echo.Context, data interface{}) error { return c.JSON(200, H{ "code": 1, diff --git a/pkg/api/session.go b/pkg/api/session.go index 3cbffa1..fce379d 100644 --- a/pkg/api/session.go +++ b/pkg/api/session.go @@ -66,15 +66,9 @@ func SessionPagingEndpoint(c echo.Context) error { func SessionDeleteEndpoint(c echo.Context) error { sessionIds := c.Param("id") split := strings.Split(sessionIds, ",") - for i := range split { - drivePath, err := model.GetRecordingPath() - if err != nil { - continue - } - if err := os.RemoveAll(path.Join(drivePath, split[i])); err != nil { - return err - } - model.DeleteSessionById(split[i]) + err := model.DeleteSessionByIds(split) + if err != nil { + return err } return Success(c, nil) @@ -132,7 +126,7 @@ func CloseSessionById(sessionId string, code int, reason string) { if s.Status == model.Connecting { // 会话还未建立成功,无需保留数据 - model.DeleteSessionById(sessionId) + _ = model.DeleteSessionById(sessionId) return } diff --git a/pkg/guacd/guacd.go b/pkg/guacd/guacd.go index c767f90..0db2947 100644 --- a/pkg/guacd/guacd.go +++ b/pkg/guacd/guacd.go @@ -254,6 +254,9 @@ func (opt *Tunnel) Read() (p []byte, err error) { if s == "rate=44100,channels=2;" { return make([]byte, 0), nil } + if s == "rate=22050,channels=2;" { + return make([]byte, 0), nil + } if s == "5.audio,1.1,31.audio/L16;" { s += "rate=44100,channels=2;" } diff --git a/pkg/handle/runner.go b/pkg/handle/runner.go index 109635c..b355e04 100644 --- a/pkg/handle/runner.go +++ b/pkg/handle/runner.go @@ -3,6 +3,7 @@ package handle import ( "github.com/robfig/cron/v3" "github.com/sirupsen/logrus" + "log" "next-terminal/pkg/guacd" "next-terminal/pkg/model" "next-terminal/pkg/utils" @@ -22,7 +23,7 @@ func RunTicker() { now := time.Now() for i := range sessions { if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 { - model.DeleteSessionById(sessions[i].ID) + _ = model.DeleteSessionById(sessions[i].ID) s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port) logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID) } @@ -40,6 +41,40 @@ func RunTicker() { } }) + _, err := c.AddFunc("0 0 0 * * ?", func() { + // 定时任务 每日凌晨检查超过时长限制的会话 + property, err := model.FindPropertyByName("session-saved-limit") + if err != nil { + return + } + if property.Value == "" || property.Value == "-" { + return + } + limit, err := strconv.Atoi(property.Value) + if err != nil { + return + } + sessions, err := model.FindOutTimeSessions(limit) + if err != nil { + return + } + + if sessions != nil && len(sessions) > 0 { + var sessionIds []string + for i := range sessions { + sessionIds = append(sessionIds, sessions[i].ID) + } + err := model.DeleteSessionByIds(sessionIds) + if err != nil { + logrus.Errorf("删除离线会话失败 %v", err) + } + } + }) + + if err != nil { + log.Fatal(err) + } + c.Start() } diff --git a/pkg/model/session.go b/pkg/model/session.go index c2fec30..b0cbf08 100644 --- a/pkg/model/session.go +++ b/pkg/model/session.go @@ -3,6 +3,8 @@ package model import ( "next-terminal/pkg/global" "next-terminal/pkg/utils" + "os" + "path" "time" ) @@ -126,6 +128,12 @@ func FindSessionByStatusIn(statuses []string) (o []Session, err error) { return } +func FindOutTimeSessions(dayLimit int) (o []Session, err error) { + limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour) + err = global.DB.Where("status = ? and connected_time < ?", Disconnected, limitTime).Find(&o).Error + return +} + func CreateNewSession(o *Session) (err error) { err = global.DB.Create(o).Error return @@ -154,8 +162,24 @@ func UpdateSessionWindowSizeById(width, height int, id string) error { return UpdateSessionById(&session, id) } -func DeleteSessionById(id string) { - global.DB.Where("id = ?", id).Delete(&Session{}) +func DeleteSessionById(id string) error { + return global.DB.Where("id = ?", id).Delete(&Session{}).Error +} + +func DeleteSessionByIds(sessionIds []string) error { + drivePath, err := GetRecordingPath() + if err != nil { + return err + } + for i := range sessionIds { + if err := os.RemoveAll(path.Join(drivePath, sessionIds[i])); err != nil { + return err + } + if err := DeleteSessionById(sessionIds[i]); err != nil { + return err + } + } + return nil } func DeleteSessionByStatus(status string) { diff --git a/screenshot/donate.png b/screenshot/donate.png deleted file mode 100644 index 381a9e0..0000000 Binary files a/screenshot/donate.png and /dev/null differ diff --git a/screenshot/donate_wx.png b/screenshot/donate_wx.png new file mode 100644 index 0000000..be86d0f Binary files /dev/null and b/screenshot/donate_wx.png differ diff --git a/screenshot/donate_zfb.png b/screenshot/donate_zfb.png new file mode 100644 index 0000000..2fe9da0 Binary files /dev/null and b/screenshot/donate_zfb.png differ diff --git a/web/src/components/access/Access.js b/web/src/components/access/Access.js index d748635..4a40dc3 100644 --- a/web/src/components/access/Access.js +++ b/web/src/components/access/Access.js @@ -314,9 +314,6 @@ class Access extends Component { }; onKeyDown = (keysym) => { - // if (this.state.clipboardVisible || this.state.fileSystemVisible) { - // return true; - // } this.state.client.sendKeyEvent(1, keysym); if (keysym === 65288) { return false; @@ -426,6 +423,7 @@ class Access extends Component { const sink = new Guacamole.InputSink(); display.appendChild(sink.getElement()); + sink.focus(); // Keyboard const keyboard = new Guacamole.Keyboard(sink.getElement()); @@ -582,7 +580,7 @@ class Access extends Component { -