fix: harden tenant auth and quality gates

This commit is contained in:
hectorzhao
2026-08-28 11:44:16 +08:00
parent c3bf8af3e6
commit 2744690f9f
51 changed files with 1750 additions and 466 deletions
@@ -1,20 +1,11 @@
import { Body, Controller, Get, Param, Post, Put, Query } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Put, Query, UsePipes } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { TenantId } from '../common/tenant-id.decorator';
import { CurrentTenantId } from '../auth/current-tenant-id.decorator';
import { RequireRecentAuthentication } from '../auth/require-recent-authentication.decorator';
import { CurrentSessionUserId } from '../auth/current-session-user.decorator';
import { DeleteTargetDto, DeletionGovernanceService } from '../deletion-governance/deletion-governance.service';
import {
CreateSignatureMaterialDto,
CreateSmsApplicationDto,
CreateSmsDrainageInfoDto,
CreateSmsSignatureDto,
CreateSmsTemplateDto,
StatusChangeDto,
UpdateSmsTemplateDto,
UpdateSmsDrainageInfoDto,
UpdateSmsSignatureDto,
} from './sms-config.contracts';
import { ClientDrainageInfoDto, ClientDrainageInfoUpdateDto, ClientSignatureMaterialDto, ClientSmsApplicationDto, ClientSmsSignatureDto, ClientSmsSignatureUpdateDto, ClientSmsTemplateDto, ClientSmsTemplateUpdateDto, ClientStatusChangeDto } from '../common/client-write.dto';
import { strictValidationPipe } from '../common/strict-validation.pipe';
import { DeletionGovernanceService } from '../deletion-governance/deletion-governance.service';
import { SmsConfigService } from './sms-config.service';
@ApiTags('client-sms-config')
@@ -23,29 +14,30 @@ export class ClientSmsConfigController {
constructor(private readonly smsConfig: SmsConfigService, private readonly deletions: DeletionGovernanceService) {}
@Get('applications')
listApplications(@TenantId() tenantId?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
listApplications(@CurrentTenantId() tenantId: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
return page || pageSize
? this.smsConfig.listApplicationsPage({ tenantId, page: Number(page), pageSize: Number(pageSize) })
: this.smsConfig.listApplications(tenantId);
}
@Get('application-options')
listApplicationOptions(@TenantId() tenantId?: string) {
listApplicationOptions(@CurrentTenantId() tenantId: string) {
return this.smsConfig.listApplicationOptions(tenantId);
}
@Post('applications')
createApplication(@Body() body: CreateSmsApplicationDto) {
return this.smsConfig.createApplication(body);
@UsePipes(strictValidationPipe)
createApplication(@CurrentTenantId() tenantId: string, @Body() body: ClientSmsApplicationDto) {
return this.smsConfig.createApplication({ ...body, tenantId });
}
@Get('applications/:id/cmpp-params')
getApplicationCmppParams(@Param('id') applicationId: string, @TenantId() tenantId?: string) {
getApplicationCmppParams(@Param('id') applicationId: string, @CurrentTenantId() tenantId: string) {
return this.smsConfig.getApplicationCmppParams(applicationId, tenantId);
}
@Get('applications/:id/report-fields')
getApplicationReportFields(@Param('id') applicationId: string, @Query('reportType') reportType: 'signature' | 'drainage' = 'drainage', @TenantId() tenantId?: string) {
getApplicationReportFields(@Param('id') applicationId: string, @Query('reportType') reportType: 'signature' | 'drainage' = 'drainage', @CurrentTenantId() tenantId: string) {
return this.smsConfig.getApplication(applicationId, tenantId).then(() => this.smsConfig.getClientApplicationReportFields(applicationId, reportType));
}
@@ -56,110 +48,122 @@ export class ClientSmsConfigController {
@Post('applications/:id/secret/reset')
@RequireRecentAuthentication()
resetApplicationSecret(@Param('id') applicationId: string, @Body() body: StatusChangeDto) {
return this.smsConfig.resetApplicationSecret(applicationId, body);
@UsePipes(strictValidationPipe)
resetApplicationSecret(@Param('id') applicationId: string, @Body() body: ClientStatusChangeDto, @CurrentTenantId() tenantId: string, @CurrentSessionUserId() operatorId?: string) {
return this.smsConfig.resetClientApplicationSecret(applicationId, { ...body, operatorId }, tenantId);
}
@Post('applications/:id/status')
@RequireRecentAuthentication()
changeApplicationStatus(@Param('id') applicationId: string, @Body() body: StatusChangeDto) {
return this.smsConfig.changeApplicationStatus(applicationId, body);
@UsePipes(strictValidationPipe)
changeApplicationStatus(@Param('id') applicationId: string, @Body() body: ClientStatusChangeDto, @CurrentTenantId() tenantId: string, @CurrentSessionUserId() operatorId?: string) {
return this.smsConfig.changeClientApplicationStatus(applicationId, { ...body, operatorId }, tenantId);
}
@Get('signatures')
listSignatures(@TenantId() tenantId?: string) {
listSignatures(@CurrentTenantId() tenantId: string) {
return this.smsConfig.listClientSignatures(tenantId);
}
@Get('signature-options')
listSignatureOptions(@TenantId() tenantId?: string) {
listSignatureOptions(@CurrentTenantId() tenantId: string) {
return this.smsConfig.listSignatureOptions(tenantId);
}
@Get('signatures-workspace')
getSignatureWorkspace(@TenantId() tenantId?: string, @Query('keyword') keyword?: string, @Query('applicationId') applicationId?: string, @Query('status') status?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
getSignatureWorkspace(@CurrentTenantId() tenantId: string, @Query('keyword') keyword?: string, @Query('applicationId') applicationId?: string, @Query('status') status?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
return this.smsConfig.getClientSignatureWorkspace(tenantId, { keyword, applicationId, status, page: Number(page), pageSize: Number(pageSize) });
}
@Post('signatures')
async createSignature(@Body() body: CreateSmsSignatureDto, @TenantId() tenantId?: string) {
const signature = await this.smsConfig.createSignature({ ...body, tenantId: tenantId ?? body.tenantId });
return this.smsConfig.getClientSignatureView(signature.id, tenantId ?? body.tenantId);
@UsePipes(strictValidationPipe)
async createSignature(@Body() body: ClientSmsSignatureDto, @CurrentTenantId() tenantId: string) {
const signature = await this.smsConfig.createSignature({ ...body, tenantId });
return this.smsConfig.getClientSignatureView(signature.id, tenantId);
}
@Put('signatures/:id')
async updateSignature(@Param('id') signatureId: string, @Body() body: UpdateSmsSignatureDto, @TenantId() tenantId?: string) {
@UsePipes(strictValidationPipe)
async updateSignature(@Param('id') signatureId: string, @Body() body: ClientSmsSignatureUpdateDto, @CurrentTenantId() tenantId: string) {
await this.smsConfig.updateClientSignature(signatureId, body, tenantId);
return this.smsConfig.getClientSignatureView(signatureId, tenantId);
}
@Post('signatures/:id/materials')
createSignatureMaterial(@Param('id') signatureId: string, @Body() body: Omit<CreateSignatureMaterialDto, 'signatureId'>) {
return this.smsConfig.createSignatureMaterial({ ...body, signatureId });
@UsePipes(strictValidationPipe)
createSignatureMaterial(@Param('id') signatureId: string, @Body() body: ClientSignatureMaterialDto, @CurrentTenantId() tenantId: string) {
return this.smsConfig.createClientSignatureMaterial({ ...body, signatureId }, tenantId);
}
@Get('drainage-infos')
listDrainageInfos(@TenantId() tenantId?: string) {
listDrainageInfos(@CurrentTenantId() tenantId: string) {
return this.smsConfig.listClientDrainageInfos(tenantId);
}
@Post('signatures/:id/drainage-infos')
async createDrainageInfo(@Param('id') signatureId: string, @Body() body: CreateSmsDrainageInfoDto, @TenantId() tenantId?: string) {
@UsePipes(strictValidationPipe)
async createDrainageInfo(@Param('id') signatureId: string, @Body() body: ClientDrainageInfoDto, @CurrentTenantId() tenantId: string) {
const item = await this.smsConfig.createDrainageInfo(signatureId, body, {}, tenantId);
return this.smsConfig.getClientDrainageInfoView(item.id, tenantId);
}
@Put('drainage-infos/:id')
async updateDrainageInfo(@Param('id') itemId: string, @Body() body: UpdateSmsDrainageInfoDto, @TenantId() tenantId?: string) {
@UsePipes(strictValidationPipe)
async updateDrainageInfo(@Param('id') itemId: string, @Body() body: ClientDrainageInfoUpdateDto, @CurrentTenantId() tenantId: string) {
await this.smsConfig.updateDrainageInfo(itemId, body, {}, tenantId);
return this.smsConfig.getClientDrainageInfoView(itemId, tenantId);
}
@Post('drainage-infos/:id/status')
async changeDrainageInfoStatus(@Param('id') itemId: string, @Body() body: StatusChangeDto, @TenantId() tenantId?: string) {
await this.smsConfig.changeDrainageInfoStatus(itemId, body, tenantId);
@UsePipes(strictValidationPipe)
async changeDrainageInfoStatus(@Param('id') itemId: string, @Body() body: ClientStatusChangeDto, @CurrentTenantId() tenantId: string, @CurrentSessionUserId() operatorId?: string) {
await this.smsConfig.changeDrainageInfoStatus(itemId, { ...body, operatorId }, tenantId);
if (body.status === 'deleted') return { id: itemId, status: 'deleted' };
return this.smsConfig.getClientDrainageInfoView(itemId, tenantId);
}
@Post('signatures/:id/submit')
async submitSignature(@Param('id') signatureId: string, @TenantId() tenantId?: string) {
async submitSignature(@Param('id') signatureId: string, @CurrentTenantId() tenantId: string) {
await this.smsConfig.submitSignature(signatureId, tenantId);
return this.smsConfig.getClientSignatureView(signatureId, tenantId);
}
@Post('signatures/:id/status')
async changeSignatureStatus(@Param('id') signatureId: string, @Body() body: StatusChangeDto & DeleteTargetDto, @TenantId() tenantId?: string, @CurrentSessionUserId() operatorId?: string) {
@UsePipes(strictValidationPipe)
async changeSignatureStatus(@Param('id') signatureId: string, @Body() body: ClientStatusChangeDto, @CurrentTenantId() tenantId: string, @CurrentSessionUserId() operatorId?: string) {
if (body.status === 'deleted') return this.deletions.delete('signature', signatureId, { ...body, operatorId }, tenantId);
await this.smsConfig.changeSignatureStatus(signatureId, body, tenantId);
await this.smsConfig.changeSignatureStatus(signatureId, { ...body, operatorId }, tenantId);
return this.smsConfig.getClientSignatureView(signatureId, tenantId);
}
@Get('templates')
listTemplates(@TenantId() tenantId?: string, @Query('includeHistory') includeHistory?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
listTemplates(@CurrentTenantId() tenantId: string, @Query('includeHistory') includeHistory?: string, @Query('keyword') keyword?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string) {
return page || pageSize
? this.smsConfig.listTemplatesPage({ tenantId, status: includeHistory === 'true' ? 'all' : 'approved', keyword, page: Number(page), pageSize: Number(pageSize) })
: this.smsConfig.listClientTemplates(tenantId, includeHistory === 'true');
}
@Post('templates')
createTemplate(@Body() body: CreateSmsTemplateDto, @TenantId() tenantId?: string) {
return this.smsConfig.createTemplate({ ...body, tenantId: tenantId ?? body.tenantId });
@UsePipes(strictValidationPipe)
createTemplate(@Body() body: ClientSmsTemplateDto, @CurrentTenantId() tenantId: string) {
return this.smsConfig.createTemplate({ ...body, tenantId });
}
@Put('templates/:id')
updateTemplate(@Param('id') templateId: string, @Body() body: UpdateSmsTemplateDto, @TenantId() tenantId?: string) {
@UsePipes(strictValidationPipe)
updateTemplate(@Param('id') templateId: string, @Body() body: ClientSmsTemplateUpdateDto, @CurrentTenantId() tenantId: string) {
return this.smsConfig.updateClientTemplate(templateId, body, tenantId);
}
@Post('templates/:id/submit')
submitTemplate(@Param('id') templateId: string, @TenantId() tenantId?: string) {
submitTemplate(@Param('id') templateId: string, @CurrentTenantId() tenantId: string) {
return this.smsConfig.submitTemplate(templateId, tenantId);
}
@Post('templates/:id/status')
changeTemplateStatus(@Param('id') templateId: string, @Body() body: StatusChangeDto & DeleteTargetDto, @TenantId() tenantId?: string, @CurrentSessionUserId() operatorId?: string) {
@UsePipes(strictValidationPipe)
changeTemplateStatus(@Param('id') templateId: string, @Body() body: ClientStatusChangeDto, @CurrentTenantId() tenantId: string, @CurrentSessionUserId() operatorId?: string) {
if (body.status === 'deleted') return this.deletions.delete('template', templateId, { ...body, operatorId }, tenantId);
return this.smsConfig.changeTemplateStatus(templateId, body, tenantId);
return this.smsConfig.changeTemplateStatus(templateId, { ...body, operatorId }, tenantId);
}
}
+15
View File
@@ -81,10 +81,20 @@ export class SmsConfigService implements OnModuleInit, OnModuleDestroy {
return this.applications.resetApplicationSecret(applicationId, data);
}
async resetClientApplicationSecret(applicationId: string, data: StatusChangeDto, tenantId: string) {
await this.applications.getApplication(applicationId, tenantId);
return this.applications.resetApplicationSecret(applicationId, data);
}
async changeApplicationStatus(applicationId: string, data: StatusChangeDto) {
return this.lifecycle.changeApplicationStatus(applicationId, data);
}
async changeClientApplicationStatus(applicationId: string, data: StatusChangeDto, tenantId: string) {
await this.applications.getApplication(applicationId, tenantId);
return this.lifecycle.changeApplicationStatus(applicationId, data);
}
async getApplicationDeactivationPreview(applicationId: string) {
return this.lifecycle.getApplicationDeactivationPreview(applicationId);
}
@@ -177,6 +187,11 @@ export class SmsConfigService implements OnModuleInit, OnModuleDestroy {
return this.signatures.createSignatureMaterial(data);
}
async createClientSignatureMaterial(data: CreateSignatureMaterialDto, tenantId: string) {
await this.signatures.getClientSignatureView(data.signatureId, tenantId);
return this.signatures.createSignatureMaterial(data);
}
async submitSignature(signatureId: string, tenantId?: string) {
return this.signatures.submitSignature(signatureId, tenantId);
}