github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/service/user/setting.go (about) 1 package user 2 3 import ( 4 "crypto/md5" 5 "fmt" 6 "net/http" 7 "net/url" 8 "os" 9 "path/filepath" 10 "strings" 11 12 model "github.com/cloudreve/Cloudreve/v3/models" 13 "github.com/cloudreve/Cloudreve/v3/pkg/serializer" 14 "github.com/cloudreve/Cloudreve/v3/pkg/util" 15 "github.com/gin-gonic/gin" 16 "github.com/pquerna/otp/totp" 17 ) 18 19 // SettingService 通用设置服务 20 type SettingService struct { 21 } 22 23 // SettingListService 通用设置列表服务 24 type SettingListService struct { 25 Page int `form:"page" binding:"required,min=1"` 26 } 27 28 // AvatarService 头像服务 29 type AvatarService struct { 30 Size string `uri:"size" binding:"required,eq=l|eq=m|eq=s"` 31 } 32 33 // SettingUpdateService 设定更改服务 34 type SettingUpdateService struct { 35 Option string `uri:"option" binding:"required,eq=nick|eq=theme|eq=homepage|eq=vip|eq=qq|eq=policy|eq=password|eq=2fa|eq=authn"` 36 } 37 38 // OptionsChangeHandler 属性更改接口 39 type OptionsChangeHandler interface { 40 Update(*gin.Context, *model.User) serializer.Response 41 } 42 43 // ChangerNick 昵称更改服务 44 type ChangerNick struct { 45 Nick string `json:"nick" binding:"required,min=1,max=255"` 46 } 47 48 // PolicyChange 更改存储策略 49 type PolicyChange struct { 50 ID string `json:"id" binding:"required"` 51 } 52 53 // HomePage 更改个人主页开关 54 type HomePage struct { 55 Enabled bool `json:"status"` 56 } 57 58 // PasswordChange 更改密码 59 type PasswordChange struct { 60 Old string `json:"old" binding:"required,min=4,max=64"` 61 New string `json:"new" binding:"required,min=4,max=64"` 62 } 63 64 // Enable2FA 开启二步验证 65 type Enable2FA struct { 66 Code string `json:"code" binding:"required"` 67 } 68 69 // DeleteWebAuthn 删除WebAuthn凭证 70 type DeleteWebAuthn struct { 71 ID string `json:"id" binding:"required"` 72 } 73 74 // ThemeChose 主题选择 75 type ThemeChose struct { 76 Theme string `json:"theme" binding:"required,hexcolor|rgb|rgba|hsl"` 77 } 78 79 // Update 更新主题设定 80 func (service *ThemeChose) Update(c *gin.Context, user *model.User) serializer.Response { 81 user.OptionsSerialized.PreferredTheme = service.Theme 82 if err := user.UpdateOptions(); err != nil { 83 return serializer.DBErr("Failed to update user preferences", err) 84 } 85 86 return serializer.Response{} 87 } 88 89 // Update 删除凭证 90 func (service *DeleteWebAuthn) Update(c *gin.Context, user *model.User) serializer.Response { 91 user.RemoveAuthn(service.ID) 92 return serializer.Response{} 93 } 94 95 // Update 更改二步验证设定 96 func (service *Enable2FA) Update(c *gin.Context, user *model.User) serializer.Response { 97 if user.TwoFactor == "" { 98 // 开启2FA 99 secret, ok := util.GetSession(c, "2fa_init").(string) 100 if !ok { 101 return serializer.Err(serializer.CodeInternalSetting, "You have not initiated 2FA session", nil) 102 } 103 104 if !totp.Validate(service.Code, secret) { 105 return serializer.ParamErr("Incorrect 2FA code", nil) 106 } 107 108 if err := user.Update(map[string]interface{}{"two_factor": secret}); err != nil { 109 return serializer.DBErr("Failed to update user preferences", err) 110 } 111 112 } else { 113 // 关闭2FA 114 if !totp.Validate(service.Code, user.TwoFactor) { 115 return serializer.ParamErr("Incorrect 2FA code", nil) 116 } 117 118 if err := user.Update(map[string]interface{}{"two_factor": ""}); err != nil { 119 return serializer.DBErr("Failed to update user preferences", err) 120 } 121 } 122 123 return serializer.Response{} 124 } 125 126 // Init2FA 初始化二步验证 127 func (service *SettingService) Init2FA(c *gin.Context, user *model.User) serializer.Response { 128 key, err := totp.Generate(totp.GenerateOpts{ 129 Issuer: "Cloudreve", 130 AccountName: user.Email, 131 }) 132 if err != nil { 133 return serializer.Err(serializer.CodeInternalSetting, "Failed to generate TOTP secret", err) 134 } 135 136 util.SetSession(c, map[string]interface{}{"2fa_init": key.Secret()}) 137 return serializer.Response{Data: key.Secret()} 138 } 139 140 // Update 更改密码 141 func (service *PasswordChange) Update(c *gin.Context, user *model.User) serializer.Response { 142 // 验证老密码 143 if ok, _ := user.CheckPassword(service.Old); !ok { 144 return serializer.Err(serializer.CodeIncorrectPassword, "", nil) 145 } 146 147 // 更改为新密码 148 user.SetPassword(service.New) 149 if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil { 150 return serializer.DBErr("Failed to update password", err) 151 } 152 153 return serializer.Response{} 154 } 155 156 // Update 切换个人主页开关 157 func (service *HomePage) Update(c *gin.Context, user *model.User) serializer.Response { 158 user.OptionsSerialized.ProfileOff = !service.Enabled 159 if err := user.UpdateOptions(); err != nil { 160 return serializer.DBErr("Failed to update user preferences", err) 161 } 162 163 return serializer.Response{} 164 } 165 166 // Update 更改昵称 167 func (service *ChangerNick) Update(c *gin.Context, user *model.User) serializer.Response { 168 if err := user.Update(map[string]interface{}{"nick": service.Nick}); err != nil { 169 return serializer.DBErr("Failed to update user", err) 170 } 171 172 return serializer.Response{} 173 } 174 175 // Get 获取用户头像 176 func (service *AvatarService) Get(c *gin.Context) serializer.Response { 177 // 查找目标用户 178 uid, _ := c.Get("object_id") 179 user, err := model.GetActiveUserByID(uid.(uint)) 180 if err != nil { 181 return serializer.Err(serializer.CodeUserNotFound, "", err) 182 } 183 184 // 未设定头像时,返回404错误 185 if user.Avatar == "" { 186 c.Status(404) 187 return serializer.Response{} 188 } 189 190 // 获取头像设置 191 sizes := map[string]string{ 192 "s": model.GetSettingByName("avatar_size_s"), 193 "m": model.GetSettingByName("avatar_size_m"), 194 "l": model.GetSettingByName("avatar_size_l"), 195 } 196 197 // Gravatar 头像重定向 198 if user.Avatar == "gravatar" { 199 server := model.GetSettingByName("gravatar_server") 200 gravatarRoot, err := url.Parse(server) 201 if err != nil { 202 return serializer.Err(serializer.CodeInternalSetting, "Failed to parse Gravatar server", err) 203 } 204 email_lowered := strings.ToLower(user.Email) 205 has := md5.Sum([]byte(email_lowered)) 206 avatar, _ := url.Parse(fmt.Sprintf("/avatar/%x?d=mm&s=%s", has, sizes[service.Size])) 207 208 return serializer.Response{ 209 Code: -301, 210 Data: gravatarRoot.ResolveReference(avatar).String(), 211 } 212 } 213 214 // 本地文件头像 215 if user.Avatar == "file" { 216 avatarRoot := util.RelativePath(model.GetSettingByName("avatar_path")) 217 sizeToInt := map[string]string{ 218 "s": "0", 219 "m": "1", 220 "l": "2", 221 } 222 223 avatar, err := os.Open(filepath.Join(avatarRoot, fmt.Sprintf("avatar_%d_%s.png", user.ID, sizeToInt[service.Size]))) 224 if err != nil { 225 c.Status(404) 226 return serializer.Response{} 227 } 228 defer avatar.Close() 229 230 http.ServeContent(c.Writer, c.Request, "avatar.png", user.UpdatedAt, avatar) 231 return serializer.Response{} 232 } 233 234 c.Status(404) 235 return serializer.Response{} 236 } 237 238 // ListTasks 列出任务 239 func (service *SettingListService) ListTasks(c *gin.Context, user *model.User) serializer.Response { 240 tasks, total := model.ListTasks(user.ID, service.Page, 10, "updated_at desc") 241 return serializer.BuildTaskList(tasks, total) 242 } 243 244 // Settings 获取用户设定 245 func (service *SettingService) Settings(c *gin.Context, user *model.User) serializer.Response { 246 return serializer.Response{ 247 Data: map[string]interface{}{ 248 "uid": user.ID, 249 "homepage": !user.OptionsSerialized.ProfileOff, 250 "two_factor": user.TwoFactor != "", 251 "prefer_theme": user.OptionsSerialized.PreferredTheme, 252 "themes": model.GetSettingByName("themes"), 253 "authn": serializer.BuildWebAuthnList(user.WebAuthnCredentials()), 254 }, 255 } 256 }