Skip to content

Commit 9573859

Browse files
authored
Merge pull request QuantumNous#3257 from seefs001/fix/passkey-verify
enhance channel key viewing
2 parents efab41c + c77c824 commit 9573859

2 files changed

Lines changed: 50 additions & 92 deletions

File tree

controller/passkey.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,15 @@ func PasskeyVerifyFinish(c *gin.Context) {
470470
return
471471
}
472472

473+
session := sessions.Default(c)
474+
// Mark passkey as ready; /api/verify will convert this into the final secure verification session.
475+
session.Set(PasskeyReadySessionKey, time.Now().Unix())
476+
session.Delete(SecureVerificationSessionKey)
477+
if err := session.Save(); err != nil {
478+
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
479+
return
480+
}
481+
473482
c.JSON(http.StatusOK, gin.H{
474483
"success": true,
475484
"message": "Passkey 验证成功",

controller/secure_verification.go

Lines changed: 41 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ import (
77

88
"github.com/QuantumNous/new-api/common"
99
"github.com/QuantumNous/new-api/model"
10-
passkeysvc "github.com/QuantumNous/new-api/service/passkey"
11-
"github.com/QuantumNous/new-api/setting/system_setting"
12-
1310
"github.com/gin-contrib/sessions"
1411
"github.com/gin-gonic/gin"
1512
)
1613

1714
const (
18-
// SecureVerificationSessionKey 安全验证的 session key
15+
// SecureVerificationSessionKey means the user has fully passed secure verification.
1916
SecureVerificationSessionKey = "secure_verified_at"
17+
// PasskeyReadySessionKey means WebAuthn finished and /api/verify can finalize step-up verification.
18+
PasskeyReadySessionKey = "secure_passkey_ready_at"
2019
// SecureVerificationTimeout 验证有效期(秒)
2120
SecureVerificationTimeout = 300 // 5分钟
21+
// PasskeyReadyTimeout passkey ready 标记有效期(秒)
22+
PasskeyReadyTimeout = 60
2223
)
2324

2425
type UniversalVerifyRequest struct {
@@ -76,6 +77,7 @@ func UniversalVerify(c *gin.Context) {
7677
// 根据验证方式进行验证
7778
var verified bool
7879
var verifyMethod string
80+
var err error
7981

8082
switch req.Method {
8183
case "2fa":
@@ -95,10 +97,16 @@ func UniversalVerify(c *gin.Context) {
9597
common.ApiError(c, fmt.Errorf("用户未启用Passkey"))
9698
return
9799
}
98-
// Passkey 验证需要先调用 PasskeyVerifyBegin 和 PasskeyVerifyFinish
99-
// 这里只是验证 Passkey 验证流程是否已经完成
100-
// 实际上,前端应该先调用这两个接口,然后再调用本接口
101-
verified = true // Passkey 验证逻辑已在 PasskeyVerifyFinish 中完成
100+
// Passkey branch only trusts the short-lived marker written by PasskeyVerifyFinish.
101+
verified, err = consumePasskeyReady(c)
102+
if err != nil {
103+
common.ApiError(c, fmt.Errorf("Passkey 验证状态异常: %v", err))
104+
return
105+
}
106+
if !verified {
107+
common.ApiError(c, fmt.Errorf("请先完成 Passkey 验证"))
108+
return
109+
}
102110
verifyMethod = "Passkey"
103111

104112
default:
@@ -112,10 +120,8 @@ func UniversalVerify(c *gin.Context) {
112120
}
113121

114122
// 验证成功,在 session 中记录时间戳
115-
session := sessions.Default(c)
116-
now := time.Now().Unix()
117-
session.Set(SecureVerificationSessionKey, now)
118-
if err := session.Save(); err != nil {
123+
now, err := setSecureVerificationSession(c)
124+
if err != nil {
119125
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
120126
return
121127
}
@@ -133,94 +139,37 @@ func UniversalVerify(c *gin.Context) {
133139
})
134140
}
135141

136-
// PasskeyVerifyAndSetSession Passkey 验证完成后设置 session
137-
// 这是一个辅助函数,供 PasskeyVerifyFinish 调用
138-
func PasskeyVerifyAndSetSession(c *gin.Context) {
142+
func setSecureVerificationSession(c *gin.Context) (int64, error) {
139143
session := sessions.Default(c)
144+
session.Delete(PasskeyReadySessionKey)
140145
now := time.Now().Unix()
141146
session.Set(SecureVerificationSessionKey, now)
142-
_ = session.Save()
143-
}
144-
145-
// PasskeyVerifyForSecure 用于安全验证的 Passkey 验证流程
146-
// 整合了 begin 和 finish 流程
147-
func PasskeyVerifyForSecure(c *gin.Context) {
148-
if !system_setting.GetPasskeySettings().Enabled {
149-
c.JSON(http.StatusOK, gin.H{
150-
"success": false,
151-
"message": "管理员未启用 Passkey 登录",
152-
})
153-
return
154-
}
155-
156-
userId := c.GetInt("id")
157-
if userId == 0 {
158-
c.JSON(http.StatusUnauthorized, gin.H{
159-
"success": false,
160-
"message": "未登录",
161-
})
162-
return
163-
}
164-
165-
user := &model.User{Id: userId}
166-
if err := user.FillUserById(); err != nil {
167-
common.ApiError(c, fmt.Errorf("获取用户信息失败: %v", err))
168-
return
169-
}
170-
171-
if user.Status != common.UserStatusEnabled {
172-
common.ApiError(c, fmt.Errorf("该用户已被禁用"))
173-
return
174-
}
175-
176-
credential, err := model.GetPasskeyByUserID(userId)
177-
if err != nil {
178-
c.JSON(http.StatusOK, gin.H{
179-
"success": false,
180-
"message": "该用户尚未绑定 Passkey",
181-
})
182-
return
147+
if err := session.Save(); err != nil {
148+
return 0, err
183149
}
150+
return now, nil
151+
}
184152

185-
wa, err := passkeysvc.BuildWebAuthn(c.Request)
186-
if err != nil {
187-
common.ApiError(c, err)
188-
return
153+
func consumePasskeyReady(c *gin.Context) (bool, error) {
154+
session := sessions.Default(c)
155+
readyAtRaw := session.Get(PasskeyReadySessionKey)
156+
if readyAtRaw == nil {
157+
return false, nil
189158
}
190159

191-
waUser := passkeysvc.NewWebAuthnUser(user, credential)
192-
sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.VerifySessionKey)
193-
if err != nil {
194-
common.ApiError(c, err)
195-
return
160+
readyAt, ok := readyAtRaw.(int64)
161+
if !ok {
162+
session.Delete(PasskeyReadySessionKey)
163+
_ = session.Save()
164+
return false, fmt.Errorf("无效的 Passkey 验证状态")
196165
}
197-
198-
_, err = wa.FinishLogin(waUser, *sessionData, c.Request)
199-
if err != nil {
200-
common.ApiError(c, err)
201-
return
166+
session.Delete(PasskeyReadySessionKey)
167+
if err := session.Save(); err != nil {
168+
return false, err
202169
}
203-
204-
// 更新凭证的最后使用时间
205-
now := time.Now()
206-
credential.LastUsedAt = &now
207-
if err := model.UpsertPasskeyCredential(credential); err != nil {
208-
common.ApiError(c, err)
209-
return
170+
// Expired ready markers cannot be reused.
171+
if time.Now().Unix()-readyAt >= PasskeyReadyTimeout {
172+
return false, nil
210173
}
211-
212-
// 验证成功,设置 session
213-
PasskeyVerifyAndSetSession(c)
214-
215-
// 记录日志
216-
model.RecordLog(userId, model.LogTypeSystem, "Passkey 安全验证成功")
217-
218-
c.JSON(http.StatusOK, gin.H{
219-
"success": true,
220-
"message": "Passkey 验证成功",
221-
"data": gin.H{
222-
"verified": true,
223-
"expires_at": time.Now().Unix() + SecureVerificationTimeout,
224-
},
225-
})
174+
return true, nil
226175
}

0 commit comments

Comments
 (0)