diff --git a/models/user.go b/models/user.go index 22c1173..66b8047 100644 --- a/models/user.go +++ b/models/user.go @@ -172,6 +172,13 @@ func GetActiveUserByID(ID interface{}) (User, error) { return user, result.Error } +// GetActiveUserByOpenID 用OpenID获取可登录用户 +func GetActiveUserByOpenID(openid string) (User, error) { + var user User + result := DB.Set("gorm:auto_preload", true).Where("status = ? and open_id = ?", Active, openid).Find(&user) + return user, result.Error +} + // GetUserByEmail 用Email获取用户 func GetUserByEmail(email string) (User, error) { var user User diff --git a/pkg/serializer/user.go b/pkg/serializer/user.go index 6de6e11..a213fb4 100644 --- a/pkg/serializer/user.go +++ b/pkg/serializer/user.go @@ -25,6 +25,7 @@ type User struct { CreatedAt int64 `json:"created_at"` PreferredTheme string `json:"preferred_theme"` Score int `json:"score"` + Anonymous bool `json:"anonymous"` Policy policy `json:"policy"` Group group `json:"group"` Tags []tag `json:"tags"` @@ -97,6 +98,7 @@ func BuildUser(user model.User) User { CreatedAt: user.CreatedAt.Unix(), PreferredTheme: user.OptionsSerialized.PreferredTheme, Score: user.Score, + Anonymous: user.IsAnonymous(), Policy: policy{ SaveType: user.Policy.Type, MaxSize: fmt.Sprintf("%.2fmb", float64(user.Policy.MaxSize)/(1024*1024)), diff --git a/routers/controllers/user.go b/routers/controllers/user.go index d9b2911..34a753b 100644 --- a/routers/controllers/user.go +++ b/routers/controllers/user.go @@ -5,6 +5,7 @@ import ( "fmt" model "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/authn" + "github.com/HFO4/cloudreve/pkg/qq" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/thumb" "github.com/HFO4/cloudreve/pkg/util" @@ -126,6 +127,34 @@ func UserLogin(c *gin.Context) { } else { c.JSON(200, ErrorResponse(err)) } +} + +// User2FALogin 用户二步验证登录 +func User2FALogin(c *gin.Context) { + var service user.Enable2FA + if err := c.ShouldBindJSON(&service); err == nil { + res := service.Login(c) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + +// UserQQLogin 初始化QQ登录 +func UserQQLogin(c *gin.Context) { + // 新建绑定 + res, err := qq.NewLoginRequest() + if err != nil { + c.JSON(200, serializer.Err(serializer.CodeNotSet, "无法使用QQ登录", err)) + return + } + + // 设定QQ登录会话Secret + util.SetSession(c, map[string]interface{}{"qq_login_secret": res.SecretKey}) + + c.JSON(200, serializer.Response{ + Data: res.URL, + }) } diff --git a/routers/router.go b/routers/router.go index 7dc37f2..5349803 100644 --- a/routers/router.go +++ b/routers/router.go @@ -103,6 +103,10 @@ func InitMasterRouter() *gin.Engine { { // 用户登录 user.POST("session", controllers.UserLogin) + // 用户登录 + user.POST("2fa", controllers.User2FALogin) + // 初始化QQ登录 + user.POST("qq", controllers.UserQQLogin) // WebAuthn登陆初始化 user.GET("authn/:username", middleware.IsFunctionEnabled("authn_enabled"), diff --git a/service/user/login.go b/service/user/login.go index 2aad907..73c3791 100644 --- a/service/user/login.go +++ b/service/user/login.go @@ -6,6 +6,7 @@ import ( "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-gonic/gin" "github.com/mojocn/base64Captcha" + "github.com/pquerna/otp/totp" ) // UserLoginService 管理用户登录的服务 @@ -16,6 +17,32 @@ type UserLoginService struct { CaptchaCode string `form:"captchaCode" json:"captchaCode"` } +// Login 二步验证继续登录 +func (service *Enable2FA) Login(c *gin.Context) serializer.Response { + if uid, ok := util.GetSession(c, "2fa_user_id").(uint); ok { + // 查找用户 + expectedUser, err := model.GetActiveUserByID(uid) + if err != nil { + return serializer.Err(serializer.CodeNotFound, "用户不存在", nil) + } + + // 验证二步验证代码 + if !totp.Validate(service.Code, expectedUser.TwoFactor) { + return serializer.ParamErr("验证代码不正确", nil) + } + + //登陆成功,清空并设置session + util.DeleteSession(c, "2fa_user_id") + util.SetSession(c, map[string]interface{}{ + "user_id": expectedUser.ID, + }) + + return serializer.BuildUserResponse(expectedUser) + } + + return serializer.Err(serializer.CodeNotFound, "登录会话不存在", nil) +} + // Login 用户登录函数 func (service *UserLoginService) Login(c *gin.Context) serializer.Response { isCaptchaRequired := model.GetSettingByName("login_captcha") @@ -44,7 +71,11 @@ func (service *UserLoginService) Login(c *gin.Context) serializer.Response { } if expectedUser.TwoFactor != "" { - //TODO 二步验证处理 + // 需要二步验证 + util.SetSession(c, map[string]interface{}{ + "2fa_user_id": expectedUser.ID, + }) + return serializer.Response{Code: 203} } //登陆成功,清空并设置session diff --git a/service/vas/qq.go b/service/vas/qq.go index 2bbe3d2..b5f25b8 100644 --- a/service/vas/qq.go +++ b/service/vas/qq.go @@ -16,11 +16,14 @@ type QQCallbackService struct { // Callback 处理QQ互联回调 func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) serializer.Response { + state := util.GetSession(c, "qq_login_secret") if stateStr, ok := state.(string); !ok || stateStr != service.State { return serializer.Err(serializer.CodeSignExpired, "请求过期,请重试", nil) } + util.DeleteSession(c, "qq_login_secret") + // 获取OpenID credential, err := qq.Callback(service.Code) if err != nil { return serializer.Err(serializer.CodeNotSet, "无法获取登录状态", err) @@ -28,6 +31,7 @@ func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) ser // 如果已登录,则绑定已有用户 if user != nil { + if user.OpenID != "" { return serializer.Err(serializer.CodeCallbackError, "您已绑定了QQ账号,请先解除绑定", nil) } @@ -37,6 +41,21 @@ func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) ser return serializer.Response{ Data: "/setting", } + + } + + // 未登录,尝试查找用户 + if expectedUser, err := model.GetActiveUserByOpenID(credential.OpenID); err == nil { + // 用户绑定了此QQ,设定为登录状态 + util.SetSession(c, map[string]interface{}{ + "user_id": expectedUser.ID, + }) + res := serializer.BuildUserResponse(expectedUser) + res.Code = 203 + return res + + } else { + // 无匹配用户,创建新用户 } return serializer.Response{}