github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/service/user/login.go (about)

     1  package user
     2  
     3  import (
     4  	"fmt"
     5  	model "github.com/cloudreve/Cloudreve/v3/models"
     6  	"github.com/cloudreve/Cloudreve/v3/pkg/auth"
     7  	"github.com/cloudreve/Cloudreve/v3/pkg/cache"
     8  	"github.com/cloudreve/Cloudreve/v3/pkg/email"
     9  	"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
    10  	"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
    11  	"github.com/cloudreve/Cloudreve/v3/pkg/util"
    12  	"github.com/gin-gonic/gin"
    13  	"github.com/gofrs/uuid"
    14  	"github.com/pquerna/otp/totp"
    15  	"net/url"
    16  )
    17  
    18  // UserLoginService 管理用户登录的服务
    19  type UserLoginService struct {
    20  	//TODO 细致调整验证规则
    21  	UserName string `form:"userName" json:"userName" binding:"required,email"`
    22  	Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
    23  }
    24  
    25  // UserResetEmailService 发送密码重设邮件服务
    26  type UserResetEmailService struct {
    27  	UserName string `form:"userName" json:"userName" binding:"required,email"`
    28  }
    29  
    30  // UserResetService 密码重设服务
    31  type UserResetService struct {
    32  	Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
    33  	ID       string `json:"id" binding:"required"`
    34  	Secret   string `json:"secret" binding:"required"`
    35  }
    36  
    37  // Reset 重设密码
    38  func (service *UserResetService) Reset(c *gin.Context) serializer.Response {
    39  	// 取得原始用户ID
    40  	uid, err := hashid.DecodeHashID(service.ID, hashid.UserID)
    41  	if err != nil {
    42  		return serializer.Err(serializer.CodeInvalidTempLink, "Invalid link", err)
    43  	}
    44  
    45  	// 检查重设会话
    46  	resetSession, exist := cache.Get(fmt.Sprintf("user_reset_%d", uid))
    47  	if !exist || resetSession.(string) != service.Secret {
    48  		return serializer.Err(serializer.CodeTempLinkExpired, "Link is expired", err)
    49  	}
    50  
    51  	// 重设用户密码
    52  	user, err := model.GetActiveUserByID(uid)
    53  	if err != nil {
    54  		return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
    55  	}
    56  
    57  	user.SetPassword(service.Password)
    58  	if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil {
    59  		return serializer.DBErr("Failed to reset password", err)
    60  	}
    61  
    62  	cache.Deletes([]string{fmt.Sprintf("%d", uid)}, "user_reset_")
    63  	return serializer.Response{}
    64  }
    65  
    66  // Reset 发送密码重设邮件
    67  func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response {
    68  	// 查找用户
    69  	if user, err := model.GetUserByEmail(service.UserName); err == nil {
    70  
    71  		if user.Status == model.Baned || user.Status == model.OveruseBaned {
    72  			return serializer.Err(serializer.CodeUserBaned, "This user is banned", nil)
    73  		}
    74  		if user.Status == model.NotActivicated {
    75  			return serializer.Err(serializer.CodeUserNotActivated, "This user is not activated", nil)
    76  		}
    77  		// 创建密码重设会话
    78  		secret := util.RandStringRunes(32)
    79  		cache.Set(fmt.Sprintf("user_reset_%d", user.ID), secret, 3600)
    80  
    81  		// 生成用户访问的重设链接
    82  		controller, _ := url.Parse("/reset")
    83  		finalURL := model.GetSiteURL().ResolveReference(controller)
    84  		queries := finalURL.Query()
    85  		queries.Add("id", hashid.HashID(user.ID, hashid.UserID))
    86  		queries.Add("sign", secret)
    87  		finalURL.RawQuery = queries.Encode()
    88  
    89  		// 发送密码重设邮件
    90  		title, body := email.NewResetEmail(user.Nick, finalURL.String())
    91  		if err := email.Send(user.Email, title, body); err != nil {
    92  			return serializer.Err(serializer.CodeFailedSendEmail, "Failed to send email", err)
    93  		}
    94  
    95  	}
    96  
    97  	return serializer.Response{}
    98  }
    99  
   100  // Login 二步验证继续登录
   101  func (service *Enable2FA) Login(c *gin.Context) serializer.Response {
   102  	if uid, ok := util.GetSession(c, "2fa_user_id").(uint); ok {
   103  		// 查找用户
   104  		expectedUser, err := model.GetActiveUserByID(uid)
   105  		if err != nil {
   106  			return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
   107  		}
   108  
   109  		// 验证二步验证代码
   110  		if !totp.Validate(service.Code, expectedUser.TwoFactor) {
   111  			return serializer.Err(serializer.Code2FACodeErr, "2FA code not correct", nil)
   112  		}
   113  
   114  		//登陆成功,清空并设置session
   115  		util.DeleteSession(c, "2fa_user_id")
   116  		util.SetSession(c, map[string]interface{}{
   117  			"user_id": expectedUser.ID,
   118  		})
   119  
   120  		return serializer.BuildUserResponse(expectedUser)
   121  	}
   122  
   123  	return serializer.Err(serializer.CodeLoginSessionNotExist, "Login session not exist", nil)
   124  }
   125  
   126  // Login 用户登录函数
   127  func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
   128  	expectedUser, err := model.GetUserByEmail(service.UserName)
   129  	// 一系列校验
   130  	if err != nil {
   131  		return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", err)
   132  	}
   133  	if authOK, _ := expectedUser.CheckPassword(service.Password); !authOK {
   134  		return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", nil)
   135  	}
   136  	if expectedUser.Status == model.Baned || expectedUser.Status == model.OveruseBaned {
   137  		return serializer.Err(serializer.CodeUserBaned, "This account has been blocked", nil)
   138  	}
   139  	if expectedUser.Status == model.NotActivicated {
   140  		return serializer.Err(serializer.CodeUserNotActivated, "This account is not activated", nil)
   141  	}
   142  
   143  	if expectedUser.TwoFactor != "" {
   144  		// 需要二步验证
   145  		util.SetSession(c, map[string]interface{}{
   146  			"2fa_user_id": expectedUser.ID,
   147  		})
   148  		return serializer.Response{Code: 203}
   149  	}
   150  
   151  	//登陆成功,清空并设置session
   152  	util.SetSession(c, map[string]interface{}{
   153  		"user_id": expectedUser.ID,
   154  	})
   155  
   156  	return serializer.BuildUserResponse(expectedUser)
   157  
   158  }
   159  
   160  // CopySessionService service for copy user session
   161  type CopySessionService struct {
   162  	ID string `uri:"id" binding:"required,uuid4"`
   163  }
   164  
   165  const CopySessionTTL = 60
   166  
   167  // Prepare generates the URL with short expiration duration
   168  func (s *CopySessionService) Prepare(c *gin.Context, user *model.User) serializer.Response {
   169  	// 用户组有效期
   170  	urlID := uuid.Must(uuid.NewV4())
   171  	if err := cache.Set(fmt.Sprintf("copy_session_%s", urlID.String()), user.ID, CopySessionTTL); err != nil {
   172  		return serializer.Err(serializer.CodeInternalSetting, "Failed to create copy session", err)
   173  	}
   174  
   175  	base := model.GetSiteURL()
   176  	apiBaseURI, _ := url.Parse("/api/v3/user/session/copy/" + urlID.String())
   177  	apiURL := base.ResolveReference(apiBaseURI)
   178  	res, err := auth.SignURI(auth.General, apiURL.String(), CopySessionTTL)
   179  	if err != nil {
   180  		return serializer.Err(serializer.CodeInternalSetting, "Failed to sign temp URL", err)
   181  	}
   182  
   183  	return serializer.Response{
   184  		Data: res.String(),
   185  	}
   186  }
   187  
   188  // Copy a new session from active session, refresh max-age
   189  func (s *CopySessionService) Copy(c *gin.Context) serializer.Response {
   190  	// 用户组有效期
   191  	cacheKey := fmt.Sprintf("copy_session_%s", s.ID)
   192  	uid, ok := cache.Get(cacheKey)
   193  	if !ok {
   194  		return serializer.Err(serializer.CodeNotFound, "", nil)
   195  	}
   196  
   197  	cache.Deletes([]string{cacheKey}, "")
   198  	util.SetSession(c, map[string]interface{}{
   199  		"user_id": uid.(uint),
   200  	})
   201  
   202  	return serializer.Response{}
   203  }