feat: harden CMPP delivery and platform workflows

This commit is contained in:
hectorzhao
2026-07-20 18:07:29 +08:00
parent 80fb5a8f53
commit f02c33cbb7
61 changed files with 1834 additions and 281 deletions
@@ -0,0 +1,41 @@
import { ManualOperationAuditMiddleware } from './manual-operation-audit.middleware';
describe('ManualOperationAuditMiddleware', () => {
it('records a successful authenticated mutation without storing request bodies', async () => {
const prisma = { operationLog: { create: jest.fn().mockResolvedValue({ id: 'log-1' }) } };
const middleware = new ManualOperationAuditMiddleware(prisma as never);
let finish: (() => void) | undefined;
const response = { statusCode: 200, once: (_event: 'finish', listener: () => void) => { finish = listener; } };
const next = jest.fn();
middleware.use({
method: 'PUT',
originalUrl: '/api/admin/enterprise-applications/c12345678901234567890?view=full',
sessionUserId: 'user-1',
header: () => 'jest',
}, response, next);
finish?.();
await Promise.resolve();
expect(next).toHaveBeenCalled();
expect(prisma.operationLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
userId: 'user-1',
action: 'manual_operation.put',
resource: 'enterprise-applications',
resourceId: 'c12345678901234567890',
detail: { method: 'PUT', path: '/api/admin/enterprise-applications/c12345678901234567890', statusCode: 200 },
}),
});
});
it('does not record unauthenticated, read-only, or failed requests', () => {
const prisma = { operationLog: { create: jest.fn() } };
const middleware = new ManualOperationAuditMiddleware(prisma as never);
const response = { statusCode: 500, once: (_event: 'finish', listener: () => void) => listener() };
middleware.use({ method: 'POST', originalUrl: '/api/admin/test', sessionUserId: 'user-1', header: () => undefined }, response, jest.fn());
middleware.use({ method: 'GET', originalUrl: '/api/admin/test', sessionUserId: 'user-1', header: () => undefined }, response, jest.fn());
middleware.use({ method: 'POST', originalUrl: '/api/admin/test', header: () => undefined }, response, jest.fn());
expect(prisma.operationLog.create).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,64 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { requestContext } from '../common/request-context';
import { PrismaService } from '../prisma/prisma.service';
type AuditRequest = {
method?: string;
originalUrl?: string;
url?: string;
sessionUserId?: string;
header(name: string): string | undefined;
};
type AuditResponse = {
statusCode?: number;
once(event: 'finish', listener: () => void): void;
};
const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
@Injectable()
export class ManualOperationAuditMiddleware implements NestMiddleware {
constructor(private readonly prisma: PrismaService) {}
use(request: AuditRequest, response: AuditResponse, next: () => void) {
const method = (request.method ?? '').toUpperCase();
const userId = request.sessionUserId;
const path = (request.originalUrl ?? request.url ?? '').split('?')[0];
if (!userId || !MUTATING_METHODS.has(method)) {
next();
return;
}
const { resource, resourceId } = operationResource(path);
const ipAddress = requestContext.getStore()?.ipAddress;
const userAgent = request.header('user-agent');
response.once('finish', () => {
const statusCode = response.statusCode ?? 200;
if (statusCode >= 400) return;
void this.prisma.operationLog.create({
data: {
userId,
action: `manual_operation.${method.toLowerCase()}`,
resource,
resourceId,
ipAddress,
userAgent,
detail: { method, path, statusCode },
},
}).catch(() => undefined);
});
next();
}
}
function operationResource(path: string) {
const segments = path.split('/').filter(Boolean).filter((segment) => !['api', 'admin', 'client'].includes(segment));
const resource = segments[0] ?? 'manual_operation';
const resourceId = segments.find((segment, index) => index > 0 && looksLikeResourceId(segment));
return { resource, resourceId };
}
function looksLikeResourceId(value: string) {
return /^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(value) || /^c[a-z0-9]{20,}$/i.test(value);
}