Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c77c8242
authored
Mar 15, 2026
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
enhance channel key viewing
parent
e4144d60
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
50 additions
and
92 deletions
+50
-92
controller/passkey.go
+9
-0
controller/secure_verification.go
+41
-92
No files found.
controller/passkey.go
View file @
c77c8242
...
@@ -470,6 +470,15 @@ func PasskeyVerifyFinish(c *gin.Context) {
...
@@ -470,6 +470,15 @@ func PasskeyVerifyFinish(c *gin.Context) {
return
return
}
}
session
:=
sessions
.
Default
(
c
)
// Mark passkey as ready; /api/verify will convert this into the final secure verification session.
session
.
Set
(
PasskeyReadySessionKey
,
time
.
Now
()
.
Unix
())
session
.
Delete
(
SecureVerificationSessionKey
)
if
err
:=
session
.
Save
();
err
!=
nil
{
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"保存验证状态失败: %v"
,
err
))
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"success"
:
true
,
"message"
:
"Passkey 验证成功"
,
"message"
:
"Passkey 验证成功"
,
...
...
controller/secure_verification.go
View file @
c77c8242
...
@@ -7,18 +7,19 @@ import (
...
@@ -7,18 +7,19 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/model"
passkeysvc
"github.com/QuantumNous/new-api/service/passkey"
"github.com/QuantumNous/new-api/setting/system_setting"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
)
)
const
(
const
(
// SecureVerificationSessionKey
安全验证的 session key
// SecureVerificationSessionKey
means the user has fully passed secure verification.
SecureVerificationSessionKey
=
"secure_verified_at"
SecureVerificationSessionKey
=
"secure_verified_at"
// PasskeyReadySessionKey means WebAuthn finished and /api/verify can finalize step-up verification.
PasskeyReadySessionKey
=
"secure_passkey_ready_at"
// SecureVerificationTimeout 验证有效期(秒)
// SecureVerificationTimeout 验证有效期(秒)
SecureVerificationTimeout
=
300
// 5分钟
SecureVerificationTimeout
=
300
// 5分钟
// PasskeyReadyTimeout passkey ready 标记有效期(秒)
PasskeyReadyTimeout
=
60
)
)
type
UniversalVerifyRequest
struct
{
type
UniversalVerifyRequest
struct
{
...
@@ -76,6 +77,7 @@ func UniversalVerify(c *gin.Context) {
...
@@ -76,6 +77,7 @@ func UniversalVerify(c *gin.Context) {
// 根据验证方式进行验证
// 根据验证方式进行验证
var
verified
bool
var
verified
bool
var
verifyMethod
string
var
verifyMethod
string
var
err
error
switch
req
.
Method
{
switch
req
.
Method
{
case
"2fa"
:
case
"2fa"
:
...
@@ -95,10 +97,16 @@ func UniversalVerify(c *gin.Context) {
...
@@ -95,10 +97,16 @@ func UniversalVerify(c *gin.Context) {
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"用户未启用Passkey"
))
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"用户未启用Passkey"
))
return
return
}
}
// Passkey 验证需要先调用 PasskeyVerifyBegin 和 PasskeyVerifyFinish
// Passkey branch only trusts the short-lived marker written by PasskeyVerifyFinish.
// 这里只是验证 Passkey 验证流程是否已经完成
verified
,
err
=
consumePasskeyReady
(
c
)
// 实际上,前端应该先调用这两个接口,然后再调用本接口
if
err
!=
nil
{
verified
=
true
// Passkey 验证逻辑已在 PasskeyVerifyFinish 中完成
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"Passkey 验证状态异常: %v"
,
err
))
return
}
if
!
verified
{
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"请先完成 Passkey 验证"
))
return
}
verifyMethod
=
"Passkey"
verifyMethod
=
"Passkey"
default
:
default
:
...
@@ -112,10 +120,8 @@ func UniversalVerify(c *gin.Context) {
...
@@ -112,10 +120,8 @@ func UniversalVerify(c *gin.Context) {
}
}
// 验证成功,在 session 中记录时间戳
// 验证成功,在 session 中记录时间戳
session
:=
sessions
.
Default
(
c
)
now
,
err
:=
setSecureVerificationSession
(
c
)
now
:=
time
.
Now
()
.
Unix
()
if
err
!=
nil
{
session
.
Set
(
SecureVerificationSessionKey
,
now
)
if
err
:=
session
.
Save
();
err
!=
nil
{
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"保存验证状态失败: %v"
,
err
))
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"保存验证状态失败: %v"
,
err
))
return
return
}
}
...
@@ -133,94 +139,37 @@ func UniversalVerify(c *gin.Context) {
...
@@ -133,94 +139,37 @@ func UniversalVerify(c *gin.Context) {
})
})
}
}
// PasskeyVerifyAndSetSession Passkey 验证完成后设置 session
func
setSecureVerificationSession
(
c
*
gin
.
Context
)
(
int64
,
error
)
{
// 这是一个辅助函数,供 PasskeyVerifyFinish 调用
func
PasskeyVerifyAndSetSession
(
c
*
gin
.
Context
)
{
session
:=
sessions
.
Default
(
c
)
session
:=
sessions
.
Default
(
c
)
session
.
Delete
(
PasskeyReadySessionKey
)
now
:=
time
.
Now
()
.
Unix
()
now
:=
time
.
Now
()
.
Unix
()
session
.
Set
(
SecureVerificationSessionKey
,
now
)
session
.
Set
(
SecureVerificationSessionKey
,
now
)
_
=
session
.
Save
()
if
err
:=
session
.
Save
();
err
!=
nil
{
}
return
0
,
err
// PasskeyVerifyForSecure 用于安全验证的 Passkey 验证流程
// 整合了 begin 和 finish 流程
func
PasskeyVerifyForSecure
(
c
*
gin
.
Context
)
{
if
!
system_setting
.
GetPasskeySettings
()
.
Enabled
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"管理员未启用 Passkey 登录"
,
})
return
}
userId
:=
c
.
GetInt
(
"id"
)
if
userId
==
0
{
c
.
JSON
(
http
.
StatusUnauthorized
,
gin
.
H
{
"success"
:
false
,
"message"
:
"未登录"
,
})
return
}
user
:=
&
model
.
User
{
Id
:
userId
}
if
err
:=
user
.
FillUserById
();
err
!=
nil
{
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"获取用户信息失败: %v"
,
err
))
return
}
if
user
.
Status
!=
common
.
UserStatusEnabled
{
common
.
ApiError
(
c
,
fmt
.
Errorf
(
"该用户已被禁用"
))
return
}
credential
,
err
:=
model
.
GetPasskeyByUserID
(
userId
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"该用户尚未绑定 Passkey"
,
})
return
}
}
return
now
,
nil
}
wa
,
err
:=
passkeysvc
.
BuildWebAuthn
(
c
.
Request
)
func
consumePasskeyReady
(
c
*
gin
.
Context
)
(
bool
,
error
)
{
if
err
!=
nil
{
session
:=
sessions
.
Default
(
c
)
common
.
ApiError
(
c
,
err
)
readyAtRaw
:=
session
.
Get
(
PasskeyReadySessionKey
)
return
if
readyAtRaw
==
nil
{
return
false
,
nil
}
}
waUser
:=
passkeysvc
.
NewWebAuthnUser
(
user
,
credential
)
readyAt
,
ok
:=
readyAtRaw
.
(
int64
)
sessionData
,
err
:=
passkeysvc
.
PopSessionData
(
c
,
passkeysvc
.
VerifySessionKey
)
if
!
ok
{
if
err
!=
nil
{
session
.
Delete
(
PasskeyReadySessionKey
)
common
.
ApiError
(
c
,
err
)
_
=
session
.
Save
(
)
return
return
false
,
fmt
.
Errorf
(
"无效的 Passkey 验证状态"
)
}
}
session
.
Delete
(
PasskeyReadySessionKey
)
_
,
err
=
wa
.
FinishLogin
(
waUser
,
*
sessionData
,
c
.
Request
)
if
err
:=
session
.
Save
();
err
!=
nil
{
if
err
!=
nil
{
return
false
,
err
common
.
ApiError
(
c
,
err
)
return
}
}
// Expired ready markers cannot be reused.
// 更新凭证的最后使用时间
if
time
.
Now
()
.
Unix
()
-
readyAt
>=
PasskeyReadyTimeout
{
now
:=
time
.
Now
()
return
false
,
nil
credential
.
LastUsedAt
=
&
now
if
err
:=
model
.
UpsertPasskeyCredential
(
credential
);
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
}
return
true
,
nil
// 验证成功,设置 session
PasskeyVerifyAndSetSession
(
c
)
// 记录日志
model
.
RecordLog
(
userId
,
model
.
LogTypeSystem
,
"Passkey 安全验证成功"
)
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"Passkey 验证成功"
,
"data"
:
gin
.
H
{
"verified"
:
true
,
"expires_at"
:
time
.
Now
()
.
Unix
()
+
SecureVerificationTimeout
,
},
})
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment