diff --git a/go.mod b/go.mod index cd6e200..84ee37d 100644 --- a/go.mod +++ b/go.mod @@ -26,10 +26,15 @@ require ( github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pkg/errors v0.8.0 + github.com/pquerna/otp v1.2.0 github.com/qingwg/payjs v0.0.0-20190928033402-c53dbe16b371 github.com/qiniu/api.v7/v7 v7.4.0 github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 github.com/robfig/cron/v3 v3.0.1 + github.com/sec51/convert v0.0.0-20190309075348-ebe586d87951 // indirect + github.com/sec51/cryptoengine v0.0.0-20180911112225-2306d105a49e // indirect + github.com/sec51/gf256 v0.0.0-20160126143050-2454accbeb9e // indirect + github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1 // indirect github.com/smartwalle/alipay/v3 v3.0.13 github.com/smartystreets/goconvey v1.6.4 // indirect github.com/speps/go-hashids v2.0.0+incompatible diff --git a/go.sum b/go.sum index e9f02ff..07cfc29 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -155,6 +157,8 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -176,6 +180,18 @@ 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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sec51/convert v0.0.0-20190309075348-ebe586d87951 h1:taE4tHnTci7boZpw+Xozd6KYy8AJ5WZoIuaJhQ8FvqQ= +github.com/sec51/convert v0.0.0-20190309075348-ebe586d87951/go.mod h1:SRX4rc9r9AHj56zHfvY1XBPwhLU0vFZdIX7HaD1C3z8= +github.com/sec51/cryptoengine v0.0.0-20180911112225-2306d105a49e h1:HsNjVWYeVdO/zoSfNBwJOs1PuSQCSCkwqW6Lp1TtZGs= +github.com/sec51/cryptoengine v0.0.0-20180911112225-2306d105a49e/go.mod h1:g7izN9sUffTPdvcrt39y/ZephG5oJ9XizhJxxBOYDL0= +github.com/sec51/gf256 v0.0.0-20160126143050-2454accbeb9e h1:wKXba8dfsFjbxkMpzZBKt8gkJAMSm1fIf1OSWQFQrVA= +github.com/sec51/gf256 v0.0.0-20160126143050-2454accbeb9e/go.mod h1:hCjOqSOB9PBw5MdJ+0uSLCBV7FbLy0xwOR+c193HkcE= +github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1 h1:CI9zS8HvMiibvXM/F3IthY797GW77fNYgioJl/8Xzzk= +github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1/go.mod h1:uPm44Rj3uXSSOvmKmoeRuAUNUgwH2JHW5KIzqFFS/j4= +github.com/sec51/twofactor v1.0.0 h1:1BTbzPhyMyB0YvcWxgNxEkI7WDNsBLvR+z699YWGMC8= +github.com/sec51/twofactor v1.0.0/go.mod h1:CjtKwpvQSs9SYzLUsRH7gML+TgKeIofT8uxoy7RTLQI= +github.com/sec51/twofactor v1.0.1-0.20180911112802-cd97c894b2cc h1:6LobCnVM1FDKXZ8eiOVb4awJf28LjYNk6hydz2FxRBI= +github.com/sec51/twofactor v1.0.1-0.20180911112802-cd97c894b2cc/go.mod h1:CjtKwpvQSs9SYzLUsRH7gML+TgKeIofT8uxoy7RTLQI= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartwalle/alipay v1.0.2 h1:y/uC+6OcVDVxKJ5Bs1qi6aogjDEXwD3XswDXJ5zJurk= github.com/smartwalle/alipay/v3 v3.0.13 h1:f1Cdnxh6TfbaziLw0i/4h+f8tw9RJwG8y4xye7vTTgY= @@ -247,6 +263,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/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= @@ -281,6 +298,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/zhevron/go-twofactor.v1 v1.0.1 h1:OQIgDHcZiRW2ZQC2cJTQ2nI20ryOMnSKJXeg3zW3008= +gopkg.in/zhevron/go-twofactor.v1 v1.0.1/go.mod h1:FiXd57g4Gh9F4MoQ5fjlKhQMiNvaMtFuEuZZE9D3e/I= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/models/user.go b/models/user.go index 7c81046..22c1173 100644 --- a/models/user.go +++ b/models/user.go @@ -301,6 +301,14 @@ func (user *User) Update(val map[string]interface{}) error { return DB.Model(user).Updates(val).Error } +// UpdateOptions 更新用户偏好设定 +func (user *User) UpdateOptions() error { + if err := user.SerializeOptions(); err != nil { + return err + } + return user.Update(map[string]interface{}{"options": user.Options}) +} + // GetGroupExpiredUsers 获取用户组过期的用户 func GetGroupExpiredUsers() []User { var users []User diff --git a/pkg/qq/connect.go b/pkg/qq/connect.go index c534205..a85ec81 100644 --- a/pkg/qq/connect.go +++ b/pkg/qq/connect.go @@ -66,7 +66,7 @@ func NewLoginRequest() (*LoginPage, error) { } func getCallbackURL() string { - return "https://drive.aoaoao.me/Callback/QQ" + //return "https://drive.aoaoao.me/Callback/QQ" // 生成回调地址 gateway, _ := url.Parse("/#/login/qq") callback := model.GetSiteURL().ResolveReference(gateway).String() diff --git a/routers/controllers/user.go b/routers/controllers/user.go index 5a8b16d..7f15f2b 100644 --- a/routers/controllers/user.go +++ b/routers/controllers/user.go @@ -257,6 +257,14 @@ func UpdateOption(c *gin.Context) { subService = &user.VIPUnsubscribe{} case "qq": subService = &user.QQBind{} + case "policy": + subService = &user.PolicyChange{} + case "homepage": + subService = &user.HomePage{} + case "password": + subService = &user.PasswordChange{} + case "2fa": + subService = &user.Enable2FA{} } subErr = c.ShouldBindJSON(subService) @@ -272,3 +280,14 @@ func UpdateOption(c *gin.Context) { c.JSON(200, ErrorResponse(err)) } } + +// UserInit2FA 初始化二步验证 +func UserInit2FA(c *gin.Context) { + var service user.SettingService + if err := c.ShouldBindUri(&service); err == nil { + res := service.Init2FA(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index fb28ed7..fc891c3 100644 --- a/routers/router.go +++ b/routers/router.go @@ -284,6 +284,8 @@ func InitMasterRouter() *gin.Engine { setting.PUT("avatar", controllers.UseGravatar) // 更改用户设定 setting.PATCH(":option", controllers.UpdateOption) + // 获得二步验证初始化信息 + setting.GET("2fa", controllers.UserInit2FA) } } diff --git a/service/callback/upload.go b/service/callback/upload.go index 9cc6722..d7a24a4 100644 --- a/service/callback/upload.go +++ b/service/callback/upload.go @@ -135,7 +135,7 @@ func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer. fileHeader := local.FileStream{ Size: callbackBody.Size, VirtualPath: callbackSession.VirtualPath, - Name: callbackBody.Name, + Name: callbackSession.Name, } // 生成上下文 diff --git a/service/user/setting.go b/service/user/setting.go index db48f9d..6ccb7fc 100644 --- a/service/user/setting.go +++ b/service/user/setting.go @@ -4,10 +4,12 @@ import ( "crypto/md5" "fmt" model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/qq" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-gonic/gin" + "github.com/pquerna/otp/totp" "net/http" "net/url" "os" @@ -31,7 +33,7 @@ type AvatarService struct { // SettingUpdateService 设定更改服务 type SettingUpdateService struct { - Option string `uri:"option" binding:"required,eq=nick|eq=theme|eq=homepage|eq=vip|eq=qq"` + Option string `uri:"option" binding:"required,eq=nick|eq=theme|eq=homepage|eq=vip|eq=qq|eq=policy|eq=password|eq=2fa"` } // OptionsChangeHandler 属性更改接口 @@ -52,6 +54,116 @@ type VIPUnsubscribe struct { type QQBind struct { } +// PolicyChange 更改存储策略 +type PolicyChange struct { + ID string `json:"id" binding:"required"` +} + +// HomePage 更改个人主页开关 +type HomePage struct { + Enabled bool `json:"status"` +} + +// PasswordChange 更改密码 +type PasswordChange struct { + Old string `json:"old" binding:"required,min=4,max=64"` + New string `json:"new" binding:"required,min=4,max=64"` +} + +// Enable2FA 开启二步验证 +type Enable2FA struct { + Code string `json:"code" binding:"required"` +} + +// Update 更改密码 +func (service *Enable2FA) Update(c *gin.Context, user *model.User) serializer.Response { + if user.TwoFactor == "" { + + secret, ok := util.GetSession(c, "2fa_init").(string) + if !ok { + return serializer.Err(serializer.CodeParamErr, "未初始化二步验证", nil) + } + + if !totp.Validate(service.Code, secret) { + return serializer.ParamErr("验证码不正确", nil) + } + + if err := user.Update(map[string]interface{}{"two_factor": secret}); err != nil { + return serializer.DBErr("无法更新二步验证设定", err) + } + + } + + return serializer.Response{} +} + +// Init2FA 初始化二步验证 +func (service *SettingService) Init2FA(c *gin.Context, user *model.User) serializer.Response { + key, err := totp.Generate(totp.GenerateOpts{ + Issuer: "Cloudreve", + AccountName: user.Email, + }) + if err != nil { + return serializer.Err(serializer.CodeInternalSetting, "无法生成验密钥", err) + } + + util.SetSession(c, map[string]interface{}{"2fa_init": key.Secret()}) + return serializer.Response{Data: key.Secret()} +} + +// Update 更改密码 +func (service *PasswordChange) Update(c *gin.Context, user *model.User) serializer.Response { + // 验证老密码 + if ok, _ := user.CheckPassword(service.Old); !ok { + return serializer.Err(serializer.CodeParamErr, "原密码不正确", nil) + } + + // 更改为新密码 + user.SetPassword(service.New) + if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil { + return serializer.DBErr("密码更换失败", err) + } + + return serializer.Response{} +} + +// Update 切换个人主页开关 +func (service *HomePage) Update(c *gin.Context, user *model.User) serializer.Response { + user.OptionsSerialized.ProfileOff = !service.Enabled + if err := user.UpdateOptions(); err != nil { + return serializer.DBErr("存储策略切换失败", err) + } + + return serializer.Response{} +} + +// Update 更改用户偏好的存储策略 +func (service *PolicyChange) Update(c *gin.Context, user *model.User) serializer.Response { + // 取得存储策略的ID + rawID, err := hashid.DecodeHashID(service.ID, hashid.PolicyID) + if err != nil { + return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err) + } + + // 用户是否可以切换到此存储策略 + if !util.ContainsUint(user.Group.PolicyList, rawID) { + return serializer.Err(serializer.CodeNoPermissionErr, "存储策略不可用", nil) + } + + // 查找存储策略 + if _, err := model.GetPolicyByID(rawID); err != nil { + return serializer.Err(serializer.CodeNoPermissionErr, "存储策略不可用", nil) + } + + // 切换存储策略 + user.OptionsSerialized.PreferredPolicy = rawID + if err := user.UpdateOptions(); err != nil { + return serializer.DBErr("存储策略切换失败", err) + } + + return serializer.Response{} +} + // Update 绑定或解绑QQ func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Response { // 解除绑定 @@ -59,7 +171,9 @@ func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Respo if err := user.Update(map[string]interface{}{"open_id": ""}); err != nil { return serializer.DBErr("接触绑定失败", err) } - return serializer.Response{} + return serializer.Response{ + Data: "", + } } // 新建绑定