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  }