mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-11-04 04:47:24 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			362 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package user
 | 
						||
 | 
						||
import (
 | 
						||
	"crypto/md5"
 | 
						||
	"fmt"
 | 
						||
	model "github.com/HFO4/cloudreve/models"
 | 
						||
	"github.com/HFO4/cloudreve/pkg/hashid"
 | 
						||
	"github.com/HFO4/cloudreve/pkg/qq"
 | 
						||
	"github.com/HFO4/cloudreve/pkg/serializer"
 | 
						||
	"github.com/HFO4/cloudreve/pkg/util"
 | 
						||
	"github.com/gin-gonic/gin"
 | 
						||
	"github.com/pquerna/otp/totp"
 | 
						||
	"net/http"
 | 
						||
	"net/url"
 | 
						||
	"os"
 | 
						||
	"path/filepath"
 | 
						||
	"time"
 | 
						||
)
 | 
						||
 | 
						||
// 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"`
 | 
						||
}
 | 
						||
 | 
						||
// VIPUnsubscribe 用户组解约服务
 | 
						||
type VIPUnsubscribe struct {
 | 
						||
}
 | 
						||
 | 
						||
// QQBind QQ互联服务
 | 
						||
type QQBind struct {
 | 
						||
}
 | 
						||
 | 
						||
// 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"`
 | 
						||
}
 | 
						||
 | 
						||
// 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("主题切换失败", 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.CodeParamErr, "未初始化二步验证", nil)
 | 
						||
		}
 | 
						||
 | 
						||
		if !totp.Validate(service.Code, secret) {
 | 
						||
			return serializer.ParamErr("验证码不正确", nil)
 | 
						||
		}
 | 
						||
 | 
						||
		if err := user.Update(map[string]interface{}{"two_factor": secret}); err != nil {
 | 
						||
			return serializer.DBErr("无法更新二步验证设定", err)
 | 
						||
		}
 | 
						||
 | 
						||
	} else {
 | 
						||
		// 关闭2FA
 | 
						||
		if !totp.Validate(service.Code, user.TwoFactor) {
 | 
						||
			return serializer.ParamErr("验证码不正确", nil)
 | 
						||
		}
 | 
						||
 | 
						||
		if err := user.Update(map[string]interface{}{"two_factor": ""}); err != nil {
 | 
						||
			return serializer.DBErr("无法更新二步验证设定", 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, "无法生成验密钥", 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.CodeParamErr, "原密码不正确", nil)
 | 
						||
	}
 | 
						||
 | 
						||
	// 更改为新密码
 | 
						||
	user.SetPassword(service.New)
 | 
						||
	if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil {
 | 
						||
		return serializer.DBErr("密码更换失败", 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("存储策略切换失败", err)
 | 
						||
	}
 | 
						||
 | 
						||
	return serializer.Response{}
 | 
						||
}
 | 
						||
 | 
						||
// Update 更改用户偏好的存储策略
 | 
						||
func (service *PolicyChange) Update(c *gin.Context, user *model.User) serializer.Response {
 | 
						||
	// 取得存储策略的ID
 | 
						||
	rawID, err := hashid.DecodeHashID(service.ID, hashid.PolicyID)
 | 
						||
	if err != nil {
 | 
						||
		return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err)
 | 
						||
	}
 | 
						||
 | 
						||
	// 用户是否可以切换到此存储策略
 | 
						||
	if !util.ContainsUint(user.Group.PolicyList, rawID) {
 | 
						||
		return serializer.Err(serializer.CodeNoPermissionErr, "存储策略不可用", nil)
 | 
						||
	}
 | 
						||
 | 
						||
	// 查找存储策略
 | 
						||
	if _, err := model.GetPolicyByID(rawID); err != nil {
 | 
						||
		return serializer.Err(serializer.CodeNoPermissionErr, "存储策略不可用", nil)
 | 
						||
	}
 | 
						||
 | 
						||
	// 切换存储策略
 | 
						||
	user.OptionsSerialized.PreferredPolicy = rawID
 | 
						||
	if err := user.UpdateOptions(); err != nil {
 | 
						||
		return serializer.DBErr("存储策略切换失败", err)
 | 
						||
	}
 | 
						||
 | 
						||
	return serializer.Response{}
 | 
						||
}
 | 
						||
 | 
						||
// Update 绑定或解绑QQ
 | 
						||
func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Response {
 | 
						||
	// 解除绑定
 | 
						||
	if user.OpenID != "" {
 | 
						||
		if err := user.Update(map[string]interface{}{"open_id": ""}); err != nil {
 | 
						||
			return serializer.DBErr("接触绑定失败", err)
 | 
						||
		}
 | 
						||
		return serializer.Response{
 | 
						||
			Data: "",
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// 新建绑定
 | 
						||
	res, err := qq.NewLoginRequest()
 | 
						||
	if err != nil {
 | 
						||
		return serializer.Err(serializer.CodeNotSet, "无法使用QQ登录", err)
 | 
						||
	}
 | 
						||
 | 
						||
	// 设定QQ登录会话Secret
 | 
						||
	util.SetSession(c, map[string]interface{}{"qq_login_secret": res.SecretKey})
 | 
						||
 | 
						||
	return serializer.Response{
 | 
						||
		Data: res.URL,
 | 
						||
	}
 | 
						||
}
 | 
						||
 | 
						||
// Update 用户组解约
 | 
						||
func (service *VIPUnsubscribe) Update(c *gin.Context, user *model.User) serializer.Response {
 | 
						||
	if user.GroupExpires != nil {
 | 
						||
		timeNow := time.Now()
 | 
						||
		if time.Now().Before(*user.GroupExpires) {
 | 
						||
			if err := user.Update(map[string]interface{}{"group_expires": &timeNow}); err != nil {
 | 
						||
				return serializer.DBErr("解约失败", 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("无法更新昵称", 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.CodeNotFound, "用户不存在", 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"),
 | 
						||
	}
 | 
						||
 | 
						||
	// Gravatar 头像重定向
 | 
						||
	if user.Avatar == "gravatar" {
 | 
						||
		server := model.GetSettingByName("gravatar_server")
 | 
						||
		gravatarRoot, err := url.Parse(server)
 | 
						||
		if err != nil {
 | 
						||
			return serializer.Err(serializer.CodeInternalSetting, "无法解析 Gravatar 服务器地址", err)
 | 
						||
		}
 | 
						||
 | 
						||
		has := md5.Sum([]byte(user.Email))
 | 
						||
		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 := 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)
 | 
						||
}
 | 
						||
 | 
						||
// Policy 获取用户存储策略设置
 | 
						||
func (service *SettingService) Policy(c *gin.Context, user *model.User) serializer.Response {
 | 
						||
	// 取得用户可用存储策略
 | 
						||
	available := make([]model.Policy, 0, len(user.Group.PolicyList))
 | 
						||
	for _, id := range user.Group.PolicyList {
 | 
						||
		if policy, err := model.GetPolicyByID(id); err == nil {
 | 
						||
			available = append(available, policy)
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// 取得用户当前策略
 | 
						||
	current := user.Policy
 | 
						||
 | 
						||
	return serializer.BuildPolicySettingRes(available, ¤t)
 | 
						||
}
 | 
						||
 | 
						||
// Settings 获取用户设定
 | 
						||
func (service *SettingService) Settings(c *gin.Context, user *model.User) serializer.Response {
 | 
						||
	// 取得存储策略设定
 | 
						||
	policy := service.Policy(c, user)
 | 
						||
 | 
						||
	// 用户组有效期
 | 
						||
	var groupExpires int64
 | 
						||
	if user.GroupExpires != nil {
 | 
						||
		if expires := user.GroupExpires.Unix() - time.Now().Unix(); expires > 0 {
 | 
						||
			groupExpires = user.GroupExpires.Unix()
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	return serializer.Response{
 | 
						||
		Data: map[string]interface{}{
 | 
						||
			"policy":        policy.Data.(map[string]interface{}),
 | 
						||
			"uid":           user.ID,
 | 
						||
			"qq":            user.OpenID != "",
 | 
						||
			"homepage":      !user.OptionsSerialized.ProfileOff,
 | 
						||
			"two_factor":    user.TwoFactor != "",
 | 
						||
			"prefer_theme":  user.OptionsSerialized.PreferredTheme,
 | 
						||
			"themes":        model.GetSettingByName("themes"),
 | 
						||
			"group_expires": groupExpires,
 | 
						||
			"authn":         serializer.BuildWebAuthnList(user.WebAuthnCredentials()),
 | 
						||
		},
 | 
						||
	}
 | 
						||
}
 |