fix: require proper verification for passkey changes (#4393)
This commit is contained in:
@@ -36,6 +36,10 @@ func PasskeyRegisterBegin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !requirePasskeyRegistrationVerification(c, user.Id) {
|
||||
return
|
||||
}
|
||||
|
||||
credential, err := model.GetPasskeyByUserID(user.Id)
|
||||
if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
|
||||
common.ApiError(c, err)
|
||||
@@ -96,6 +100,10 @@ func PasskeyRegisterFinish(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !requirePasskeyRegistrationVerification(c, user.Id) {
|
||||
return
|
||||
}
|
||||
|
||||
wa, err := passkeysvc.BuildWebAuthn(c.Request)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
@@ -151,6 +159,10 @@ func PasskeyDelete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !requirePasskeyDeleteVerification(c, user.Id) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := model.DeletePasskeyByUserID(user.Id); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
@@ -474,6 +486,7 @@ func PasskeyVerifyFinish(c *gin.Context) {
|
||||
// Mark passkey as ready; /api/verify will convert this into the final secure verification session.
|
||||
session.Set(PasskeyReadySessionKey, time.Now().Unix())
|
||||
session.Delete(SecureVerificationSessionKey)
|
||||
session.Delete(secureVerificationMethodSessionKey)
|
||||
if err := session.Save(); err != nil {
|
||||
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
|
||||
return
|
||||
@@ -504,3 +517,60 @@ func getSessionUser(c *gin.Context) (*model.User, error) {
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func requirePasskeyRegistrationVerification(c *gin.Context, userID int) bool {
|
||||
twoFA, err := model.GetTwoFAByUserId(userID)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return false
|
||||
}
|
||||
if twoFA == nil || !twoFA.IsEnabled {
|
||||
return true
|
||||
}
|
||||
return requireSecureVerificationMethod(c, secureVerificationMethod2FA)
|
||||
}
|
||||
|
||||
func requirePasskeyDeleteVerification(c *gin.Context, userID int) bool {
|
||||
twoFA, err := model.GetTwoFAByUserId(userID)
|
||||
if err != nil {
|
||||
common.ApiError(c, err)
|
||||
return false
|
||||
}
|
||||
if twoFA != nil && twoFA.IsEnabled {
|
||||
return requireSecureVerificationMethod(c, secureVerificationMethod2FA)
|
||||
}
|
||||
|
||||
_, err = model.GetPasskeyByUserID(userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrPasskeyNotFound) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该用户尚未绑定 Passkey",
|
||||
})
|
||||
return false
|
||||
}
|
||||
common.ApiError(c, err)
|
||||
return false
|
||||
}
|
||||
|
||||
return requireSecureVerificationMethod(c, secureVerificationMethodPasskey)
|
||||
}
|
||||
|
||||
func requireSecureVerificationMethod(c *gin.Context, method string) bool {
|
||||
session := sessions.Default(c)
|
||||
verifiedAt, ok := session.Get(SecureVerificationSessionKey).(int64)
|
||||
if !ok || time.Now().Unix()-verifiedAt >= SecureVerificationTimeout {
|
||||
session.Delete(SecureVerificationSessionKey)
|
||||
session.Delete(secureVerificationMethodSessionKey)
|
||||
_ = session.Save()
|
||||
common.ApiErrorMsg(c, "请先完成安全验证")
|
||||
return false
|
||||
}
|
||||
|
||||
if verifiedMethod, ok := session.Get(secureVerificationMethodSessionKey).(string); !ok || verifiedMethod != method {
|
||||
common.ApiErrorMsg(c, "请先完成对应的安全验证")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ import (
|
||||
|
||||
const (
|
||||
// SecureVerificationSessionKey means the user has fully passed secure verification.
|
||||
SecureVerificationSessionKey = "secure_verified_at"
|
||||
SecureVerificationSessionKey = "secure_verified_at"
|
||||
secureVerificationMethodSessionKey = "secure_verified_method"
|
||||
secureVerificationMethod2FA = "2fa"
|
||||
secureVerificationMethodPasskey = "passkey"
|
||||
// PasskeyReadySessionKey means WebAuthn finished and /api/verify can finalize step-up verification.
|
||||
PasskeyReadySessionKey = "secure_passkey_ready_at"
|
||||
// SecureVerificationTimeout 验证有效期(秒)
|
||||
@@ -120,7 +123,7 @@ func UniversalVerify(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 验证成功,在 session 中记录时间戳
|
||||
now, err := setSecureVerificationSession(c)
|
||||
now, err := setSecureVerificationSession(c, req.Method)
|
||||
if err != nil {
|
||||
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
|
||||
return
|
||||
@@ -139,11 +142,12 @@ func UniversalVerify(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func setSecureVerificationSession(c *gin.Context) (int64, error) {
|
||||
func setSecureVerificationSession(c *gin.Context, method string) (int64, error) {
|
||||
session := sessions.Default(c)
|
||||
session.Delete(PasskeyReadySessionKey)
|
||||
now := time.Now().Unix()
|
||||
session.Set(SecureVerificationSessionKey, now)
|
||||
session.Set(secureVerificationMethodSessionKey, method)
|
||||
if err := session.Save(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user