mirror of https://github.com/cloudreve/Cloudreve
Feat: 2-FA login verification
parent
11e45bc751
commit
7c07b623f6
|
@ -172,6 +172,13 @@ func GetActiveUserByID(ID interface{}) (User, error) {
|
||||||
return user, result.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获取用户
|
// GetUserByEmail 用Email获取用户
|
||||||
func GetUserByEmail(email string) (User, error) {
|
func GetUserByEmail(email string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
|
|
|
@ -25,6 +25,7 @@ type User struct {
|
||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
PreferredTheme string `json:"preferred_theme"`
|
PreferredTheme string `json:"preferred_theme"`
|
||||||
Score int `json:"score"`
|
Score int `json:"score"`
|
||||||
|
Anonymous bool `json:"anonymous"`
|
||||||
Policy policy `json:"policy"`
|
Policy policy `json:"policy"`
|
||||||
Group group `json:"group"`
|
Group group `json:"group"`
|
||||||
Tags []tag `json:"tags"`
|
Tags []tag `json:"tags"`
|
||||||
|
@ -97,6 +98,7 @@ func BuildUser(user model.User) User {
|
||||||
CreatedAt: user.CreatedAt.Unix(),
|
CreatedAt: user.CreatedAt.Unix(),
|
||||||
PreferredTheme: user.OptionsSerialized.PreferredTheme,
|
PreferredTheme: user.OptionsSerialized.PreferredTheme,
|
||||||
Score: user.Score,
|
Score: user.Score,
|
||||||
|
Anonymous: user.IsAnonymous(),
|
||||||
Policy: policy{
|
Policy: policy{
|
||||||
SaveType: user.Policy.Type,
|
SaveType: user.Policy.Type,
|
||||||
MaxSize: fmt.Sprintf("%.2fmb", float64(user.Policy.MaxSize)/(1024*1024)),
|
MaxSize: fmt.Sprintf("%.2fmb", float64(user.Policy.MaxSize)/(1024*1024)),
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
"github.com/HFO4/cloudreve/pkg/authn"
|
"github.com/HFO4/cloudreve/pkg/authn"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/qq"
|
||||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
"github.com/HFO4/cloudreve/pkg/thumb"
|
"github.com/HFO4/cloudreve/pkg/thumb"
|
||||||
"github.com/HFO4/cloudreve/pkg/util"
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
|
@ -126,6 +127,34 @@ func UserLogin(c *gin.Context) {
|
||||||
} else {
|
} else {
|
||||||
c.JSON(200, ErrorResponse(err))
|
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("session", controllers.UserLogin)
|
||||||
|
// 用户登录
|
||||||
|
user.POST("2fa", controllers.User2FALogin)
|
||||||
|
// 初始化QQ登录
|
||||||
|
user.POST("qq", controllers.UserQQLogin)
|
||||||
// WebAuthn登陆初始化
|
// WebAuthn登陆初始化
|
||||||
user.GET("authn/:username",
|
user.GET("authn/:username",
|
||||||
middleware.IsFunctionEnabled("authn_enabled"),
|
middleware.IsFunctionEnabled("authn_enabled"),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/HFO4/cloudreve/pkg/util"
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mojocn/base64Captcha"
|
"github.com/mojocn/base64Captcha"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserLoginService 管理用户登录的服务
|
// UserLoginService 管理用户登录的服务
|
||||||
|
@ -16,6 +17,32 @@ type UserLoginService struct {
|
||||||
CaptchaCode string `form:"captchaCode" json:"captchaCode"`
|
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 用户登录函数
|
// Login 用户登录函数
|
||||||
func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
|
func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
|
||||||
isCaptchaRequired := model.GetSettingByName("login_captcha")
|
isCaptchaRequired := model.GetSettingByName("login_captcha")
|
||||||
|
@ -44,7 +71,11 @@ func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
if expectedUser.TwoFactor != "" {
|
if expectedUser.TwoFactor != "" {
|
||||||
//TODO 二步验证处理
|
// 需要二步验证
|
||||||
|
util.SetSession(c, map[string]interface{}{
|
||||||
|
"2fa_user_id": expectedUser.ID,
|
||||||
|
})
|
||||||
|
return serializer.Response{Code: 203}
|
||||||
}
|
}
|
||||||
|
|
||||||
//登陆成功,清空并设置session
|
//登陆成功,清空并设置session
|
||||||
|
|
|
@ -16,11 +16,14 @@ type QQCallbackService struct {
|
||||||
|
|
||||||
// Callback 处理QQ互联回调
|
// Callback 处理QQ互联回调
|
||||||
func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) serializer.Response {
|
func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) serializer.Response {
|
||||||
|
|
||||||
state := util.GetSession(c, "qq_login_secret")
|
state := util.GetSession(c, "qq_login_secret")
|
||||||
if stateStr, ok := state.(string); !ok || stateStr != service.State {
|
if stateStr, ok := state.(string); !ok || stateStr != service.State {
|
||||||
return serializer.Err(serializer.CodeSignExpired, "请求过期,请重试", nil)
|
return serializer.Err(serializer.CodeSignExpired, "请求过期,请重试", nil)
|
||||||
}
|
}
|
||||||
|
util.DeleteSession(c, "qq_login_secret")
|
||||||
|
|
||||||
|
// 获取OpenID
|
||||||
credential, err := qq.Callback(service.Code)
|
credential, err := qq.Callback(service.Code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serializer.Err(serializer.CodeNotSet, "无法获取登录状态", err)
|
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 != nil {
|
||||||
|
|
||||||
if user.OpenID != "" {
|
if user.OpenID != "" {
|
||||||
return serializer.Err(serializer.CodeCallbackError, "您已绑定了QQ账号,请先解除绑定", nil)
|
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{
|
return serializer.Response{
|
||||||
Data: "/setting",
|
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{}
|
return serializer.Response{}
|
||||||
|
|
Loading…
Reference in New Issue