fix: correct application cmpp credentials

This commit is contained in:
hectorzhao
2026-07-09 17:18:39 +08:00
parent 57a4c86a79
commit 767f0ca9aa
13 changed files with 149 additions and 24 deletions
+50 -8
View File
@@ -1,6 +1,6 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { randomBytes, randomInt, createHash } from 'node:crypto';
import { randomInt, randomUUID } from 'node:crypto';
import { PrismaService } from '../prisma/prisma.service';
export interface CreateSmsApplicationDto {
@@ -9,6 +9,8 @@ export interface CreateSmsApplicationDto {
scene?: string;
callbackUrl?: string;
cmppAccount?: string;
cmppEnterpriseCode?: string;
passwordCipher?: string;
cmppMaxConnections?: number;
cmppWindowSize?: number;
dailyLimit?: number;
@@ -154,9 +156,10 @@ export class SmsConfigService {
}
async createApplication(data: CreateSmsApplicationDto) {
const secret = randomBytes(24).toString('hex');
const secret = normalizeApplicationPassword(data.passwordCipher);
const queuePriority = normalizeApplicationQueuePriority(data.queuePriority);
const cmppAccount = data.cmppAccount ? await this.validateAndReserveCmppAccount(data.cmppAccount) : await this.generateCmppAccount();
const cmppEnterpriseCode = await this.resolveCmppEnterpriseCode(data.cmppEnterpriseCode, data.tenantId);
return this.prisma.smsApplication.create({
data: {
tenantId: data.tenantId,
@@ -164,7 +167,8 @@ export class SmsConfigService {
scene: data.scene,
callbackUrl: data.callbackUrl,
cmppAccount,
secretHash: hashSecret(secret),
cmppEnterpriseCode,
secretHash: secret,
cmppMaxConnections: getPositiveInteger(data.cmppMaxConnections, 1, 'cmppMaxConnections'),
cmppWindowSize: getPositiveInteger(data.cmppWindowSize, 16, 'cmppWindowSize'),
dailyLimit: data.dailyLimit,
@@ -191,6 +195,12 @@ export class SmsConfigService {
const cmppAccount = data.cmppAccount === undefined
? undefined
: await this.validateAndReserveCmppAccount(data.cmppAccount, applicationId);
const cmppEnterpriseCode = data.cmppEnterpriseCode === undefined
? undefined
: normalizeEnterpriseCode(data.cmppEnterpriseCode);
const secretHash = data.passwordCipher === undefined
? undefined
: normalizeApplicationPassword(data.passwordCipher);
return this.prisma.$transaction(async (tx) => {
if (data.ipAllowlist) {
@@ -203,6 +213,8 @@ export class SmsConfigService {
scene: data.scene,
callbackUrl: data.callbackUrl,
cmppAccount,
cmppEnterpriseCode,
secretHash,
cmppMaxConnections: data.cmppMaxConnections === undefined ? undefined : getPositiveInteger(data.cmppMaxConnections, 1, 'cmppMaxConnections'),
cmppWindowSize: data.cmppWindowSize === undefined ? undefined : getPositiveInteger(data.cmppWindowSize, 16, 'cmppWindowSize'),
dailyLimit: data.dailyLimit,
@@ -286,10 +298,10 @@ export class SmsConfigService {
if (!application) {
throw new NotFoundException('Application not found');
}
const secret = randomBytes(24).toString('hex');
const secret = generateApplicationPassword();
const updated = await this.prisma.smsApplication.update({
where: { id: applicationId },
data: { secretHash: hashSecret(secret) },
data: { secretHash: secret },
});
await this.writeOperationLog(application.tenantId, data.operatorId, 'sms_application.secret_reset', 'sms_application', applicationId, {
reason: data.reason,
@@ -357,7 +369,7 @@ export class SmsConfigService {
appCode: application.id,
gatewayHost: channel?.gatewayHost ?? '',
gatewayPort: channel?.gatewayPort ?? 0,
enterpriseCode: channel?.enterpriseCode ?? application.tenant.code,
enterpriseCode: application.cmppEnterpriseCode,
account: application.cmppAccount,
passwordCipher: application.secretHash,
srcId: channel?.srcId ?? '',
@@ -390,6 +402,17 @@ export class SmsConfigService {
throw new BadRequestException('Unable to generate unique CMPP account');
}
private async resolveCmppEnterpriseCode(cmppEnterpriseCode: string | undefined, tenantId: string) {
if (cmppEnterpriseCode !== undefined) {
return normalizeEnterpriseCode(cmppEnterpriseCode);
}
const tenant = await this.prisma.tenant.findUnique({ where: { id: tenantId }, select: { code: true } });
if (!tenant) {
throw new BadRequestException('tenantId does not reference an existing tenant');
}
return normalizeEnterpriseCode(tenant.code);
}
async disconnectApplicationConnection(applicationId: string, connectionId: string, data: StatusChangeDto = { status: 'disconnected' }) {
const application = await this.prisma.smsApplication.findUnique({ where: { id: applicationId } });
if (!application) {
@@ -760,8 +783,27 @@ interface TemplateVariableInput {
required?: boolean;
}
function hashSecret(secret: string) {
return createHash('sha256').update(secret).digest('hex');
function normalizeEnterpriseCode(value: string) {
const enterpriseCode = value.trim();
if (!enterpriseCode) {
throw new BadRequestException('cmppEnterpriseCode is required');
}
if (enterpriseCode.length > 32) {
throw new BadRequestException('cmppEnterpriseCode must be at most 32 characters');
}
return enterpriseCode;
}
function normalizeApplicationPassword(value: string | undefined) {
const password = value?.trim() || generateApplicationPassword();
if (password.length !== 16) {
throw new BadRequestException('passwordCipher must be 16 characters');
}
return password;
}
function generateApplicationPassword() {
return randomUUID().replace(/-/g, '').slice(0, 16);
}
function estimateBillingUnits(content: string) {