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
95738594
authored
Mar 15, 2026
by
Calcium-Ion
Committed by
GitHub
Mar 15, 2026
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3257 from seefs001/fix/passkey-verify
enhance channel key viewing
parents
efab41c4
c77c8242
Hide 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 @
95738594
...
@@ -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 @
95738594
...
@@ -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