fix: require proper verification for passkey changes (#4393)
This commit is contained in:
@@ -36,6 +36,10 @@ func PasskeyRegisterBegin(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !requirePasskeyRegistrationVerification(c, user.Id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
credential, err := model.GetPasskeyByUserID(user.Id)
|
credential, err := model.GetPasskeyByUserID(user.Id)
|
||||||
if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
|
if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
|
||||||
common.ApiError(c, err)
|
common.ApiError(c, err)
|
||||||
@@ -96,6 +100,10 @@ func PasskeyRegisterFinish(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !requirePasskeyRegistrationVerification(c, user.Id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
wa, err := passkeysvc.BuildWebAuthn(c.Request)
|
wa, err := passkeysvc.BuildWebAuthn(c.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ApiError(c, err)
|
common.ApiError(c, err)
|
||||||
@@ -151,6 +159,10 @@ func PasskeyDelete(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !requirePasskeyDeleteVerification(c, user.Id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := model.DeletePasskeyByUserID(user.Id); err != nil {
|
if err := model.DeletePasskeyByUserID(user.Id); err != nil {
|
||||||
common.ApiError(c, err)
|
common.ApiError(c, err)
|
||||||
return
|
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.
|
// Mark passkey as ready; /api/verify will convert this into the final secure verification session.
|
||||||
session.Set(PasskeyReadySessionKey, time.Now().Unix())
|
session.Set(PasskeyReadySessionKey, time.Now().Unix())
|
||||||
session.Delete(SecureVerificationSessionKey)
|
session.Delete(SecureVerificationSessionKey)
|
||||||
|
session.Delete(secureVerificationMethodSessionKey)
|
||||||
if err := session.Save(); err != nil {
|
if err := session.Save(); err != nil {
|
||||||
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
|
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
|
||||||
return
|
return
|
||||||
@@ -504,3 +517,60 @@ func getSessionUser(c *gin.Context) (*model.User, error) {
|
|||||||
}
|
}
|
||||||
return user, nil
|
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 (
|
const (
|
||||||
// SecureVerificationSessionKey means the user has fully passed secure verification.
|
// 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 means WebAuthn finished and /api/verify can finalize step-up verification.
|
||||||
PasskeyReadySessionKey = "secure_passkey_ready_at"
|
PasskeyReadySessionKey = "secure_passkey_ready_at"
|
||||||
// SecureVerificationTimeout 验证有效期(秒)
|
// SecureVerificationTimeout 验证有效期(秒)
|
||||||
@@ -120,7 +123,7 @@ func UniversalVerify(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证成功,在 session 中记录时间戳
|
// 验证成功,在 session 中记录时间戳
|
||||||
now, err := setSecureVerificationSession(c)
|
now, err := setSecureVerificationSession(c, req.Method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
|
common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
|
||||||
return
|
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 := sessions.Default(c)
|
||||||
session.Delete(PasskeyReadySessionKey)
|
session.Delete(PasskeyReadySessionKey)
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
session.Set(SecureVerificationSessionKey, now)
|
session.Set(SecureVerificationSessionKey, now)
|
||||||
|
session.Set(secureVerificationMethodSessionKey, method)
|
||||||
if err := session.Save(); err != nil {
|
if err := session.Save(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// SecureVerificationSessionKey 安全验证的 session key(与 controller 保持一致)
|
// SecureVerificationSessionKey 安全验证的 session key(与 controller 保持一致)
|
||||||
SecureVerificationSessionKey = "secure_verified_at"
|
SecureVerificationSessionKey = "secure_verified_at"
|
||||||
|
secureVerificationMethodSessionKey = "secure_verified_method"
|
||||||
// SecureVerificationTimeout 验证有效期(秒)
|
// SecureVerificationTimeout 验证有效期(秒)
|
||||||
SecureVerificationTimeout = 300 // 5分钟
|
SecureVerificationTimeout = 300 // 5分钟
|
||||||
)
|
)
|
||||||
@@ -48,8 +49,7 @@ func SecureVerificationRequired() gin.HandlerFunc {
|
|||||||
verifiedAt, ok := verifiedAtRaw.(int64)
|
verifiedAt, ok := verifiedAtRaw.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
// session 数据格式错误
|
// session 数据格式错误
|
||||||
session.Delete(SecureVerificationSessionKey)
|
clearSecureVerificationSession(session)
|
||||||
_ = session.Save()
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "验证状态异常,请重新验证",
|
"message": "验证状态异常,请重新验证",
|
||||||
@@ -63,8 +63,7 @@ func SecureVerificationRequired() gin.HandlerFunc {
|
|||||||
elapsed := time.Now().Unix() - verifiedAt
|
elapsed := time.Now().Unix() - verifiedAt
|
||||||
if elapsed >= SecureVerificationTimeout {
|
if elapsed >= SecureVerificationTimeout {
|
||||||
// 验证已过期,清除 session
|
// 验证已过期,清除 session
|
||||||
session.Delete(SecureVerificationSessionKey)
|
clearSecureVerificationSession(session)
|
||||||
_ = session.Save()
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "验证已过期,请重新验证",
|
"message": "验证已过期,请重新验证",
|
||||||
@@ -74,11 +73,16 @@ func SecureVerificationRequired() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证有效,继续处理请求
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearSecureVerificationSession(session sessions.Session) {
|
||||||
|
session.Delete(SecureVerificationSessionKey)
|
||||||
|
session.Delete(secureVerificationMethodSessionKey)
|
||||||
|
_ = session.Save()
|
||||||
|
}
|
||||||
|
|
||||||
// OptionalSecureVerification 可选的安全验证中间件
|
// OptionalSecureVerification 可选的安全验证中间件
|
||||||
// 如果用户已验证,则在 context 中设置标记,但不阻止请求继续
|
// 如果用户已验证,则在 context 中设置标记,但不阻止请求继续
|
||||||
// 用于某些需要区分是否已验证的场景
|
// 用于某些需要区分是否已验证的场景
|
||||||
@@ -109,8 +113,7 @@ func OptionalSecureVerification() gin.HandlerFunc {
|
|||||||
|
|
||||||
elapsed := time.Now().Unix() - verifiedAt
|
elapsed := time.Now().Unix() - verifiedAt
|
||||||
if elapsed >= SecureVerificationTimeout {
|
if elapsed >= SecureVerificationTimeout {
|
||||||
session.Delete(SecureVerificationSessionKey)
|
clearSecureVerificationSession(session)
|
||||||
_ = session.Save()
|
|
||||||
c.Set("secure_verified", false)
|
c.Set("secure_verified", false)
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
@@ -126,6 +129,5 @@ func OptionalSecureVerification() gin.HandlerFunc {
|
|||||||
// 用于用户登出或需要强制重新验证的场景
|
// 用于用户登出或需要强制重新验证的场景
|
||||||
func ClearSecureVerification(c *gin.Context) {
|
func ClearSecureVerification(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
session.Delete(SecureVerificationSessionKey)
|
clearSecureVerificationSession(session)
|
||||||
_ = session.Save()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ import EmailBindModal from './personal/modals/EmailBindModal';
|
|||||||
import WeChatBindModal from './personal/modals/WeChatBindModal';
|
import WeChatBindModal from './personal/modals/WeChatBindModal';
|
||||||
import AccountDeleteModal from './personal/modals/AccountDeleteModal';
|
import AccountDeleteModal from './personal/modals/AccountDeleteModal';
|
||||||
import ChangePasswordModal from './personal/modals/ChangePasswordModal';
|
import ChangePasswordModal from './personal/modals/ChangePasswordModal';
|
||||||
|
import SecureVerificationModal from '../common/modals/SecureVerificationModal';
|
||||||
|
import { useSecureVerification } from '../../hooks/common/useSecureVerification';
|
||||||
|
|
||||||
const PersonalSetting = () => {
|
const PersonalSetting = () => {
|
||||||
const [userState, userDispatch] = useContext(UserContext);
|
const [userState, userDispatch] = useContext(UserContext);
|
||||||
@@ -76,6 +78,10 @@ const PersonalSetting = () => {
|
|||||||
const [passkeyRegisterLoading, setPasskeyRegisterLoading] = useState(false);
|
const [passkeyRegisterLoading, setPasskeyRegisterLoading] = useState(false);
|
||||||
const [passkeyDeleteLoading, setPasskeyDeleteLoading] = useState(false);
|
const [passkeyDeleteLoading, setPasskeyDeleteLoading] = useState(false);
|
||||||
const [passkeySupported, setPasskeySupported] = useState(false);
|
const [passkeySupported, setPasskeySupported] = useState(false);
|
||||||
|
const [
|
||||||
|
passkeyRequiredVerificationMethod,
|
||||||
|
setPasskeyRequiredVerificationMethod,
|
||||||
|
] = useState(null);
|
||||||
const [notificationSettings, setNotificationSettings] = useState({
|
const [notificationSettings, setNotificationSettings] = useState({
|
||||||
warningType: 'email',
|
warningType: 'email',
|
||||||
warningThreshold: 100000,
|
warningThreshold: 100000,
|
||||||
@@ -91,6 +97,34 @@ const PersonalSetting = () => {
|
|||||||
recordIpLog: false,
|
recordIpLog: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
isModalVisible: isPasskeyVerificationModalVisible,
|
||||||
|
verificationMethods: passkeyVerificationMethods,
|
||||||
|
verificationState: passkeyVerificationState,
|
||||||
|
startVerification: startPasskeyVerification,
|
||||||
|
executeVerification: executePasskeyVerification,
|
||||||
|
cancelVerification: cancelPasskeyVerification,
|
||||||
|
setVerificationCode: setPasskeyVerificationCode,
|
||||||
|
switchVerificationMethod: switchPasskeyVerificationMethod,
|
||||||
|
checkVerificationMethods: checkPasskeyVerificationMethods,
|
||||||
|
} = useSecureVerification({
|
||||||
|
onSuccess: () => {
|
||||||
|
setPasskeyRequiredVerificationMethod(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const visiblePasskeyVerificationMethods = passkeyRequiredVerificationMethod
|
||||||
|
? {
|
||||||
|
...passkeyVerificationMethods,
|
||||||
|
has2FA:
|
||||||
|
passkeyRequiredVerificationMethod === '2fa' &&
|
||||||
|
passkeyVerificationMethods.has2FA,
|
||||||
|
hasPasskey:
|
||||||
|
passkeyRequiredVerificationMethod === 'passkey' &&
|
||||||
|
passkeyVerificationMethods.hasPasskey,
|
||||||
|
}
|
||||||
|
: passkeyVerificationMethods;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let saved = localStorage.getItem('status');
|
let saved = localStorage.getItem('status');
|
||||||
if (saved) {
|
if (saved) {
|
||||||
@@ -203,18 +237,57 @@ const PersonalSetting = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegisterPasskey = async () => {
|
const startPasskeyManagementVerification = async (apiCall, options = {}) => {
|
||||||
if (!passkeySupported || !window.PublicKeyCredential) {
|
const methods = await checkPasskeyVerificationMethods();
|
||||||
|
const requiredMethod = methods.has2FA
|
||||||
|
? '2fa'
|
||||||
|
: methods.hasPasskey
|
||||||
|
? 'passkey'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!requiredMethod) {
|
||||||
|
showError(t('您需要先启用两步验证或 Passkey 才能执行此操作'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredMethod === 'passkey' && !methods.passkeySupported) {
|
||||||
showInfo(t('当前设备不支持 Passkey'));
|
showInfo(t('当前设备不支持 Passkey'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPasskeyRequiredVerificationMethod(requiredMethod);
|
||||||
|
await startPasskeyVerification(apiCall, {
|
||||||
|
preferredMethod: requiredMethod,
|
||||||
|
title: t('安全验证'),
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startPasskeyRegistration = async () => {
|
||||||
|
const methods = await checkPasskeyVerificationMethods();
|
||||||
|
if (!methods.has2FA) {
|
||||||
|
try {
|
||||||
|
await registerPasskey();
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.message || t('Passkey 注册失败,请重试'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPasskeyRequiredVerificationMethod('2fa');
|
||||||
|
await startPasskeyVerification(registerPasskey, {
|
||||||
|
preferredMethod: '2fa',
|
||||||
|
title: t('安全验证'),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerPasskey = async () => {
|
||||||
setPasskeyRegisterLoading(true);
|
setPasskeyRegisterLoading(true);
|
||||||
try {
|
try {
|
||||||
const beginRes = await API.post('/api/user/passkey/register/begin');
|
const beginRes = await API.post('/api/user/passkey/register/begin');
|
||||||
const { success, message, data } = beginRes.data;
|
const { success, message, data } = beginRes.data;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
showError(message || t('无法发起 Passkey 注册'));
|
throw new Error(message || t('无法发起 Passkey 注册'));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKey = prepareCredentialCreationOptions(
|
const publicKey = prepareCredentialCreationOptions(
|
||||||
@@ -223,49 +296,69 @@ const PersonalSetting = () => {
|
|||||||
const credential = await navigator.credentials.create({ publicKey });
|
const credential = await navigator.credentials.create({ publicKey });
|
||||||
const payload = buildRegistrationResult(credential);
|
const payload = buildRegistrationResult(credential);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
showError(t('Passkey 注册失败,请重试'));
|
throw new Error(t('Passkey 注册失败,请重试'));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const finishRes = await API.post(
|
const finishRes = await API.post(
|
||||||
'/api/user/passkey/register/finish',
|
'/api/user/passkey/register/finish',
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
if (finishRes.data.success) {
|
if (!finishRes.data.success) {
|
||||||
showSuccess(t('Passkey 注册成功'));
|
throw new Error(
|
||||||
await loadPasskeyStatus();
|
finishRes.data.message || t('Passkey 注册失败,请重试'),
|
||||||
} else {
|
);
|
||||||
showError(finishRes.data.message || t('Passkey 注册失败,请重试'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showSuccess(t('Passkey 注册成功'));
|
||||||
|
await loadPasskeyStatus();
|
||||||
|
return finishRes.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.name === 'AbortError') {
|
if (error?.name === 'AbortError') {
|
||||||
showInfo(t('已取消 Passkey 注册'));
|
showInfo(t('已取消 Passkey 注册'));
|
||||||
} else {
|
return { cancelled: true };
|
||||||
showError(t('Passkey 注册失败,请重试'));
|
|
||||||
}
|
}
|
||||||
|
throw new Error(error?.message || t('Passkey 注册失败,请重试'));
|
||||||
} finally {
|
} finally {
|
||||||
setPasskeyRegisterLoading(false);
|
setPasskeyRegisterLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemovePasskey = async () => {
|
const handleRegisterPasskey = async () => {
|
||||||
|
if (!passkeySupported || !window.PublicKeyCredential) {
|
||||||
|
showInfo(t('当前设备不支持 Passkey'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await startPasskeyRegistration();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePasskey = async () => {
|
||||||
setPasskeyDeleteLoading(true);
|
setPasskeyDeleteLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await API.delete('/api/user/passkey');
|
const res = await API.delete('/api/user/passkey');
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (!success) {
|
||||||
showSuccess(t('Passkey 已解绑'));
|
throw new Error(message || t('操作失败,请重试'));
|
||||||
await loadPasskeyStatus();
|
|
||||||
} else {
|
|
||||||
showError(message || t('操作失败,请重试'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showSuccess(t('Passkey 已解绑'));
|
||||||
|
await loadPasskeyStatus();
|
||||||
|
return res.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(t('操作失败,请重试'));
|
throw new Error(error?.message || t('操作失败,请重试'));
|
||||||
} finally {
|
} finally {
|
||||||
setPasskeyDeleteLoading(false);
|
setPasskeyDeleteLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRemovePasskey = async () => {
|
||||||
|
await startPasskeyManagementVerification(removePasskey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasskeyVerificationCancel = () => {
|
||||||
|
setPasskeyRequiredVerificationMethod(null);
|
||||||
|
cancelPasskeyVerification();
|
||||||
|
};
|
||||||
|
|
||||||
const getUserData = async () => {
|
const getUserData = async () => {
|
||||||
let res = await API.get(`/api/user/self`);
|
let res = await API.get(`/api/user/self`);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
@@ -556,6 +649,18 @@ const PersonalSetting = () => {
|
|||||||
turnstileSiteKey={turnstileSiteKey}
|
turnstileSiteKey={turnstileSiteKey}
|
||||||
setTurnstileToken={setTurnstileToken}
|
setTurnstileToken={setTurnstileToken}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SecureVerificationModal
|
||||||
|
visible={isPasskeyVerificationModalVisible}
|
||||||
|
verificationMethods={visiblePasskeyVerificationMethods}
|
||||||
|
verificationState={passkeyVerificationState}
|
||||||
|
onVerify={executePasskeyVerification}
|
||||||
|
onCancel={handlePasskeyVerificationCancel}
|
||||||
|
onCodeChange={setPasskeyVerificationCode}
|
||||||
|
onMethodSwitch={switchPasskeyVerificationMethod}
|
||||||
|
title={passkeyVerificationState.title}
|
||||||
|
description={passkeyVerificationState.description}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user