package user import ( "crypto/md5" "fmt" "net/http" "net/url" "os" "path/filepath" "strings" model "github.com/cloudreve/Cloudreve/v3/models" "github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/gin-gonic/gin" "github.com/pquerna/otp/totp" ) // SettingService 通用设置服务 type SettingService struct { } // SettingListService 通用设置列表服务 type SettingListService struct { Page int `form:"page" binding:"required,min=1"` } // AvatarService 头像服务 type AvatarService struct { Size string `uri:"size" binding:"required,eq=l|eq=m|eq=s"` } // SettingUpdateService 设定更改服务 type SettingUpdateService struct { Option string `uri:"option" binding:"required,eq=nick|eq=theme|eq=homepage|eq=vip|eq=qq|eq=policy|eq=password|eq=2fa|eq=authn"` } // OptionsChangeHandler 属性更改接口 type OptionsChangeHandler interface { Update(*gin.Context, *model.User) serializer.Response } // ChangerNick 昵称更改服务 type ChangerNick struct { Nick string `json:"nick" binding:"required,min=1,max=255"` } // PolicyChange 更改存储策略 type PolicyChange struct { ID string `json:"id" binding:"required"` } // HomePage 更改个人主页开关 type HomePage struct { Enabled bool `json:"status"` } // PasswordChange 更改密码 type PasswordChange struct { Old string `json:"old" binding:"required,min=4,max=64"` New string `json:"new" binding:"required,min=4,max=64"` } // Enable2FA 开启二步验证 type Enable2FA struct { Code string `json:"code" binding:"required"` } // DeleteWebAuthn 删除WebAuthn凭证 type DeleteWebAuthn struct { ID string `json:"id" binding:"required"` } // ThemeChose 主题选择 type ThemeChose struct { Theme string `json:"theme" binding:"required,hexcolor|rgb|rgba|hsl"` } const avatarMaxAge = 3600 // Update 更新主题设定 func (service *ThemeChose) Update(c *gin.Context, user *model.User) serializer.Response { user.OptionsSerialized.PreferredTheme = service.Theme if err := user.UpdateOptions(); err != nil { return serializer.DBErr("Failed to update user preferences", err) } return serializer.Response{} } // Update 删除凭证 func (service *DeleteWebAuthn) Update(c *gin.Context, user *model.User) serializer.Response { user.RemoveAuthn(service.ID) return serializer.Response{} } // Update 更改二步验证设定 func (service *Enable2FA) Update(c *gin.Context, user *model.User) serializer.Response { if user.TwoFactor == "" { // 开启2FA secret, ok := util.GetSession(c, "2fa_init").(string) if !ok { return serializer.Err(serializer.CodeInternalSetting, "You have not initiated 2FA session", nil) } if !totp.Validate(service.Code, secret) { return serializer.ParamErr("Incorrect 2FA code", nil) } if err := user.Update(map[string]interface{}{"two_factor": secret}); err != nil { return serializer.DBErr("Failed to update user preferences", err) } } else { // 关闭2FA if !totp.Validate(service.Code, user.TwoFactor) { return serializer.ParamErr("Incorrect 2FA code", nil) } if err := user.Update(map[string]interface{}{"two_factor": ""}); err != nil { return serializer.DBErr("Failed to update user preferences", err) } } return serializer.Response{} } // Init2FA 初始化二步验证 func (service *SettingService) Init2FA(c *gin.Context, user *model.User) serializer.Response { key, err := totp.Generate(totp.GenerateOpts{ Issuer: "Cloudreve", AccountName: user.Email, }) if err != nil { return serializer.Err(serializer.CodeInternalSetting, "Failed to generate TOTP secret", err) } util.SetSession(c, map[string]interface{}{"2fa_init": key.Secret()}) return serializer.Response{Data: key.Secret()} } // Update 更改密码 func (service *PasswordChange) Update(c *gin.Context, user *model.User) serializer.Response { // 验证老密码 if ok, _ := user.CheckPassword(service.Old); !ok { return serializer.Err(serializer.CodeIncorrectPassword, "", nil) } // 更改为新密码 user.SetPassword(service.New) if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil { return serializer.DBErr("Failed to update password", err) } return serializer.Response{} } // Update 切换个人主页开关 func (service *HomePage) Update(c *gin.Context, user *model.User) serializer.Response { user.OptionsSerialized.ProfileOff = !service.Enabled if err := user.UpdateOptions(); err != nil { return serializer.DBErr("Failed to update user preferences", err) } return serializer.Response{} } // Update 更改昵称 func (service *ChangerNick) Update(c *gin.Context, user *model.User) serializer.Response { if err := user.Update(map[string]interface{}{"nick": service.Nick}); err != nil { return serializer.DBErr("Failed to update user", err) } return serializer.Response{} } // Get 获取用户头像 func (service *AvatarService) Get(c *gin.Context) serializer.Response { // 查找目标用户 uid, _ := c.Get("object_id") user, err := model.GetActiveUserByID(uid.(uint)) if err != nil { return serializer.Err(serializer.CodeUserNotFound, "", err) } // 未设定头像时,返回404错误 if user.Avatar == "" { c.Status(404) return serializer.Response{} } // 获取头像设置 sizes := map[string]string{ "s": model.GetSettingByName("avatar_size_s"), "m": model.GetSettingByName("avatar_size_m"), "l": model.GetSettingByName("avatar_size_l"), } c.Header("Cache-Control", fmt.Sprintf("max-age=%d", avatarMaxAge)) // Gravatar 头像重定向 if user.Avatar == "gravatar" { server := model.GetSettingByName("gravatar_server") gravatarRoot, err := url.Parse(server) if err != nil { return serializer.Err(serializer.CodeInternalSetting, "Failed to parse Gravatar server", err) } email_lowered := strings.ToLower(user.Email) has := md5.Sum([]byte(email_lowered)) avatar, _ := url.Parse(fmt.Sprintf("/avatar/%x?d=mm&s=%s", has, sizes[service.Size])) return serializer.Response{ Code: -301, Data: gravatarRoot.ResolveReference(avatar).String(), } } // 本地文件头像 if user.Avatar == "file" { avatarRoot := util.RelativePath(model.GetSettingByName("avatar_path")) sizeToInt := map[string]string{ "s": "0", "m": "1", "l": "2", } avatar, err := os.Open(filepath.Join(avatarRoot, fmt.Sprintf("avatar_%d_%s.png", user.ID, sizeToInt[service.Size]))) if err != nil { c.Status(404) return serializer.Response{} } defer avatar.Close() http.ServeContent(c.Writer, c.Request, "avatar.png", user.UpdatedAt, avatar) return serializer.Response{} } c.Status(404) return serializer.Response{} } // ListTasks 列出任务 func (service *SettingListService) ListTasks(c *gin.Context, user *model.User) serializer.Response { tasks, total := model.ListTasks(user.ID, service.Page, 10, "updated_at desc") return serializer.BuildTaskList(tasks, total) } // Settings 获取用户设定 func (service *SettingService) Settings(c *gin.Context, user *model.User) serializer.Response { return serializer.Response{ Data: map[string]interface{}{ "uid": user.ID, "homepage": !user.OptionsSerialized.ProfileOff, "two_factor": user.TwoFactor != "", "prefer_theme": user.OptionsSerialized.PreferredTheme, "themes": model.GetSettingByName("themes"), "authn": serializer.BuildWebAuthnList(user.WebAuthnCredentials()), }, } }