mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-11-04 13:16:02 +08:00 
			
		
		
		
	Feat: user register / send activate email
This commit is contained in:
		@ -83,8 +83,8 @@ func addDefaultSettings() {
 | 
				
			|||||||
		{Name: "siteURL", Value: ``, Type: "basic"},
 | 
							{Name: "siteURL", Value: ``, Type: "basic"},
 | 
				
			||||||
		{Name: "siteName", Value: `Cloudreve`, Type: "basic"},
 | 
							{Name: "siteName", Value: `Cloudreve`, Type: "basic"},
 | 
				
			||||||
		{Name: "siteStatus", Value: `open`, Type: "basic"},
 | 
							{Name: "siteStatus", Value: `open`, Type: "basic"},
 | 
				
			||||||
		{Name: "regStatus", Value: `0`, Type: "register"},
 | 
							{Name: "register_enabled", Value: `1`, Type: "register"},
 | 
				
			||||||
		{Name: "defaultGroup", Value: `3`, Type: "register"},
 | 
							{Name: "default_group", Value: `2`, Type: "register"},
 | 
				
			||||||
		{Name: "siteKeywords", Value: `网盘,网盘`, Type: "basic"},
 | 
							{Name: "siteKeywords", Value: `网盘,网盘`, Type: "basic"},
 | 
				
			||||||
		{Name: "siteDes", Value: `Cloudreve`, Type: "basic"},
 | 
							{Name: "siteDes", Value: `Cloudreve`, Type: "basic"},
 | 
				
			||||||
		{Name: "siteTitle", Value: `平步云端`, Type: "basic"},
 | 
							{Name: "siteTitle", Value: `平步云端`, Type: "basic"},
 | 
				
			||||||
@ -177,7 +177,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, value := range defaultSettings {
 | 
						for _, value := range defaultSettings {
 | 
				
			||||||
		DB.Where(Setting{Name: value.Name}).FirstOrCreate(&value)
 | 
							DB.Where(Setting{Name: value.Name}).Create(&value)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -31,11 +31,9 @@ type User struct {
 | 
				
			|||||||
	Password        string `json:"-"`
 | 
						Password        string `json:"-"`
 | 
				
			||||||
	Status          int
 | 
						Status          int
 | 
				
			||||||
	GroupID         uint
 | 
						GroupID         uint
 | 
				
			||||||
	ActivationKey   string `json:"-"`
 | 
					 | 
				
			||||||
	Storage         uint64
 | 
						Storage         uint64
 | 
				
			||||||
	OpenID          string `json:"-"`
 | 
						OpenID          string `json:"-"`
 | 
				
			||||||
	TwoFactor       string `json:"-"`
 | 
						TwoFactor       string `json:"-"`
 | 
				
			||||||
	Delay           int
 | 
					 | 
				
			||||||
	Avatar          string
 | 
						Avatar          string
 | 
				
			||||||
	Options         string `json:"-",gorm:"type:text"`
 | 
						Options         string `json:"-",gorm:"type:text"`
 | 
				
			||||||
	Authn           string `gorm:"type:text"`
 | 
						Authn           string `gorm:"type:text"`
 | 
				
			||||||
@ -55,8 +53,8 @@ type User struct {
 | 
				
			|||||||
// UserOption 用户个性化配置字段
 | 
					// UserOption 用户个性化配置字段
 | 
				
			||||||
type UserOption struct {
 | 
					type UserOption struct {
 | 
				
			||||||
	ProfileOff      bool   `json:"profile_off,omitempty"`
 | 
						ProfileOff      bool   `json:"profile_off,omitempty"`
 | 
				
			||||||
	PreferredPolicy uint   `json:"preferred_policy"`
 | 
						PreferredPolicy uint   `json:"preferred_policy,omitempty"`
 | 
				
			||||||
	PreferredTheme  string `json:"preferred_theme"`
 | 
						PreferredTheme  string `json:"preferred_theme,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Root 获取用户的根目录
 | 
					// Root 获取用户的根目录
 | 
				
			||||||
@ -190,7 +188,6 @@ func GetUserByEmail(email string) (User, error) {
 | 
				
			|||||||
func NewUser() User {
 | 
					func NewUser() User {
 | 
				
			||||||
	options := UserOption{}
 | 
						options := UserOption{}
 | 
				
			||||||
	return User{
 | 
						return User{
 | 
				
			||||||
		Avatar:            "default",
 | 
					 | 
				
			||||||
		OptionsSerialized: options,
 | 
							OptionsSerialized: options,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -19,3 +19,17 @@ func NewOveruseNotification(userName, reason string) (string, string) {
 | 
				
			|||||||
	return fmt.Sprintf("【%s】空间容量超额提醒", options["siteName"]),
 | 
						return fmt.Sprintf("【%s】空间容量超额提醒", options["siteName"]),
 | 
				
			||||||
		util.Replace(replace, options["over_used_template"])
 | 
							util.Replace(replace, options["over_used_template"])
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewActivationEmail 新建激活邮件
 | 
				
			||||||
 | 
					func NewActivationEmail(userName, activateURL string) (string, string) {
 | 
				
			||||||
 | 
						options := model.GetSettingByNames("siteName", "siteURL", "siteTitle", "mail_activation_template")
 | 
				
			||||||
 | 
						replace := map[string]string{
 | 
				
			||||||
 | 
							"{siteTitle}":     options["siteName"],
 | 
				
			||||||
 | 
							"{userName}":      userName,
 | 
				
			||||||
 | 
							"{activationUrl}": activateURL,
 | 
				
			||||||
 | 
							"{siteUrl}":       options["siteURL"],
 | 
				
			||||||
 | 
							"{siteSecTitle}":  options["siteTitle"],
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fmt.Sprintf("【%s】注册激活", options["siteName"]),
 | 
				
			||||||
 | 
							util.Replace(replace, options["mail_activation_template"])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,6 @@ func SetSession(c *gin.Context, list map[string]interface{}) {
 | 
				
			|||||||
// GetSession 获取session
 | 
					// GetSession 获取session
 | 
				
			||||||
func GetSession(c *gin.Context, key string) interface{} {
 | 
					func GetSession(c *gin.Context, key string) interface{} {
 | 
				
			||||||
	s := sessions.Default(c)
 | 
						s := sessions.Default(c)
 | 
				
			||||||
	Log().Debug("Key:%s Val:%s", key, s.Get(key))
 | 
					 | 
				
			||||||
	return s.Get(key)
 | 
						return s.Get(key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -129,6 +129,17 @@ func UserLogin(c *gin.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserRegister 用户注册
 | 
				
			||||||
 | 
					func UserRegister(c *gin.Context) {
 | 
				
			||||||
 | 
						var service user.UserRegisterService
 | 
				
			||||||
 | 
						if err := c.ShouldBindJSON(&service); err == nil {
 | 
				
			||||||
 | 
							res := service.Register(c)
 | 
				
			||||||
 | 
							c.JSON(200, res)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							c.JSON(200, ErrorResponse(err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// User2FALogin 用户二步验证登录
 | 
					// User2FALogin 用户二步验证登录
 | 
				
			||||||
func User2FALogin(c *gin.Context) {
 | 
					func User2FALogin(c *gin.Context) {
 | 
				
			||||||
	var service user.Enable2FA
 | 
						var service user.Enable2FA
 | 
				
			||||||
 | 
				
			|||||||
@ -103,6 +103,8 @@ func InitMasterRouter() *gin.Engine {
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			// 用户登录
 | 
								// 用户登录
 | 
				
			||||||
			user.POST("session", controllers.UserLogin)
 | 
								user.POST("session", controllers.UserLogin)
 | 
				
			||||||
 | 
								// 用户注册
 | 
				
			||||||
 | 
								user.POST("", middleware.IsFunctionEnabled("register_enabled"), controllers.UserRegister)
 | 
				
			||||||
			// 用户登录
 | 
								// 用户登录
 | 
				
			||||||
			user.POST("2fa", controllers.User2FALogin)
 | 
								user.POST("2fa", controllers.User2FALogin)
 | 
				
			||||||
			// 初始化QQ登录
 | 
								// 初始化QQ登录
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,7 @@ func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
 | 
				
			|||||||
	if model.IsTrueVal(isCaptchaRequired) {
 | 
						if model.IsTrueVal(isCaptchaRequired) {
 | 
				
			||||||
		// TODO 验证码校验
 | 
							// TODO 验证码校验
 | 
				
			||||||
		captchaID := util.GetSession(c, "captchaID")
 | 
							captchaID := util.GetSession(c, "captchaID")
 | 
				
			||||||
 | 
							util.DeleteSession(c, "captchaID")
 | 
				
			||||||
		if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
 | 
							if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
 | 
				
			||||||
			return serializer.ParamErr("验证码错误", nil)
 | 
								return serializer.ParamErr("验证码错误", nil)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										93
									
								
								service/user/register.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								service/user/register.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					package user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						model "github.com/HFO4/cloudreve/models"
 | 
				
			||||||
 | 
						"github.com/HFO4/cloudreve/pkg/auth"
 | 
				
			||||||
 | 
						"github.com/HFO4/cloudreve/pkg/email"
 | 
				
			||||||
 | 
						"github.com/HFO4/cloudreve/pkg/hashid"
 | 
				
			||||||
 | 
						"github.com/HFO4/cloudreve/pkg/serializer"
 | 
				
			||||||
 | 
						"github.com/HFO4/cloudreve/pkg/util"
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/mojocn/base64Captcha"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UserRegisterService 管理用户注册的服务
 | 
				
			||||||
 | 
					type UserRegisterService struct {
 | 
				
			||||||
 | 
						//TODO 细致调整验证规则
 | 
				
			||||||
 | 
						UserName    string `form:"userName" json:"userName" binding:"required,email"`
 | 
				
			||||||
 | 
						Password    string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
 | 
				
			||||||
 | 
						CaptchaCode string `form:"captchaCode" json:"captchaCode"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Register 新用户注册
 | 
				
			||||||
 | 
					func (service *UserRegisterService) Register(c *gin.Context) serializer.Response {
 | 
				
			||||||
 | 
						// 相关设定
 | 
				
			||||||
 | 
						options := model.GetSettingByNames("email_active", "reg_captcha")
 | 
				
			||||||
 | 
						// 检查验证码
 | 
				
			||||||
 | 
						isCaptchaRequired := model.IsTrueVal(options["reg_captcha"])
 | 
				
			||||||
 | 
						if isCaptchaRequired {
 | 
				
			||||||
 | 
							captchaID := util.GetSession(c, "captchaID")
 | 
				
			||||||
 | 
							util.DeleteSession(c, "captchaID")
 | 
				
			||||||
 | 
							if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
 | 
				
			||||||
 | 
								return serializer.ParamErr("验证码错误", nil)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 相关设定
 | 
				
			||||||
 | 
						isEmailRequired := model.IsTrueVal(options["email_active"])
 | 
				
			||||||
 | 
						defaultGroup := model.GetIntSetting("default_group", 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 创建新的用户对象
 | 
				
			||||||
 | 
						user := model.NewUser()
 | 
				
			||||||
 | 
						user.Email = service.UserName
 | 
				
			||||||
 | 
						user.Nick = strings.Split(service.UserName, "@")[0]
 | 
				
			||||||
 | 
						user.SetPassword(service.Password)
 | 
				
			||||||
 | 
						user.Status = model.Active
 | 
				
			||||||
 | 
						if isEmailRequired {
 | 
				
			||||||
 | 
							user.Status = model.NotActivicated
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user.GroupID = uint(defaultGroup)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 创建用户
 | 
				
			||||||
 | 
						if err := model.DB.Create(&user).Error; err != nil {
 | 
				
			||||||
 | 
							return serializer.DBErr("此邮箱已被使用", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 发送激活邮件
 | 
				
			||||||
 | 
						if isEmailRequired {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 签名激活请求API
 | 
				
			||||||
 | 
							base := model.GetSiteURL()
 | 
				
			||||||
 | 
							userID := hashid.HashID(user.ID, hashid.UserID)
 | 
				
			||||||
 | 
							controller, _ := url.Parse("/api/v3/user/activate/" + userID)
 | 
				
			||||||
 | 
							activateURL, err := auth.SignURI(auth.General, base.ResolveReference(controller).String(), 86400)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return serializer.Err(serializer.CodeEncryptError, "无法签名激活URL", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 取得签名
 | 
				
			||||||
 | 
							credential := activateURL.Query().Get("sign")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 生成对用户访问的激活地址
 | 
				
			||||||
 | 
							controller, _ = url.Parse("/activate")
 | 
				
			||||||
 | 
							finalURL := base.ResolveReference(controller)
 | 
				
			||||||
 | 
							queries := finalURL.Query()
 | 
				
			||||||
 | 
							queries.Add("id", userID)
 | 
				
			||||||
 | 
							queries.Add("sign", credential)
 | 
				
			||||||
 | 
							finalURL.RawQuery = queries.Encode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 返送激活邮件
 | 
				
			||||||
 | 
							title, body := email.NewActivationEmail(user.Email,
 | 
				
			||||||
 | 
								strings.ReplaceAll(finalURL.String(), "/activate", "/#/activate"),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							if err := email.Send(user.Email, title, body); err != nil {
 | 
				
			||||||
 | 
								return serializer.Err(serializer.CodeInternalSetting, "无法发送激活邮件", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return serializer.Response{Code: 203}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return serializer.Response{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user