@@ -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
1714const (
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
2425type 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