diff --git a/go.mod b/go.mod index be4b43c..db596f1 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/antonfisher/nested-logrus-formatter v1.3.0 github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 + github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/labstack/echo/v4 v4.1.17 github.com/labstack/gommon v0.3.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index ffa85e4..ede144e 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -151,9 +153,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -182,16 +182,12 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= -github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -215,7 +211,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -300,7 +295,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -308,7 +302,6 @@ golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirS golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -354,7 +347,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/api/user.go b/pkg/api/user.go index a7bf5a3..157c2cf 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -14,10 +14,11 @@ func UserCreateEndpoint(c echo.Context) error { if err := c.Bind(&item); err != nil { return err } + password := item.Password var pass []byte var err error - if pass, err = utils.Encoder.Encode([]byte(item.Password)); err != nil { + if pass, err = utils.Encoder.Encode([]byte(password)); err != nil { return err } item.Password = string(pass) @@ -28,6 +29,10 @@ func UserCreateEndpoint(c echo.Context) error { if err := model.CreateNewUser(&item); err != nil { return err } + + if item.Mail != "" { + go model.SendMail(item.Mail, "[Next Terminal] 注册通知", "你好,"+item.Nickname+"。管理员为你注册了账号:"+item.Username+" 密码:"+password) + } return Success(c, item) } @@ -36,11 +41,12 @@ func UserPagingEndpoint(c echo.Context) error { pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) username := c.QueryParam("username") nickname := c.QueryParam("nickname") + mail := c.QueryParam("mail") order := c.QueryParam("order") field := c.QueryParam("field") - items, total, err := model.FindPageUser(pageIndex, pageSize, username, nickname, order, field) + items, total, err := model.FindPageUser(pageIndex, pageSize, username, nickname, mail, order, field) if err != nil { return err } @@ -109,6 +115,11 @@ func UserChangePasswordEndpoint(c echo.Context) error { id := c.Param("id") password := c.QueryParam("password") + user, err := model.FindUserById(id) + if err != nil { + return err + } + passwd, err := utils.Encoder.Encode([]byte(password)) if err != nil { return err @@ -117,6 +128,11 @@ func UserChangePasswordEndpoint(c echo.Context) error { Password: string(passwd), } model.UpdateUserById(u, id) + + if user.Mail != "" { + go model.SendMail(user.Mail, "[Next Terminal] 密码修改通知", "你好,"+user.Nickname+"。管理员已将你的密码修改为:"+password) + } + return Success(c, "") } diff --git a/pkg/model/property.go b/pkg/model/property.go index 6cb749c..f5a8294 100644 --- a/pkg/model/property.go +++ b/pkg/model/property.go @@ -1,12 +1,19 @@ package model import ( + "github.com/jordan-wright/email" + "github.com/sirupsen/logrus" + "net/smtp" "next-terminal/pkg/global" "next-terminal/pkg/guacd" ) const ( - SshMode = "ssh-mode" + SshMode = "ssh-mode" + MailHost = "mail-host" + MailPort = "mail-port" + MailUsername = "mail-username" + MailPassword = "mail-password" ) type Property struct { @@ -64,3 +71,26 @@ func GetRecordingPath() (string, error) { } return property.Value, nil } + +func SendMail(to, subject, text string) { + propertiesMap := FindAllPropertiesMap() + host := propertiesMap[MailHost] + port := propertiesMap[MailPort] + username := propertiesMap[MailUsername] + password := propertiesMap[MailPassword] + + if host == "" || port == "" || username == "" || password == "" { + logrus.Debugf("邮箱信息不完整,跳过发送邮件。") + return + } + + e := email.NewEmail() + e.From = "Next Terminal <" + username + ">" + e.To = []string{to} + e.Subject = subject + e.Text = []byte(text) + err := e.Send(host+":"+port, smtp.PlainAuth("", username, password, host)) + if err != nil { + logrus.Errorf("邮件发送失败: %v", err.Error()) + } +} diff --git a/pkg/model/user.go b/pkg/model/user.go index 6f13533..14825f0 100644 --- a/pkg/model/user.go +++ b/pkg/model/user.go @@ -28,6 +28,8 @@ type UserVo struct { ID string `json:"id"` Username string `json:"username"` Nickname string `json:"nickname"` + TOTPSecret string `json:"totpSecret"` + Mail string `json:"mail"` Online bool `json:"online"` Enabled bool `json:"enabled"` Created utils.JsonTime `json:"created"` @@ -50,8 +52,8 @@ func FindAllUser() (o []User) { return } -func FindPageUser(pageIndex, pageSize int, username, nickname, order, field string) (o []UserVo, total int64, err error) { - db := global.DB.Table("users").Select("users.id,users.username,users.nickname,users.online,users.enabled,users.created,users.type, count(resource_sharers.user_id) as sharer_asset_count").Joins("left join resource_sharers on users.id = resource_sharers.user_id and resource_sharers.resource_type = 'asset'").Group("users.id") +func FindPageUser(pageIndex, pageSize int, username, nickname, mail, order, field string) (o []UserVo, total int64, err error) { + db := global.DB.Table("users").Select("users.id,users.username,users.nickname,users.mail,users.online,users.enabled,users.created,users.type, count(resource_sharers.user_id) as sharer_asset_count, users.totp_secret").Joins("left join resource_sharers on users.id = resource_sharers.user_id and resource_sharers.resource_type = 'asset'").Group("users.id") dbCounter := global.DB.Table("users") if len(username) > 0 { db = db.Where("users.username like ?", "%"+username+"%") @@ -63,6 +65,11 @@ func FindPageUser(pageIndex, pageSize int, username, nickname, order, field stri dbCounter = dbCounter.Where("nickname like ?", "%"+nickname+"%") } + if len(mail) > 0 { + db = db.Where("users.mail like ?", "%"+mail+"%") + dbCounter = dbCounter.Where("mail like ?", "%"+mail+"%") + } + err = dbCounter.Count(&total).Error if err != nil { return nil, 0, err @@ -86,6 +93,14 @@ func FindPageUser(pageIndex, pageSize int, username, nickname, order, field stri if o == nil { o = make([]UserVo, 0) } + + for i := 0; i < len(o); i++ { + if o[i].TOTPSecret == "" || o[i].TOTPSecret == "-" { + o[i].TOTPSecret = "0" + } else { + o[i].TOTPSecret = "1" + } + } return } diff --git a/web/src/components/session/LoginLog.js b/web/src/components/session/LoginLog.js index 021bf3d..1d28aae 100644 --- a/web/src/components/session/LoginLog.js +++ b/web/src/components/session/LoginLog.js @@ -318,6 +318,7 @@ class LoginLog extends Component { onSearch={this.handleSearchByNickname} onChange={this.handleChangeByUserId} filterOption={false} + allowClear > {userOptions} diff --git a/web/src/components/session/OfflineSession.js b/web/src/components/session/OfflineSession.js index b795112..f6ec99b 100644 --- a/web/src/components/session/OfflineSession.js +++ b/web/src/components/session/OfflineSession.js @@ -379,6 +379,7 @@ class OfflineSession extends Component { onSearch={this.handleSearchByNickname} onChange={this.handleChangeByUserId} filterOption={false} + allowClear > {userOptions} diff --git a/web/src/components/session/OnlineSession.js b/web/src/components/session/OnlineSession.js index 22f7a0e..ba90f3e 100644 --- a/web/src/components/session/OnlineSession.js +++ b/web/src/components/session/OnlineSession.js @@ -374,6 +374,7 @@ class OnlineSession extends Component { onSearch={this.handleSearchByNickname} onChange={this.handleChangeByUserId} filterOption={false} + allowClear > {userOptions} diff --git a/web/src/components/setting/Setting.js b/web/src/components/setting/Setting.js index b7d5186..bf9f714 100644 --- a/web/src/components/setting/Setting.js +++ b/web/src/components/setting/Setting.js @@ -42,6 +42,7 @@ class Setting extends Component { sshSettingFormRef = React.createRef(); vncSettingFormRef = React.createRef(); otherSettingFormRef = React.createRef(); + mailSettingFormRef = React.createRef(); componentDidMount() { this.getProperties(); @@ -98,6 +99,10 @@ class Setting extends Component { if (this.otherSettingFormRef.current) { this.otherSettingFormRef.current.setFieldsValue(properties) } + + if (this.mailSettingFormRef.current) { + this.mailSettingFormRef.current.setFieldsValue(properties) + } } else { message.error(result['message']); } @@ -561,6 +566,77 @@ class Setting extends Component { +