Ver Fonte

fix: passkey security

Seefs há 5 meses atrás
pai
commit
e71407ee62

+ 8 - 13
controller/passkey.go

@@ -189,13 +189,8 @@ func PasskeyStatus(c *gin.Context) {
 	}
 
 	data := gin.H{
-		"enabled":         true,
-		"last_used_at":    credential.LastUsedAt,
-		"backup_eligible": credential.BackupEligible,
-		"backup_state":    credential.BackupState,
-	}
-	if credential != nil {
-		data["credential_aaguid"] = fmt.Sprintf("%x", credential.AAGUID)
+		"enabled":      true,
+		"last_used_at": credential.LastUsedAt,
 	}
 
 	c.JSON(http.StatusOK, gin.H{
@@ -278,14 +273,14 @@ func PasskeyLoginFinish(c *gin.Context) {
 			return nil, errors.New("该用户已被禁用")
 		}
 
-		// 验证用户句柄(如果提供的话)
 		if len(userHandle) > 0 {
-			if userID, parseErr := strconv.Atoi(string(userHandle)); parseErr == nil {
-				if userID != user.Id {
-					return nil, errors.New("用户句柄与凭证不匹配")
-				}
+			userID, parseErr := strconv.Atoi(string(userHandle))
+			if parseErr != nil {
+				// 记录异常但继续验证,因为某些客户端可能使用非数字格式
+				common.SysLog(fmt.Sprintf("PasskeyLogin: userHandle parse error for credential, length: %d", len(userHandle)))
+			} else if userID != user.Id {
+				return nil, errors.New("用户句柄与凭证不匹配")
 			}
-			// 如果解析失败,不做严格验证,因为某些情况下userHandle可能为空或格式不同
 		}
 
 		return passkeysvc.NewWebAuthnUser(user, credential), nil

+ 1 - 1
router/api-router.go

@@ -99,7 +99,7 @@ func SetApiRouter(router *gin.Engine) {
 				adminRoute.POST("/manage", controller.ManageUser)
 				adminRoute.PUT("/", controller.UpdateUser)
 				adminRoute.DELETE("/:id", controller.DeleteUser)
-				adminRoute.DELETE("/:id/passkey", controller.AdminResetPasskey)
+				adminRoute.DELETE("/:id/reset_passkey", controller.AdminResetPasskey)
 
 				// Admin 2FA routes
 				adminRoute.GET("/2fa/stats", controller.Admin2FAStats)

+ 34 - 20
web/src/components/table/users/UsersColumnDefs.jsx

@@ -26,7 +26,9 @@ import {
   Progress,
   Popover,
   Typography,
+  Dropdown,
 } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
 import { renderGroup, renderNumber, renderQuota } from '../../../helpers';
 
 /**
@@ -213,6 +215,28 @@ const renderOperations = (
     return <></>;
   }
 
+  const moreMenu = [
+    {
+      node: 'item',
+      name: t('重置 Passkey'),
+      onClick: () => showResetPasskeyModal(record),
+    },
+    {
+      node: 'item',
+      name: t('重置 2FA'),
+      onClick: () => showResetTwoFAModal(record),
+    },
+    {
+      node: 'divider',
+    },
+    {
+      node: 'item',
+      name: t('注销'),
+      type: 'danger',
+      onClick: () => showDeleteModal(record),
+    },
+  ];
+
   return (
     <Space>
       {record.status === 1 ? (
@@ -255,27 +279,17 @@ const renderOperations = (
       >
         {t('降级')}
       </Button>
-      <Button
-        type='warning'
-        size='small'
-        onClick={() => showResetPasskeyModal(record)}
+      <Dropdown
+        menu={moreMenu}
+        trigger='click'
+        position='bottomRight'
       >
-        {t('重置 Passkey')}
-      </Button>
-      <Button
-        type='warning'
-        size='small'
-        onClick={() => showResetTwoFAModal(record)}
-      >
-        {t('重置 2FA')}
-      </Button>
-      <Button
-        type='danger'
-        size='small'
-        onClick={() => showDeleteModal(record)}
-      >
-        {t('注销')}
-      </Button>
+        <Button
+          type='tertiary'
+          size='small'
+          icon={<IconMore />}
+        />
+      </Dropdown>
     </Space>
   );
 };

+ 1 - 1
web/src/hooks/users/useUsersData.jsx

@@ -159,7 +159,7 @@ const searchUsers = async (
       return;
     }
     try {
-      const res = await API.delete(`/api/user/${user.id}/passkey`);
+      const res = await API.delete(`/api/user/${user.id}/reset_passkey`);
       const { success, message } = res.data;
       if (success) {
         showSuccess(t('Passkey 已重置'));