Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
hexbee
Cloudreve
提交
c817f70f
C
Cloudreve
项目概览
hexbee
/
Cloudreve
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
C
Cloudreve
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
c817f70f
编写于
2月 21, 2020
作者:
H
HFO4
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Feat: Webauthn / theme changing
上级
80268e33
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
144 addition
and
21 deletion
+144
-21
middleware/option.go
middleware/option.go
+14
-0
models/migration.go
models/migration.go
+1
-0
models/user_authn.go
models/user_authn.go
+29
-4
pkg/authn/auth.go
pkg/authn/auth.go
+8
-6
pkg/serializer/setting.go
pkg/serializer/setting.go
+2
-0
pkg/serializer/user.go
pkg/serializer/user.go
+21
-0
routers/controllers/site.go
routers/controllers/site.go
+1
-0
routers/controllers/user.go
routers/controllers/user.go
+20
-4
routers/router.go
routers/router.go
+8
-3
service/user/setting.go
service/user/setting.go
+40
-4
未找到文件。
middleware/
hahsid
.go
→
middleware/
option
.go
浏览文件 @
c817f70f
package
middleware
import
(
model
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/gin-gonic/gin"
...
...
@@ -24,3 +25,16 @@ func HashID(IDType int) gin.HandlerFunc {
c
.
Next
()
}
}
// IsFunctionEnabled 当功能未开启时阻止访问
func
IsFunctionEnabled
(
key
string
)
gin
.
HandlerFunc
{
return
func
(
c
*
gin
.
Context
)
{
if
!
model
.
IsTrueVal
(
model
.
GetSettingByName
(
key
))
{
c
.
JSON
(
200
,
serializer
.
Err
(
serializer
.
CodeNoPermissionErr
,
"未开启此功能"
,
nil
))
c
.
Abort
()
return
}
c
.
Next
()
}
}
models/migration.go
浏览文件 @
c817f70f
...
...
@@ -173,6 +173,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{
Name
:
"cron_garbage_collect"
,
Value
:
"@hourly"
,
Type
:
"cron"
},
{
Name
:
"cron_notify_user"
,
Value
:
"@hourly"
,
Type
:
"cron"
},
{
Name
:
"cron_ban_user"
,
Value
:
"@hourly"
,
Type
:
"cron"
},
{
Name
:
"authn_enabled"
,
Value
:
"1"
,
Type
:
"authn"
},
}
for
_
,
value
:=
range
defaultSettings
{
...
...
models/user_authn.go
浏览文件 @
c817f70f
package
model
import
(
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/duo-labs/webauthn/webauthn"
"net/url"
)
/*
...
...
@@ -30,7 +33,10 @@ func (user User) WebAuthnDisplayName() string {
// WebAuthnIcon 获得用户头像
func
(
user
User
)
WebAuthnIcon
()
string
{
return
"https://cdn4.buysellads.net/uu/1/46074/1559075156-slack-carbon-red_2x.png"
avatar
,
_
:=
url
.
Parse
(
"/api/v3/user/avatar/"
+
hashid
.
HashID
(
user
.
ID
,
hashid
.
UserID
)
+
"/l"
)
base
:=
GetSiteURL
()
base
.
Scheme
=
"https"
return
base
.
ResolveReference
(
avatar
)
.
String
()
}
// WebAuthnCredentials 获得已注册的验证器凭证
...
...
@@ -44,10 +50,29 @@ func (user User) WebAuthnCredentials() []webauthn.Credential {
}
// RegisterAuthn 添加新的验证器
func
(
user
*
User
)
RegisterAuthn
(
credential
*
webauthn
.
Credential
)
{
res
,
err
:=
json
.
Marshal
([]
webauthn
.
Credential
{
*
credential
})
func
(
user
*
User
)
RegisterAuthn
(
credential
*
webauthn
.
Credential
)
error
{
exists
:=
user
.
WebAuthnCredentials
()
exists
=
append
(
exists
,
*
credential
)
res
,
err
:=
json
.
Marshal
(
exists
)
if
err
!=
nil
{
fmt
.
Println
(
err
)
return
err
}
return
DB
.
Model
(
user
)
.
Update
(
"authn"
,
string
(
res
))
.
Error
}
// RemoveAuthn 删除验证器
func
(
user
*
User
)
RemoveAuthn
(
id
string
)
{
exists
:=
user
.
WebAuthnCredentials
()
for
i
:=
0
;
i
<
len
(
exists
);
i
++
{
idEncoded
:=
base64
.
StdEncoding
.
EncodeToString
(
exists
[
i
]
.
ID
)
if
idEncoded
==
id
{
exists
[
len
(
exists
)
-
1
],
exists
[
i
]
=
exists
[
i
],
exists
[
len
(
exists
)
-
1
]
exists
=
exists
[
:
len
(
exists
)
-
1
]
break
}
}
res
,
_
:=
json
.
Marshal
(
exists
)
DB
.
Model
(
user
)
.
Update
(
"authn"
,
string
(
res
))
}
pkg/authn/auth.go
浏览文件 @
c817f70f
package
authn
import
(
"fmt"
model
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/duo-labs/webauthn/webauthn"
)
var
AuthnInstance
*
webauthn
.
WebAuthn
// Init 初始化webauthn
func
Init
()
{
var
err
error
base
:=
model
.
GetSiteURL
()
AuthnInstance
,
err
=
webauthn
.
New
(
&
webauthn
.
Config
{
RPDisplayName
:
"Duo Labs"
,
// Display Name for your site
RPID
:
"localhost"
,
// Generally the FQDN for your site
RPOrigin
:
"http://localhost:3000"
,
// The origin URL for WebAuthn requests
RPIcon
:
"https://duo.com/logo.png"
,
// Optional icon URL for your site
RPDisplayName
:
model
.
GetSettingByName
(
"siteName"
),
// Display Name for your site
RPID
:
base
.
Hostname
(),
// Generally the FQDN for your site
RPOrigin
:
base
.
String
(),
// The origin URL for WebAuthn requests
})
if
err
!=
nil
{
fmt
.
Println
(
err
)
util
.
Log
()
.
Error
(
"无法初始化WebAuthn, %s"
,
err
)
}
}
pkg/serializer/setting.go
浏览文件 @
c817f70f
...
...
@@ -16,6 +16,7 @@ type SiteConfig struct {
ShareScoreRate
string
`json:"share_score_rate"`
HomepageViewMethod
string
`json:"home_view_method"`
ShareViewMethod
string
`json:"share_view_method"`
Authn
bool
`json:"authn"'`
User
User
`json:"user"`
}
...
...
@@ -75,6 +76,7 @@ func BuildSiteConfig(settings map[string]string, user *model.User) Response {
ShareScoreRate
:
checkSettingValue
(
settings
,
"share_score_rate"
),
HomepageViewMethod
:
checkSettingValue
(
settings
,
"home_view_method"
),
ShareViewMethod
:
checkSettingValue
(
settings
,
"share_view_method"
),
Authn
:
model
.
IsTrueVal
(
checkSettingValue
(
settings
,
"authn_enabled"
)),
User
:
userRes
,
}}
return
res
...
...
pkg/serializer/user.go
浏览文件 @
c817f70f
...
...
@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/hashid"
"github.com/duo-labs/webauthn/webauthn"
)
// CheckLogin 检查登录
...
...
@@ -64,6 +65,26 @@ type storage struct {
Total
uint64
`json:"total"`
}
// WebAuthnCredentials 外部验证器凭证
type
WebAuthnCredentials
struct
{
ID
[]
byte
`json:"id"`
FingerPrint
string
`json:"fingerprint"`
}
// BuildWebAuthnList 构建设置页面凭证列表
func
BuildWebAuthnList
(
credentials
[]
webauthn
.
Credential
)
[]
WebAuthnCredentials
{
res
:=
make
([]
WebAuthnCredentials
,
0
,
len
(
credentials
))
for
_
,
v
:=
range
credentials
{
credential
:=
WebAuthnCredentials
{
ID
:
v
.
ID
,
FingerPrint
:
fmt
.
Sprintf
(
"% X"
,
v
.
Authenticator
.
AAGUID
),
}
res
=
append
(
res
,
credential
)
}
return
res
}
// BuildUser 序列化用户
func
BuildUser
(
user
model
.
User
)
User
{
tags
,
_
:=
model
.
GetTagsByUID
(
user
.
ID
)
...
...
routers/controllers/site.go
浏览文件 @
c817f70f
...
...
@@ -25,6 +25,7 @@ func SiteConfig(c *gin.Context) {
"share_score_rate"
,
"home_view_method"
,
"share_view_method"
,
"authn_enabled"
,
)
// 如果已登录,则同时返回用户信息和标签
...
...
routers/controllers/user.go
浏览文件 @
c817f70f
...
...
@@ -2,6 +2,7 @@ package controllers
import
(
"encoding/json"
"fmt"
model
"github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/authn"
"github.com/HFO4/cloudreve/pkg/serializer"
...
...
@@ -17,7 +18,7 @@ func StartLoginAuthn(c *gin.Context) {
userName
:=
c
.
Param
(
"username"
)
expectedUser
,
err
:=
model
.
GetUserByEmail
(
userName
)
if
err
!=
nil
{
c
.
JSON
(
200
,
serializer
.
Err
(
401
,
"用户
邮箱或密码错误
"
,
err
))
c
.
JSON
(
200
,
serializer
.
Err
(
401
,
"用户
不存在
"
,
err
))
return
}
...
...
@@ -56,7 +57,7 @@ func FinishLoginAuthn(c *gin.Context) {
_
,
err
=
authn
.
AuthnInstance
.
FinishLogin
(
expectedUser
,
sessionData
,
c
.
Request
)
if
err
!=
nil
{
c
.
JSON
(
200
,
serializer
.
Err
(
401
,
"
用户邮箱或密码错误
"
,
err
))
c
.
JSON
(
200
,
serializer
.
Err
(
401
,
"
登录验证失败
"
,
err
))
return
}
...
...
@@ -96,13 +97,24 @@ func FinishRegAuthn(c *gin.Context) {
err
:=
json
.
Unmarshal
(
sessionDataJSON
,
&
sessionData
)
credential
,
err
:=
authn
.
AuthnInstance
.
FinishRegistration
(
currUser
,
sessionData
,
c
.
Request
)
if
err
!=
nil
{
c
.
JSON
(
200
,
ErrorResponse
(
err
))
return
}
currUser
.
RegisterAuthn
(
credential
)
err
=
currUser
.
RegisterAuthn
(
credential
)
if
err
!=
nil
{
c
.
JSON
(
200
,
ErrorResponse
(
err
))
return
}
c
.
JSON
(
200
,
serializer
.
Response
{
Code
:
0
})
c
.
JSON
(
200
,
serializer
.
Response
{
Code
:
0
,
Data
:
map
[
string
]
interface
{}{
"id"
:
credential
.
ID
,
"fingerprint"
:
fmt
.
Sprintf
(
"% X"
,
credential
.
Authenticator
.
AAGUID
),
},
})
}
// UserLogin 用户登录
...
...
@@ -265,6 +277,10 @@ func UpdateOption(c *gin.Context) {
subService
=
&
user
.
PasswordChange
{}
case
"2fa"
:
subService
=
&
user
.
Enable2FA
{}
case
"authn"
:
subService
=
&
user
.
DeleteWebAuthn
{}
case
"theme"
:
subService
=
&
user
.
ThemeChose
{}
}
subErr
=
c
.
ShouldBindJSON
(
subService
)
...
...
routers/router.go
浏览文件 @
c817f70f
...
...
@@ -104,9 +104,13 @@ func InitMasterRouter() *gin.Engine {
// 用户登录
user
.
POST
(
"session"
,
controllers
.
UserLogin
)
// WebAuthn登陆初始化
user
.
GET
(
"authn/:username"
,
controllers
.
StartLoginAuthn
)
user
.
GET
(
"authn/:username"
,
middleware
.
IsFunctionEnabled
(
"authn_enabled"
),
controllers
.
StartLoginAuthn
)
// WebAuthn登陆
user
.
POST
(
"authn/finish/:username"
,
controllers
.
FinishLoginAuthn
)
user
.
POST
(
"authn/finish/:username"
,
middleware
.
IsFunctionEnabled
(
"authn_enabled"
),
controllers
.
FinishLoginAuthn
)
// 获取用户主页展示用分享
user
.
GET
(
"profile/:id"
,
middleware
.
HashID
(
hashid
.
UserID
),
...
...
@@ -263,7 +267,8 @@ func InitMasterRouter() *gin.Engine {
user
.
DELETE
(
"session"
,
controllers
.
UserSignOut
)
// WebAuthn 注册相关
authn
:=
user
.
Group
(
"authn"
)
authn
:=
user
.
Group
(
"authn"
,
middleware
.
IsFunctionEnabled
(
"authn_enabled"
))
{
authn
.
PUT
(
""
,
controllers
.
StartRegAuthn
)
authn
.
PUT
(
"finish"
,
controllers
.
FinishRegAuthn
)
...
...
service/user/setting.go
浏览文件 @
c817f70f
...
...
@@ -33,7 +33,7 @@ type AvatarService struct {
// 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"`
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 属性更改接口
...
...
@@ -75,10 +75,36 @@ type Enable2FA struct {
Code
string
`json:"code" binding:"required"`
}
// Update 更改密码
// 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
)
...
...
@@ -92,6 +118,15 @@ func (service *Enable2FA) Update(c *gin.Context, user *model.User) serializer.Re
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
{}
...
...
@@ -318,8 +353,9 @@ func (service *SettingService) Settings(c *gin.Context, user *model.User) serial
"homepage"
:
!
user
.
OptionsSerialized
.
ProfileOff
,
"two_factor"
:
user
.
TwoFactor
!=
""
,
"prefer_theme"
:
user
.
OptionsSerialized
.
PreferredTheme
,
"themes"
:
model
.
GetSettingByName
s
(
"themes"
),
"themes"
:
model
.
GetSettingByName
(
"themes"
),
"group_expires"
:
groupExpires
,
"authn"
:
serializer
.
BuildWebAuthnList
(
user
.
WebAuthnCredentials
()),
},
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录