init
This commit is contained in:
205
service/user/login.go
Normal file
205
service/user/login.go
Normal file
@ -0,0 +1,205 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
|
||||
"github.com/gofrs/uuid"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/email"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
// UserLoginService 管理用户登录的服务
|
||||
type UserLoginService struct {
|
||||
//TODO 细致调整验证规则
|
||||
UserName string `form:"userName" json:"userName" binding:"required,email"`
|
||||
Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
|
||||
}
|
||||
|
||||
// UserResetEmailService 发送密码重设邮件服务
|
||||
type UserResetEmailService struct {
|
||||
UserName string `form:"userName" json:"userName" binding:"required,email"`
|
||||
}
|
||||
|
||||
// UserResetService 密码重设服务
|
||||
type UserResetService struct {
|
||||
Password string `form:"Password" json:"Password" binding:"required,min=4,max=64"`
|
||||
ID string `json:"id" binding:"required"`
|
||||
Secret string `json:"secret" binding:"required"`
|
||||
}
|
||||
|
||||
// Reset 重设密码
|
||||
func (service *UserResetService) Reset(c *gin.Context) serializer.Response {
|
||||
// 取得原始用户ID
|
||||
uid, err := hashid.DecodeHashID(service.ID, hashid.UserID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInvalidTempLink, "Invalid link", err)
|
||||
}
|
||||
|
||||
// 检查重设会话
|
||||
resetSession, exist := cache.Get(fmt.Sprintf("user_reset_%d", uid))
|
||||
if !exist || resetSession.(string) != service.Secret {
|
||||
return serializer.Err(serializer.CodeTempLinkExpired, "Link is expired", err)
|
||||
}
|
||||
|
||||
// 重设用户密码
|
||||
user, err := model.GetActiveUserByID(uid)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
|
||||
}
|
||||
|
||||
user.SetPassword(service.Password)
|
||||
if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil {
|
||||
return serializer.DBErr("Failed to reset password", err)
|
||||
}
|
||||
|
||||
cache.Deletes([]string{fmt.Sprintf("%d", uid)}, "user_reset_")
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Reset 发送密码重设邮件
|
||||
func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response {
|
||||
// 查找用户
|
||||
if user, err := model.GetUserByEmail(service.UserName); err == nil {
|
||||
|
||||
if user.Status == model.Baned || user.Status == model.OveruseBaned {
|
||||
return serializer.Err(serializer.CodeUserBaned, "This user is banned", nil)
|
||||
}
|
||||
if user.Status == model.NotActivicated {
|
||||
return serializer.Err(serializer.CodeUserNotActivated, "This user is not activated", nil)
|
||||
}
|
||||
// 创建密码重设会话
|
||||
secret := util.RandStringRunes(32)
|
||||
cache.Set(fmt.Sprintf("user_reset_%d", user.ID), secret, 3600)
|
||||
|
||||
// 生成用户访问的重设链接
|
||||
controller, _ := url.Parse("/reset")
|
||||
finalURL := model.GetSiteURL().ResolveReference(controller)
|
||||
queries := finalURL.Query()
|
||||
queries.Add("id", hashid.HashID(user.ID, hashid.UserID))
|
||||
queries.Add("sign", secret)
|
||||
finalURL.RawQuery = queries.Encode()
|
||||
|
||||
// 发送密码重设邮件
|
||||
title, body := email.NewResetEmail(user.Nick, finalURL.String())
|
||||
if err := email.Send(user.Email, title, body); err != nil {
|
||||
return serializer.Err(serializer.CodeFailedSendEmail, "Failed to send email", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Login 二步验证继续登录
|
||||
func (service *Enable2FA) Login(c *gin.Context) serializer.Response {
|
||||
if uid, ok := util.GetSession(c, "2fa_user_id").(uint); ok {
|
||||
// 查找用户
|
||||
expectedUser, err := model.GetActiveUserByID(uid)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
|
||||
}
|
||||
|
||||
// 验证二步验证代码
|
||||
if !totp.Validate(service.Code, expectedUser.TwoFactor) {
|
||||
return serializer.Err(serializer.Code2FACodeErr, "2FA code not correct", nil)
|
||||
}
|
||||
|
||||
//登陆成功,清空并设置session
|
||||
util.DeleteSession(c, "2fa_user_id")
|
||||
util.SetSession(c, map[string]interface{}{
|
||||
"user_id": expectedUser.ID,
|
||||
})
|
||||
|
||||
return serializer.BuildUserResponse(expectedUser)
|
||||
}
|
||||
|
||||
return serializer.Err(serializer.CodeLoginSessionNotExist, "Login session not exist", nil)
|
||||
}
|
||||
|
||||
// Login 用户登录函数
|
||||
func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
|
||||
expectedUser, err := model.GetUserByEmail(service.UserName)
|
||||
// 一系列校验
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", err)
|
||||
}
|
||||
if authOK, _ := expectedUser.CheckPassword(service.Password); !authOK {
|
||||
return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", nil)
|
||||
}
|
||||
if expectedUser.Status == model.Baned || expectedUser.Status == model.OveruseBaned {
|
||||
return serializer.Err(serializer.CodeUserBaned, "This account has been blocked", nil)
|
||||
}
|
||||
if expectedUser.Status == model.NotActivicated {
|
||||
return serializer.Err(serializer.CodeUserNotActivated, "This account is not activated", nil)
|
||||
}
|
||||
|
||||
if expectedUser.TwoFactor != "" {
|
||||
// 需要二步验证
|
||||
util.SetSession(c, map[string]interface{}{
|
||||
"2fa_user_id": expectedUser.ID,
|
||||
})
|
||||
return serializer.Response{Code: 203}
|
||||
}
|
||||
|
||||
//登陆成功,清空并设置session
|
||||
util.SetSession(c, map[string]interface{}{
|
||||
"user_id": expectedUser.ID,
|
||||
})
|
||||
|
||||
return serializer.BuildUserResponse(expectedUser)
|
||||
|
||||
}
|
||||
|
||||
// CopySessionService service for copy user session
|
||||
type CopySessionService struct {
|
||||
ID string `uri:"id" binding:"required,uuid4"`
|
||||
}
|
||||
|
||||
const CopySessionTTL = 60
|
||||
|
||||
// Prepare generates the URL with short expiration duration
|
||||
func (s *CopySessionService) Prepare(c *gin.Context, user *model.User) serializer.Response {
|
||||
// 用户组有效期
|
||||
urlID := uuid.Must(uuid.NewV4())
|
||||
if err := cache.Set(fmt.Sprintf("copy_session_%s", urlID.String()), user.ID, CopySessionTTL); err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to create copy session", err)
|
||||
}
|
||||
|
||||
base := model.GetSiteURL()
|
||||
apiBaseURI, _ := url.Parse("/api/v3/user/session/copy/" + urlID.String())
|
||||
apiURL := base.ResolveReference(apiBaseURI)
|
||||
res, err := auth.SignURI(auth.General, apiURL.String(), CopySessionTTL)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to sign temp URL", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
Data: res.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy a new session from active session, refresh max-age
|
||||
func (s *CopySessionService) Copy(c *gin.Context) serializer.Response {
|
||||
// 用户组有效期
|
||||
cacheKey := fmt.Sprintf("copy_session_%s", s.ID)
|
||||
uid, ok := cache.Get(cacheKey)
|
||||
if !ok {
|
||||
return serializer.Err(serializer.CodeNotFound, "", nil)
|
||||
}
|
||||
|
||||
cache.Deletes([]string{cacheKey}, "")
|
||||
util.SetSession(c, map[string]interface{}{
|
||||
"user_id": uid.(uint),
|
||||
})
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
129
service/user/register.go
Normal file
129
service/user/register.go
Normal file
@ -0,0 +1,129 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/email"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Register 新用户注册
|
||||
func (service *UserRegisterService) Register(c *gin.Context) serializer.Response {
|
||||
// 相关设定
|
||||
options := model.GetSettingByNames("email_active", "reg_captcha", "mail_domain_filter", "mail_domain_filter_list")
|
||||
|
||||
// 检查是否在邮件域黑名单里
|
||||
if options["mail_domain_filter"] != "0" {
|
||||
filterList := strings.Split(options["mail_domain_filter_list"], ",")
|
||||
emailSplit := strings.Split(service.UserName, "@")
|
||||
emailDomain := emailSplit[len(emailSplit)-1]
|
||||
inList := util.ContainsString(filterList, emailDomain)
|
||||
domainErr := serializer.Err(serializer.CodeEmailProviderBaned, "Email provider banned", nil)
|
||||
if options["mail_domain_filter"] == "1" && !inList {
|
||||
return domainErr
|
||||
}
|
||||
if options["mail_domain_filter"] == "2" && inList {
|
||||
return domainErr
|
||||
}
|
||||
}
|
||||
|
||||
// 相关设定
|
||||
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)
|
||||
userNotActivated := false
|
||||
// 创建用户
|
||||
if err := model.DB.Create(&user).Error; err != nil {
|
||||
//检查已存在使用者是否尚未激活
|
||||
expectedUser, err := model.GetUserByEmail(service.UserName)
|
||||
if expectedUser.Status == model.NotActivicated {
|
||||
userNotActivated = true
|
||||
user = expectedUser
|
||||
} else {
|
||||
return serializer.Err(serializer.CodeEmailExisted, "Email already in use", 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, "Failed to sign the activation link", 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,
|
||||
finalURL.String(),
|
||||
)
|
||||
if err := email.Send(user.Email, title, body); err != nil {
|
||||
return serializer.Err(serializer.CodeFailedSendEmail, "Failed to send activation email", err)
|
||||
}
|
||||
if userNotActivated == true {
|
||||
//原本在上面要抛出的DBErr,放来这边抛出
|
||||
return serializer.Err(serializer.CodeEmailSent, "User is not activated, activation email has been resent", nil)
|
||||
} else {
|
||||
return serializer.Response{Code: 203}
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Activate 激活用户
|
||||
func (service *SettingService) Activate(c *gin.Context) serializer.Response {
|
||||
// 查找待激活用户
|
||||
uid, _ := c.Get("object_id")
|
||||
user, err := model.GetUserByID(uid.(uint))
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "User not fount", err)
|
||||
}
|
||||
|
||||
// 检查状态
|
||||
if user.Status != model.NotActivicated {
|
||||
return serializer.Err(serializer.CodeUserCannotActivate, "This user cannot be activated", nil)
|
||||
}
|
||||
|
||||
// 激活用户
|
||||
user.SetStatus(model.Active)
|
||||
|
||||
return serializer.Response{Data: user.Email}
|
||||
}
|
396
service/user/setting.go
Normal file
396
service/user/setting.go
Normal file
@ -0,0 +1,396 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/qq"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// 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("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 *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.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
// 用户是否可以切换到此存储策略
|
||||
if !util.ContainsUint(user.Group.PolicyList, rawID) {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 查找存储策略
|
||||
if _, err := model.GetPolicyByID(rawID); err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
// 切换存储策略
|
||||
user.OptionsSerialized.PreferredPolicy = rawID
|
||||
if err := user.UpdateOptions(); err != nil {
|
||||
return serializer.DBErr("Failed to update user preferences", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Update 绑定或解绑QQ
|
||||
func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Response {
|
||||
// 解除绑定
|
||||
if user.OpenID != "" {
|
||||
// 只通过QQ登录的用户无法解除绑定
|
||||
if strings.HasSuffix(user.Email, "@login.qq.com") {
|
||||
return serializer.Err(serializer.CodeNoPermissionErr, "This user cannot be unlinked", nil)
|
||||
}
|
||||
|
||||
if err := user.Update(map[string]interface{}{"open_id": ""}); err != nil {
|
||||
return serializer.DBErr("Failed to update user open id", err)
|
||||
}
|
||||
return serializer.Response{
|
||||
Data: "",
|
||||
}
|
||||
}
|
||||
|
||||
// 新建绑定
|
||||
res, err := qq.NewLoginRequest()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "Failed to start QQ login request", 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("Failed to update user", 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"),
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.BuildPolicySettingRes(available)
|
||||
}
|
||||
|
||||
// Nodes 获取用户可选节点
|
||||
func (service *SettingService) Nodes(c *gin.Context, user *model.User) serializer.Response {
|
||||
if !user.Group.OptionsSerialized.SelectNode {
|
||||
return serializer.Err(serializer.CodeGroupNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
availableNodesID := user.Group.OptionsSerialized.AvailableNodes
|
||||
|
||||
// All nodes available
|
||||
if len(availableNodesID) == 0 {
|
||||
nodes, err := model.GetNodesByStatus(model.NodeActive)
|
||||
if err != nil {
|
||||
return serializer.DBErr("Failed to list nodes", err)
|
||||
}
|
||||
|
||||
availableNodesID = lo.Map[model.Node, uint](nodes, func(node model.Node, index int) uint {
|
||||
return node.ID
|
||||
})
|
||||
}
|
||||
|
||||
// 取得用户可用存储策略
|
||||
available := lo.FilterMap[uint, *model.Node](availableNodesID,
|
||||
func(id uint, index int) (*model.Node, bool) {
|
||||
if node := cluster.Default.GetNodeByID(id); node != nil {
|
||||
return node.DBModel(), node.IsActive() && node.IsFeatureEnabled("aria2")
|
||||
}
|
||||
|
||||
return nil, false
|
||||
})
|
||||
|
||||
return serializer.BuildNodeOptionRes(available)
|
||||
}
|
||||
|
||||
// Settings 获取用户设定
|
||||
func (service *SettingService) Settings(c *gin.Context, user *model.User) serializer.Response {
|
||||
// 用户组有效期
|
||||
var groupExpires *time.Time
|
||||
if user.GroupExpires != nil {
|
||||
if expires := user.GroupExpires.Unix() - time.Now().Unix(); expires > 0 {
|
||||
groupExpires = user.GroupExpires
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
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()),
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user