From f4c9535e1225f7bcf0230b225295dce863e8f1cc Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 28 Feb 2026 05:22:37 -0800 Subject: [PATCH] =?UTF-8?q?feat(capability):=20=E8=A1=A5=E9=BD=90=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=90=8E=E7=AB=AF=20API=20=E8=83=BD=E5=8A=9B=E6=8B=A6?= =?UTF-8?q?=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 背景 审计发现 13 项用户能力中,部分后端 API 端点缺少 @RequireCapability 拦截,用户可绕过前端 UI 限制直接调用 API。本次逐服务补齐。 ## Phase 1: 高优先级 — 操作端点 ### auth-service - POST /auth/password/change → @RequireCapability('PROFILE_EDIT') 修改登录密码需要 PROFILE_EDIT 能力 - POST /auth/trade-password/set → @RequireCapability('PROFILE_EDIT') 设置交易密码需要 PROFILE_EDIT 能力 - POST /auth/trade-password/change → @RequireCapability('PROFILE_EDIT') 修改交易密码需要 PROFILE_EDIT 能力 - POST /auth/trade-password/verify → @RequireCapability('TRADING') 验证交易密码是交易前置步骤,需要 TRADING 能力 ### trading-service - POST /c2c/orders/:orderNo/cancel → @RequireCapability('C2C') C2C 取消订单是唯一缺失 C2C 能力检查的操作端点 ## Phase 2: 低优先级 — 查看端点 ### trading-service - GET /trading/orders → VIEW_RECORDS (用户订单列表) - GET /trading/trades → VIEW_RECORDS (成交记录) - GET /transfers/history → VIEW_RECORDS (划转历史) - GET /p2p/transfers/:accountSequence → VIEW_RECORDS (P2P转账历史) - GET /c2c/orders/my → VIEW_RECORDS (我的C2C订单) ### contribution-service - GET /contribution/accounts/:accountSequence/active → VIEW_ASSET - GET /contribution/accounts/:accountSequence/planting-ledger → VIEW_RECORDS ## 能力覆盖总览 (补齐后) | 能力 | 端点数 | 状态 | |------|--------|------| | LOGIN | 全局 | ✅ JwtAuthGuard 拦截 | | TRADING | 3 | ✅ createOrder, cancelOrder, verifyTradePassword | | C2C | 6 | ✅ create, take, cancel, confirmPayment, confirmReceived, uploadProof | | TRANSFER_IN | 1 | ✅ transferIn | | TRANSFER_OUT | 1 | ✅ transferOut | | P2P_SEND | 1 | ✅ transfer | | KYC | 1 | ✅ submitKyc | | PROFILE_EDIT | 3 | ✅ changePassword, setTradePassword, changeTradePassword | | VIEW_ASSET | 2 | ✅ getMyAsset, getActiveContribution | | VIEW_TEAM | 2 | ✅ getMyTeamInfo, getDirectReferrals | | VIEW_RECORDS | 6 | ✅ 各服务历史记录端点 | | P2P_RECEIVE | 0 | 仅前端展示控制(无后端操作端点) | | MINING_CLAIM | 0 | mining-service 需后续重构(@Public 类级别) | Co-Authored-By: Claude Opus 4.6 --- .../src/api/controllers/password.controller.ts | 5 ++++- .../src/api/controllers/trade-password.controller.ts | 11 ++++++++--- .../src/api/controllers/contribution.controller.ts | 2 ++ .../src/api/controllers/c2c.controller.ts | 2 ++ .../src/api/controllers/p2p-transfer.controller.ts | 1 + .../src/api/controllers/trading.controller.ts | 2 ++ .../src/api/controllers/transfer.controller.ts | 1 + 7 files changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/services/auth-service/src/api/controllers/password.controller.ts b/backend/services/auth-service/src/api/controllers/password.controller.ts index d7f91242..b20367a5 100644 --- a/backend/services/auth-service/src/api/controllers/password.controller.ts +++ b/backend/services/auth-service/src/api/controllers/password.controller.ts @@ -9,7 +9,9 @@ import { import { ThrottlerGuard } from '@nestjs/throttler'; import { PasswordService } from '@/application/services'; import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard'; +import { CapabilityGuard } from '@/shared/guards/capability.guard'; import { CurrentUser } from '@/shared/decorators/current-user.decorator'; +import { RequireCapability } from '@/shared/decorators/require-capability.decorator'; class ResetPasswordDto { phone: string; @@ -46,7 +48,8 @@ export class PasswordController { */ @Post('change') @HttpCode(HttpStatus.OK) - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CapabilityGuard) + @RequireCapability('PROFILE_EDIT') async changePassword( @CurrentUser() user: { accountSequence: string }, @Body() dto: ChangePasswordDto, diff --git a/backend/services/auth-service/src/api/controllers/trade-password.controller.ts b/backend/services/auth-service/src/api/controllers/trade-password.controller.ts index 278ab1f7..6ea615d3 100644 --- a/backend/services/auth-service/src/api/controllers/trade-password.controller.ts +++ b/backend/services/auth-service/src/api/controllers/trade-password.controller.ts @@ -11,7 +11,9 @@ import { IsString, IsNotEmpty } from 'class-validator'; import { ThrottlerGuard } from '@nestjs/throttler'; import { TradePasswordService } from '@/application/services/trade-password.service'; import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard'; +import { CapabilityGuard } from '@/shared/guards/capability.guard'; import { CurrentUser } from '@/shared/decorators/current-user.decorator'; +import { RequireCapability } from '@/shared/decorators/require-capability.decorator'; class SetTradePasswordDto { @IsString() @@ -62,7 +64,8 @@ export class TradePasswordController { */ @Post('set') @HttpCode(HttpStatus.OK) - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CapabilityGuard) + @RequireCapability('PROFILE_EDIT') async setTradePassword( @CurrentUser() user: { accountSequence: string }, @Body() dto: SetTradePasswordDto, @@ -82,7 +85,8 @@ export class TradePasswordController { */ @Post('change') @HttpCode(HttpStatus.OK) - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CapabilityGuard) + @RequireCapability('PROFILE_EDIT') async changeTradePassword( @CurrentUser() user: { accountSequence: string }, @Body() dto: ChangeTradePasswordDto, @@ -102,7 +106,8 @@ export class TradePasswordController { */ @Post('verify') @HttpCode(HttpStatus.OK) - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CapabilityGuard) + @RequireCapability('TRADING') async verifyTradePassword( @CurrentUser() user: { accountSequence: string }, @Body() dto: VerifyTradePasswordDto, diff --git a/backend/services/contribution-service/src/api/controllers/contribution.controller.ts b/backend/services/contribution-service/src/api/controllers/contribution.controller.ts index ad986ae6..7d2f6163 100644 --- a/backend/services/contribution-service/src/api/controllers/contribution.controller.ts +++ b/backend/services/contribution-service/src/api/controllers/contribution.controller.ts @@ -79,6 +79,7 @@ export class ContributionController { } @Get('accounts/:accountSequence/active') + @RequireCapability('VIEW_ASSET') @ApiOperation({ summary: '获取账户活跃算力统计' }) @ApiParam({ name: 'accountSequence', description: '账户序号' }) @ApiResponse({ status: 200, type: ActiveContributionResponse }) @@ -105,6 +106,7 @@ export class ContributionController { } @Get('accounts/:accountSequence/planting-ledger') + @RequireCapability('VIEW_RECORDS') @ApiOperation({ summary: '获取账户认种分类账' }) @ApiParam({ name: 'accountSequence', description: '账户序号' }) @ApiQuery({ name: 'page', required: false, type: Number, description: '页码' }) diff --git a/backend/services/trading-service/src/api/controllers/c2c.controller.ts b/backend/services/trading-service/src/api/controllers/c2c.controller.ts index 76cd6143..7ed6be1f 100644 --- a/backend/services/trading-service/src/api/controllers/c2c.controller.ts +++ b/backend/services/trading-service/src/api/controllers/c2c.controller.ts @@ -111,6 +111,7 @@ export class C2cController { } @Get('orders/my') + @RequireCapability('VIEW_RECORDS') @ApiOperation({ summary: '获取我的C2C订单' }) @ApiResponse({ status: 200, description: '我的订单列表' }) async getMyOrders( @@ -213,6 +214,7 @@ export class C2cController { } @Post('orders/:orderNo/cancel') + @RequireCapability('C2C') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '取消C2C订单' }) @ApiParam({ name: 'orderNo', description: '订单号' }) diff --git a/backend/services/trading-service/src/api/controllers/p2p-transfer.controller.ts b/backend/services/trading-service/src/api/controllers/p2p-transfer.controller.ts index 5eb2508f..b583d9e5 100644 --- a/backend/services/trading-service/src/api/controllers/p2p-transfer.controller.ts +++ b/backend/services/trading-service/src/api/controllers/p2p-transfer.controller.ts @@ -59,6 +59,7 @@ export class P2pTransferController { } @Get('transfers/:accountSequence') + @RequireCapability('VIEW_RECORDS') @ApiOperation({ summary: '获取P2P转账历史' }) @ApiParam({ name: 'accountSequence', required: true, description: '账户序列号' }) @ApiQuery({ name: 'page', required: false, type: Number }) diff --git a/backend/services/trading-service/src/api/controllers/trading.controller.ts b/backend/services/trading-service/src/api/controllers/trading.controller.ts index a3a6af6c..0106f31f 100644 --- a/backend/services/trading-service/src/api/controllers/trading.controller.ts +++ b/backend/services/trading-service/src/api/controllers/trading.controller.ts @@ -88,6 +88,7 @@ export class TradingController { } @Get('orders') + @RequireCapability('VIEW_RECORDS') @ApiOperation({ summary: '获取用户订单列表' }) @ApiQuery({ name: 'page', required: false, type: Number }) @ApiQuery({ name: 'pageSize', required: false, type: Number }) @@ -127,6 +128,7 @@ export class TradingController { } @Get('trades') + @RequireCapability('VIEW_RECORDS') @ApiOperation({ summary: '获取用户成交记录(含手续费明细)' }) @ApiQuery({ name: 'page', required: false, type: Number }) @ApiQuery({ name: 'pageSize', required: false, type: Number }) diff --git a/backend/services/trading-service/src/api/controllers/transfer.controller.ts b/backend/services/trading-service/src/api/controllers/transfer.controller.ts index 56782c00..3316652c 100644 --- a/backend/services/trading-service/src/api/controllers/transfer.controller.ts +++ b/backend/services/trading-service/src/api/controllers/transfer.controller.ts @@ -40,6 +40,7 @@ export class TransferController { } @Get('history') + @RequireCapability('VIEW_RECORDS') @ApiOperation({ summary: '获取划转历史' }) @ApiQuery({ name: 'page', required: false, type: Number }) @ApiQuery({ name: 'pageSize', required: false, type: Number })