You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudreve/service/user/login.go

204 lines
6.6 KiB

5 years ago
package user
import (
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/email"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"github.com/pquerna/otp/totp"
"net/url"
5 years ago
)
// UserLoginService 管理用户登录的服务
type UserLoginService struct {
//TODO 细致调整验证规则
UserName string `form:"userName" json:"userName" binding:"required,email"`
Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
5 years ago
}
// UserResetEmailService 发送密码重设邮件服务
type UserResetEmailService struct {
UserName string `form:"userName" json:"userName" binding:"required,email"`
}
// UserResetService 密码重设服务
type UserResetService struct {
Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
ID string `json:"id" binding:"required"`
Secret string `json:"secret" binding:"required"`
}
// Reset 重设密码
func (service *UserResetService) Reset(c *gin.Context) serializer.Response {
// 取得原始用户ID
uid, err := hashid.DecodeHashID(service.ID, hashid.UserID)
if err != nil {
return serializer.Err(serializer.CodeInvalidTempLink, "Invalid link", err)
}
// 检查重设会话
resetSession, exist := cache.Get(fmt.Sprintf("user_reset_%d", uid))
if !exist || resetSession.(string) != service.Secret {
return serializer.Err(serializer.CodeTempLinkExpired, "Link is expired", err)
}
// 重设用户密码
user, err := model.GetActiveUserByID(uid)
if err != nil {
return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
}
user.SetPassword(service.Password)
if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil {
return serializer.DBErr("Failed to reset password", err)
}
cache.Deletes([]string{fmt.Sprintf("%d", uid)}, "user_reset_")
return serializer.Response{}
}
// Reset 发送密码重设邮件
func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response {
// 查找用户
if user, err := model.GetUserByEmail(service.UserName); err == nil {
if user.Status == model.Baned || user.Status == model.OveruseBaned {
return serializer.Err(serializer.CodeUserBaned, "This user is banned", nil)
}
if user.Status == model.NotActivicated {
return serializer.Err(serializer.CodeUserNotActivated, "This user is not activated", nil)
}
// 创建密码重设会话
secret := util.RandStringRunes(32)
cache.Set(fmt.Sprintf("user_reset_%d", user.ID), secret, 3600)
// 生成用户访问的重设链接
controller, _ := url.Parse("/reset")
finalURL := model.GetSiteURL().ResolveReference(controller)
queries := finalURL.Query()
queries.Add("id", hashid.HashID(user.ID, hashid.UserID))
queries.Add("sign", secret)
finalURL.RawQuery = queries.Encode()
// 发送密码重设邮件
title, body := email.NewResetEmail(user.Nick, finalURL.String())
if err := email.Send(user.Email, title, body); err != nil {
return serializer.Err(serializer.CodeFailedSendEmail, "Failed to send email", err)
}
}
return serializer.Response{}
}
// 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.CodeUserNotFound, "User not found", nil)
}
// 验证二步验证代码
if !totp.Validate(service.Code, expectedUser.TwoFactor) {
return serializer.Err(serializer.Code2FACodeErr, "2FA code not correct", 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.CodeLoginSessionNotExist, "Login session not exist", nil)
}
5 years ago
// Login 用户登录函数
func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
expectedUser, err := model.GetUserByEmail(service.UserName)
// 一系列校验
if err != nil {
return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", err)
5 years ago
}
if authOK, _ := expectedUser.CheckPassword(service.Password); !authOK {
return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", nil)
5 years ago
}
if expectedUser.Status == model.Baned || expectedUser.Status == model.OveruseBaned {
return serializer.Err(serializer.CodeUserBaned, "This account has been blocked", nil)
5 years ago
}
if expectedUser.Status == model.NotActivicated {
return serializer.Err(serializer.CodeUserNotActivated, "This account is not activated", nil)
5 years ago
}
if expectedUser.TwoFactor != "" {
// 需要二步验证
util.SetSession(c, map[string]interface{}{
"2fa_user_id": expectedUser.ID,
})
return serializer.Response{Code: 203}
5 years ago
}
//登陆成功清空并设置session
util.SetSession(c, map[string]interface{}{
5 years ago
"user_id": expectedUser.ID,
})
5 years ago
return serializer.BuildUserResponse(expectedUser)
5 years ago
}
// CopySessionService service for copy user session
type CopySessionService struct {
ID string `uri:"id" binding:"required,uuid4"`
}
const CopySessionTTL = 60
// Prepare generates the URL with short expiration duration
func (s *CopySessionService) Prepare(c *gin.Context, user *model.User) serializer.Response {
// 用户组有效期
urlID := uuid.Must(uuid.NewV4())
if err := cache.Set(fmt.Sprintf("copy_session_%s", urlID.String()), user.ID, CopySessionTTL); err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to create copy session", err)
}
base := model.GetSiteURL()
apiBaseURI, _ := url.Parse("/api/v3/user/session/copy/" + urlID.String())
apiURL := base.ResolveReference(apiBaseURI)
res, err := auth.SignURI(auth.General, apiURL.String(), CopySessionTTL)
if err != nil {
return serializer.Err(serializer.CodeInternalSetting, "Failed to sign temp URL", err)
}
return serializer.Response{
Data: res.String(),
}
}
// Copy a new session from active session, refresh max-age
func (s *CopySessionService) Copy(c *gin.Context) serializer.Response {
// 用户组有效期
cacheKey := fmt.Sprintf("copy_session_%s", s.ID)
uid, ok := cache.Get(cacheKey)
if !ok {
return serializer.Err(serializer.CodeNotFound, "", nil)
}
cache.Deletes([]string{cacheKey}, "")
util.SetSession(c, map[string]interface{}{
"user_id": uid.(uint),
})
return serializer.Response{}
}