diff --git a/models/migration.go b/models/migration.go index 7c9fb2c..87fc173 100644 --- a/models/migration.go +++ b/models/migration.go @@ -83,8 +83,8 @@ func addDefaultSettings() { {Name: "siteURL", Value: ``, Type: "basic"}, {Name: "siteName", Value: `Cloudreve`, Type: "basic"}, {Name: "siteStatus", Value: `open`, Type: "basic"}, - {Name: "regStatus", Value: `0`, Type: "register"}, - {Name: "defaultGroup", Value: `3`, Type: "register"}, + {Name: "register_enabled", Value: `1`, Type: "register"}, + {Name: "default_group", Value: `2`, Type: "register"}, {Name: "siteKeywords", Value: `网盘,网盘`, Type: "basic"}, {Name: "siteDes", Value: `Cloudreve`, 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 { - DB.Where(Setting{Name: value.Name}).FirstOrCreate(&value) + DB.Where(Setting{Name: value.Name}).Create(&value) } } diff --git a/models/user.go b/models/user.go index 66b8047..b56df9b 100644 --- a/models/user.go +++ b/models/user.go @@ -31,11 +31,9 @@ type User struct { Password string `json:"-"` Status int GroupID uint - ActivationKey string `json:"-"` Storage uint64 OpenID string `json:"-"` TwoFactor string `json:"-"` - Delay int Avatar string Options string `json:"-",gorm:"type:text"` Authn string `gorm:"type:text"` @@ -55,8 +53,8 @@ type User struct { // UserOption 用户个性化配置字段 type UserOption struct { ProfileOff bool `json:"profile_off,omitempty"` - PreferredPolicy uint `json:"preferred_policy"` - PreferredTheme string `json:"preferred_theme"` + PreferredPolicy uint `json:"preferred_policy,omitempty"` + PreferredTheme string `json:"preferred_theme,omitempty"` } // Root 获取用户的根目录 @@ -190,7 +188,6 @@ func GetUserByEmail(email string) (User, error) { func NewUser() User { options := UserOption{} return User{ - Avatar: "default", OptionsSerialized: options, } } diff --git a/pkg/email/template.go b/pkg/email/template.go index 809336c..ab327a7 100644 --- a/pkg/email/template.go +++ b/pkg/email/template.go @@ -19,3 +19,17 @@ func NewOveruseNotification(userName, reason string) (string, string) { return fmt.Sprintf("【%s】空间容量超额提醒", options["siteName"]), 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"]) +} diff --git a/pkg/util/session.go b/pkg/util/session.go index ffc00e9..705eee1 100644 --- a/pkg/util/session.go +++ b/pkg/util/session.go @@ -21,7 +21,6 @@ func SetSession(c *gin.Context, list map[string]interface{}) { // GetSession 获取session func GetSession(c *gin.Context, key string) interface{} { s := sessions.Default(c) - Log().Debug("Key:%s Val:%s", key, s.Get(key)) return s.Get(key) } diff --git a/routers/controllers/user.go b/routers/controllers/user.go index 34a753b..83ecbba 100644 --- a/routers/controllers/user.go +++ b/routers/controllers/user.go @@ -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 用户二步验证登录 func User2FALogin(c *gin.Context) { var service user.Enable2FA diff --git a/routers/router.go b/routers/router.go index 5349803..5e501fc 100644 --- a/routers/router.go +++ b/routers/router.go @@ -103,6 +103,8 @@ func InitMasterRouter() *gin.Engine { { // 用户登录 user.POST("session", controllers.UserLogin) + // 用户注册 + user.POST("", middleware.IsFunctionEnabled("register_enabled"), controllers.UserRegister) // 用户登录 user.POST("2fa", controllers.User2FALogin) // 初始化QQ登录 diff --git a/service/user/login.go b/service/user/login.go index 73c3791..42eaa9a 100644 --- a/service/user/login.go +++ b/service/user/login.go @@ -51,6 +51,7 @@ func (service *UserLoginService) Login(c *gin.Context) serializer.Response { if model.IsTrueVal(isCaptchaRequired) { // TODO 验证码校验 captchaID := util.GetSession(c, "captchaID") + util.DeleteSession(c, "captchaID") if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) { return serializer.ParamErr("验证码错误", nil) } diff --git a/service/user/register.go b/service/user/register.go new file mode 100644 index 0000000..54d12e0 --- /dev/null +++ b/service/user/register.go @@ -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{} +}