Feat: 2-FA login verification
This commit is contained in:
parent
11e45bc751
commit
7c07b623f6
6 changed files with 93 additions and 1 deletions
|
@ -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
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{}
|
||||
|
|
Loading…
Add table
Reference in a new issue