mirror of https://github.com/cloudreve/Cloudreve
Feat: login / register setting in dashboard
parent
04d13bd071
commit
3ce4b87f2b
|
@ -106,9 +106,9 @@ solid #e9e9e9;"bgcolor="#fff"><tbody><tr style="font-family: 'Helvetica Neue',He
|
||||||
{Name: "onedrive_callback_check", Value: `20`, Type: "timeout"},
|
{Name: "onedrive_callback_check", Value: `20`, Type: "timeout"},
|
||||||
{Name: "aria2_call_timeout", Value: `5`, Type: "timeout"},
|
{Name: "aria2_call_timeout", Value: `5`, Type: "timeout"},
|
||||||
{Name: "onedrive_chunk_retries", Value: `1`, Type: "retry"},
|
{Name: "onedrive_chunk_retries", Value: `1`, Type: "retry"},
|
||||||
{Name: "allowdVisitorDownload", Value: `false`, Type: "share"},
|
|
||||||
{Name: "login_captcha", Value: `0`, Type: "login"},
|
{Name: "login_captcha", Value: `0`, Type: "login"},
|
||||||
{Name: "qq_login", Value: `0`, Type: "login"},
|
{Name: "qq_login", Value: `0`, Type: "login"},
|
||||||
|
{Name: "qq_direct_login", Value: `0`, Type: "login"},
|
||||||
{Name: "qq_login_id", Value: ``, Type: "login"},
|
{Name: "qq_login_id", Value: ``, Type: "login"},
|
||||||
{Name: "qq_login_key", Value: ``, Type: "login"},
|
{Name: "qq_login_key", Value: ``, Type: "login"},
|
||||||
{Name: "reg_captcha", Value: `0`, Type: "login"},
|
{Name: "reg_captcha", Value: `0`, Type: "login"},
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package email
|
package email
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Driver 邮件发送驱动
|
// Driver 邮件发送驱动
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
|
@ -19,6 +22,11 @@ var (
|
||||||
|
|
||||||
// Send 发送邮件
|
// Send 发送邮件
|
||||||
func Send(to, title, body string) error {
|
func Send(to, title, body string) error {
|
||||||
|
// 忽略通过QQ登录的邮箱
|
||||||
|
if strings.HasSuffix(to, "@login.qq.com") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if Client == nil {
|
if Client == nil {
|
||||||
return ErrNoActiveDriver
|
return ErrNoActiveDriver
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,12 @@ type UserCredentials struct {
|
||||||
AccessToken string
|
AccessToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserInfo 用户信息
|
||||||
|
type UserInfo struct {
|
||||||
|
Nick string
|
||||||
|
Avatar string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNotEnabled 未开启登录功能
|
// ErrNotEnabled 未开启登录功能
|
||||||
ErrNotEnabled = serializer.NewError(serializer.CodeNoPermissionErr, "QQ登录功能未开启", nil)
|
ErrNotEnabled = serializer.NewError(serializer.CodeNoPermissionErr, "QQ登录功能未开启", nil)
|
||||||
|
@ -89,6 +95,20 @@ func getAccessTokenURL(code string) string {
|
||||||
return api.String()
|
return api.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUserInfoURL(openid, ak string) string {
|
||||||
|
// 获取相关设定
|
||||||
|
options := model.GetSettingByNames("qq_login_id", "qq_login_key")
|
||||||
|
|
||||||
|
api, _ := url.Parse("https://graph.qq.com/user/get_user_info")
|
||||||
|
queries := api.Query()
|
||||||
|
queries.Add("oauth_consumer_key", options["qq_login_id"])
|
||||||
|
queries.Add("openid", openid)
|
||||||
|
queries.Add("access_token", ak)
|
||||||
|
api.RawQuery = queries.Encode()
|
||||||
|
|
||||||
|
return api.String()
|
||||||
|
}
|
||||||
|
|
||||||
func getResponse(body string) (map[string]interface{}, error) {
|
func getResponse(body string) (map[string]interface{}, error) {
|
||||||
var res map[string]interface{}
|
var res map[string]interface{}
|
||||||
|
|
||||||
|
@ -157,3 +177,35 @@ func Callback(code string) (*UserCredentials, error) {
|
||||||
|
|
||||||
return nil, ErrDecodeResponse
|
return nil, ErrDecodeResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserInfo 使用凭证获取用户信息
|
||||||
|
func GetUserInfo(credential *UserCredentials) (*UserInfo, error) {
|
||||||
|
api := getUserInfoURL(credential.OpenID, credential.AccessToken)
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
client := request.HTTPClient{}
|
||||||
|
res := client.Request("GET", api, nil)
|
||||||
|
resp, err := res.GetResponse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrObtainAccessToken.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resSerialized map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(resp), &resSerialized); err != nil {
|
||||||
|
return nil, ErrDecodeResponse.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果服务端返回错误
|
||||||
|
if msg, ok := resSerialized["msg"]; ok && msg.(string) != "" {
|
||||||
|
return nil, ErrObtainAccessToken.WithError(errors.New(msg.(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if avatar, ok := resSerialized["figureurl_qq_2"]; ok {
|
||||||
|
return &UserInfo{
|
||||||
|
Nick: resSerialized["nickname"].(string),
|
||||||
|
Avatar: avatar.(string),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrDecodeResponse
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ type Thumb struct {
|
||||||
|
|
||||||
// NewThumbFromFile 从文件数据获取新的Thumb对象,
|
// NewThumbFromFile 从文件数据获取新的Thumb对象,
|
||||||
// 尝试通过文件名name解码图像
|
// 尝试通过文件名name解码图像
|
||||||
func NewThumbFromFile(file io.ReadSeeker, name string) (*Thumb, error) {
|
func NewThumbFromFile(file io.Reader, name string) (*Thumb, error) {
|
||||||
ext := strings.ToLower(filepath.Ext(name))
|
ext := strings.ToLower(filepath.Ext(name))
|
||||||
// 无扩展名时
|
// 无扩展名时
|
||||||
if len(ext) == 0 {
|
if len(ext) == 0 {
|
||||||
|
|
|
@ -46,3 +46,14 @@ func AdminGetSetting(c *gin.Context) {
|
||||||
c.JSON(200, ErrorResponse(err))
|
c.JSON(200, ErrorResponse(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminGetGroups 获取用户组列表
|
||||||
|
func AdminGetGroups(c *gin.Context) {
|
||||||
|
var service admin.NoParamService
|
||||||
|
if err := c.ShouldBindUri(&service); err == nil {
|
||||||
|
res := service.GroupList()
|
||||||
|
c.JSON(200, res)
|
||||||
|
} else {
|
||||||
|
c.JSON(200, ErrorResponse(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -297,6 +297,8 @@ func InitMasterRouter() *gin.Engine {
|
||||||
admin.PATCH("setting", controllers.AdminChangeSetting)
|
admin.PATCH("setting", controllers.AdminChangeSetting)
|
||||||
// 获取设置
|
// 获取设置
|
||||||
admin.POST("setting", controllers.AdminGetSetting)
|
admin.POST("setting", controllers.AdminGetSetting)
|
||||||
|
// 获取用户组列表
|
||||||
|
admin.GET("groups", controllers.AdminGetGroups)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户
|
// 用户
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupList 获取用户组列表
|
||||||
|
func (service *NoParamService) GroupList() serializer.Response {
|
||||||
|
var res []model.Group
|
||||||
|
model.DB.Model(&model.Group{}).Find(&res)
|
||||||
|
return serializer.Response{Data: res}
|
||||||
|
}
|
|
@ -36,13 +36,20 @@ func (service *BatchSettingGet) Get() serializer.Response {
|
||||||
|
|
||||||
// Change 批量更改站点设定
|
// Change 批量更改站点设定
|
||||||
func (service *BatchSettingChangeService) Change() serializer.Response {
|
func (service *BatchSettingChangeService) Change() serializer.Response {
|
||||||
|
cacheClean := make([]string, 0, len(service.Options))
|
||||||
|
|
||||||
for _, setting := range service.Options {
|
for _, setting := range service.Options {
|
||||||
|
|
||||||
if err := model.DB.Model(&model.Setting{}).Where("name = ?", setting.Key).Update("value", setting.Value).Error; err != nil {
|
if err := model.DB.Model(&model.Setting{}).Where("name = ?", setting.Key).Update("value", setting.Value).Error; err != nil {
|
||||||
|
cache.Deletes(cacheClean, "setting_")
|
||||||
return serializer.DBErr("设置 "+setting.Key+" 更新失败", err)
|
return serializer.DBErr("设置 "+setting.Key+" 更新失败", err)
|
||||||
}
|
}
|
||||||
cache.Deletes([]string{setting.Key}, "setting_")
|
|
||||||
|
cacheClean = append(cacheClean, setting.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache.Deletes(cacheClean, "setting_")
|
||||||
|
|
||||||
return serializer.Response{}
|
return serializer.Response{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -203,8 +204,13 @@ func (service *PolicyChange) Update(c *gin.Context, user *model.User) serializer
|
||||||
func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Response {
|
func (service *QQBind) Update(c *gin.Context, user *model.User) serializer.Response {
|
||||||
// 解除绑定
|
// 解除绑定
|
||||||
if user.OpenID != "" {
|
if user.OpenID != "" {
|
||||||
|
// 只通过QQ登录的用户无法解除绑定
|
||||||
|
if strings.HasSuffix(user.Email, "@login.qq.com") {
|
||||||
|
return serializer.Err(serializer.CodeNoPermissionErr, "无法解绑此账号", nil)
|
||||||
|
}
|
||||||
|
|
||||||
if err := user.Update(map[string]interface{}{"open_id": ""}); err != nil {
|
if err := user.Update(map[string]interface{}{"open_id": ""}); err != nil {
|
||||||
return serializer.DBErr("接触绑定失败", err)
|
return serializer.DBErr("接除绑定失败", err)
|
||||||
}
|
}
|
||||||
return serializer.Response{
|
return serializer.Response{
|
||||||
Data: "",
|
Data: "",
|
||||||
|
|
|
@ -3,7 +3,9 @@ package vas
|
||||||
import (
|
import (
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
"github.com/HFO4/cloudreve/pkg/qq"
|
"github.com/HFO4/cloudreve/pkg/qq"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/request"
|
||||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/thumb"
|
||||||
"github.com/HFO4/cloudreve/pkg/util"
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
@ -54,9 +56,52 @@ func (service *QQCallbackService) Callback(c *gin.Context, user *model.User) ser
|
||||||
res.Code = 203
|
res.Code = 203
|
||||||
return res
|
return res
|
||||||
|
|
||||||
} else {
|
|
||||||
// 无匹配用户,创建新用户
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializer.Response{}
|
// 无匹配用户,创建新用户
|
||||||
|
if !model.IsTrueVal(model.GetSettingByName("qq_direct_login")) {
|
||||||
|
return serializer.Err(serializer.CodeNoPermissionErr, "此QQ号未绑定任何账号", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
userInfo, err := qq.GetUserInfo(credential)
|
||||||
|
if err != nil {
|
||||||
|
return serializer.Err(serializer.CodeNotSet, "无法获取用户信息", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成邮箱地址
|
||||||
|
fakeEmail := util.RandStringRunes(16) + "@login.qq.com"
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
defaultGroup := model.GetIntSetting("default_group", 2)
|
||||||
|
|
||||||
|
newUser := model.NewUser()
|
||||||
|
newUser.Email = fakeEmail
|
||||||
|
newUser.Nick = userInfo.Nick
|
||||||
|
newUser.SetPassword("")
|
||||||
|
newUser.Status = model.Active
|
||||||
|
newUser.GroupID = uint(defaultGroup)
|
||||||
|
newUser.OpenID = credential.OpenID
|
||||||
|
newUser.Avatar = "file"
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
if err := model.DB.Create(&newUser).Error; err != nil {
|
||||||
|
return serializer.DBErr("此邮箱已被使用", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载头像
|
||||||
|
r := request.HTTPClient{}
|
||||||
|
rawAvatar := r.Request("GET", userInfo.Avatar, nil)
|
||||||
|
if avatar, err := thumb.NewThumbFromFile(rawAvatar.Response.Body, "avatar.jpg"); err == nil {
|
||||||
|
avatar.CreateAvatar(newUser.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
util.SetSession(c, map[string]interface{}{"user_id": newUser.ID})
|
||||||
|
|
||||||
|
newUser, _ = model.GetActiveUserByID(newUser.ID)
|
||||||
|
|
||||||
|
res := serializer.BuildUserResponse(newUser)
|
||||||
|
res.Code = 203
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue